Skip to contentSkip to conversation

Device / Driver PCI / Platform Rust abstractions

PATCH Superseded Danilo Krummrich 8 participants 36 messages Dec 12, 2024 – Feb 2, 2025

Subsystems (9)
PCI Driver core Rust Device tree +5 more subsystems

v6 of 7 Superseded Latest is v7 →

lore
Share & tools
lore

Danilo Krummrich

author maintainer
#0 lore raw

v6 cover Device / Driver PCI / Platform Rust abstractions

This patch series implements the necessary Rust abstractions to implement device drivers in Rust.

This includes some basic generalizations for driver registration, handling of ID tables, MMIO operations and device resource handling.

Those generalizations are used to implement device driver support for two busses, the PCI and platform bus (with OF IDs) in order to provide some evidence that the generalizations work as intended.

The patch series also includes two patches adding two driver samples, one PCI driver and one platform driver.

The PCI bits are motivated by the Nova driver project [1], but are used by at least one more OOT driver (rnvme [2]).

The platform bits, besides adding some more evidence to the base abstractions, are required by a few more OOT drivers aiming at going upstream, i.e. rvkms [3], cpufreq-dt [4], asahi [5] and the i2c work from Fabien [6].

The patches of this series can also be [7], [8] and [9].

Changes in v6:
==============
- Module:
- remove patch to pass the module name in Module::init
- add a patch adding the ModuleMetadata trait to retreive the module name

- Driver:
- generate module structure within module_driver!, such that we get a unique module type for which ModuleMetadata can be implemented to retreive the module name
- let Adapter::of_id_table return Option<of::IdTable<Self::IdInfo>>

- Platform:
- change type of platform::Driver::OF_ID_TABLE to Option<of::IdTable>

- RCU:
- add RCU abstraction to MAINTAINERS file

- MISC:
- pick up a couple of RBs

Changes in v5:
==============
- Opaque:
- add Opaque::pin_init

- Revocable:
- use const generic instead of bool in revoke_internal
- don't open code things with Opaque::try_ffi_init, use Opaque::pin_init instead

- Driver:
- fix DriverOps -> RegistrationOps in commit message
- fix signature of register and unregister to take an Opaque<T::RegType> instead of a mutable reference
- implement generic driver::Adapter

- ID table:
- fix module_device_table! macro, according to commit 054a9cd395a7 (modpost: rename alias symbol for MODULE_DEVICE_TABLE())

- PCI:
- remove pci::DeviceId argument from probe()
- provide vendor_id and device_id accessor functions
- use addr_of_mut! for the device in probe()

- OF:
- move OF IdTable type alias from platform.rs to of.rs

- Platform:
- use addr_of_mut! for the device in probe()
- move generic OF code to driver::Adapter

- MISC:
- rebase onto latest rust-next (-rc2)

Changes in v4:
==============
- Revocable
- convert Revocable::data to Opaque
- introduce revoke_nosync, which does not wait for an RCU grace period
- fix minor typo

- Device ID
- fix ID table export name to always be unique
- remove #![allow(stable_features)]
- move #![allow(const_refs_to_cell)] to the "stable in 1.83" list

- I/O
- split Io<SIZE> in IoRaw<SIZE> and Io<SIZE>
- add rust helper for ioremap() and iounmap() (needed by doctests)

- Devres
- optimze drop by using revoke_nosync (we're guaranteed there are no more concurrent users, hence no need to wait for a full grace period)

- OF
- rename "Open Firmware" -> "Device Tree / Open Firmware" (Rob)
- remove unused C header reference
- add TODO comment to loop in DeviceId::new
- remove DeviceId::compatible; was only used in sample code
- add missing #[repr(transparent)]
- use #[cfg(CONFIG_OF)] for the relevant functions

- PCI
- let Rust helper for pci_resource_len() return resource_size_t

- PCI Sample
- remove unnecessary from_le

- Platform
- fix example compatible string
- add example for platform::Driver
- rename ID_TABLE to OF_ID_TABLE
- don't allow public use of of_match_table; convert to of_id_info to retrieve the of::DeviceId to gather the corresponding IdInfo
- remove wrong example reference in the documentation of platform::Driver
- remove as_dev, as we already implement AsRef for platform::Device

- Platform Sample
- fix compatible string; add corresponding entry to drivers/of/unittest-data/tests-platform.dtsi
- remove usage of of_match_table

- MISC
- fix a few spelling mistakes
- rebase onto rust-next (v6.13-rc1)

Changes in v3:
==============
- add commits for Opaque::try_ffi_init and InPlaceModule
- rename DriverOps to RegistrationOps
- rework device ID abstractions to get rid of almost all macro magic (thanks to Gary Guo for working this out!)
- this is possible due to recently stabilized language features
- add modpost alias generation for device ID tables
- unfortunately, this is the part that still requires some macro magic in the device ID abstractions
- PCI
- represent the driver private data with the driver specific Driver instance and bind it's lifetime to the time of the driver being bound to a device
- this allows us to handle class / subsystem registrations in a cleaner way
- get rid of Driver::remove
- Rust drivers should bind cleanup code to the Drop implementation of the corresponding structure instead and put it into their driver structure for automatic cleanup
- add a sample PCI driver
- add abstractions for struct of_device_id
- add abstractions for the platform bus, including a sample driver
- update the MAINTAINERS file accordingly
- currently this turns out a bit messy, but it should become better once the build system supports a treewide distribution of the kernel crate
- I didn't add myself as maintainer, but (if requested) I'm willing to do so and help with maintenance

Changes in v2:
==============
- statically initialize driver structures (Greg)
- move base device ID abstractions to a separate source file (Greg)
- remove DeviceRemoval trait in favor of using a Devres callback to unregister drivers
- remove device::Data, we don't need this abstraction anymore now that we Devres to revoke resources and registrations
- pass the module name to Module::init and InPlaceModule::init in a separate patch
- rework of Io including compile time boundary checks (Miguel, Wedson)
- adjust PCI abstractions accordingly and implement a module_pci_driver! macro
- rework pci::Bar to support a const SIZE
- increase the total amount of Documentation, rephrase some safety comments and commit messages for less ambiguity
- fix compilation issues with some documentation examples

[1] https://gitlab.freedesktop.org/drm/nova/-/tree/nova-next
[2] https://github.com/metaspace/linux/tree/rnvme
[3] https://lore.kernel.org/all/[email protected]/
[4] https://git.kernel.org/pub/scm/linux/kernel/git/vireshk/linux.git/log/?h=rust/cpufreq-dt
[5] https://github.com/AsahiLinux/linux
[6] https://github.com/Fabo/linux/tree/fparent/rust-i2c
[7] https://github.com/Rust-for-Linux/linux/tree/staging/rust-device
[8] https://github.com/Rust-for-Linux/linux/tree/staging/rust-pci
[9] https://github.com/Rust-for-Linux/linux/tree/staging/dev

Danilo Krummrich (14):
rust: module: add trait ModuleMetadata
rust: implement generic driver registration
rust: implement IdArray, IdTable and RawDeviceId
rust: types: add Opaque::pin_init
rust: add io::{Io, IoRaw} base types
rust: add devres abstraction
rust: pci: add basic PCI device / driver abstractions
rust: pci: implement I/O mappable pci::Bar
samples: rust: add Rust PCI sample driver
rust: of: add of::DeviceId abstraction
rust: driver: implement Adapter
rust: platform: add basic platform device / driver abstractions
samples: rust: add Rust platform sample driver
MAINTAINERS: add Danilo to DRIVER CORE

Wedson Almeida Filho (2):
rust: add rcu abstraction
rust: add Revocable type

 MAINTAINERS                                  |  10 +
 drivers/of/unittest-data/tests-platform.dtsi |   5 +
 rust/bindings/bindings_helper.h              |   3 +
 rust/helpers/device.c                        |  10 +
 rust/helpers/helpers.c                       |   5 +
 rust/helpers/io.c                            | 101 +++++
 rust/helpers/pci.c                           |  18 +
 rust/helpers/platform.c                      |  13 +
 rust/helpers/rcu.c                           |  13 +
 rust/kernel/device_id.rs                     | 165 +++++++
 rust/kernel/devres.rs                        | 179 ++++++++
 rust/kernel/driver.rs                        | 174 ++++++++
 rust/kernel/io.rs                            | 260 +++++++++++
 rust/kernel/lib.rs                           |  20 +
 rust/kernel/of.rs                            |  60 +++
 rust/kernel/pci.rs                           | 438 +++++++++++++++++++
 rust/kernel/platform.rs                      | 198 +++++++++
 rust/kernel/revocable.rs                     | 223 ++++++++++
 rust/kernel/sync.rs                          |   1 +
 rust/kernel/sync/rcu.rs                      |  47 ++
 rust/kernel/types.rs                         |  11 +
 rust/macros/module.rs                        |   4 +
 samples/rust/Kconfig                         |  21 +
 samples/rust/Makefile                        |   2 +
 samples/rust/rust_driver_pci.rs              | 110 +++++
 samples/rust/rust_driver_platform.rs         |  49 +++
 26 files changed, 2140 insertions(+)
 create mode 100644 rust/helpers/device.c
 create mode 100644 rust/helpers/io.c
 create mode 100644 rust/helpers/pci.c
 create mode 100644 rust/helpers/platform.c
 create mode 100644 rust/helpers/rcu.c
 create mode 100644 rust/kernel/device_id.rs
 create mode 100644 rust/kernel/devres.rs
 create mode 100644 rust/kernel/driver.rs
 create mode 100644 rust/kernel/io.rs
 create mode 100644 rust/kernel/of.rs
 create mode 100644 rust/kernel/pci.rs
 create mode 100644 rust/kernel/platform.rs
 create mode 100644 rust/kernel/revocable.rs
 create mode 100644 rust/kernel/sync/rcu.rs
 create mode 100644 samples/rust/rust_driver_pci.rs
 create mode 100644 samples/rust/rust_driver_platform.rs

base-commit: fac04efc5c793dccbd07e2d59af9f90b7fc0dca4

Signature · 1 line
2.47.1
author maintainer
#1 lore raw

PATCH v6 1/16 rust: module: add trait `ModuleMetadata`

In order to access static metadata of a Rust kernel module, add the ModuleMetadata trait.

In particular, this trait provides the name of a Rust kernel module as specified by the module! macro.

---
 rust/kernel/lib.rs    | 6 ++++++
 rust/macros/module.rs | 4 ++++
 2 files changed, 10 insertions(+)
rust/kernel/lib.rs +6 -0
index e1065a7551a3..61b82b78b915 100644
@@ -116,6 +116,12 @@ fn init(module: &'static ThisModule) -> impl init::PinInit<Self, error::Error> {
     }
 }
 
+/// Metadata attached to a [`Module`] or [`InPlaceModule`].
+pub trait ModuleMetadata {
+    /// The name of the module as specified in the `module!` macro.
+    const NAME: &'static crate::str::CStr;
+}
+
 /// Equivalent to `THIS_MODULE` in the C API.
 ///
 /// C header: [`include/linux/init.h`](srctree/include/linux/init.h)
rust/macros/module.rs +4 -0
index 2587f41b0d39..cdf94f4982df 100644
@@ -228,6 +228,10 @@ pub(crate) fn module(ts: TokenStream) -> TokenStream {
                 kernel::ThisModule::from_ptr(core::ptr::null_mut())
             }};
 
+            impl kernel::ModuleMetadata for {type_} {{
+                const NAME: &'static kernel::str::CStr = kernel::c_str!(\"{name}\");
+            }}
+
             // Double nested modules, since then nobody can access the public items inside.
             mod __module_init {{
                 mod __module_init {{
Signature · 1 line
2.47.1
author maintainer
#2 lore raw

PATCH v6 2/16 rust: implement generic driver registration

In reply to Danilo Krummrich

Implement the generic Registration type and the RegistrationOps trait.

The Registration structure is the common type that represents a driver registration and is typically bound to the lifetime of a module. However, it doesn't implement actual calls to the kernel's driver core to register drivers itself.

Instead the RegistrationOps trait is provided to subsystems, which have to implement RegistrationOps::register and RegistrationOps::unregister. Subsystems have to provide an implementation for both of those methods where the subsystem specific variants to register / unregister a driver have to implemented.

For instance, the PCI subsystem would call __pci_register_driver() from RegistrationOps::register and pci_unregister_driver() from DrvierOps::unregister.

---
 MAINTAINERS           |   1 +
 rust/kernel/driver.rs | 117 ++++++++++++++++++++++++++++++++++++++++++
 rust/kernel/lib.rs    |   1 +
 3 files changed, 119 insertions(+)
 create mode 100644 rust/kernel/driver.rs
MAINTAINERS +1 -0
index 17daa9ee9384..93fa6ec737c0 100644
@@ -7033,6 +7033,7 @@ F:	include/linux/kobj*
 F:	include/linux/property.h
 F:	lib/kobj*
 F:	rust/kernel/device.rs
+F:	rust/kernel/driver.rs
 
 DRIVERS FOR OMAP ADAPTIVE VOLTAGE SCALING (AVS)
 M:	Nishanth Menon <[email protected]>
rust/kernel/driver.rs new file +117 -0

117 lines not on this page · raw message

rust/kernel/lib.rs +1 -0
index 61b82b78b915..7818407f9aac 100644
@@ -35,6 +35,7 @@
 mod build_assert;
 pub mod cred;
 pub mod device;
+pub mod driver;
 pub mod error;
 #[cfg(CONFIG_RUST_FW_LOADER_ABSTRACTIONS)]
 pub mod firmware;
Signature · 1 line
2.47.1
author maintainer
#3 lore raw

PATCH v6 3/16 rust: implement `IdArray`, `IdTable` and `RawDeviceId`

In reply to Danilo Krummrich

Most subsystems use some kind of ID to match devices and drivers. Hence, we have to provide Rust drivers an abstraction to register an ID table for the driver to match.

Generally, those IDs are subsystem specific and hence need to be implemented by the corresponding subsystem. However, the IdArray, IdTable and RawDeviceId types provide a generalized implementation that makes the life of subsystems easier to do so.

---
 MAINTAINERS              |   1 +
 rust/kernel/device_id.rs | 165 +++++++++++++++++++++++++++++++++++++++
 rust/kernel/lib.rs       |   6 ++
 3 files changed, 172 insertions(+)
 create mode 100644 rust/kernel/device_id.rs
MAINTAINERS +1 -0
index 93fa6ec737c0..1c12722bbe9a 100644
@@ -7033,6 +7033,7 @@ F:	include/linux/kobj*
 F:	include/linux/property.h
 F:	lib/kobj*
 F:	rust/kernel/device.rs
+F:	rust/kernel/device_id.rs
 F:	rust/kernel/driver.rs
 
 DRIVERS FOR OMAP ADAPTIVE VOLTAGE SCALING (AVS)
rust/kernel/device_id.rs new file +165 -0

165 lines not on this page · raw message

rust/kernel/lib.rs +6 -0
index 7818407f9aac..66149ac5c0c9 100644
@@ -18,6 +18,11 @@
 #![feature(inline_const)]
 #![feature(lint_reasons)]
 #![feature(unsize)]
+// Stable in Rust 1.83
+#![feature(const_maybe_uninit_as_mut_ptr)]
+#![feature(const_mut_refs)]
+#![feature(const_ptr_write)]
+#![feature(const_refs_to_cell)]
 
 // Ensure conditional compilation based on the kernel configuration works;
 // otherwise we may silently break things like initcall handling.
@@ -35,6 +40,7 @@
 mod build_assert;
 pub mod cred;
 pub mod device;
+pub mod device_id;
 pub mod driver;
 pub mod error;
 #[cfg(CONFIG_RUST_FW_LOADER_ABSTRACTIONS)]
Signature · 1 line
2.47.1
author maintainer
#4 lore raw

PATCH v6 4/16 rust: add rcu abstraction

In reply to Danilo Krummrich

From: Wedson Almeida Filho <[email protected]>

Add a simple abstraction to guard critical code sections with an rcu read lock.

---
 MAINTAINERS             |  1 +
 rust/helpers/helpers.c  |  1 +
 rust/helpers/rcu.c      | 13 ++++++++++++
 rust/kernel/sync.rs     |  1 +
 rust/kernel/sync/rcu.rs | 47 +++++++++++++++++++++++++++++++++++++++++
 5 files changed, 63 insertions(+)
 create mode 100644 rust/helpers/rcu.c
 create mode 100644 rust/kernel/sync/rcu.rs
MAINTAINERS +1 -0
index 1c12722bbe9a..f0259b6d958c 100644
@@ -19688,6 +19688,7 @@ T:	git git://git.kernel.org/pub/scm/linux/kernel/git/paulmck/linux-rcu.git dev
 F:	Documentation/RCU/
 F:	include/linux/rcu*
 F:	kernel/rcu/
+F:	rust/kernel/sync/rcu.rs
 X:	Documentation/RCU/torture.rst
 X:	include/linux/srcu*.h
 X:	kernel/rcu/srcu*.c
rust/helpers/helpers.c +1 -0
index dcf827a61b52..060750af6524 100644
@@ -20,6 +20,7 @@
 #include "page.c"
 #include "pid_namespace.c"
 #include "rbtree.c"
+#include "rcu.c"
 #include "refcount.c"
 #include "security.c"
 #include "signal.c"
rust/helpers/rcu.c new file +13 -0
new file mode 100644
index 000000000000..f1cec6583513
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/rcupdate.h>
+
+void rust_helper_rcu_read_lock(void)
+{
+	rcu_read_lock();
+}
+
+void rust_helper_rcu_read_unlock(void)
+{
+	rcu_read_unlock();
+}
rust/kernel/sync.rs +1 -0
index 1eab7ebf25fd..0654008198b2 100644
@@ -12,6 +12,7 @@
 pub mod lock;
 mod locked_by;
 pub mod poll;
+pub mod rcu;
 
 pub use arc::{Arc, ArcBorrow, UniqueArc};
 pub use condvar::{new_condvar, CondVar, CondVarTimeoutResult};
rust/kernel/sync/rcu.rs new file +47 -0

47 lines not on this page · raw message

Signature · 1 line
2.47.1
author maintainer
#5 lore raw

PATCH v6 5/16 rust: types: add `Opaque::pin_init`

In reply to Danilo Krummrich

Analogous to Opaque::new add Opaque::pin_init, which instead of a value T takes a PinInit<T> and returns a PinInit<Opaque<T>>.

---
 rust/kernel/types.rs | 11 +++++++++++
 1 file changed, 11 insertions(+)
rust/kernel/types.rs +11 -0
index ec6457bb3084..3aea6af9a0bc 100644
@@ -281,6 +281,17 @@ pub const fn uninit() -> Self {
         }
     }
 
+    /// Create an opaque pin-initializer from the given pin-initializer.
+    pub fn pin_init(slot: impl PinInit<T>) -> impl PinInit<Self> {
+        Self::ffi_init(|ptr: *mut T| {
+            // SAFETY:
+            //   - `ptr` is a valid pointer to uninitialized memory,
+            //   - `slot` is not accessed on error; the call is infallible,
+            //   - `slot` is pinned in memory.
+            let _ = unsafe { init::PinInit::<T>::__pinned_init(slot, ptr) };
+        })
+    }
+
     /// Creates a pin-initializer from the given initializer closure.
     ///
     /// The returned initializer calls the given closure with the pointer to the inner `T` of this
Signature · 1 line
2.47.1
author maintainer
#6 lore raw

PATCH v6 6/16 rust: add `Revocable` type

In reply to Danilo Krummrich

From: Wedson Almeida Filho <[email protected]>

Revocable allows access to objects to be safely revoked at run time.

This is useful, for example, for resources allocated during device probe; when the device is removed, the driver should stop accessing the device resources even if another state is kept in memory due to existing references (i.e., device context data is ref-counted and has a non-zero refcount after removal of the device).

---
 rust/kernel/lib.rs       |   1 +
 rust/kernel/revocable.rs | 223 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 224 insertions(+)
 create mode 100644 rust/kernel/revocable.rs
rust/kernel/lib.rs +1 -0
index 66149ac5c0c9..5702ce32ec8e 100644
@@ -60,6 +60,7 @@
 pub mod prelude;
 pub mod print;
 pub mod rbtree;
+pub mod revocable;
 pub mod security;
 pub mod seq_file;
 pub mod sizes;
rust/kernel/revocable.rs new file +223 -0

223 lines not on this page · raw message

Signature · 1 line
2.47.1
#7 lore raw

Re: [PATCH v6 06/16] rust: add `Revocable` type

Quoted 161 lines · On Thu, 12 Dec 2024 17:33:37 +0100 Danilo Krummrich <[email protected]> wrote:

From: Wedson Almeida Filho <[email protected]>

Revocable allows access to objects to be safely revoked at run time.

This is useful, for example, for resources allocated during device probe; when the device is removed, the driver should stop accessing the device resources even if another state is kept in memory due to existing references (i.e., device context data is ref-counted and has a non-zero refcount after removal of the device).

---
 rust/kernel/lib.rs       |   1 +
 rust/kernel/revocable.rs | 223 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 224 insertions(+)
 create mode 100644 rust/kernel/revocable.rs
rust/kernel/lib.rs +1 -0
index 66149ac5c0c9..5702ce32ec8e 100644
@@ -60,6 +60,7 @@
 pub mod prelude;
 pub mod print;
 pub mod rbtree;
+pub mod revocable;
 pub mod security;
 pub mod seq_file;
 pub mod sizes;
rust/kernel/revocable.rs new file +124 -0
new file mode 100644
index 000000000000..e464d59eb6b5
@@ -0,0 +1,223 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Revocable objects.
+//!
+//! The [`Revocable`] type wraps other types and allows access to them to be revoked. The existence
+//! of a [`RevocableGuard`] ensures that objects remain valid.
+
+use crate::{bindings, prelude::*, sync::rcu, types::Opaque};
+use core::{
+    marker::PhantomData,
+    ops::Deref,
+    ptr::drop_in_place,
+    sync::atomic::{AtomicBool, Ordering},
+};
+
+/// An object that can become inaccessible at runtime.
+///
+/// Once access is revoked and all concurrent users complete (i.e., all existing instances of
+/// [`RevocableGuard`] are dropped), the wrapped object is also dropped.
+///
+/// # Examples
+///
+/// ```
+/// # use kernel::revocable::Revocable;
+///
+/// struct Example {
+///     a: u32,
+///     b: u32,
+/// }
+///
+/// fn add_two(v: &Revocable<Example>) -> Option<u32> {
+///     let guard = v.try_access()?;
+///     Some(guard.a + guard.b)
+/// }
+///
+/// let v = KBox::pin_init(Revocable::new(Example { a: 10, b: 20 }), GFP_KERNEL).unwrap();
+/// assert_eq!(add_two(&v), Some(30));
+/// v.revoke();
+/// assert_eq!(add_two(&v), None);
+/// ```
+///
+/// Sample example as above, but explicitly using the rcu read side lock.
+///
+/// ```
+/// # use kernel::revocable::Revocable;
+/// use kernel::sync::rcu;
+///
+/// struct Example {
+///     a: u32,
+///     b: u32,
+/// }
+///
+/// fn add_two(v: &Revocable<Example>) -> Option<u32> {
+///     let guard = rcu::read_lock();
+///     let e = v.try_access_with_guard(&guard)?;
+///     Some(e.a + e.b)
+/// }
+///
+/// let v = KBox::pin_init(Revocable::new(Example { a: 10, b: 20 }), GFP_KERNEL).unwrap();
+/// assert_eq!(add_two(&v), Some(30));
+/// v.revoke();
+/// assert_eq!(add_two(&v), None);
+/// ```
+#[pin_data(PinnedDrop)]
+pub struct Revocable<T> {
+    is_available: AtomicBool,
+    #[pin]
+    data: Opaque<T>,
+}
+
+// SAFETY: `Revocable` is `Send` if the wrapped object is also `Send`. This is because while the
+// functionality exposed by `Revocable` can be accessed from any thread/CPU, it is possible that
+// this isn't supported by the wrapped object.
+unsafe impl<T: Send> Send for Revocable<T> {}
+
+// SAFETY: `Revocable` is `Sync` if the wrapped object is both `Send` and `Sync`. We require `Send`
+// from the wrapped object as well because  of `Revocable::revoke`, which can trigger the `Drop`
+// implementation of the wrapped object from an arbitrary thread.
+unsafe impl<T: Sync + Send> Sync for Revocable<T> {}
+
+impl<T> Revocable<T> {
+    /// Creates a new revocable instance of the given data.
+    pub fn new(data: impl PinInit<T>) -> impl PinInit<Self> {
+        pin_init!(Self {
+            is_available: AtomicBool::new(true),
+            data <- Opaque::pin_init(data),
+        })
+    }
+
+    /// Tries to access the revocable wrapped object.
+    ///
+    /// Returns `None` if the object has been revoked and is therefore no longer accessible.
+    ///
+    /// Returns a guard that gives access to the object otherwise; the object is guaranteed to
+    /// remain accessible while the guard is alive. In such cases, callers are not allowed to sleep
+    /// because another CPU may be waiting to complete the revocation of this object.
+    pub fn try_access(&self) -> Option<RevocableGuard<'_, T>> {
+        let guard = rcu::read_lock();
+        if self.is_available.load(Ordering::Relaxed) {
+            // Since `self.is_available` is true, data is initialised and has to remain valid
+            // because the RCU read side lock prevents it from being dropped.
+            Some(RevocableGuard::new(self.data.get(), guard))
+        } else {
+            None
+        }
+    }
+
+    /// Tries to access the revocable wrapped object.
+    ///
+    /// Returns `None` if the object has been revoked and is therefore no longer accessible.
+    ///
+    /// Returns a shared reference to the object otherwise; the object is guaranteed to
+    /// remain accessible while the rcu read side guard is alive. In such cases, callers are not
+    /// allowed to sleep because another CPU may be waiting to complete the revocation of this
+    /// object.
+    pub fn try_access_with_guard<'a>(&'a self, _guard: &'a rcu::Guard) -> Option<&'a T> {
+        if self.is_available.load(Ordering::Relaxed) {
+            // SAFETY: Since `self.is_available` is true, data is initialised and has to remain
+            // valid because the RCU read side lock prevents it from being dropped.
+            Some(unsafe { &*self.data.get() })
+        } else {
+            None
+        }
+    }

rust/kernel/revocable.rs line 134

Quoted 10 lines
rust/kernel/revocable.rs new file +10 -0
+
+    /// # Safety
+    ///
+    /// Callers must ensure that there are no more concurrent users of the revocable object.
+    unsafe fn revoke_internal<const SYNC: bool>(&self) {
+        if self
+            .is_available
+            .compare_exchange(true, false, Ordering::Relaxed, Ordering::Relaxed)
+            .is_ok()
+        {

The comment I made in a previous series was somehow lost, so I put it back here:
You can use self.is_available.swap(false, Ordering::Relaxed) instead of a CAS, it is IMO clearer and optimizes better on some architectures.

Quoted 90 lines
rust/kernel/revocable.rs new file +89 -1
+            if SYNC {
+                // SAFETY: Just an FFI call, there are no further requirements.
+                unsafe { bindings::synchronize_rcu() };
+            }
+
+            // SAFETY: We know `self.data` is valid because only one CPU can succeed the
+            // `compare_exchange` above that takes `is_available` from `true` to `false`.
+            unsafe { drop_in_place(self.data.get()) };
+        }
+    }
+
+    /// Revokes access to and drops the wrapped object.
+    ///
+    /// Access to the object is revoked immediately to new callers of [`Revocable::try_access`],
+    /// expecting that there are no concurrent users of the object.
+    ///
+    /// # Safety
+    ///
+    /// Callers must ensure that there are no more concurrent users of the revocable object.
+    pub unsafe fn revoke_nosync(&self) {
+        // SAFETY: By the safety requirement of this function, the caller ensures that nobody is
+        // accessing the data anymore and hence we don't have to wait for the grace period to
+        // finish.
+        unsafe { self.revoke_internal::<false>() }
+    }
+
+    /// Revokes access to and drops the wrapped object.
+    ///
+    /// Access to the object is revoked immediately to new callers of [`Revocable::try_access`].
+    ///
+    /// If there are concurrent users of the object (i.e., ones that called
+    /// [`Revocable::try_access`] beforehand and still haven't dropped the returned guard), this
+    /// function waits for the concurrent access to complete before dropping the wrapped object.
+    pub fn revoke(&self) {
+        // SAFETY: By passing `true` we ask `revoke_internal` to wait for the grace period to
+        // finish.
+        unsafe { self.revoke_internal::<true>() }
+    }
+}
+
+#[pinned_drop]
+impl<T> PinnedDrop for Revocable<T> {
+    fn drop(self: Pin<&mut Self>) {
+        // Drop only if the data hasn't been revoked yet (in which case it has already been
+        // dropped).
+        // SAFETY: We are not moving out of `p`, only dropping in place
+        let p = unsafe { self.get_unchecked_mut() };
+        if *p.is_available.get_mut() {
+            // SAFETY: We know `self.data` is valid because no other CPU has changed
+            // `is_available` to `false` yet, and no other CPU can do it anymore because this CPU
+            // holds the only reference (mutable) to `self` now.
+            unsafe { drop_in_place(p.data.get()) };
+        }
+    }
+}
+
+/// A guard that allows access to a revocable object and keeps it alive.
+///
+/// CPUs may not sleep while holding on to [`RevocableGuard`] because it's in atomic context
+/// holding the RCU read-side lock.
+///
+/// # Invariants
+///
+/// The RCU read-side lock is held while the guard is alive.
+pub struct RevocableGuard<'a, T> {
+    data_ref: *const T,
+    _rcu_guard: rcu::Guard,
+    _p: PhantomData<&'a ()>,
+}
+
+impl<T> RevocableGuard<'_, T> {
+    fn new(data_ref: *const T, rcu_guard: rcu::Guard) -> Self {
+        Self {
+            data_ref,
+            _rcu_guard: rcu_guard,
+            _p: PhantomData,
+        }
+    }
+}
+
+impl<T> Deref for RevocableGuard<'_, T> {
+    type Target = T;
+
+    fn deref(&self) -> &Self::Target {
+        // SAFETY: By the type invariants, we hold the rcu read-side lock, so the object is
+        // guaranteed to remain valid.
+        unsafe { &*self.data_ref }
+    }
+}
--
Quoted 3 lines

2.47.1

Benoît du Garreau

author maintainer
#8 lore raw

Re: [PATCH v6 06/16] rust: add `Revocable` type

Quoted 169 lines · Benoît du Garreau
Quoted 167 lines · On Thu, 12 Dec 2024 17:33:37 +0100 Danilo Krummrich <[email protected]> wrote:

From: Wedson Almeida Filho <[email protected]>

Revocable allows access to objects to be safely revoked at run time.

This is useful, for example, for resources allocated during device probe; when the device is removed, the driver should stop accessing the device resources even if another state is kept in memory due to existing references (i.e., device context data is ref-counted and has a non-zero refcount after removal of the device).

---
 rust/kernel/lib.rs       |   1 +
 rust/kernel/revocable.rs | 223 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 224 insertions(+)
 create mode 100644 rust/kernel/revocable.rs
rust/kernel/lib.rs +1 -0
index 66149ac5c0c9..5702ce32ec8e 100644
@@ -60,6 +60,7 @@
 pub mod prelude;
 pub mod print;
 pub mod rbtree;
+pub mod revocable;
 pub mod security;
 pub mod seq_file;
 pub mod sizes;
rust/kernel/revocable.rs new file +130 -0
new file mode 100644
index 000000000000..e464d59eb6b5
@@ -0,0 +1,223 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Revocable objects.
+//!
+//! The [`Revocable`] type wraps other types and allows access to them to be revoked. The existence
+//! of a [`RevocableGuard`] ensures that objects remain valid.
+
+use crate::{bindings, prelude::*, sync::rcu, types::Opaque};
+use core::{
+    marker::PhantomData,
+    ops::Deref,
+    ptr::drop_in_place,
+    sync::atomic::{AtomicBool, Ordering},
+};
+
+/// An object that can become inaccessible at runtime.
+///
+/// Once access is revoked and all concurrent users complete (i.e., all existing instances of
+/// [`RevocableGuard`] are dropped), the wrapped object is also dropped.
+///
+/// # Examples
+///
+/// ```
+/// # use kernel::revocable::Revocable;
+///
+/// struct Example {
+///     a: u32,
+///     b: u32,
+/// }
+///
+/// fn add_two(v: &Revocable<Example>) -> Option<u32> {
+///     let guard = v.try_access()?;
+///     Some(guard.a + guard.b)
+/// }
+///
+/// let v = KBox::pin_init(Revocable::new(Example { a: 10, b: 20 }), GFP_KERNEL).unwrap();
+/// assert_eq!(add_two(&v), Some(30));
+/// v.revoke();
+/// assert_eq!(add_two(&v), None);
+/// ```
+///
+/// Sample example as above, but explicitly using the rcu read side lock.
+///
+/// ```
+/// # use kernel::revocable::Revocable;
+/// use kernel::sync::rcu;
+///
+/// struct Example {
+///     a: u32,
+///     b: u32,
+/// }
+///
+/// fn add_two(v: &Revocable<Example>) -> Option<u32> {
+///     let guard = rcu::read_lock();
+///     let e = v.try_access_with_guard(&guard)?;
+///     Some(e.a + e.b)
+/// }
+///
+/// let v = KBox::pin_init(Revocable::new(Example { a: 10, b: 20 }), GFP_KERNEL).unwrap();
+/// assert_eq!(add_two(&v), Some(30));
+/// v.revoke();
+/// assert_eq!(add_two(&v), None);
+/// ```
+#[pin_data(PinnedDrop)]
+pub struct Revocable<T> {
+    is_available: AtomicBool,
+    #[pin]
+    data: Opaque<T>,
+}
+
+// SAFETY: `Revocable` is `Send` if the wrapped object is also `Send`. This is because while the
+// functionality exposed by `Revocable` can be accessed from any thread/CPU, it is possible that
+// this isn't supported by the wrapped object.
+unsafe impl<T: Send> Send for Revocable<T> {}
+
+// SAFETY: `Revocable` is `Sync` if the wrapped object is both `Send` and `Sync`. We require `Send`
+// from the wrapped object as well because  of `Revocable::revoke`, which can trigger the `Drop`
+// implementation of the wrapped object from an arbitrary thread.
+unsafe impl<T: Sync + Send> Sync for Revocable<T> {}
+
+impl<T> Revocable<T> {
+    /// Creates a new revocable instance of the given data.
+    pub fn new(data: impl PinInit<T>) -> impl PinInit<Self> {
+        pin_init!(Self {
+            is_available: AtomicBool::new(true),
+            data <- Opaque::pin_init(data),
+        })
+    }
+
+    /// Tries to access the revocable wrapped object.
+    ///
+    /// Returns `None` if the object has been revoked and is therefore no longer accessible.
+    ///
+    /// Returns a guard that gives access to the object otherwise; the object is guaranteed to
+    /// remain accessible while the guard is alive. In such cases, callers are not allowed to sleep
+    /// because another CPU may be waiting to complete the revocation of this object.
+    pub fn try_access(&self) -> Option<RevocableGuard<'_, T>> {
+        let guard = rcu::read_lock();
+        if self.is_available.load(Ordering::Relaxed) {
+            // Since `self.is_available` is true, data is initialised and has to remain valid
+            // because the RCU read side lock prevents it from being dropped.
+            Some(RevocableGuard::new(self.data.get(), guard))
+        } else {
+            None
+        }
+    }
+
+    /// Tries to access the revocable wrapped object.
+    ///
+    /// Returns `None` if the object has been revoked and is therefore no longer accessible.
+    ///
+    /// Returns a shared reference to the object otherwise; the object is guaranteed to
+    /// remain accessible while the rcu read side guard is alive. In such cases, callers are not
+    /// allowed to sleep because another CPU may be waiting to complete the revocation of this
+    /// object.
+    pub fn try_access_with_guard<'a>(&'a self, _guard: &'a rcu::Guard) -> Option<&'a T> {
+        if self.is_available.load(Ordering::Relaxed) {
+            // SAFETY: Since `self.is_available` is true, data is initialised and has to remain
+            // valid because the RCU read side lock prevents it from being dropped.
+            Some(unsafe { &*self.data.get() })
+        } else {
+            None
+        }
+    }
+
+    /// # Safety
+    ///
+    /// Callers must ensure that there are no more concurrent users of the revocable object.
+    unsafe fn revoke_internal<const SYNC: bool>(&self) {
+        if self
Quoted 8 lines

rust/kernel/revocable.rs line 134

Quoted 4 lines
rust/kernel/revocable.rs new file +4 -0
+            .is_available
+            .compare_exchange(true, false, Ordering::Relaxed, Ordering::Relaxed)
+            .is_ok()
+        {

The comment I made in a previous series was somehow lost, so I put it back here:
You can use self.is_available.swap(false, Ordering::Relaxed) instead of a CAS, it is IMO clearer and optimizes better on some architectures.

Thanks for bringing this up again!

Quoted 96 lines
Quoted 91 lines
rust/kernel/revocable.rs new file +89 -1
+            if SYNC {
+                // SAFETY: Just an FFI call, there are no further requirements.
+                unsafe { bindings::synchronize_rcu() };
+            }
+
+            // SAFETY: We know `self.data` is valid because only one CPU can succeed the
+            // `compare_exchange` above that takes `is_available` from `true` to `false`.
+            unsafe { drop_in_place(self.data.get()) };
+        }
+    }
+
+    /// Revokes access to and drops the wrapped object.
+    ///
+    /// Access to the object is revoked immediately to new callers of [`Revocable::try_access`],
+    /// expecting that there are no concurrent users of the object.
+    ///
+    /// # Safety
+    ///
+    /// Callers must ensure that there are no more concurrent users of the revocable object.
+    pub unsafe fn revoke_nosync(&self) {
+        // SAFETY: By the safety requirement of this function, the caller ensures that nobody is
+        // accessing the data anymore and hence we don't have to wait for the grace period to
+        // finish.
+        unsafe { self.revoke_internal::<false>() }
+    }
+
+    /// Revokes access to and drops the wrapped object.
+    ///
+    /// Access to the object is revoked immediately to new callers of [`Revocable::try_access`].
+    ///
+    /// If there are concurrent users of the object (i.e., ones that called
+    /// [`Revocable::try_access`] beforehand and still haven't dropped the returned guard), this
+    /// function waits for the concurrent access to complete before dropping the wrapped object.
+    pub fn revoke(&self) {
+        // SAFETY: By passing `true` we ask `revoke_internal` to wait for the grace period to
+        // finish.
+        unsafe { self.revoke_internal::<true>() }
+    }
+}
+
+#[pinned_drop]
+impl<T> PinnedDrop for Revocable<T> {
+    fn drop(self: Pin<&mut Self>) {
+        // Drop only if the data hasn't been revoked yet (in which case it has already been
+        // dropped).
+        // SAFETY: We are not moving out of `p`, only dropping in place
+        let p = unsafe { self.get_unchecked_mut() };
+        if *p.is_available.get_mut() {
+            // SAFETY: We know `self.data` is valid because no other CPU has changed
+            // `is_available` to `false` yet, and no other CPU can do it anymore because this CPU
+            // holds the only reference (mutable) to `self` now.
+            unsafe { drop_in_place(p.data.get()) };
+        }
+    }
+}
+
+/// A guard that allows access to a revocable object and keeps it alive.
+///
+/// CPUs may not sleep while holding on to [`RevocableGuard`] because it's in atomic context
+/// holding the RCU read-side lock.
+///
+/// # Invariants
+///
+/// The RCU read-side lock is held while the guard is alive.
+pub struct RevocableGuard<'a, T> {
+    data_ref: *const T,
+    _rcu_guard: rcu::Guard,
+    _p: PhantomData<&'a ()>,
+}
+
+impl<T> RevocableGuard<'_, T> {
+    fn new(data_ref: *const T, rcu_guard: rcu::Guard) -> Self {
+        Self {
+            data_ref,
+            _rcu_guard: rcu_guard,
+            _p: PhantomData,
+        }
+    }
+}
+
+impl<T> Deref for RevocableGuard<'_, T> {
+    type Target = T;
+
+    fn deref(&self) -> &Self::Target {
+        // SAFETY: By the type invariants, we hold the rcu read-side lock, so the object is
+        // guaranteed to remain valid.
+        unsafe { &*self.data_ref }
+    }
+}
--
Quoted 3 lines

2.47.1

Benoît du Garreau

author maintainer
#9 lore raw

PATCH v6 7/16 rust: add `io::{Io, IoRaw}` base types

In reply to Danilo Krummrich

I/O memory is typically either mapped through direct calls to ioremap() or subsystem / bus specific ones such as pci_iomap().

Even though subsystem / bus specific functions to map I/O memory are based on ioremap() / iounmap() it is not desirable to re-implement them in Rust.

Instead, implement a base type for I/O mapped memory, which generically provides the corresponding accessors, such as Io::readb or Io:try_readb.

Io supports an optional const generic, such that a driver can indicate the minimal expected and required size of the mapping at compile time. Correspondingly, calls to the 'non-try' accessors, support compile time checks of the I/O memory offset to read / write, while the 'try' accessors, provide boundary checks on runtime.

IoRaw is meant to be embedded into a structure (e.g. pci::Bar or io::IoMem) which creates the actual I/O memory mapping and initializes IoRaw accordingly.

To ensure that I/O mapped memory can't out-live the device it may be bound to, subsystems must embed the corresponding I/O memory type (e.g. pci::Bar) into a Devres container, such that it gets revoked once the device is unbound.

---
 rust/helpers/helpers.c |   1 +
 rust/helpers/io.c      | 101 ++++++++++++++++
 rust/kernel/io.rs      | 260 +++++++++++++++++++++++++++++++++++++++++
 rust/kernel/lib.rs     |   1 +
 4 files changed, 363 insertions(+)
 create mode 100644 rust/helpers/io.c
 create mode 100644 rust/kernel/io.rs
rust/helpers/helpers.c +1 -0
index 060750af6524..63f9b1da179f 100644
@@ -14,6 +14,7 @@
 #include "cred.c"
 #include "err.c"
 #include "fs.c"
+#include "io.c"
 #include "jump_label.c"
 #include "kunit.c"
 #include "mutex.c"
rust/helpers/io.c new file +101 -0

101 lines not on this page · raw message

rust/kernel/io.rs new file +260 -0

260 lines not on this page · raw message

rust/kernel/lib.rs +1 -0
index 5702ce32ec8e..6c836ab73771 100644
@@ -79,6 +79,7 @@
 
 #[doc(hidden)]
 pub use bindings;
+pub mod io;
 pub use macros;
 pub use uapi;
 
Signature · 1 line
2.47.1
author maintainer
#10 lore raw

Re: [PATCH v6 07/16] rust: add `io::{Io, IoRaw}` base types

Quoted 59 lines · Danilo Krummrich

I/O memory is typically either mapped through direct calls to ioremap() or subsystem / bus specific ones such as pci_iomap().

Even though subsystem / bus specific functions to map I/O memory are based on ioremap() / iounmap() it is not desirable to re-implement them in Rust.

Instead, implement a base type for I/O mapped memory, which generically provides the corresponding accessors, such as Io::readb or Io:try_readb.

Io supports an optional const generic, such that a driver can indicate the minimal expected and required size of the mapping at compile time. Correspondingly, calls to the 'non-try' accessors, support compile time checks of the I/O memory offset to read / write, while the 'try' accessors, provide boundary checks on runtime.

IoRaw is meant to be embedded into a structure (e.g. pci::Bar or io::IoMem) which creates the actual I/O memory mapping and initializes IoRaw accordingly.

To ensure that I/O mapped memory can't out-live the device it may be bound to, subsystems must embed the corresponding I/O memory type (e.g. pci::Bar) into a Devres container, such that it gets revoked once the device is unbound.

---
 rust/helpers/helpers.c |   1 +
 rust/helpers/io.c      | 101 ++++++++++++++++
 rust/kernel/io.rs      | 260 +++++++++++++++++++++++++++++++++++++++++
 rust/kernel/lib.rs     |   1 +
 4 files changed, 363 insertions(+)
 create mode 100644 rust/helpers/io.c
 create mode 100644 rust/kernel/io.rs
rust/helpers/helpers.c +1 -0
index 060750af6524..63f9b1da179f 100644
@@ -14,6 +14,7 @@
 #include "cred.c"
 #include "err.c"
 #include "fs.c"
+#include "io.c"
 #include "jump_label.c"
 #include "kunit.c"
 #include "mutex.c"
rust/helpers/io.c new file +2 -0
new file mode 100644
index 000000000000..1dde6374c0e2
@@ -0,0 +1,101 @@
+// SPDX-License-Identifier: GPL-2.0
+

rust/helpers/io.c line 12

Quoted 10 lines
rust/helpers/io.c new file +10 -0
+#include <linux/io.h>
+
+void __iomem *rust_helper_ioremap(phys_addr_t offset, size_t size)
+{
+	return ioremap(offset, size);
+}
+
+void rust_helper_iounmap(volatile void __iomem *addr)
+{
+	return iounmap(addr);

Copy-paste mistake, obviously this return statement shouldn't be here.

Quoted 370 lines
rust/helpers/io.c new file +89 -0
+}
+
+u8 rust_helper_readb(const volatile void __iomem *addr)
+{
+	return readb(addr);
+}
+
+u16 rust_helper_readw(const volatile void __iomem *addr)
+{
+	return readw(addr);
+}
+
+u32 rust_helper_readl(const volatile void __iomem *addr)
+{
+	return readl(addr);
+}
+
+#ifdef CONFIG_64BIT
+u64 rust_helper_readq(const volatile void __iomem *addr)
+{
+	return readq(addr);
+}
+#endif
+
+void rust_helper_writeb(u8 value, volatile void __iomem *addr)
+{
+	writeb(value, addr);
+}
+
+void rust_helper_writew(u16 value, volatile void __iomem *addr)
+{
+	writew(value, addr);
+}
+
+void rust_helper_writel(u32 value, volatile void __iomem *addr)
+{
+	writel(value, addr);
+}
+
+#ifdef CONFIG_64BIT
+void rust_helper_writeq(u64 value, volatile void __iomem *addr)
+{
+	writeq(value, addr);
+}
+#endif
+
+u8 rust_helper_readb_relaxed(const volatile void __iomem *addr)
+{
+	return readb_relaxed(addr);
+}
+
+u16 rust_helper_readw_relaxed(const volatile void __iomem *addr)
+{
+	return readw_relaxed(addr);
+}
+
+u32 rust_helper_readl_relaxed(const volatile void __iomem *addr)
+{
+	return readl_relaxed(addr);
+}
+
+#ifdef CONFIG_64BIT
+u64 rust_helper_readq_relaxed(const volatile void __iomem *addr)
+{
+	return readq_relaxed(addr);
+}
+#endif
+
+void rust_helper_writeb_relaxed(u8 value, volatile void __iomem *addr)
+{
+	writeb_relaxed(value, addr);
+}
+
+void rust_helper_writew_relaxed(u16 value, volatile void __iomem *addr)
+{
+	writew_relaxed(value, addr);
+}
+
+void rust_helper_writel_relaxed(u32 value, volatile void __iomem *addr)
+{
+	writel_relaxed(value, addr);
+}
+
+#ifdef CONFIG_64BIT
+void rust_helper_writeq_relaxed(u64 value, volatile void __iomem *addr)
+{
+	writeq_relaxed(value, addr);
+}
+#endif
rust/kernel/io.rs new file +260 -0
new file mode 100644
index 000000000000..7ec3341bb411
@@ -0,0 +1,260 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Memory-mapped IO.
+//!
+//! C header: [`include/asm-generic/io.h`](srctree/include/asm-generic/io.h)
+
+use crate::error::{code::EINVAL, Result};
+use crate::{bindings, build_assert};
+
+/// Raw representation of an MMIO region.
+///
+/// By itself, the existence of an instance of this structure does not provide any guarantees that
+/// the represented MMIO region does exist or is properly mapped.
+///
+/// Instead, the bus specific MMIO implementation must convert this raw representation into an `Io`
+/// instance providing the actual memory accessors. Only by the conversion into an `Io` structure
+/// any guarantees are given.
+pub struct IoRaw<const SIZE: usize = 0> {
+    addr: usize,
+    maxsize: usize,
+}
+
+impl<const SIZE: usize> IoRaw<SIZE> {
+    /// Returns a new `IoRaw` instance on success, an error otherwise.
+    pub fn new(addr: usize, maxsize: usize) -> Result<Self> {
+        if maxsize < SIZE {
+            return Err(EINVAL);
+        }
+
+        Ok(Self { addr, maxsize })
+    }
+
+    /// Returns the base address of the MMIO region.
+    #[inline]
+    pub fn addr(&self) -> usize {
+        self.addr
+    }
+
+    /// Returns the maximum size of the MMIO region.
+    #[inline]
+    pub fn maxsize(&self) -> usize {
+        self.maxsize
+    }
+}
+
+/// IO-mapped memory, starting at the base address @addr and spanning @maxlen bytes.
+///
+/// The creator (usually a subsystem / bus such as PCI) is responsible for creating the
+/// mapping, performing an additional region request etc.
+///
+/// # Invariant
+///
+/// `addr` is the start and `maxsize` the length of valid I/O mapped memory region of size
+/// `maxsize`.
+///
+/// # Examples
+///
+/// ```no_run
+/// # use kernel::{bindings, io::{Io, IoRaw}};
+/// # use core::ops::Deref;
+///
+/// // See also [`pci::Bar`] for a real example.
+/// struct IoMem<const SIZE: usize>(IoRaw<SIZE>);
+///
+/// impl<const SIZE: usize> IoMem<SIZE> {
+///     /// # Safety
+///     ///
+///     /// [`paddr`, `paddr` + `SIZE`) must be a valid MMIO region that is mappable into the CPUs
+///     /// virtual address space.
+///     unsafe fn new(paddr: usize) -> Result<Self>{
+///         // SAFETY: By the safety requirements of this function [`paddr`, `paddr` + `SIZE`) is
+///         // valid for `ioremap`.
+///         let addr = unsafe { bindings::ioremap(paddr as _, SIZE.try_into().unwrap()) };
+///         if addr.is_null() {
+///             return Err(ENOMEM);
+///         }
+///
+///         Ok(IoMem(IoRaw::new(addr as _, SIZE)?))
+///     }
+/// }
+///
+/// impl<const SIZE: usize> Drop for IoMem<SIZE> {
+///     fn drop(&mut self) {
+///         // SAFETY: `self.0.addr()` is guaranteed to be properly mapped by `Self::new`.
+///         unsafe { bindings::iounmap(self.0.addr() as _); };
+///     }
+/// }
+///
+/// impl<const SIZE: usize> Deref for IoMem<SIZE> {
+///    type Target = Io<SIZE>;
+///
+///    fn deref(&self) -> &Self::Target {
+///         // SAFETY: The memory range stored in `self` has been properly mapped in `Self::new`.
+///         unsafe { Io::from_raw(&self.0) }
+///    }
+/// }
+///
+///# fn no_run() -> Result<(), Error> {
+/// // SAFETY: Invalid usage for example purposes.
+/// let iomem = unsafe { IoMem::<{ core::mem::size_of::<u32>() }>::new(0xBAAAAAAD)? };
+/// iomem.writel(0x42, 0x0);
+/// assert!(iomem.try_writel(0x42, 0x0).is_ok());
+/// assert!(iomem.try_writel(0x42, 0x4).is_err());
+/// # Ok(())
+/// # }
+/// ```
+#[repr(transparent)]
+pub struct Io<const SIZE: usize = 0>(IoRaw<SIZE>);
+
+macro_rules! define_read {
+    ($(#[$attr:meta])* $name:ident, $try_name:ident, $type_name:ty) => {
+        /// Read IO data from a given offset known at compile time.
+        ///
+        /// Bound checks are performed on compile time, hence if the offset is not known at compile
+        /// time, the build will fail.
+        $(#[$attr])*
+        #[inline]
+        pub fn $name(&self, offset: usize) -> $type_name {
+            let addr = self.io_addr_assert::<$type_name>(offset);
+
+            // SAFETY: By the type invariant `addr` is a valid address for MMIO operations.
+            unsafe { bindings::$name(addr as _) }
+        }
+
+        /// Read IO data from a given offset.
+        ///
+        /// Bound checks are performed on runtime, it fails if the offset (plus the type size) is
+        /// out of bounds.
+        $(#[$attr])*
+        pub fn $try_name(&self, offset: usize) -> Result<$type_name> {
+            let addr = self.io_addr::<$type_name>(offset)?;
+
+            // SAFETY: By the type invariant `addr` is a valid address for MMIO operations.
+            Ok(unsafe { bindings::$name(addr as _) })
+        }
+    };
+}
+
+macro_rules! define_write {
+    ($(#[$attr:meta])* $name:ident, $try_name:ident, $type_name:ty) => {
+        /// Write IO data from a given offset known at compile time.
+        ///
+        /// Bound checks are performed on compile time, hence if the offset is not known at compile
+        /// time, the build will fail.
+        $(#[$attr])*
+        #[inline]
+        pub fn $name(&self, value: $type_name, offset: usize) {
+            let addr = self.io_addr_assert::<$type_name>(offset);
+
+            // SAFETY: By the type invariant `addr` is a valid address for MMIO operations.
+            unsafe { bindings::$name(value, addr as _, ) }
+        }
+
+        /// Write IO data from a given offset.
+        ///
+        /// Bound checks are performed on runtime, it fails if the offset (plus the type size) is
+        /// out of bounds.
+        $(#[$attr])*
+        pub fn $try_name(&self, value: $type_name, offset: usize) -> Result {
+            let addr = self.io_addr::<$type_name>(offset)?;
+
+            // SAFETY: By the type invariant `addr` is a valid address for MMIO operations.
+            unsafe { bindings::$name(value, addr as _) }
+            Ok(())
+        }
+    };
+}
+
+impl<const SIZE: usize> Io<SIZE> {
+    /// Converts an `IoRaw` into an `Io` instance, providing the accessors to the MMIO mapping.
+    ///
+    /// # Safety
+    ///
+    /// Callers must ensure that `addr` is the start of a valid I/O mapped memory region of size
+    /// `maxsize`.
+    pub unsafe fn from_raw(raw: &IoRaw<SIZE>) -> &Self {
+        // SAFETY: `Io` is a transparent wrapper around `IoRaw`.
+        unsafe { &*core::ptr::from_ref(raw).cast() }
+    }
+
+    /// Returns the base address of this mapping.
+    #[inline]
+    pub fn addr(&self) -> usize {
+        self.0.addr()
+    }
+
+    /// Returns the maximum size of this mapping.
+    #[inline]
+    pub fn maxsize(&self) -> usize {
+        self.0.maxsize()
+    }
+
+    #[inline]
+    const fn offset_valid<U>(offset: usize, size: usize) -> bool {
+        let type_size = core::mem::size_of::<U>();
+        if let Some(end) = offset.checked_add(type_size) {
+            end <= size && offset % type_size == 0
+        } else {
+            false
+        }
+    }
+
+    #[inline]
+    fn io_addr<U>(&self, offset: usize) -> Result<usize> {
+        if !Self::offset_valid::<U>(offset, self.maxsize()) {
+            return Err(EINVAL);
+        }
+
+        // Probably no need to check, since the safety requirements of `Self::new` guarantee that
+        // this can't overflow.
+        self.addr().checked_add(offset).ok_or(EINVAL)
+    }
+
+    #[inline]
+    fn io_addr_assert<U>(&self, offset: usize) -> usize {
+        build_assert!(Self::offset_valid::<U>(offset, SIZE));
+
+        self.addr() + offset
+    }
+
+    define_read!(readb, try_readb, u8);
+    define_read!(readw, try_readw, u16);
+    define_read!(readl, try_readl, u32);
+    define_read!(
+        #[cfg(CONFIG_64BIT)]
+        readq,
+        try_readq,
+        u64
+    );
+
+    define_read!(readb_relaxed, try_readb_relaxed, u8);
+    define_read!(readw_relaxed, try_readw_relaxed, u16);
+    define_read!(readl_relaxed, try_readl_relaxed, u32);
+    define_read!(
+        #[cfg(CONFIG_64BIT)]
+        readq_relaxed,
+        try_readq_relaxed,
+        u64
+    );
+
+    define_write!(writeb, try_writeb, u8);
+    define_write!(writew, try_writew, u16);
+    define_write!(writel, try_writel, u32);
+    define_write!(
+        #[cfg(CONFIG_64BIT)]
+        writeq,
+        try_writeq,
+        u64
+    );
+
+    define_write!(writeb_relaxed, try_writeb_relaxed, u8);
+    define_write!(writew_relaxed, try_writew_relaxed, u16);
+    define_write!(writel_relaxed, try_writel_relaxed, u32);
+    define_write!(
+        #[cfg(CONFIG_64BIT)]
+        writeq_relaxed,
+        try_writeq_relaxed,
+        u64
+    );
+}
rust/kernel/lib.rs +1 -0
index 5702ce32ec8e..6c836ab73771 100644
@@ -79,6 +79,7 @@
 
 #[doc(hidden)]
 pub use bindings;
+pub mod io;
 pub use macros;
 pub use uapi;
 
Signature · 1 line
2.47.1
#11 lore raw

Re: [PATCH v6 07/16] rust: add `io::{Io, IoRaw}` base types

Quoted 233 lines · Danilo Krummrich
Quoted 59 lines · Danilo Krummrich

I/O memory is typically either mapped through direct calls to ioremap() or subsystem / bus specific ones such as pci_iomap().

Even though subsystem / bus specific functions to map I/O memory are based on ioremap() / iounmap() it is not desirable to re-implement them in Rust.

Instead, implement a base type for I/O mapped memory, which generically provides the corresponding accessors, such as Io::readb or Io:try_readb.

Io supports an optional const generic, such that a driver can indicate the minimal expected and required size of the mapping at compile time. Correspondingly, calls to the 'non-try' accessors, support compile time checks of the I/O memory offset to read / write, while the 'try' accessors, provide boundary checks on runtime.

IoRaw is meant to be embedded into a structure (e.g. pci::Bar or io::IoMem) which creates the actual I/O memory mapping and initializes IoRaw accordingly.

To ensure that I/O mapped memory can't out-live the device it may be bound to, subsystems must embed the corresponding I/O memory type (e.g. pci::Bar) into a Devres container, such that it gets revoked once the device is unbound.

---
 rust/helpers/helpers.c |   1 +
 rust/helpers/io.c      | 101 ++++++++++++++++
 rust/kernel/io.rs      | 260 +++++++++++++++++++++++++++++++++++++++++
 rust/kernel/lib.rs     |   1 +
 4 files changed, 363 insertions(+)
 create mode 100644 rust/helpers/io.c
 create mode 100644 rust/kernel/io.rs
rust/helpers/helpers.c +1 -0
index 060750af6524..63f9b1da179f 100644
@@ -14,6 +14,7 @@
 #include "cred.c"
 #include "err.c"
 #include "fs.c"
+#include "io.c"
 #include "jump_label.c"
 #include "kunit.c"
 #include "mutex.c"
rust/helpers/io.c new file +2 -0
new file mode 100644
index 000000000000..1dde6374c0e2
@@ -0,0 +1,101 @@
+// SPDX-License-Identifier: GPL-2.0
+

rust/helpers/io.c line 12

Quoted 10 lines
rust/helpers/io.c new file +10 -0
+#include <linux/io.h>
+
+void __iomem *rust_helper_ioremap(phys_addr_t offset, size_t size)
+{
+	return ioremap(offset, size);
+}
+
+void rust_helper_iounmap(volatile void __iomem *addr)
+{
+	return iounmap(addr);

Copy-paste mistake, obviously this return statement shouldn't be here.

Quoted 160 lines
rust/helpers/io.c new file +89 -0
+}
+
+u8 rust_helper_readb(const volatile void __iomem *addr)
+{
+	return readb(addr);
+}
+
+u16 rust_helper_readw(const volatile void __iomem *addr)
+{
+	return readw(addr);
+}
+
+u32 rust_helper_readl(const volatile void __iomem *addr)
+{
+	return readl(addr);
+}
+
+#ifdef CONFIG_64BIT
+u64 rust_helper_readq(const volatile void __iomem *addr)
+{
+	return readq(addr);
+}
+#endif
+
+void rust_helper_writeb(u8 value, volatile void __iomem *addr)
+{
+	writeb(value, addr);
+}
+
+void rust_helper_writew(u16 value, volatile void __iomem *addr)
+{
+	writew(value, addr);
+}
+
+void rust_helper_writel(u32 value, volatile void __iomem *addr)
+{
+	writel(value, addr);
+}
+
+#ifdef CONFIG_64BIT
+void rust_helper_writeq(u64 value, volatile void __iomem *addr)
+{
+	writeq(value, addr);
+}
+#endif
+
+u8 rust_helper_readb_relaxed(const volatile void __iomem *addr)
+{
+	return readb_relaxed(addr);
+}
+
+u16 rust_helper_readw_relaxed(const volatile void __iomem *addr)
+{
+	return readw_relaxed(addr);
+}
+
+u32 rust_helper_readl_relaxed(const volatile void __iomem *addr)
+{
+	return readl_relaxed(addr);
+}
+
+#ifdef CONFIG_64BIT
+u64 rust_helper_readq_relaxed(const volatile void __iomem *addr)
+{
+	return readq_relaxed(addr);
+}
+#endif
+
+void rust_helper_writeb_relaxed(u8 value, volatile void __iomem *addr)
+{
+	writeb_relaxed(value, addr);
+}
+
+void rust_helper_writew_relaxed(u16 value, volatile void __iomem *addr)
+{
+	writew_relaxed(value, addr);
+}
+
+void rust_helper_writel_relaxed(u32 value, volatile void __iomem *addr)
+{
+	writel_relaxed(value, addr);
+}
+
+#ifdef CONFIG_64BIT
+void rust_helper_writeq_relaxed(u64 value, volatile void __iomem *addr)
+{
+	writeq_relaxed(value, addr);
+}
+#endif
rust/kernel/io.rs new file +65 -0
new file mode 100644
index 000000000000..7ec3341bb411
@@ -0,0 +1,260 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Memory-mapped IO.
+//!
+//! C header: [`include/asm-generic/io.h`](srctree/include/asm-generic/io.h)
+
+use crate::error::{code::EINVAL, Result};
+use crate::{bindings, build_assert};
+
+/// Raw representation of an MMIO region.
+///
+/// By itself, the existence of an instance of this structure does not provide any guarantees that
+/// the represented MMIO region does exist or is properly mapped.
+///
+/// Instead, the bus specific MMIO implementation must convert this raw representation into an `Io`
+/// instance providing the actual memory accessors. Only by the conversion into an `Io` structure
+/// any guarantees are given.
+pub struct IoRaw<const SIZE: usize = 0> {
+    addr: usize,
+    maxsize: usize,
+}
+
+impl<const SIZE: usize> IoRaw<SIZE> {
+    /// Returns a new `IoRaw` instance on success, an error otherwise.
+    pub fn new(addr: usize, maxsize: usize) -> Result<Self> {
+        if maxsize < SIZE {
+            return Err(EINVAL);
+        }
+
+        Ok(Self { addr, maxsize })
+    }
+
+    /// Returns the base address of the MMIO region.
+    #[inline]
+    pub fn addr(&self) -> usize {
+        self.addr
+    }
+
+    /// Returns the maximum size of the MMIO region.
+    #[inline]
+    pub fn maxsize(&self) -> usize {
+        self.maxsize
+    }
+}
+
+/// IO-mapped memory, starting at the base address @addr and spanning @maxlen bytes.
+///
+/// The creator (usually a subsystem / bus such as PCI) is responsible for creating the
+/// mapping, performing an additional region request etc.
+///
+/// # Invariant
+///
+/// `addr` is the start and `maxsize` the length of valid I/O mapped memory region of size
+/// `maxsize`.
+///
+/// # Examples
+///
+/// ```no_run
+/// # use kernel::{bindings, io::{Io, IoRaw}};
+/// # use core::ops::Deref;
+///
+/// // See also [`pci::Bar`] for a real example.
+/// struct IoMem<const SIZE: usize>(IoRaw<SIZE>);
+///
+/// impl<const SIZE: usize> IoMem<SIZE> {
Quoted 8 lines

rust/kernel/io.rs line 73

Quoted 8 lines
rust/kernel/io.rs new file +8 -0
+///     /// # Safety
+///     ///
+///     /// [`paddr`, `paddr` + `SIZE`) must be a valid MMIO region that is mappable into the CPUs
+///     /// virtual address space.
+///     unsafe fn new(paddr: usize) -> Result<Self>{
+///         // SAFETY: By the safety requirements of this function [`paddr`, `paddr` + `SIZE`) is
+///         // valid for `ioremap`.
+///         let addr = unsafe { bindings::ioremap(paddr as _, SIZE.try_into().unwrap()) };

This is a problematic API. ioremap() does not work on some platforms like Apple Silicon. Instead, you have to use ioremap_np() for most devices.

Please add a bindings::resource abstraction and use that to construct IoMem. Then, you can check the flags for bindings::IORESOURCE_MEM_NONPOSTED and use the appropriate function, like this:

https://github.com/AsahiLinux/linux/blob/fce34c83f1dca5b10cc2c866fd8832a362de7974/rust/kernel/io_mem.rs#L152

Quoted 199 lines
Quoted 199 lines
rust/kernel/io.rs new file +187 -0
+///         if addr.is_null() {
+///             return Err(ENOMEM);
+///         }
+///
+///         Ok(IoMem(IoRaw::new(addr as _, SIZE)?))
+///     }
+/// }
+///
+/// impl<const SIZE: usize> Drop for IoMem<SIZE> {
+///     fn drop(&mut self) {
+///         // SAFETY: `self.0.addr()` is guaranteed to be properly mapped by `Self::new`.
+///         unsafe { bindings::iounmap(self.0.addr() as _); };
+///     }
+/// }
+///
+/// impl<const SIZE: usize> Deref for IoMem<SIZE> {
+///    type Target = Io<SIZE>;
+///
+///    fn deref(&self) -> &Self::Target {
+///         // SAFETY: The memory range stored in `self` has been properly mapped in `Self::new`.
+///         unsafe { Io::from_raw(&self.0) }
+///    }
+/// }
+///
+///# fn no_run() -> Result<(), Error> {
+/// // SAFETY: Invalid usage for example purposes.
+/// let iomem = unsafe { IoMem::<{ core::mem::size_of::<u32>() }>::new(0xBAAAAAAD)? };
+/// iomem.writel(0x42, 0x0);
+/// assert!(iomem.try_writel(0x42, 0x0).is_ok());
+/// assert!(iomem.try_writel(0x42, 0x4).is_err());
+/// # Ok(())
+/// # }
+/// ```
+#[repr(transparent)]
+pub struct Io<const SIZE: usize = 0>(IoRaw<SIZE>);
+
+macro_rules! define_read {
+    ($(#[$attr:meta])* $name:ident, $try_name:ident, $type_name:ty) => {
+        /// Read IO data from a given offset known at compile time.
+        ///
+        /// Bound checks are performed on compile time, hence if the offset is not known at compile
+        /// time, the build will fail.
+        $(#[$attr])*
+        #[inline]
+        pub fn $name(&self, offset: usize) -> $type_name {
+            let addr = self.io_addr_assert::<$type_name>(offset);
+
+            // SAFETY: By the type invariant `addr` is a valid address for MMIO operations.
+            unsafe { bindings::$name(addr as _) }
+        }
+
+        /// Read IO data from a given offset.
+        ///
+        /// Bound checks are performed on runtime, it fails if the offset (plus the type size) is
+        /// out of bounds.
+        $(#[$attr])*
+        pub fn $try_name(&self, offset: usize) -> Result<$type_name> {
+            let addr = self.io_addr::<$type_name>(offset)?;
+
+            // SAFETY: By the type invariant `addr` is a valid address for MMIO operations.
+            Ok(unsafe { bindings::$name(addr as _) })
+        }
+    };
+}
+
+macro_rules! define_write {
+    ($(#[$attr:meta])* $name:ident, $try_name:ident, $type_name:ty) => {
+        /// Write IO data from a given offset known at compile time.
+        ///
+        /// Bound checks are performed on compile time, hence if the offset is not known at compile
+        /// time, the build will fail.
+        $(#[$attr])*
+        #[inline]
+        pub fn $name(&self, value: $type_name, offset: usize) {
+            let addr = self.io_addr_assert::<$type_name>(offset);
+
+            // SAFETY: By the type invariant `addr` is a valid address for MMIO operations.
+            unsafe { bindings::$name(value, addr as _, ) }
+        }
+
+        /// Write IO data from a given offset.
+        ///
+        /// Bound checks are performed on runtime, it fails if the offset (plus the type size) is
+        /// out of bounds.
+        $(#[$attr])*
+        pub fn $try_name(&self, value: $type_name, offset: usize) -> Result {
+            let addr = self.io_addr::<$type_name>(offset)?;
+
+            // SAFETY: By the type invariant `addr` is a valid address for MMIO operations.
+            unsafe { bindings::$name(value, addr as _) }
+            Ok(())
+        }
+    };
+}
+
+impl<const SIZE: usize> Io<SIZE> {
+    /// Converts an `IoRaw` into an `Io` instance, providing the accessors to the MMIO mapping.
+    ///
+    /// # Safety
+    ///
+    /// Callers must ensure that `addr` is the start of a valid I/O mapped memory region of size
+    /// `maxsize`.
+    pub unsafe fn from_raw(raw: &IoRaw<SIZE>) -> &Self {
+        // SAFETY: `Io` is a transparent wrapper around `IoRaw`.
+        unsafe { &*core::ptr::from_ref(raw).cast() }
+    }
+
+    /// Returns the base address of this mapping.
+    #[inline]
+    pub fn addr(&self) -> usize {
+        self.0.addr()
+    }
+
+    /// Returns the maximum size of this mapping.
+    #[inline]
+    pub fn maxsize(&self) -> usize {
+        self.0.maxsize()
+    }
+
+    #[inline]
+    const fn offset_valid<U>(offset: usize, size: usize) -> bool {
+        let type_size = core::mem::size_of::<U>();
+        if let Some(end) = offset.checked_add(type_size) {
+            end <= size && offset % type_size == 0
+        } else {
+            false
+        }
+    }
+
+    #[inline]
+    fn io_addr<U>(&self, offset: usize) -> Result<usize> {
+        if !Self::offset_valid::<U>(offset, self.maxsize()) {
+            return Err(EINVAL);
+        }
+
+        // Probably no need to check, since the safety requirements of `Self::new` guarantee that
+        // this can't overflow.
+        self.addr().checked_add(offset).ok_or(EINVAL)
+    }
+
+    #[inline]
+    fn io_addr_assert<U>(&self, offset: usize) -> usize {
+        build_assert!(Self::offset_valid::<U>(offset, SIZE));
+
+        self.addr() + offset
+    }
+
+    define_read!(readb, try_readb, u8);
+    define_read!(readw, try_readw, u16);
+    define_read!(readl, try_readl, u32);
+    define_read!(
+        #[cfg(CONFIG_64BIT)]
+        readq,
+        try_readq,
+        u64
+    );
+
+    define_read!(readb_relaxed, try_readb_relaxed, u8);
+    define_read!(readw_relaxed, try_readw_relaxed, u16);
+    define_read!(readl_relaxed, try_readl_relaxed, u32);
+    define_read!(
+        #[cfg(CONFIG_64BIT)]
+        readq_relaxed,
+        try_readq_relaxed,
+        u64
+    );
+
+    define_write!(writeb, try_writeb, u8);
+    define_write!(writew, try_writew, u16);
+    define_write!(writel, try_writel, u32);
+    define_write!(
+        #[cfg(CONFIG_64BIT)]
+        writeq,
+        try_writeq,
+        u64
+    );
+
+    define_write!(writeb_relaxed, try_writeb_relaxed, u8);
+    define_write!(writew_relaxed, try_writew_relaxed, u16);
+    define_write!(writel_relaxed, try_writel_relaxed, u32);
+    define_write!(
+        #[cfg(CONFIG_64BIT)]
+        writeq_relaxed,
+        try_writeq_relaxed,
+        u64
+    );
+}
rust/kernel/lib.rs +1 -0
index 5702ce32ec8e..6c836ab73771 100644
@@ -79,6 +79,7 @@
 
 #[doc(hidden)]
 pub use bindings;
+pub mod io;
 pub use macros;
 pub use uapi;
 
Quoted 5 lines
Quoted 3 lines
Signature · 1 line
2.47.1

~~ Lina

author maintainer
#12 lore raw

Re: [PATCH v6 07/16] rust: add `io::{Io, IoRaw}` base types

Hi Lina,

Quoted 36 lines · Asahi Lina
Quoted 21 lines · Danilo Krummrich
Quoted 20 lines · Danilo Krummrich
rust/kernel/io.rs new file +20 -0
+/// IO-mapped memory, starting at the base address @addr and spanning @maxlen bytes.
+///
+/// The creator (usually a subsystem / bus such as PCI) is responsible for creating the
+/// mapping, performing an additional region request etc.
+///
+/// # Invariant
+///
+/// `addr` is the start and `maxsize` the length of valid I/O mapped memory region of size
+/// `maxsize`.
+///
+/// # Examples
+///
+/// ```no_run
+/// # use kernel::{bindings, io::{Io, IoRaw}};
+/// # use core::ops::Deref;
+///
+/// // See also [`pci::Bar`] for a real example.
+/// struct IoMem<const SIZE: usize>(IoRaw<SIZE>);
+///
+/// impl<const SIZE: usize> IoMem<SIZE> {
Quoted 8 lines

rust/kernel/io.rs line 73

Quoted 8 lines
rust/kernel/io.rs new file +8 -0
+///     /// # Safety
+///     ///
+///     /// [`paddr`, `paddr` + `SIZE`) must be a valid MMIO region that is mappable into the CPUs
+///     /// virtual address space.
+///     unsafe fn new(paddr: usize) -> Result<Self>{
+///         // SAFETY: By the safety requirements of this function [`paddr`, `paddr` + `SIZE`) is
+///         // valid for `ioremap`.
+///         let addr = unsafe { bindings::ioremap(paddr as _, SIZE.try_into().unwrap()) };

This is a problematic API. ioremap() does not work on some platforms like Apple Silicon. Instead, you have to use ioremap_np() for most devices.

Quoted 4 lines

Please add a bindings::resource abstraction and use that to construct IoMem. Then, you can check the flags for bindings::IORESOURCE_MEM_NONPOSTED and use the appropriate function, like this:

This is just a very simplified example of how to use IoRaw and Io base types in the scope of an example section within a doc-comment.

There is an actual IoMem implementation including a struct resource abstraction [1] from Daniel though. Maybe you want to have a look at this instead.

[1] https://lore.kernel.org/rust-for-linux/[email protected]/

Quoted 39 lines

https://github.com/AsahiLinux/linux/blob/fce34c83f1dca5b10cc2c866fd8832a362de7974/rust/kernel/io_mem.rs#L152

Quoted 35 lines
Quoted 35 lines
rust/kernel/io.rs new file +35 -0
+///         if addr.is_null() {
+///             return Err(ENOMEM);
+///         }
+///
+///         Ok(IoMem(IoRaw::new(addr as _, SIZE)?))
+///     }
+/// }
+///
+/// impl<const SIZE: usize> Drop for IoMem<SIZE> {
+///     fn drop(&mut self) {
+///         // SAFETY: `self.0.addr()` is guaranteed to be properly mapped by `Self::new`.
+///         unsafe { bindings::iounmap(self.0.addr() as _); };
+///     }
+/// }
+///
+/// impl<const SIZE: usize> Deref for IoMem<SIZE> {
+///    type Target = Io<SIZE>;
+///
+///    fn deref(&self) -> &Self::Target {
+///         // SAFETY: The memory range stored in `self` has been properly mapped in `Self::new`.
+///         unsafe { Io::from_raw(&self.0) }
+///    }
+/// }
+///
+///# fn no_run() -> Result<(), Error> {
+/// // SAFETY: Invalid usage for example purposes.
+/// let iomem = unsafe { IoMem::<{ core::mem::size_of::<u32>() }>::new(0xBAAAAAAD)? };
+/// iomem.writel(0x42, 0x0);
+/// assert!(iomem.try_writel(0x42, 0x0).is_ok());
+/// assert!(iomem.try_writel(0x42, 0x4).is_err());
+/// # Ok(())
+/// # }
+/// ```
+#[repr(transparent)]
+pub struct Io<const SIZE: usize = 0>(IoRaw<SIZE>);
#13 lore raw

Re: [PATCH v6 07/16] rust: add `io::{Io, IoRaw}` base types

Quoted 45 lines · Danilo Krummrich

Hi Lina,

Quoted 36 lines · Asahi Lina
Quoted 21 lines · Danilo Krummrich
Quoted 20 lines · Danilo Krummrich
rust/kernel/io.rs new file +20 -0
+/// IO-mapped memory, starting at the base address @addr and spanning @maxlen bytes.
+///
+/// The creator (usually a subsystem / bus such as PCI) is responsible for creating the
+/// mapping, performing an additional region request etc.
+///
+/// # Invariant
+///
+/// `addr` is the start and `maxsize` the length of valid I/O mapped memory region of size
+/// `maxsize`.
+///
+/// # Examples
+///
+/// ```no_run
+/// # use kernel::{bindings, io::{Io, IoRaw}};
+/// # use core::ops::Deref;
+///
+/// // See also [`pci::Bar`] for a real example.
+/// struct IoMem<const SIZE: usize>(IoRaw<SIZE>);
+///
+/// impl<const SIZE: usize> IoMem<SIZE> {
Quoted 8 lines

rust/kernel/io.rs line 73

Quoted 8 lines
rust/kernel/io.rs new file +8 -0
+///     /// # Safety
+///     ///
+///     /// [`paddr`, `paddr` + `SIZE`) must be a valid MMIO region that is mappable into the CPUs
+///     /// virtual address space.
+///     unsafe fn new(paddr: usize) -> Result<Self>{
+///         // SAFETY: By the safety requirements of this function [`paddr`, `paddr` + `SIZE`) is
+///         // valid for `ioremap`.
+///         let addr = unsafe { bindings::ioremap(paddr as _, SIZE.try_into().unwrap()) };

This is a problematic API. ioremap() does not work on some platforms like Apple Silicon. Instead, you have to use ioremap_np() for most devices.

Quoted 4 lines

Please add a bindings::resource abstraction and use that to construct IoMem. Then, you can check the flags for bindings::IORESOURCE_MEM_NONPOSTED and use the appropriate function, like this:

Quoted 9 lines

This is just a very simplified example of how to use IoRaw and Io base types in the scope of an example section within a doc-comment.

There is an actual IoMem implementation including a struct resource abstraction [1] from Daniel though. Maybe you want to have a look at this instead.

[1] https://lore.kernel.org/rust-for-linux/[email protected]/

That's what I get for skimming too much... I thought this had the actual implementation. Sorry!

Quoted 31 lines
Quoted 31 lines

https://github.com/AsahiLinux/linux/blob/fce34c83f1dca5b10cc2c866fd8832a362de7974/rust/kernel/io_mem.rs#L152

Quoted 27 lines
Quoted 27 lines
rust/kernel/io.rs new file +27 -0
+///         if addr.is_null() {
+///             return Err(ENOMEM);
+///         }
+///
+///         Ok(IoMem(IoRaw::new(addr as _, SIZE)?))
+///     }
+/// }
+///
+/// impl<const SIZE: usize> Drop for IoMem<SIZE> {
+///     fn drop(&mut self) {
+///         // SAFETY: `self.0.addr()` is guaranteed to be properly mapped by `Self::new`.
+///         unsafe { bindings::iounmap(self.0.addr() as _); };
+///     }
+/// }
+///
+/// impl<const SIZE: usize> Deref for IoMem<SIZE> {
+///    type Target = Io<SIZE>;
+///
+///    fn deref(&self) -> &Self::Target {
+///         // SAFETY: The memory range stored in `self` has been properly mapped in `Self::new`.
+///         unsafe { Io::from_raw(&self.0) }
+///    }
+/// }
+///
+///# fn no_run() -> Result<(), Error> {
+/// // SAFETY: Invalid usage for example purposes.
+/// let iomem = unsafe { IoMem::<{ core::mem::size_of::<u32>() }>::new(0xBAAAAAAD)? };
Quoted 9 lines
Quoted 8 lines
Quoted 8 lines

rust/kernel/io.rs line 108

Quoted 8 lines
rust/kernel/io.rs new file +8 -0
+/// iomem.writel(0x42, 0x0);
+/// assert!(iomem.try_writel(0x42, 0x0).is_ok());
+/// assert!(iomem.try_writel(0x42, 0x4).is_err());
+/// # Ok(())
+/// # }
+/// ```
+#[repr(transparent)]
+pub struct Io<const SIZE: usize = 0>(IoRaw<SIZE>);

~~ Lina

#14 lore raw

Re: [PATCH v6 07/16] rust: add `io::{Io, IoRaw}` base types

In reply to Danilo Krummrich

Hi Danilo,

Quoted 6 lines
rust/kernel/io.rs new file +6 -0
+/// ```no_run
+/// # use kernel::{bindings, io::{Io, IoRaw}};
+/// # use core::ops::Deref;
+///
+/// // See also [`pci::Bar`] for a real example.
+/// struct IoMem<const SIZE: usize>(IoRaw<SIZE>);

rust/kernel/io.rs line 73

Quoted 10 lines
rust/kernel/io.rs new file +10 -0
+///
+/// impl<const SIZE: usize> IoMem<SIZE> {
+///     /// # Safety
+///     ///
+///     /// [`paddr`, `paddr` + `SIZE`) must be a valid MMIO region that is mappable into the CPUs
+///     /// virtual address space.
+///     unsafe fn new(paddr: usize) -> Result<Self>{
+///         // SAFETY: By the safety requirements of this function [`paddr`, `paddr` + `SIZE`) is
+///         // valid for `ioremap`.
+///         let addr = unsafe { bindings::ioremap(paddr as _, SIZE.try_into().unwrap()) };

This line generates a warning when building the doctests on arm64:

warning: useless conversion to the same type: usize

    --> rust/doctests_kernel_generated.rs:3601:59
     |

3601 | let addr = unsafe { bindings::ioremap(paddr as _,
SIZE.try_into().unwrap()) };
| ^^^^^^^^^^^^^^^
|
= help: consider removing .try_into()
= help: for further information visit
https://rust-lang.github.io/rust-clippy/master/index.html#useless_conversion

Same things happens as well in devres.rs

Quoted 33 lines
rust/kernel/io.rs new file +33 -0
+///         if addr.is_null() {
+///             return Err(ENOMEM);
+///         }
+///
+///         Ok(IoMem(IoRaw::new(addr as _, SIZE)?))
+///     }
+/// }
+///
+/// impl<const SIZE: usize> Drop for IoMem<SIZE> {
+///     fn drop(&mut self) {
+///         // SAFETY: `self.0.addr()` is guaranteed to be properly mapped by `Self::new`.
+///         unsafe { bindings::iounmap(self.0.addr() as _); };
+///     }
+/// }
+///
+/// impl<const SIZE: usize> Deref for IoMem<SIZE> {
+///    type Target = Io<SIZE>;
+///
+///    fn deref(&self) -> &Self::Target {
+///         // SAFETY: The memory range stored in `self` has been properly mapped in `Self::new`.
+///         unsafe { Io::from_raw(&self.0) }
+///    }
+/// }
+///
+///# fn no_run() -> Result<(), Error> {
+/// // SAFETY: Invalid usage for example purposes.
+/// let iomem = unsafe { IoMem::<{ core::mem::size_of::<u32>() }>::new(0xBAAAAAAD)? };
+/// iomem.writel(0x42, 0x0);
+/// assert!(iomem.try_writel(0x42, 0x0).is_ok());
+/// assert!(iomem.try_writel(0x42, 0x4).is_err());
+/// # Ok(())
+/// # }
+/// ```
author maintainer
#15 lore raw

Re: [PATCH v6 07/16] rust: add `io::{Io, IoRaw}` base types

Quoted 32 lines · Fabien Parent

Hi Danilo,

Quoted 6 lines
rust/kernel/io.rs new file +6 -0
+/// ```no_run
+/// # use kernel::{bindings, io::{Io, IoRaw}};
+/// # use core::ops::Deref;
+///
+/// // See also [`pci::Bar`] for a real example.
+/// struct IoMem<const SIZE: usize>(IoRaw<SIZE>);

rust/kernel/io.rs line 73

Quoted 10 lines
rust/kernel/io.rs new file +10 -0
+///
+/// impl<const SIZE: usize> IoMem<SIZE> {
+///     /// # Safety
+///     ///
+///     /// [`paddr`, `paddr` + `SIZE`) must be a valid MMIO region that is mappable into the CPUs
+///     /// virtual address space.
+///     unsafe fn new(paddr: usize) -> Result<Self>{
+///         // SAFETY: By the safety requirements of this function [`paddr`, `paddr` + `SIZE`) is
+///         // valid for `ioremap`.
+///         let addr = unsafe { bindings::ioremap(paddr as _, SIZE.try_into().unwrap()) };

This line generates a warning when building the doctests on arm64:

warning: useless conversion to the same type: usize

    --> rust/doctests_kernel_generated.rs:3601:59
     |

3601 | let addr = unsafe { bindings::ioremap(paddr as _,
SIZE.try_into().unwrap()) };
| ^^^^^^^^^^^^^^^
|
= help: consider removing .try_into()
= help: for further information visit
https://rust-lang.github.io/rust-clippy/master/index.html#useless_conversion

Quoted 1 line

Same things happens as well in devres.rs

I think that's because arch specific ioremap() implementations sometimes use unsigned long and sometimes size_t.

I think we can just change this line to

let addr = unsafe { bindings::ioremap(paddr as _, SIZE as _) };

instead.

- Danilo

Quoted 34 lines
Quoted 34 lines
rust/kernel/io.rs new file +33 -0
+///         if addr.is_null() {
+///             return Err(ENOMEM);
+///         }
+///
+///         Ok(IoMem(IoRaw::new(addr as _, SIZE)?))
+///     }
+/// }
+///
+/// impl<const SIZE: usize> Drop for IoMem<SIZE> {
+///     fn drop(&mut self) {
+///         // SAFETY: `self.0.addr()` is guaranteed to be properly mapped by `Self::new`.
+///         unsafe { bindings::iounmap(self.0.addr() as _); };
+///     }
+/// }
+///
+/// impl<const SIZE: usize> Deref for IoMem<SIZE> {
+///    type Target = Io<SIZE>;
+///
+///    fn deref(&self) -> &Self::Target {
+///         // SAFETY: The memory range stored in `self` has been properly mapped in `Self::new`.
+///         unsafe { Io::from_raw(&self.0) }
+///    }
+/// }
+///
+///# fn no_run() -> Result<(), Error> {
+/// // SAFETY: Invalid usage for example purposes.
+/// let iomem = unsafe { IoMem::<{ core::mem::size_of::<u32>() }>::new(0xBAAAAAAD)? };
+/// iomem.writel(0x42, 0x0);
+/// assert!(iomem.try_writel(0x42, 0x0).is_ok());
+/// assert!(iomem.try_writel(0x42, 0x4).is_err());
+/// # Ok(())
+/// # }
+/// ```
author maintainer
#16 lore raw

PATCH v6 8/16 rust: add devres abstraction

In reply to Danilo Krummrich

Add a Rust abstraction for the kernel's devres (device resource management) implementation.

The Devres type acts as a container to manage the lifetime and accessibility of device bound resources. Therefore it registers a devres callback and revokes access to the resource on invocation.

Users of the Devres abstraction can simply free the corresponding resources in their Drop implementation, which is invoked when either the Devres instance goes out of scope or the devres callback leads to the resource being revoked, which implies a call to drop_in_place().

---
 MAINTAINERS            |   1 +
 rust/helpers/device.c  |  10 +++
 rust/helpers/helpers.c |   1 +
 rust/kernel/devres.rs  | 179 +++++++++++++++++++++++++++++++++++++++++
 rust/kernel/lib.rs     |   1 +
 5 files changed, 192 insertions(+)
 create mode 100644 rust/helpers/device.c
 create mode 100644 rust/kernel/devres.rs
MAINTAINERS +1 -0
index f0259b6d958c..096a7d9ee966 100644
@@ -7034,6 +7034,7 @@ F:	include/linux/property.h
 F:	lib/kobj*
 F:	rust/kernel/device.rs
 F:	rust/kernel/device_id.rs
+F:	rust/kernel/devres.rs
 F:	rust/kernel/driver.rs
 
 DRIVERS FOR OMAP ADAPTIVE VOLTAGE SCALING (AVS)
rust/helpers/device.c new file +10 -0
new file mode 100644
index 000000000000..b2135c6686b0
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/device.h>
+
+int rust_helper_devm_add_action(struct device *dev,
+				void (*action)(void *),
+				void *data)
+{
+	return devm_add_action(dev, action, data);
+}
rust/helpers/helpers.c +1 -0
index 63f9b1da179f..a3b52aa021de 100644
@@ -12,6 +12,7 @@
 #include "build_assert.c"
 #include "build_bug.c"
 #include "cred.c"
+#include "device.c"
 #include "err.c"
 #include "fs.c"
 #include "io.c"
rust/kernel/devres.rs new file +179 -0

179 lines not on this page · raw message

rust/kernel/lib.rs +1 -0
index 6c836ab73771..2b61bf99d1ee 100644
@@ -41,6 +41,7 @@
 pub mod cred;
 pub mod device;
 pub mod device_id;
+pub mod devres;
 pub mod driver;
 pub mod error;
 #[cfg(CONFIG_RUST_FW_LOADER_ABSTRACTIONS)]
Signature · 1 line
2.47.1
author maintainer
#17 lore raw

PATCH v6 9/16 rust: pci: add basic PCI device / driver abstractions

In reply to Danilo Krummrich

Implement the basic PCI abstractions required to write a basic PCI driver. This includes the following data structures:

The pci::Driver trait represents the interface to the driver and provides pci::Driver::probe for the driver to implement.

The pci::Device abstraction represents a struct pci_dev and provides abstractions for common functions, such as pci::Device::set_master.

In order to provide the PCI specific parts to a generic driver::Registration the driver::RegistrationOps trait is implemented by pci::Adapter.

pci::DeviceId implements PCI device IDs based on the generic device_id::RawDevceId abstraction.

---
 MAINTAINERS                     |   1 +
 rust/bindings/bindings_helper.h |   1 +
 rust/helpers/helpers.c          |   1 +
 rust/helpers/pci.c              |  18 ++
 rust/kernel/lib.rs              |   2 +
 rust/kernel/pci.rs              | 298 ++++++++++++++++++++++++++++++++
 6 files changed, 321 insertions(+)
 create mode 100644 rust/helpers/pci.c
 create mode 100644 rust/kernel/pci.rs
MAINTAINERS +1 -0
index 096a7d9ee966..e143969265aa 100644
@@ -18102,6 +18102,7 @@ F:	include/asm-generic/pci*
 F:	include/linux/of_pci.h
 F:	include/linux/pci*
 F:	include/uapi/linux/pci*
+F:	rust/kernel/pci.rs
 
 PCIE BANDWIDTH CONTROLLER
 M:	Ilpo Järvinen <[email protected]>
rust/bindings/bindings_helper.h +1 -0
index 5c4dfe22f41a..6d7a68e2ecb7 100644
@@ -20,6 +20,7 @@
 #include <linux/jump_label.h>
 #include <linux/mdio.h>
 #include <linux/miscdevice.h>
+#include <linux/pci.h>
 #include <linux/phy.h>
 #include <linux/pid_namespace.h>
 #include <linux/poll.h>
rust/helpers/helpers.c +1 -0
index a3b52aa021de..3fda33cd42d4 100644
@@ -20,6 +20,7 @@
 #include "kunit.c"
 #include "mutex.c"
 #include "page.c"
+#include "pci.c"
 #include "pid_namespace.c"
 #include "rbtree.c"
 #include "rcu.c"
rust/helpers/pci.c new file +18 -0
new file mode 100644
index 000000000000..8ba22f911459
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/pci.h>
+
+void rust_helper_pci_set_drvdata(struct pci_dev *pdev, void *data)
+{
+	pci_set_drvdata(pdev, data);
+}
+
+void *rust_helper_pci_get_drvdata(struct pci_dev *pdev)
+{
+	return pci_get_drvdata(pdev);
+}
+
+resource_size_t rust_helper_pci_resource_len(struct pci_dev *pdev, int bar)
+{
+	return pci_resource_len(pdev, bar);
+}
rust/kernel/lib.rs +2 -0
index 2b61bf99d1ee..1dc7eda6b480 100644
@@ -82,6 +82,8 @@
 pub use bindings;
 pub mod io;
 pub use macros;
+#[cfg(all(CONFIG_PCI, CONFIG_PCI_MSI))]
+pub mod pci;
 pub use uapi;
 
 #[doc(hidden)]
rust/kernel/pci.rs new file +298 -0

298 lines not on this page · raw message

Signature · 1 line
2.47.1
maintainer
#18 lore raw

Re: [PATCH v6 09/16] rust: pci: add basic PCI device / driver abstractions

Quoted 9 lines · Danilo Krummrich
rust/kernel/pci.rs new file +9 -0
+impl DeviceId {
+    const PCI_ANY_ID: u32 = !0;
+
+    /// PCI_DEVICE macro.
+    pub const fn new(vendor: u32, device: u32) -> Self {
+        Self(bindings::pci_device_id {
+            vendor,
+            device,
+            subvendor: DeviceId::PCI_ANY_ID,

rust/kernel/pci.rs line 135

Quoted 10 lines
rust/kernel/pci.rs new file +10 -0
+            subdevice: DeviceId::PCI_ANY_ID,
+            class: 0,
+            class_mask: 0,
+            driver_data: 0,
+            override_only: 0,
+        })
+    }
+
+    /// PCI_DEVICE_CLASS macro.
+    pub const fn with_class(class: u32, class_mask: u32) -> Self {

I know naming is hard, and I'm not going to object to this at all, but using "new()" and "with_class()" feels a bit odd and mis-matched. How about spelling it out, pci_device(), and pci_device_class()?

Anyway, not a bit deal at all, let's see how this plays out with real drivers and we can always change it later.

rust/kernel/pci.rs line 150

Quoted 2 lines
rust/kernel/pci.rs new file +2 -0
+// Allow drivers R/O access to the fields of `pci_device_id`; should we prefer accessor functions
+// to void exposing C structure fields?

Minor nit, do you mean "to avoid exposing..."?

Other than these, this looks good! If I can get an ack from the PCI maintainer, I'll be glad to queue these up in my driver core tree...

thanks,

greg k-h

author maintainer
#19 lore raw

Re: [PATCH v6 09/16] rust: pci: add basic PCI device / driver abstractions

Quoted 16 lines · Greg KH
Quoted 15 lines · Danilo Krummrich
rust/kernel/pci.rs new file +15 -0
+impl DeviceId {
+    const PCI_ANY_ID: u32 = !0;
+
+    /// PCI_DEVICE macro.
+    pub const fn new(vendor: u32, device: u32) -> Self {
+        Self(bindings::pci_device_id {
+            vendor,
+            device,
+            subvendor: DeviceId::PCI_ANY_ID,
+            subdevice: DeviceId::PCI_ANY_ID,
+            class: 0,
+            class_mask: 0,
+            driver_data: 0,
+            override_only: 0,
+        })
Quoted 8 lines

rust/kernel/pci.rs line 135

Quoted 4 lines
rust/kernel/pci.rs new file +4 -0
+    }
+
+    /// PCI_DEVICE_CLASS macro.
+    pub const fn with_class(class: u32, class_mask: u32) -> Self {

I know naming is hard, and I'm not going to object to this at all, but using "new()" and "with_class()" feels a bit odd and mis-matched. How about spelling it out, pci_device(), and pci_device_class()?

This is likely being call with module prefix, i.e. pci::DeviceId::new, so I'd rather not encode "pci" again.

Maybe pci::DeviceId::from_id and pci::DeviceId::from_class?

Quoted 8 lines

Anyway, not a bit deal at all, let's see how this plays out with real drivers and we can always change it later.

rust/kernel/pci.rs line 150

Quoted 2 lines
rust/kernel/pci.rs new file +2 -0
+// Allow drivers R/O access to the fields of `pci_device_id`; should we prefer accessor functions
+// to void exposing C structure fields?

Minor nit, do you mean "to avoid exposing..."?

Yes, but I don't think we need this comment any longer, now that we do not pass this to probe() any longer. I'll remove the Deref impl.

Quoted 3 lines

Other than these, this looks good! If I can get an ack from the PCI maintainer, I'll be glad to queue these up in my driver core tree...

Sounds good!

Quoted 4 lines

thanks,

greg k-h

author maintainer
#20 lore raw

PATCH v6 10/16 rust: pci: implement I/O mappable `pci::Bar`

In reply to Danilo Krummrich

Implement pci::Bar, pci::Device::iomap_region and pci::Device::iomap_region_sized to allow for I/O mappings of PCI BARs.

To ensure that a pci::Bar, and hence the I/O memory mapping, can't out-live the PCI device, the pci::Bar type is always embedded into a Devres container, such that the pci::Bar is revoked once the device is unbound and hence the I/O mapped memory is unmapped.

A pci::Bar can be requested with (pci::Device::iomap_region_sized) or without (pci::Device::iomap_region) a const generic representing the minimal requested size of the I/O mapped memory region. In case of the latter only runtime checked I/O reads / writes are possible.

---
 rust/kernel/pci.rs | 140 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 140 insertions(+)
rust/kernel/pci.rs +140 -0
index 238eed06f388..a91f90585a1f 100644
@@ -5,10 +5,14 @@
 //! C header: [`include/linux/pci.h`](srctree/include/linux/pci.h)
 
 use crate::{
+    alloc::flags::*,
     bindings, container_of, device,
     device_id::RawDeviceId,
+    devres::Devres,
     driver,
     error::{to_result, Result},
+    io::Io,
+    io::IoRaw,
     str::CStr,
     types::{ARef, ForeignOwnable, Opaque},
     ThisModule,
@@ -241,9 +245,115 @@ pub trait Driver {
 ///
 /// A PCI device is based on an always reference counted `device:Device` instance. Cloning a PCI
 /// device, hence, also increments the base device' reference count.
+///
+/// # Invariants
+///
+/// `Device` hold a valid reference of `ARef<device::Device>` whose underlying `struct device` is a
+/// member of a `struct pci_dev`.
 #[derive(Clone)]
 pub struct Device(ARef<device::Device>);
 
+/// A PCI BAR to perform I/O-Operations on.
+///
+/// # Invariants
+///
+/// `Bar` always holds an `IoRaw` inststance that holds a valid pointer to the start of the I/O
+/// memory mapped PCI bar and its size.
+pub struct Bar<const SIZE: usize = 0> {
+    pdev: Device,
+    io: IoRaw<SIZE>,
+    num: i32,
+}
+
+impl<const SIZE: usize> Bar<SIZE> {
+    fn new(pdev: Device, num: u32, name: &CStr) -> Result<Self> {
+        let len = pdev.resource_len(num)?;
+        if len == 0 {
+            return Err(ENOMEM);
+        }
+
+        // Convert to `i32`, since that's what all the C bindings use.
+        let num = i32::try_from(num)?;
+
+        // SAFETY:
+        // `pdev` is valid by the invariants of `Device`.
+        // `num` is checked for validity by a previous call to `Device::resource_len`.
+        // `name` is always valid.
+        let ret = unsafe { bindings::pci_request_region(pdev.as_raw(), num, name.as_char_ptr()) };
+        if ret != 0 {
+            return Err(EBUSY);
+        }
+
+        // SAFETY:
+        // `pdev` is valid by the invariants of `Device`.
+        // `num` is checked for validity by a previous call to `Device::resource_len`.
+        // `name` is always valid.
+        let ioptr: usize = unsafe { bindings::pci_iomap(pdev.as_raw(), num, 0) } as usize;
+        if ioptr == 0 {
+            // SAFETY:
+            // `pdev` valid by the invariants of `Device`.
+            // `num` is checked for validity by a previous call to `Device::resource_len`.
+            unsafe { bindings::pci_release_region(pdev.as_raw(), num) };
+            return Err(ENOMEM);
+        }
+
+        let io = match IoRaw::new(ioptr, len as usize) {
+            Ok(io) => io,
+            Err(err) => {
+                // SAFETY:
+                // `pdev` is valid by the invariants of `Device`.
+                // `ioptr` is guaranteed to be the start of a valid I/O mapped memory region.
+                // `num` is checked for validity by a previous call to `Device::resource_len`.
+                unsafe { Self::do_release(&pdev, ioptr, num) };
+                return Err(err);
+            }
+        };
+
+        Ok(Bar { pdev, io, num })
+    }
+
+    /// # Safety
+    ///
+    /// `ioptr` must be a valid pointer to the memory mapped PCI bar number `num`.
+    unsafe fn do_release(pdev: &Device, ioptr: usize, num: i32) {
+        // SAFETY:
+        // `pdev` is valid by the invariants of `Device`.
+        // `ioptr` is valid by the safety requirements.
+        // `num` is valid by the safety requirements.
+        unsafe {
+            bindings::pci_iounmap(pdev.as_raw(), ioptr as _);
+            bindings::pci_release_region(pdev.as_raw(), num);
+        }
+    }
+
+    fn release(&self) {
+        // SAFETY: The safety requirements are guaranteed by the type invariant of `self.pdev`.
+        unsafe { Self::do_release(&self.pdev, self.io.addr(), self.num) };
+    }
+}
+
+impl Bar {
+    fn index_is_valid(index: u32) -> bool {
+        // A `struct pci_dev` owns an array of resources with at most `PCI_NUM_RESOURCES` entries.
+        index < bindings::PCI_NUM_RESOURCES
+    }
+}
+
+impl<const SIZE: usize> Drop for Bar<SIZE> {
+    fn drop(&mut self) {
+        self.release();
+    }
+}
+
+impl<const SIZE: usize> Deref for Bar<SIZE> {
+    type Target = Io<SIZE>;
+
+    fn deref(&self) -> &Self::Target {
+        // SAFETY: By the type invariant of `Self`, the MMIO range in `self.io` is properly mapped.
+        unsafe { Io::from_raw(&self.io) }
+    }
+}
+
 impl Device {
     /// Create a PCI Device instance from an existing `device::Device`.
     ///
@@ -289,6 +399,36 @@ pub fn set_master(&self) {
         // SAFETY: `self.as_raw` is guaranteed to be a pointer to a valid `struct pci_dev`.
         unsafe { bindings::pci_set_master(self.as_raw()) };
     }
+
+    /// Returns the size of the given PCI bar resource.
+    pub fn resource_len(&self, bar: u32) -> Result<bindings::resource_size_t> {
+        if !Bar::index_is_valid(bar) {
+            return Err(EINVAL);
+        }
+
+        // SAFETY:
+        // - `bar` is a valid bar number, as guaranteed by the above call to `Bar::index_is_valid`,
+        // - by its type invariant `self.as_raw` is always a valid pointer to a `struct pci_dev`.
+        Ok(unsafe { bindings::pci_resource_len(self.as_raw(), bar.try_into()?) })
+    }
+
+    /// Mapps an entire PCI-BAR after performing a region-request on it. I/O operation bound checks
+    /// can be performed on compile time for offsets (plus the requested type size) < SIZE.
+    pub fn iomap_region_sized<const SIZE: usize>(
+        &self,
+        bar: u32,
+        name: &CStr,
+    ) -> Result<Devres<Bar<SIZE>>> {
+        let bar = Bar::<SIZE>::new(self.clone(), bar, name)?;
+        let devres = Devres::new(self.as_ref(), bar, GFP_KERNEL)?;
+
+        Ok(devres)
+    }
+
+    /// Mapps an entire PCI-BAR after performing a region-request on it.
+    pub fn iomap_region(&self, bar: u32, name: &CStr) -> Result<Devres<Bar>> {
+        self.iomap_region_sized::<0>(bar, name)
+    }
 }
 
 impl AsRef<device::Device> for Device {
Signature · 1 line
2.47.1
author maintainer
#21 lore raw

PATCH v6 11/16 samples: rust: add Rust PCI sample driver

In reply to Danilo Krummrich

This commit adds a sample Rust PCI driver for QEMU's "pci-testdev" device. To enable this device QEMU has to be called with -device pci-testdev.

The same driver shows how to use the PCI device / driver abstractions, as well as how to request and map PCI BARs, including a short sequence of MMIO operations.

---
 MAINTAINERS                     |   1 +
 samples/rust/Kconfig            |  11 ++++
 samples/rust/Makefile           |   1 +
 samples/rust/rust_driver_pci.rs | 110 ++++++++++++++++++++++++++++++++
 4 files changed, 123 insertions(+)
 create mode 100644 samples/rust/rust_driver_pci.rs
MAINTAINERS +1 -0
index e143969265aa..b15eb7c6b23f 100644
@@ -18103,6 +18103,7 @@ F:	include/linux/of_pci.h
 F:	include/linux/pci*
 F:	include/uapi/linux/pci*
 F:	rust/kernel/pci.rs
+F:	samples/rust/rust_driver_pci.rs
 
 PCIE BANDWIDTH CONTROLLER
 M:	Ilpo Järvinen <[email protected]>
samples/rust/Kconfig +11 -0
index b0f74a81c8f9..6d468193cdd8 100644
@@ -30,6 +30,17 @@ config SAMPLE_RUST_PRINT
 
 	  If unsure, say N.
 
+config SAMPLE_RUST_DRIVER_PCI
+	tristate "PCI Driver"
+	depends on PCI
+	help
+	  This option builds the Rust PCI driver sample.
+
+	  To compile this as a module, choose M here:
+	  the module will be called driver_pci.
+
+	  If unsure, say N.
+
 config SAMPLE_RUST_HOSTPROGS
 	bool "Host programs"
 	help
samples/rust/Makefile +1 -0
index c1a5c1655395..2f5b6bdb2fa5 100644
@@ -3,6 +3,7 @@ ccflags-y += -I$(src)				# needed for trace events
 
 obj-$(CONFIG_SAMPLE_RUST_MINIMAL)		+= rust_minimal.o
 obj-$(CONFIG_SAMPLE_RUST_PRINT)			+= rust_print.o
+obj-$(CONFIG_SAMPLE_RUST_DRIVER_PCI)		+= rust_driver_pci.o
 
 rust_print-y := rust_print_main.o rust_print_events.o
 
samples/rust/rust_driver_pci.rs new file +110 -0

110 lines not on this page · raw message

Signature · 1 line
2.47.1
author maintainer
#22 lore raw

PATCH v6 12/16 rust: of: add `of::DeviceId` abstraction

In reply to Danilo Krummrich

of::DeviceId is an abstraction around struct of_device_id.

This is used by subsequent patches, in particular the platform bus abstractions, to create OF device ID tables.

---
 MAINTAINERS        |  1 +
 rust/kernel/lib.rs |  1 +
 rust/kernel/of.rs  | 60 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 62 insertions(+)
 create mode 100644 rust/kernel/of.rs
MAINTAINERS +1 -0
index b15eb7c6b23f..025a279ac8ca 100644
@@ -17502,6 +17502,7 @@ T:	git git://git.kernel.org/pub/scm/linux/kernel/git/robh/linux.git
 F:	Documentation/ABI/testing/sysfs-firmware-ofw
 F:	drivers/of/
 F:	include/linux/of*.h
+F:	rust/kernel/of.rs
 F:	scripts/dtc/
 F:	tools/testing/selftests/dt/
 K:	of_overlay_notifier_
rust/kernel/lib.rs +1 -0
index 1dc7eda6b480..27f914b0769b 100644
@@ -56,6 +56,7 @@
 pub mod miscdevice;
 #[cfg(CONFIG_NET)]
 pub mod net;
+pub mod of;
 pub mod page;
 pub mod pid_namespace;
 pub mod prelude;
rust/kernel/of.rs new file +60 -0

60 lines not on this page · raw message

Signature · 1 line
2.47.1
author maintainer
#23 lore raw

PATCH v6 13/16 rust: driver: implement `Adapter`

In reply to Danilo Krummrich

In order to not duplicate code in bus specific implementations (e.g. platform), implement a generic driver::Adapter to represent the connection of matched drivers and devices.

Bus specific Adapter implementations can simply implement this trait to inherit generic functionality, such as matching OF or ACPI device IDs and ID table entries.

---
 rust/bindings/bindings_helper.h |  1 +
 rust/kernel/driver.rs           | 59 ++++++++++++++++++++++++++++++++-
 2 files changed, 59 insertions(+), 1 deletion(-)
rust/bindings/bindings_helper.h +1 -0
index 6d7a68e2ecb7..8fe70183a392 100644
@@ -20,6 +20,7 @@
 #include <linux/jump_label.h>
 #include <linux/mdio.h>
 #include <linux/miscdevice.h>
+#include <linux/of_device.h>
 #include <linux/pci.h>
 #include <linux/phy.h>
 #include <linux/pid_namespace.h>
rust/kernel/driver.rs +58 -1

68 lines not on this page · raw message

Signature · 1 line
2.47.1
#24 lore raw

Re: [PATCH v6 13/16] rust: driver: implement `Adapter`

Hi Danilo,

Quoted 11 lines · Danilo Krummrich

In order to not duplicate code in bus specific implementations (e.g. platform), implement a generic driver::Adapter to represent the connection of matched drivers and devices.

Bus specific Adapter implementations can simply implement this trait to inherit generic functionality, such as matching OF or ACPI device IDs and ID table entries.

---

I get some warnings when applying this patch:

Quoted 11 lines

RUSTC L rust/kernel.o
warning: unused import: device_id
--> /home/aeh/src/linux-rust/rnvme-v6.13-rc3/rust/kernel/driver.rs:10:13

   |
10 |     device, device_id, init::PinInit, of, str::CStr, try_pin_init, types::Opaque, ThisModule,
   |             ^^^^^^^^^
   |

= note: #[warn(unused_imports)] on by default

warning: missing documentation for an associated function
--> /home/aeh/src/linux-rust/rnvme-v6.13-rc3/rust/kernel/driver.rs:158:5

Quoted 7 lines
    |
158 |     fn of_id_info(_dev: &device::Device) -> Option<&'static Self::IdInfo> {
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |

= note: requested on the command line with -W missing-docs

warning: 2 warnings emitted

Looks like the latter one is from patch 13.

Best regards,
Andreas Hindborg

author maintainer
#25 lore raw

Re: [PATCH v6 13/16] rust: driver: implement `Adapter`

Quoted 13 lines · Andreas Hindborg

Hi Danilo,

Quoted 8 lines · Danilo Krummrich

In order to not duplicate code in bus specific implementations (e.g. platform), implement a generic driver::Adapter to represent the connection of matched drivers and devices.

Bus specific Adapter implementations can simply implement this trait to inherit generic functionality, such as matching OF or ACPI device IDs and ID table entries.

Quoted 5 lines
Quoted 3 lines

---

I get some warnings when applying this patch:

Ah, that's because your're probably having CONFIG_OF=n and some imports are only used within of_id_info.

Gonna fix it for CONFIG_OF=n.

Quoted 28 lines
Quoted 12 lines

RUSTC L rust/kernel.o
warning: unused import: device_id
--> /home/aeh/src/linux-rust/rnvme-v6.13-rc3/rust/kernel/driver.rs:10:13

   |
10 |     device, device_id, init::PinInit, of, str::CStr, try_pin_init, types::Opaque, ThisModule,
   |             ^^^^^^^^^
   |

= note: #[warn(unused_imports)] on by default

warning: missing documentation for an associated function
--> /home/aeh/src/linux-rust/rnvme-v6.13-rc3/rust/kernel/driver.rs:158:5

Quoted 7 lines
    |
158 |     fn of_id_info(_dev: &device::Device) -> Option<&'static Self::IdInfo> {
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |

= note: requested on the command line with -W missing-docs

warning: 2 warnings emitted

Looks like the latter one is from patch 13.

Best regards,
Andreas Hindborg

author maintainer
#26 lore raw

PATCH v6 14/16 rust: platform: add basic platform device / driver abstractions

In reply to Danilo Krummrich

Implement the basic platform bus abstractions required to write a basic platform driver. This includes the following data structures:

The platform::Driver trait represents the interface to the driver and provides platform::Driver::probe for the driver to implement.

The platform::Device abstraction represents a struct platform_device.

In order to provide the platform bus specific parts to a generic driver::Registration the driver::RegistrationOps trait is implemented by platform::Adapter.

---
 MAINTAINERS                     |   1 +
 rust/bindings/bindings_helper.h |   1 +
 rust/helpers/helpers.c          |   1 +
 rust/helpers/platform.c         |  13 +++
 rust/kernel/lib.rs              |   1 +
 rust/kernel/platform.rs         | 198 ++++++++++++++++++++++++++++++++
 6 files changed, 215 insertions(+)
 create mode 100644 rust/helpers/platform.c
 create mode 100644 rust/kernel/platform.rs
MAINTAINERS +1 -0
index 025a279ac8ca..fec876068c40 100644
@@ -7036,6 +7036,7 @@ F:	rust/kernel/device.rs
 F:	rust/kernel/device_id.rs
 F:	rust/kernel/devres.rs
 F:	rust/kernel/driver.rs
+F:	rust/kernel/platform.rs
 
 DRIVERS FOR OMAP ADAPTIVE VOLTAGE SCALING (AVS)
 M:	Nishanth Menon <[email protected]>
rust/bindings/bindings_helper.h +1 -0
index 8fe70183a392..e9fdceb568b8 100644
@@ -24,6 +24,7 @@
 #include <linux/pci.h>
 #include <linux/phy.h>
 #include <linux/pid_namespace.h>
+#include <linux/platform_device.h>
 #include <linux/poll.h>
 #include <linux/refcount.h>
 #include <linux/sched.h>
rust/helpers/helpers.c +1 -0
index 3fda33cd42d4..0640b7e115be 100644
@@ -20,6 +20,7 @@
 #include "kunit.c"
 #include "mutex.c"
 #include "page.c"
+#include "platform.c"
 #include "pci.c"
 #include "pid_namespace.c"
 #include "rbtree.c"
rust/helpers/platform.c new file +13 -0
new file mode 100644
index 000000000000..ab9b9f317301
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/platform_device.h>
+
+void *rust_helper_platform_get_drvdata(const struct platform_device *pdev)
+{
+	return platform_get_drvdata(pdev);
+}
+
+void rust_helper_platform_set_drvdata(struct platform_device *pdev, void *data)
+{
+	platform_set_drvdata(pdev, data);
+}
rust/kernel/lib.rs +1 -0
index 27f914b0769b..e59250dc6c6e 100644
@@ -59,6 +59,7 @@
 pub mod of;
 pub mod page;
 pub mod pid_namespace;
+pub mod platform;
 pub mod prelude;
 pub mod print;
 pub mod rbtree;
rust/kernel/platform.rs new file +198 -0

198 lines not on this page · raw message

Signature · 1 line
2.47.1
author maintainer
#27 lore raw

PATCH v6 15/16 samples: rust: add Rust platform sample driver

In reply to Danilo Krummrich

Add a sample Rust platform driver illustrating the usage of the platform bus abstractions.

This driver probes through either a match of device / driver name or a match within the OF ID table.

---
 MAINTAINERS                                  |  1 +
 drivers/of/unittest-data/tests-platform.dtsi |  5 ++
 samples/rust/Kconfig                         | 10 ++++
 samples/rust/Makefile                        |  1 +
 samples/rust/rust_driver_platform.rs         | 49 ++++++++++++++++++++
 5 files changed, 66 insertions(+)
 create mode 100644 samples/rust/rust_driver_platform.rs
MAINTAINERS +1 -0
index fec876068c40..95bd7dc88ad8 100644
@@ -7037,6 +7037,7 @@ F:	rust/kernel/device_id.rs
 F:	rust/kernel/devres.rs
 F:	rust/kernel/driver.rs
 F:	rust/kernel/platform.rs
+F:	samples/rust/rust_driver_platform.rs
 
 DRIVERS FOR OMAP ADAPTIVE VOLTAGE SCALING (AVS)
 M:	Nishanth Menon <[email protected]>
drivers/of/unittest-data/tests-platform.dtsi +5 -0
index fa39611071b3..2caaf1c10ee6 100644
@@ -33,6 +33,11 @@ dev@100 {
 					reg = <0x100>;
 				};
 			};
+
+			test-device@2 {
+				compatible = "test,rust-device";
+				reg = <0x2>;
+			};
 		};
 	};
 };
samples/rust/Kconfig +10 -0
index 6d468193cdd8..70126b750426 100644
@@ -41,6 +41,16 @@ config SAMPLE_RUST_DRIVER_PCI
 
 	  If unsure, say N.
 
+config SAMPLE_RUST_DRIVER_PLATFORM
+	tristate "Platform Driver"
+	help
+	  This option builds the Rust Platform driver sample.
+
+	  To compile this as a module, choose M here:
+	  the module will be called rust_driver_platform.
+
+	  If unsure, say N.
+
 config SAMPLE_RUST_HOSTPROGS
 	bool "Host programs"
 	help
samples/rust/Makefile +1 -0
index 2f5b6bdb2fa5..761d13fff018 100644
@@ -4,6 +4,7 @@ ccflags-y += -I$(src)				# needed for trace events
 obj-$(CONFIG_SAMPLE_RUST_MINIMAL)		+= rust_minimal.o
 obj-$(CONFIG_SAMPLE_RUST_PRINT)			+= rust_print.o
 obj-$(CONFIG_SAMPLE_RUST_DRIVER_PCI)		+= rust_driver_pci.o
+obj-$(CONFIG_SAMPLE_RUST_DRIVER_PLATFORM)	+= rust_driver_platform.o
 
 rust_print-y := rust_print_main.o rust_print_events.o
 
samples/rust/rust_driver_platform.rs new file +49 -0

49 lines not on this page · raw message

Signature · 1 line
2.47.1
maintainer
#28 lore raw

Re: [PATCH v6 15/16] samples: rust: add Rust platform sample driver

Quoted 84 lines · Danilo Krummrich

Add a sample Rust platform driver illustrating the usage of the platform bus abstractions.

This driver probes through either a match of device / driver name or a match within the OF ID table.

---
 MAINTAINERS                                  |  1 +
 drivers/of/unittest-data/tests-platform.dtsi |  5 ++
 samples/rust/Kconfig                         | 10 ++++
 samples/rust/Makefile                        |  1 +
 samples/rust/rust_driver_platform.rs         | 49 ++++++++++++++++++++
 5 files changed, 66 insertions(+)
 create mode 100644 samples/rust/rust_driver_platform.rs
MAINTAINERS +1 -0
index fec876068c40..95bd7dc88ad8 100644
@@ -7037,6 +7037,7 @@ F:	rust/kernel/device_id.rs
 F:	rust/kernel/devres.rs
 F:	rust/kernel/driver.rs
 F:	rust/kernel/platform.rs
+F:	samples/rust/rust_driver_platform.rs
 
 DRIVERS FOR OMAP ADAPTIVE VOLTAGE SCALING (AVS)
 M:	Nishanth Menon <[email protected]>
drivers/of/unittest-data/tests-platform.dtsi +5 -0
index fa39611071b3..2caaf1c10ee6 100644
@@ -33,6 +33,11 @@ dev@100 {
 					reg = <0x100>;
 				};
 			};
+
+			test-device@2 {
+				compatible = "test,rust-device";
+				reg = <0x2>;
+			};
 		};
 	};
 };
samples/rust/Kconfig +10 -0
index 6d468193cdd8..70126b750426 100644
@@ -41,6 +41,16 @@ config SAMPLE_RUST_DRIVER_PCI
 
 	  If unsure, say N.
 
+config SAMPLE_RUST_DRIVER_PLATFORM
+	tristate "Platform Driver"
+	help
+	  This option builds the Rust Platform driver sample.
+
+	  To compile this as a module, choose M here:
+	  the module will be called rust_driver_platform.
+
+	  If unsure, say N.
+
 config SAMPLE_RUST_HOSTPROGS
 	bool "Host programs"
 	help
samples/rust/Makefile +1 -0
index 2f5b6bdb2fa5..761d13fff018 100644
@@ -4,6 +4,7 @@ ccflags-y += -I$(src)				# needed for trace events
 obj-$(CONFIG_SAMPLE_RUST_MINIMAL)		+= rust_minimal.o
 obj-$(CONFIG_SAMPLE_RUST_PRINT)			+= rust_print.o
 obj-$(CONFIG_SAMPLE_RUST_DRIVER_PCI)		+= rust_driver_pci.o
+obj-$(CONFIG_SAMPLE_RUST_DRIVER_PLATFORM)	+= rust_driver_platform.o
 
 rust_print-y := rust_print_main.o rust_print_events.o
 
samples/rust/rust_driver_platform.rs new file +0 -0
new file mode 100644
index 000000000000..8120609e2940

samples/rust/rust_driver_platform.rs line 3

Quoted 3 lines
samples/rust/rust_driver_platform.rs new file +3 -0
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Rust Platform driver sample.

Nit, I think your employer will want a copyright line on these, but hey, I could be wrong! Your call...

Quoted 35 lines
samples/rust/rust_driver_platform.rs new file +35 -0
+use kernel::{c_str, of, platform, prelude::*};
+
+struct SampleDriver {
+    pdev: platform::Device,
+}
+
+struct Info(u32);
+
+kernel::of_device_table!(
+    OF_TABLE,
+    MODULE_OF_TABLE,
+    <SampleDriver as platform::Driver>::IdInfo,
+    [(of::DeviceId::new(c_str!("test,rust-device")), Info(42))]
+);
+
+impl platform::Driver for SampleDriver {
+    type IdInfo = Info;
+    const OF_ID_TABLE: Option<of::IdTable<Self::IdInfo>> = Some(&OF_TABLE);
+
+    fn probe(pdev: &mut platform::Device, info: Option<&Self::IdInfo>) -> Result<Pin<KBox<Self>>> {
+        dev_dbg!(pdev.as_ref(), "Probe Rust Platform driver sample.\n");
+
+        if let Some(info) = info {
+            dev_info!(pdev.as_ref(), "Probed with info: '{}'.\n", info.0);
+        }
+
+        let drvdata = KBox::new(Self { pdev: pdev.clone() }, GFP_KERNEL)?;
+
+        Ok(drvdata.into())
+    }
+}
+
+impl Drop for SampleDriver {
+    fn drop(&mut self) {
+        dev_dbg!(self.pdev.as_ref(), "Remove Rust Platform driver sample.\n");

samples/rust/rust_driver_platform.rs line 49

Quoted 10 lines
samples/rust/rust_driver_platform.rs new file +10 -0
+    }
+}
+
+kernel::module_platform_driver! {
+    type: SampleDriver,
+    name: "rust_driver_platform",
+    author: "Danilo Krummrich",
+    description: "Rust Platform driver",
+    license: "GPL v2",
+}

That is almost too simple. Seriously nice work, let's wait a few days for some others to review the series and I'll be glad to apply it to my tree!

greg k-h

author maintainer
#29 lore raw

Re: [PATCH v6 15/16] samples: rust: add Rust platform sample driver

Quoted 133 lines · Greg KH
Quoted 84 lines · Danilo Krummrich

Add a sample Rust platform driver illustrating the usage of the platform bus abstractions.

This driver probes through either a match of device / driver name or a match within the OF ID table.

---
 MAINTAINERS                                  |  1 +
 drivers/of/unittest-data/tests-platform.dtsi |  5 ++
 samples/rust/Kconfig                         | 10 ++++
 samples/rust/Makefile                        |  1 +
 samples/rust/rust_driver_platform.rs         | 49 ++++++++++++++++++++
 5 files changed, 66 insertions(+)
 create mode 100644 samples/rust/rust_driver_platform.rs
MAINTAINERS +1 -0
index fec876068c40..95bd7dc88ad8 100644
@@ -7037,6 +7037,7 @@ F:	rust/kernel/device_id.rs
 F:	rust/kernel/devres.rs
 F:	rust/kernel/driver.rs
 F:	rust/kernel/platform.rs
+F:	samples/rust/rust_driver_platform.rs
 
 DRIVERS FOR OMAP ADAPTIVE VOLTAGE SCALING (AVS)
 M:	Nishanth Menon <[email protected]>
drivers/of/unittest-data/tests-platform.dtsi +5 -0
index fa39611071b3..2caaf1c10ee6 100644
@@ -33,6 +33,11 @@ dev@100 {
 					reg = <0x100>;
 				};
 			};
+
+			test-device@2 {
+				compatible = "test,rust-device";
+				reg = <0x2>;
+			};
 		};
 	};
 };
samples/rust/Kconfig +10 -0
index 6d468193cdd8..70126b750426 100644
@@ -41,6 +41,16 @@ config SAMPLE_RUST_DRIVER_PCI
 
 	  If unsure, say N.
 
+config SAMPLE_RUST_DRIVER_PLATFORM
+	tristate "Platform Driver"
+	help
+	  This option builds the Rust Platform driver sample.
+
+	  To compile this as a module, choose M here:
+	  the module will be called rust_driver_platform.
+
+	  If unsure, say N.
+
 config SAMPLE_RUST_HOSTPROGS
 	bool "Host programs"
 	help
samples/rust/Makefile +1 -0
index 2f5b6bdb2fa5..761d13fff018 100644
@@ -4,6 +4,7 @@ ccflags-y += -I$(src)				# needed for trace events
 obj-$(CONFIG_SAMPLE_RUST_MINIMAL)		+= rust_minimal.o
 obj-$(CONFIG_SAMPLE_RUST_PRINT)			+= rust_print.o
 obj-$(CONFIG_SAMPLE_RUST_DRIVER_PCI)		+= rust_driver_pci.o
+obj-$(CONFIG_SAMPLE_RUST_DRIVER_PLATFORM)	+= rust_driver_platform.o
 
 rust_print-y := rust_print_main.o rust_print_events.o
 
samples/rust/rust_driver_platform.rs new file +0 -0
new file mode 100644
index 000000000000..8120609e2940

samples/rust/rust_driver_platform.rs line 3

Quoted 3 lines
samples/rust/rust_driver_platform.rs new file +3 -0
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Rust Platform driver sample.

Nit, I think your employer will want a copyright line on these, but hey, I could be wrong! Your call...

Quoted 41 lines
samples/rust/rust_driver_platform.rs new file +41 -0
+use kernel::{c_str, of, platform, prelude::*};
+
+struct SampleDriver {
+    pdev: platform::Device,
+}
+
+struct Info(u32);
+
+kernel::of_device_table!(
+    OF_TABLE,
+    MODULE_OF_TABLE,
+    <SampleDriver as platform::Driver>::IdInfo,
+    [(of::DeviceId::new(c_str!("test,rust-device")), Info(42))]
+);
+
+impl platform::Driver for SampleDriver {
+    type IdInfo = Info;
+    const OF_ID_TABLE: Option<of::IdTable<Self::IdInfo>> = Some(&OF_TABLE);
+
+    fn probe(pdev: &mut platform::Device, info: Option<&Self::IdInfo>) -> Result<Pin<KBox<Self>>> {
+        dev_dbg!(pdev.as_ref(), "Probe Rust Platform driver sample.\n");
+
+        if let Some(info) = info {
+            dev_info!(pdev.as_ref(), "Probed with info: '{}'.\n", info.0);
+        }
+
+        let drvdata = KBox::new(Self { pdev: pdev.clone() }, GFP_KERNEL)?;
+
+        Ok(drvdata.into())
+    }
+}
+
+impl Drop for SampleDriver {
+    fn drop(&mut self) {
+        dev_dbg!(self.pdev.as_ref(), "Remove Rust Platform driver sample.\n");
+    }
+}
+
+kernel::module_platform_driver! {
+    type: SampleDriver,
+    name: "rust_driver_platform",
Quoted 8 lines

samples/rust/rust_driver_platform.rs line 49

Quoted 4 lines
samples/rust/rust_driver_platform.rs new file +4 -0
+    author: "Danilo Krummrich",
+    description: "Rust Platform driver",
+    license: "GPL v2",
+}

That is almost too simple. Seriously nice work, let's wait a few days for some others to review the series and I'll be glad to apply it to my tree!

Thanks! If nothing else comes up, I'll send you a v7 end of this week addressing the two minor things I just replied to (remove wrong return statement in iounmap() helper, pci::DeviceId naming and Deref impl).

- Danilo

Quoted 2 lines

greg k-h

#30 lore raw

Re: [PATCH v6 15/16] samples: rust: add Rust platform sample driver

Quoted 4 lines · Danilo Krummrich

Thanks! If nothing else comes up, I'll send you a v7 end of this week addressing the two minor things I just replied to (remove wrong return statement in iounmap() helper, pci::DeviceId naming and Deref impl).

If you are going to send v7, then could you please take the chance to update core::ffi::c_* to kernel::ffi::c_*? (since we are migrating to our custom mapping -- rust-next completes the migration and enables the new mapping)

I think you have only 3 cases here, and the change should not break anything in your case, i.e. it is just a replacement.

Thanks!

Cheers,
Miguel

author maintainer
#31 lore raw

Re: [PATCH v6 15/16] samples: rust: add Rust platform sample driver

Quoted 10 lines · Miguel Ojeda
Quoted 4 lines · Danilo Krummrich

Thanks! If nothing else comes up, I'll send you a v7 end of this week addressing the two minor things I just replied to (remove wrong return statement in iounmap() helper, pci::DeviceId naming and Deref impl).

If you are going to send v7, then could you please take the chance to update core::ffi::c_* to kernel::ffi::c_*? (since we are migrating to our custom mapping -- rust-next completes the migration and enables the new mapping)

Sure!

Quoted 8 lines

I think you have only 3 cases here, and the change should not break anything in your case, i.e. it is just a replacement.

Thanks!

Cheers,
Miguel

author maintainer
#32 lore raw

PATCH v6 16/16 MAINTAINERS: add Danilo to DRIVER CORE

In reply to Danilo Krummrich

Let's keep an eye on the Rust abstractions; add myself as reviewer to DRIVER CORE.

---
 MAINTAINERS | 1 +
 1 file changed, 1 insertion(+)
MAINTAINERS +1 -0
index 95bd7dc88ad8..6b9e10551392 100644
@@ -7021,6 +7021,7 @@ F:	include/linux/component.h
 DRIVER CORE, KOBJECTS, DEBUGFS AND SYSFS
 M:	Greg Kroah-Hartman <[email protected]>
 R:	"Rafael J. Wysocki" <[email protected]>
+R:	Danilo Krummrich <[email protected]>
 S:	Supported
 T:	git git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/driver-core.git
 F:	Documentation/core-api/kobject.rst
Signature · 1 line
2.47.1
Tested-by on the v6 series
#33 lore raw
In reply to Danilo Krummrich
Quoted 13 lines · Danilo Krummrich

This patch series implements the necessary Rust abstractions to implement device drivers in Rust.

This includes some basic generalizations for driver registration, handling of ID tables, MMIO operations and device resource handling.

Those generalizations are used to implement device driver support for two busses, the PCI and platform bus (with OF IDs) in order to provide some evidence that the generalizations work as intended.

The patch series also includes two patches adding two driver samples, one PCI driver and one platform driver.

Quoted 8 lines

The PCI bits are motivated by the Nova driver project [1], but are used by at least one more OOT driver (rnvme [2]).

The platform bits, besides adding some more evidence to the base abstractions, are required by a few more OOT drivers aiming at going upstream, i.e. rvkms [3], cpufreq-dt [4], asahi [5] and the i2c work from Fabien [6].

The patches of this series can also be [7], [8] and [9].

With v6.13-rc2 and Fabien's regmap/regulator/I2C on top I gave it a try on x86 with qemu. Additionally cross compiled for arm64 and will try it on real hardware once I have it available. But previous versions of this series have been fine on that, already. No issues observed for running the samples and for the examples/KUnit. So:

Many thanks!

Dirk

author maintainer
#34 lore raw
Quoted 23 lines · Dirk Behme
Quoted 13 lines · Danilo Krummrich

This patch series implements the necessary Rust abstractions to implement device drivers in Rust.

This includes some basic generalizations for driver registration, handling of ID tables, MMIO operations and device resource handling.

Those generalizations are used to implement device driver support for two busses, the PCI and platform bus (with OF IDs) in order to provide some evidence that the generalizations work as intended.

The patch series also includes two patches adding two driver samples, one PCI driver and one platform driver.

Quoted 8 lines

The PCI bits are motivated by the Nova driver project [1], but are used by at least one more OOT driver (rnvme [2]).

The platform bits, besides adding some more evidence to the base abstractions, are required by a few more OOT drivers aiming at going upstream, i.e. rvkms [3], cpufreq-dt [4], asahi [5] and the i2c work from Fabien [6].

The patches of this series can also be [7], [8] and [9].

Quoted 7 lines

With v6.13-rc2 and Fabien's regmap/regulator/I2C on top I gave it a try on x86 with qemu. Additionally cross compiled for arm64 and will try it on real hardware once I have it available. But previous versions of this series have been fine on that, already. No issues observed for running the samples and for the examples/KUnit. So:

Thanks for testing all this, very much appreciated!

Quoted 4 lines

Many thanks!

Dirk

Tested-by on the v6 series
#35 lore raw
In reply to Danilo Krummrich
Quoted 249 lines · Danilo Krummrich

This patch series implements the necessary Rust abstractions to implement device drivers in Rust.

This includes some basic generalizations for driver registration, handling of ID tables, MMIO operations and device resource handling.

Those generalizations are used to implement device driver support for two busses, the PCI and platform bus (with OF IDs) in order to provide some evidence that the generalizations work as intended.

The patch series also includes two patches adding two driver samples, one PCI driver and one platform driver.

The PCI bits are motivated by the Nova driver project [1], but are used by at least one more OOT driver (rnvme [2]).

The platform bits, besides adding some more evidence to the base abstractions, are required by a few more OOT drivers aiming at going upstream, i.e. rvkms [3], cpufreq-dt [4], asahi [5] and the i2c work from Fabien [6].

The patches of this series can also be [7], [8] and [9].

Changes in v6:
==============
- Module:
- remove patch to pass the module name in Module::init
- add a patch adding the ModuleMetadata trait to retreive the module name

- Driver:
- generate module structure within module_driver!, such that we get a unique module type for which ModuleMetadata can be implemented to retreive the module name
- let Adapter::of_id_table return Option<of::IdTable<Self::IdInfo>>

- Platform:
- change type of platform::Driver::OF_ID_TABLE to Option<of::IdTable>

- RCU:
- add RCU abstraction to MAINTAINERS file

- MISC:
- pick up a couple of RBs

Changes in v5:
==============
- Opaque:
- add Opaque::pin_init

- Revocable:
- use const generic instead of bool in revoke_internal
- don't open code things with Opaque::try_ffi_init, use Opaque::pin_init instead

- Driver:
- fix DriverOps -> RegistrationOps in commit message
- fix signature of register and unregister to take an Opaque<T::RegType> instead of a mutable reference
- implement generic driver::Adapter

- ID table:
- fix module_device_table! macro, according to commit 054a9cd395a7 (modpost: rename alias symbol for MODULE_DEVICE_TABLE())

- PCI:
- remove pci::DeviceId argument from probe()
- provide vendor_id and device_id accessor functions
- use addr_of_mut! for the device in probe()

- OF:
- move OF IdTable type alias from platform.rs to of.rs

- Platform:
- use addr_of_mut! for the device in probe()
- move generic OF code to driver::Adapter

- MISC:
- rebase onto latest rust-next (-rc2)

Changes in v4:
==============
- Revocable
- convert Revocable::data to Opaque
- introduce revoke_nosync, which does not wait for an RCU grace period
- fix minor typo

- Device ID
- fix ID table export name to always be unique
- remove #![allow(stable_features)]
- move #![allow(const_refs_to_cell)] to the "stable in 1.83" list

- I/O
- split Io<SIZE> in IoRaw<SIZE> and Io<SIZE>
- add rust helper for ioremap() and iounmap() (needed by doctests)

- Devres
- optimze drop by using revoke_nosync (we're guaranteed there are no more concurrent users, hence no need to wait for a full grace period)

- OF
- rename "Open Firmware" -> "Device Tree / Open Firmware" (Rob)
- remove unused C header reference
- add TODO comment to loop in DeviceId::new
- remove DeviceId::compatible; was only used in sample code
- add missing #[repr(transparent)]
- use #[cfg(CONFIG_OF)] for the relevant functions

- PCI
- let Rust helper for pci_resource_len() return resource_size_t

- PCI Sample
- remove unnecessary from_le

- Platform
- fix example compatible string
- add example for platform::Driver
- rename ID_TABLE to OF_ID_TABLE
- don't allow public use of of_match_table; convert to of_id_info to retrieve the of::DeviceId to gather the corresponding IdInfo
- remove wrong example reference in the documentation of platform::Driver
- remove as_dev, as we already implement AsRef for platform::Device

- Platform Sample
- fix compatible string; add corresponding entry to drivers/of/unittest-data/tests-platform.dtsi
- remove usage of of_match_table

- MISC
- fix a few spelling mistakes
- rebase onto rust-next (v6.13-rc1)

Changes in v3:
==============
- add commits for Opaque::try_ffi_init and InPlaceModule
- rename DriverOps to RegistrationOps
- rework device ID abstractions to get rid of almost all macro magic (thanks to Gary Guo for working this out!)
- this is possible due to recently stabilized language features
- add modpost alias generation for device ID tables
- unfortunately, this is the part that still requires some macro magic in the device ID abstractions
- PCI
- represent the driver private data with the driver specific Driver instance and bind it's lifetime to the time of the driver being bound to a device
- this allows us to handle class / subsystem registrations in a cleaner way
- get rid of Driver::remove
- Rust drivers should bind cleanup code to the Drop implementation of the corresponding structure instead and put it into their driver structure for automatic cleanup
- add a sample PCI driver
- add abstractions for struct of_device_id
- add abstractions for the platform bus, including a sample driver
- update the MAINTAINERS file accordingly
- currently this turns out a bit messy, but it should become better once the build system supports a treewide distribution of the kernel crate
- I didn't add myself as maintainer, but (if requested) I'm willing to do so and help with maintenance

Changes in v2:
==============
- statically initialize driver structures (Greg)
- move base device ID abstractions to a separate source file (Greg)
- remove DeviceRemoval trait in favor of using a Devres callback to unregister drivers
- remove device::Data, we don't need this abstraction anymore now that we Devres to revoke resources and registrations
- pass the module name to Module::init and InPlaceModule::init in a separate patch
- rework of Io including compile time boundary checks (Miguel, Wedson)
- adjust PCI abstractions accordingly and implement a module_pci_driver! macro
- rework pci::Bar to support a const SIZE
- increase the total amount of Documentation, rephrase some safety comments and commit messages for less ambiguity
- fix compilation issues with some documentation examples

[1] https://gitlab.freedesktop.org/drm/nova/-/tree/nova-next
[2] https://github.com/metaspace/linux/tree/rnvme
[3] https://lore.kernel.org/all/[email protected]/
[4] https://git.kernel.org/pub/scm/linux/kernel/git/vireshk/linux.git/log/?h=rust/cpufreq-dt
[5] https://github.com/AsahiLinux/linux
[6] https://github.com/Fabo/linux/tree/fparent/rust-i2c
[7] https://github.com/Rust-for-Linux/linux/tree/staging/rust-device
[8] https://github.com/Rust-for-Linux/linux/tree/staging/rust-pci
[9] https://github.com/Rust-for-Linux/linux/tree/staging/dev

Danilo Krummrich (14):
rust: module: add trait ModuleMetadata
rust: implement generic driver registration
rust: implement IdArray, IdTable and RawDeviceId
rust: types: add Opaque::pin_init
rust: add io::{Io, IoRaw} base types
rust: add devres abstraction
rust: pci: add basic PCI device / driver abstractions
rust: pci: implement I/O mappable pci::Bar
samples: rust: add Rust PCI sample driver
rust: of: add of::DeviceId abstraction
rust: driver: implement Adapter
rust: platform: add basic platform device / driver abstractions
samples: rust: add Rust platform sample driver
MAINTAINERS: add Danilo to DRIVER CORE

Wedson Almeida Filho (2):
rust: add rcu abstraction
rust: add Revocable type

 MAINTAINERS                                  |  10 +
 drivers/of/unittest-data/tests-platform.dtsi |   5 +
 rust/bindings/bindings_helper.h              |   3 +
 rust/helpers/device.c                        |  10 +
 rust/helpers/helpers.c                       |   5 +
 rust/helpers/io.c                            | 101 +++++
 rust/helpers/pci.c                           |  18 +
 rust/helpers/platform.c                      |  13 +
 rust/helpers/rcu.c                           |  13 +
 rust/kernel/device_id.rs                     | 165 +++++++
 rust/kernel/devres.rs                        | 179 ++++++++
 rust/kernel/driver.rs                        | 174 ++++++++
 rust/kernel/io.rs                            | 260 +++++++++++
 rust/kernel/lib.rs                           |  20 +
 rust/kernel/of.rs                            |  60 +++
 rust/kernel/pci.rs                           | 438 +++++++++++++++++++
 rust/kernel/platform.rs                      | 198 +++++++++
 rust/kernel/revocable.rs                     | 223 ++++++++++
 rust/kernel/sync.rs                          |   1 +
 rust/kernel/sync/rcu.rs                      |  47 ++
 rust/kernel/types.rs                         |  11 +
 rust/macros/module.rs                        |   4 +
 samples/rust/Kconfig                         |  21 +
 samples/rust/Makefile                        |   2 +
 samples/rust/rust_driver_pci.rs              | 110 +++++
 samples/rust/rust_driver_platform.rs         |  49 +++
 26 files changed, 2140 insertions(+)
 create mode 100644 rust/helpers/device.c
 create mode 100644 rust/helpers/io.c
 create mode 100644 rust/helpers/pci.c
 create mode 100644 rust/helpers/platform.c
 create mode 100644 rust/helpers/rcu.c
 create mode 100644 rust/kernel/device_id.rs
 create mode 100644 rust/kernel/devres.rs
 create mode 100644 rust/kernel/driver.rs
 create mode 100644 rust/kernel/io.rs
 create mode 100644 rust/kernel/of.rs
 create mode 100644 rust/kernel/pci.rs
 create mode 100644 rust/kernel/platform.rs
 create mode 100644 rust/kernel/revocable.rs
 create mode 100644 rust/kernel/sync/rcu.rs
 create mode 100644 samples/rust/rust_driver_pci.rs
 create mode 100644 samples/rust/rust_driver_platform.rs
Quoted 4 lines

base-commit: fac04efc5c793dccbd07e2d59af9f90b7fc0dca4 --
2.47.1

Tested on arm64 as dependency for my own series:
https://lkml.org/lkml/2024/12/18/1358