Device / Driver PCI / Platform Rust abstractions
Subsystems (9)
Danilo Krummrich
maintainerv7 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 v7:
==============
- Revocable:
- replace compare_exchange with swap
- Driver:
- fix warning when CONFIG_OF=n
- I/O:
- remove unnecessary return statement in rust_helper_iounmap()
- fix cast in doctest for bindings::ioremap
- Devres:
- fix cast in doctest for bindings::ioremap
- PCI:
- remove Deref of pci::DeviceId
- rename DeviceId constructors
- new -> from_id
- with_class -> from_class`
- MISC:
- use kernel::ffi::c_* instead of core::ffi::c_*
- rebase onto latest rust-next (0c5928deada15a8d075516e6e0d9ee19011bb000)
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
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 | 178 ++++++++ rust/kernel/driver.rs | 173 ++++++++ rust/kernel/io.rs | 260 +++++++++++ rust/kernel/lib.rs | 20 + rust/kernel/of.rs | 60 +++ rust/kernel/pci.rs | 432 +++++++++++++++++++ rust/kernel/platform.rs | 198 +++++++++ rust/kernel/revocable.rs | 219 ++++++++++ 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, 2128 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: 0c5928deada15a8d075516e6e0d9ee19011bb000
Signature · 1 line
2.47.1
Danilo Krummrich
maintainerPATCH v7 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
@@ -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 : &'staticcrate ::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
@@ -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
Re: [PATCH v7 01/16] rust: module: add trait `ModuleMetadata`
Quoted 7 lines · On Thu, 19 Dec 2024 18:04:03 +0100 Danilo Krummrich <[email protected]> wrote:
In order to access static metadata of a Rust kernel module, add the
ModuleMetadatatrait.In particular, this trait provides the name of a Rust kernel module as specified by the
module!macro.Signed-off-by Danilo Krummrich <[email protected]>
Quoted 37 lines
--- rust/kernel/lib.rs | 6 ++++++ rust/macros/module.rs | 4 ++++ 2 files changed, 10 insertions(+)rust/kernel/lib.rs +6
-0@@ -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 : &'staticcrate ::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@@ -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 {{
Danilo Krummrich
maintainerPATCH v7 2/16 rust: implement generic driver registration
In reply to Danilo KrummrichImplement 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
@@ -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
@@ -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
Gary Guo
maintainerRe: [PATCH v7 02/16] rust: implement generic driver registration
Quoted 14 lines · On Thu, 19 Dec 2024 18:04:04 +0100 Danilo Krummrich <[email protected]> wrote:
Implement the generic
Registrationtype and theRegistrationOpstrait.The
Registrationstructure 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
RegistrationOpstrait is provided to subsystems, which have to implementRegistrationOps::registerandRegistrationOps::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.
Quoted 7 lines
For instance, the PCI subsystem would call
__pci_register_driver()fromRegistrationOps::registerandpci_unregister_driver()fromDrvierOps::unregister.Co-developed-by Wedson Almeida Filho <[email protected]>Signed-off-by Wedson Almeida Filho <[email protected]>Signed-off-by Danilo Krummrich <[email protected]>
Hi Danilo,
I think there're soundness issues with this API, please see comments inlined below.
Best,
Gary
Quoted 43 lines
--- MAINTAINERS | 1 + rust/kernel/driver.rs | 117 ++++++++++++++++++++++++++++++++++++++++++ rust/kernel/lib.rs | 1 + 3 files changed, 119 insertions(+) create mode 100644 rust/kernel/driver.rsMAINTAINERS +1
-0@@ -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 +18
-0@@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Generic support for drivers of different buses (e.g., PCI, Platform, Amba, etc.). +//! +//! Each bus / subsystem is expected to implement [`RegistrationOps`], which allows drivers to +//! register using the [`Registration`] class. + +use crate ::error::{Error ,Result }; +use crate ::{init::PinInit , str::CStr , try_pin_init, types::Opaque ,ThisModule }; +use core::pin::Pin ; +use macros::{pin_data, pinned_drop}; + +/// The [`RegistrationOps`] trait serves as generic interface for subsystems (e.g., PCI, Platform, +/// Amba, etc.) to provide the corresponding subsystem specific implementation to register / +/// unregister a driver of the particular type (`RegType`). +/// +/// For instance, the PCI subsystem would set `RegType` to `bindings::pci_driver` and call +/// `bindings::__pci_register_driver` from `RegistrationOps::register` and
rust/kernel/driver.rs line 28
Quoted 10 lines
rust/kernel/driver.rs new file +10
-0+/// `bindings::pci_unregister_driver` from `RegistrationOps::unregister`. +pub trait RegistrationOps { +/// The type that holds information about the registration. This is typically a struct defined +/// by the C portion of the kernel. +type RegType :Default ; + +/// Registers a driver. +/// +/// On success, `reg` must remain pinned and valid until the matching call to +/// [`RegistrationOps::unregister`].
This looks like an obligation for the caller, so this function should be unsafe?
rust/kernel/driver.rs line 35
Quoted 7 lines
rust/kernel/driver.rs new file +7
-0+fn register ( + reg: &Opaque <Self ::RegType >, + name: &'staticCStr , + module: &'staticThisModule , + ) ->Result ; + +/// Unregisters a driver previously registered with [`RegistrationOps::register`].
Similarly this is an obligation for the caller.
Quoted 20 lines
rust/kernel/driver.rs new file +20
-0+fn unregister (reg: &Opaque <Self ::RegType >); +} + +/// A [`Registration`] is a generic type that represents the registration of some driver type (e.g. +/// `bindings::pci_driver`). Therefore a [`Registration`] must be initialized with a type that +/// implements the [`RegistrationOps`] trait, such that the generic `T::register` and +/// `T::unregister` calls result in the subsystem specific registration calls. +/// +///Once the `Registration` structure is dropped, the driver is unregistered. +#[pin_data( PinnedDrop )] +pub struct Registration <T :RegistrationOps > { +#[pin] + reg:Opaque <T ::RegType >, +} + +// SAFETY: `Registration` has no fields or methods accessible via `&Registration`, so it is safe to +// share references to it with multiple threads as nothing can be done. +unsafe impl <T :RegistrationOps >Sync for Registration <T > {} + +// SAFETY: Both registration and unregistration are implemented in C and safe to be performed from
rust/kernel/driver.rs line 65
Quoted 10 lines
rust/kernel/driver.rs new file +10
-0+// any thread, so `Registration` is `Send`. +unsafe impl<T: RegistrationOps> Send for Registration<T> {} + +impl<T: RegistrationOps> Registration<T> { + /// Creates a new instance of the registration object. + pub fn new(name: &'static CStr, module: &'static ThisModule) -> impl PinInit<Self, Error> { + try_pin_init!(Self { + reg <- Opaque::try_ffi_init(|ptr: *mut T::RegType| { + // SAFETY: `try_ffi_init` guarantees that `ptr` is valid for write. + unsafe { ptr.write(T::RegType::default()) };
Any reason that this is initialised with a default, and not be up to T::register to initialise?
rust/kernel/driver.rs line 68
Quoted 3 lines
rust/kernel/driver.rs new file +3
-0+ +// SAFETY: `try_ffi_init` guarantees that `ptr` is valid for write, and it has +// just been initialised above, so it's also valid for read.
Opaque can hold uninitialised value so as long as ptr is not dangling this is fine. There's no need to actually initialise.
Quoted 10 lines
rust/kernel/driver.rs new file +10
-0+let drv =unsafe { &*(ptras *const Opaque <T ::RegType >) }; + +T ::register (drv, name, module) + }), + }) + } +} + +#[pinned_drop] +impl <T :RegistrationOps >PinnedDrop for Registration <T > {
rust/kernel/driver.rs line 88
Quoted 10 lines
rust/kernel/driver.rs new file +10
-0+fn drop (self:Pin <&mut Self >) { +T ::unregister (&self.reg); + } +} + +/// Declares a kernel module that exposes a single driver. +/// +/// It is meant to be used as a helper by other subsystems so they can more easily expose their own +/// macros. +#[macro_export]
I think this is supposed to be used by other macros only? If so, please add #[doc(hidden)].
Quoted 41 lines
rust/kernel/driver.rs new file +29
-0+macro_rules! module_driver { + (<$gen_type:ident>, $driver_ops:ty, {type : $type:ty, $($f:tt)* }) => { +type Ops <$gen_type> = $driver_ops; + +#[$crate::prelude::pin_data] +struct DriverModule { +#[pin] + _driver: $crate::driver::Registration <Ops <$type>>, + } + +impl $crate::InPlaceModule for DriverModule { +fn init ( + module: &'static $crate::ThisModule + ) ->impl $crate::init::PinInit <Self , $crate::error::Error > { + $crate::try_pin_init!(Self { + _driver <- $crate ::driver::Registration ::new ( + <Self as $crate ::ModuleMetadata >::NAME , + module, + ), + }) + } + } + + $crate::prelude::module! { +type :DriverModule , + $($f)* + } + } +}rust/kernel/lib.rs +1
-0@@ -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;
Danilo Krummrich
maintainerRe: [PATCH v7 02/16] rust: implement generic driver registration
Hi Gary,
Quoted 21 lines · Gary Guo
Quoted 18 lines · On Thu, 19 Dec 2024 18:04:04 +0100 Danilo Krummrich <[email protected]> wrote:
Implement the generic
Registrationtype and theRegistrationOpstrait.The
Registrationstructure 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
RegistrationOpstrait is provided to subsystems, which have to implementRegistrationOps::registerandRegistrationOps::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()fromRegistrationOps::registerandpci_unregister_driver()fromDrvierOps::unregister.
Quoted 8 lines
Quoted 3 lines
Co-developed-by Wedson Almeida Filho <[email protected]>Signed-off-by Wedson Almeida Filho <[email protected]>Signed-off-by Danilo Krummrich <[email protected]>Hi Danilo,
I think there're soundness issues with this API, please see comments inlined below.
Just in case you did not note, this series has been applied to the driver-core tree already.
Quoted 69 lines
Best,
GaryQuoted 43 lines
--- MAINTAINERS | 1 + rust/kernel/driver.rs | 117 ++++++++++++++++++++++++++++++++++++++++++ rust/kernel/lib.rs | 1 + 3 files changed, 119 insertions(+) create mode 100644 rust/kernel/driver.rsMAINTAINERS +1
-0@@ -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 +18
-0@@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Generic support for drivers of different buses (e.g., PCI, Platform, Amba, etc.). +//! +//! Each bus / subsystem is expected to implement [`RegistrationOps`], which allows drivers to +//! register using the [`Registration`] class. + +use crate ::error::{Error ,Result }; +use crate ::{init::PinInit , str::CStr , try_pin_init, types::Opaque ,ThisModule }; +use core::pin::Pin ; +use macros::{pin_data, pinned_drop}; + +/// The [`RegistrationOps`] trait serves as generic interface for subsystems (e.g., PCI, Platform, +/// Amba, etc.) to provide the corresponding subsystem specific implementation to register / +/// unregister a driver of the particular type (`RegType`). +/// +/// For instance, the PCI subsystem would set `RegType` to `bindings::pci_driver` and call +/// `bindings::__pci_register_driver` from `RegistrationOps::register` and rust/kernel/driver.rs line 28
Quoted 10 lines
rust/kernel/driver.rs new file +10
-0+/// `bindings::pci_unregister_driver` from `RegistrationOps::unregister`. +pub trait RegistrationOps { +/// The type that holds information about the registration. This is typically a struct defined +/// by the C portion of the kernel. +type RegType :Default ; + +/// Registers a driver. +/// +/// On success, `reg` must remain pinned and valid until the matching call to +/// [`RegistrationOps::unregister`]. This looks like an obligation for the caller, so this function should be unsafe?
rust/kernel/driver.rs line 35
Quoted 7 lines
rust/kernel/driver.rs new file +7
-0+fn register ( + reg: &Opaque <Self ::RegType >, + name: &'staticCStr , + module: &'staticThisModule , + ) ->Result ; + +/// Unregisters a driver previously registered with [`RegistrationOps::register`].
Quoted 1 line
Similarly this is an obligation for the caller.
I think you're right. I didn't want this to be unsafe, and when I got rid of the raw pointer, and hence removed the unsafe, I did miss the fact you point out here. It's a bit unfortunate, especially, since register and unregister are only ever called from this file.
I'll send a patch to fix it up, unless you want to send one yourself.
Quoted 26 lines
Quoted 26 lines
rust/kernel/driver.rs new file +25
-0+fn unregister (reg: &Opaque <Self ::RegType >); +} + +/// A [`Registration`] is a generic type that represents the registration of some driver type (e.g. +/// `bindings::pci_driver`). Therefore a [`Registration`] must be initialized with a type that +/// implements the [`RegistrationOps`] trait, such that the generic `T::register` and +/// `T::unregister` calls result in the subsystem specific registration calls. +/// +///Once the `Registration` structure is dropped, the driver is unregistered. +#[pin_data( PinnedDrop )] +pub struct Registration <T :RegistrationOps > { +#[pin] + reg:Opaque <T ::RegType >, +} + +// SAFETY: `Registration` has no fields or methods accessible via `&Registration`, so it is safe to +// share references to it with multiple threads as nothing can be done. +unsafe impl <T :RegistrationOps >Sync for Registration <T > {} + +// SAFETY: Both registration and unregistration are implemented in C and safe to be performed from +// any thread, so `Registration` is `Send`. +unsafe impl <T :RegistrationOps >Send for Registration <T > {} + +impl <T :RegistrationOps >Registration <T > { +/// Creates a new instance of the registration object.
Quoted 8 lines
rust/kernel/driver.rs line 65
Quoted 5 lines
rust/kernel/driver.rs new file +5
-0+ pub fn new(name: &'static CStr, module: &'static ThisModule) -> impl PinInit<Self, Error> { + try_pin_init!(Self { + reg <- Opaque::try_ffi_init(|ptr: *mut T::RegType| { + // SAFETY: `try_ffi_init` guarantees that `ptr` is valid for write. + unsafe { ptr.write(T::RegType::default()) };Any reason that this is initialised with a default, and not be up to
T::registerto initialise?
T::RegType is always an FFI type, so this can be in the common code.
Quoted 7 lines
rust/kernel/driver.rs line 68
Quoted 4 lines
rust/kernel/driver.rs new file +3
-0+ +// SAFETY: `try_ffi_init` guarantees that `ptr` is valid for write, and it has +// just been initialised above, so it's also valid for read. Opaque can hold uninitialised value so as long as
ptris not dangling this is fine. There's no need to actually initialise.
I think the "initialized" part is from before it was an Opaque.
Quoted 16 lines
Quoted 16 lines
rust/kernel/driver.rs new file +15
-0+let drv =unsafe { &*(ptras *const Opaque <T ::RegType >) }; + +T ::register (drv, name, module) + }), + }) + } +} + +#[pinned_drop] +impl <T :RegistrationOps >PinnedDrop for Registration <T > { +fn drop (self:Pin <&mut Self >) { +T ::unregister (&self.reg); + } +} +
Quoted 8 lines
rust/kernel/driver.rs line 88
Quoted 5 lines
rust/kernel/driver.rs new file +5
-0+/// Declares a kernel module that exposes a single driver. +/// +/// It is meant to be used as a helper by other subsystems so they can more easily expose their own +/// macros. +#[macro_export] I think this is supposed to be used by other macros only? If so, please add
#[doc(hidden)].
Why? It's a public kernel API to be used by bus abstractions. I don't think documentation should be for drivers only.
Quoted 43 lines
Quoted 42 lines
rust/kernel/driver.rs new file +29
-0+macro_rules! module_driver { + (<$gen_type:ident>, $driver_ops:ty, {type : $type:ty, $($f:tt)* }) => { +type Ops <$gen_type> = $driver_ops; + +#[$crate::prelude::pin_data] +struct DriverModule { +#[pin] + _driver: $crate::driver::Registration <Ops <$type>>, + } + +impl $crate::InPlaceModule for DriverModule { +fn init ( + module: &'static $crate::ThisModule + ) ->impl $crate::init::PinInit <Self , $crate::error::Error > { + $crate::try_pin_init!(Self { + _driver <- $crate ::driver::Registration ::new ( + <Self as $crate ::ModuleMetadata >::NAME , + module, + ), + }) + } + } + + $crate::prelude::module! { +type :DriverModule , + $($f)* + } + } +}rust/kernel/lib.rs +1
-0@@ -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;
Danilo Krummrich
maintainerPATCH v7 3/16 rust: implement `IdArray`, `IdTable` and `RawDeviceId`
In reply to Danilo KrummrichMost 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
@@ -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
@@ -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
Gary Guo
maintainerRe: [PATCH v7 03/16] rust: implement `IdArray`, `IdTable` and `RawDeviceId`
Quoted 9 lines · On Thu, 19 Dec 2024 18:04:05 +0100 Danilo Krummrich <[email protected]> wrote:
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,IdTableandRawDeviceIdtypes provide a generalized implementation that makes the life of subsystems easier to do so.
Quoted 7 lines
Co-developed-by Wedson Almeida Filho <[email protected]>Signed-off-by Wedson Almeida Filho <[email protected]>Co-developed-by Gary Guo <[email protected]>Signed-off-by Gary Guo <[email protected]>Co-developed-by Fabien Parent <[email protected]>Signed-off-by Fabien Parent <[email protected]>Signed-off-by Danilo Krummrich <[email protected]>
Thank you for converting my prototype to a working patch. There's a nit below.
Quoted 25 lines
--- 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.rsMAINTAINERS +1
-0@@ -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 +0
-0
rust/kernel/device_id.rs line 59
Quoted 10 lines
rust/kernel/device_id.rs new file +8
-0+ +impl <T :RawDeviceId ,const N :usize >RawIdArray <T ,N > { +#[doc(hidden)] +pub const fn size (&self) ->usize { + core::mem::size_of ::<Self >() + } +} +
This is not necessary, see below.
Quoted 6 lines
<snip>
rust/kernel/device_id.rs new file +4
-0+ +/// Create device table alias for modpost. +#[macro_export] +macro_rules! module_device_table {
rust/kernel/device_id.rs line 163
Quoted 10 lines
rust/kernel/device_id.rs new file +10
-0+ ($table_type: literal, $module_table_name:ident, $table_name:ident) => { +#[rustfmt::skip] +#[export_name = +concat! ( "__mod_device_table__" , $table_type, +"__" , module_path! (), +"_" , line! (), +"_" , stringify! ($table_name)) +] + static $module_table_name: [core::mem::MaybeUninit<u8>; $table_name.raw_ids().size()] = +unsafe { core::mem::transmute_copy ($table_name.raw_ids ()) };
const_size_of_val will be stable in Rust 1.85, so we can start using the feature and
static $module_table_name: [core::mem::MaybeUninit<u8>; core::mem::size_of_val($table_name.raw_ids())] =
unsafe { core::mem::transmute_copy($table_name.raw_ids()) };should work.
Quoted 26 lines
rust/kernel/device_id.rs new file +2
-0+ }; +}rust/kernel/lib.rs +6
-0@@ -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 )]
Danilo Krummrich
maintainerRe: [PATCH v7 03/16] rust: implement `IdArray`, `IdTable` and `RawDeviceId`
Quoted 80 lines · Gary Guo
Quoted 9 lines · On Thu, 19 Dec 2024 18:04:05 +0100 Danilo Krummrich <[email protected]> wrote:
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,IdTableandRawDeviceIdtypes provide a generalized implementation that makes the life of subsystems easier to do so.Quoted 7 lines
Co-developed-by Wedson Almeida Filho <[email protected]>Signed-off-by Wedson Almeida Filho <[email protected]>Co-developed-by Gary Guo <[email protected]>Signed-off-by Gary Guo <[email protected]>Co-developed-by Fabien Parent <[email protected]>Signed-off-by Fabien Parent <[email protected]>Signed-off-by Danilo Krummrich <[email protected]>Thank you for converting my prototype to a working patch. There's a nit below.
Quoted 25 lines
--- 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.rsMAINTAINERS +1
-0@@ -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 +0
-0rust/kernel/device_id.rs line 59
Quoted 10 lines
rust/kernel/device_id.rs new file +8
-0+ +impl <T :RawDeviceId ,const N :usize >RawIdArray <T ,N > { +#[doc(hidden)] +pub const fn size (&self) ->usize { + core::mem::size_of ::<Self >() + } +} +This is not necessary, see below.
Quoted 6 lines
<snip>
rust/kernel/device_id.rs new file +4
-0+ +/// Create device table alias for modpost. +#[macro_export] +macro_rules! module_device_table {rust/kernel/device_id.rs line 163
Quoted 10 lines
rust/kernel/device_id.rs new file +10
-0+ ($table_type: literal, $module_table_name:ident, $table_name:ident) => { +#[rustfmt::skip] +#[export_name = +concat! ( "__mod_device_table__" , $table_type, +"__" , module_path! (), +"_" , line! (), +"_" , stringify! ($table_name)) +] + static $module_table_name: [core::mem::MaybeUninit<u8>; $table_name.raw_ids().size()] = +unsafe { core::mem::transmute_copy ($table_name.raw_ids ()) };
const_size_of_valwill be stable in Rust 1.85, so we can start using the feature and
Quoted 4 lines
static $module_table_name: [core::mem::MaybeUninit<u8>; core::mem::size_of_val($table_name.raw_ids())] =
unsafe { core::mem::transmute_copy($table_name.raw_ids()) };should work.
Thanks for the note, this indeed saves a few lines.
Since the series has been applied already, feel free to send a patch.
Quoted 28 lines
Quoted 27 lines
rust/kernel/device_id.rs new file +2
-0+ }; +}rust/kernel/lib.rs +6
-0@@ -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 )]
Re: [PATCH v7 03/16] rust: implement `IdArray`, `IdTable` and `RawDeviceId`
In reply to Danilo KrummrichQuoted 26 lines · Danilo Krummrich
rust/kernel/device_id.rs new file +25
-0+/// Marker trait to indicate a Rust device ID type represents a corresponding C device ID type. +/// +/// This is meant to be implemented by buses/subsystems so that they can use [`IdTable`] to +/// guarantee (at compile-time) zero-termination of device id tables provided by drivers. +/// +/// # Safety +/// +/// Implementers must ensure that: +/// - `Self` is layout-compatible with [`RawDeviceId::RawType`]; i.e. it's safe to transmute to +/// `RawDeviceId`. +/// +/// This requirement is needed so `IdArray::new` can convert `Self` to `RawType` when building +/// the ID table. +/// +/// Ideally, this should be achieved using a const function that does conversion instead of +/// transmute; however, const trait functions relies on `const_trait_impl` unstable feature, +/// which is broken/gone in Rust 1.73. +/// +/// - `DRIVER_DATA_OFFSET` is the offset of context/data field of the device ID (usually named +/// `driver_data`) of the device ID, the field is suitable sized to write a `usize` value. +/// +/// Similar to the previous requirement, the data should ideally be added during `Self` to +/// `RawType` conversion, but there's currently no way to do it when using traits in const. +pub unsafe trait RawDeviceId { +/// The raw type that holds the device id.
rust/kernel/device_id.rs line 44
Quoted 10 lines
rust/kernel/device_id.rs new file +10
-0+/// +/// Id tables created from [`Self`] are going to hold this type in its zero-terminated array. +type RawType :Copy ; + +/// The offset to the context/data field. +const DRIVER_DATA_OFFSET :usize ; + +/// The index stored at `DRIVER_DATA_OFFSET` of the implementor of the [`RawDeviceId`] trait. +fn index (&self) ->usize ; +}
Very late to the game here, but have a question about the use of OFFSET here. Why is this preferred to a method that returns a pointer to the field?
Danilo Krummrich
maintainerRe: [PATCH v7 03/16] rust: implement `IdArray`, `IdTable` and `RawDeviceId`
Quoted 33 lines · Tamir Duberstein
Quoted 32 lines · Danilo Krummrich
rust/kernel/device_id.rs new file +31
-0+/// Marker trait to indicate a Rust device ID type represents a corresponding C device ID type. +/// +/// This is meant to be implemented by buses/subsystems so that they can use [`IdTable`] to +/// guarantee (at compile-time) zero-termination of device id tables provided by drivers. +/// +/// # Safety +/// +/// Implementers must ensure that: +/// - `Self` is layout-compatible with [`RawDeviceId::RawType`]; i.e. it's safe to transmute to +/// `RawDeviceId`. +/// +/// This requirement is needed so `IdArray::new` can convert `Self` to `RawType` when building +/// the ID table. +/// +/// Ideally, this should be achieved using a const function that does conversion instead of +/// transmute; however, const trait functions relies on `const_trait_impl` unstable feature, +/// which is broken/gone in Rust 1.73. +/// +/// - `DRIVER_DATA_OFFSET` is the offset of context/data field of the device ID (usually named +/// `driver_data`) of the device ID, the field is suitable sized to write a `usize` value. +/// +/// Similar to the previous requirement, the data should ideally be added during `Self` to +/// `RawType` conversion, but there's currently no way to do it when using traits in const. +pub unsafe trait RawDeviceId { +/// The raw type that holds the device id. +/// +/// Id tables created from [`Self`] are going to hold this type in its zero-terminated array. +type RawType :Copy ; + +/// The offset to the context/data field. +const DRIVER_DATA_OFFSET :usize ;
Quoted 8 lines
rust/kernel/device_id.rs line 44
Quoted 4 lines
rust/kernel/device_id.rs new file +4
-0+ +/// The index stored at `DRIVER_DATA_OFFSET` of the implementor of the [`RawDeviceId`] trait. +fn index (&self) ->usize ; +}Very late to the game here, but have a question about the use of OFFSET here. Why is this preferred to a method that returns a pointer to the field?
We need it from const context, trait methods can't be const (yet).
Re: [PATCH v7 03/16] rust: implement `IdArray`, `IdTable` and `RawDeviceId`
Quoted 44 lines · Danilo Krummrich
Quoted 33 lines · Tamir Duberstein
Quoted 32 lines · Danilo Krummrich
rust/kernel/device_id.rs new file +31
-0+/// Marker trait to indicate a Rust device ID type represents a corresponding C device ID type. +/// +/// This is meant to be implemented by buses/subsystems so that they can use [`IdTable`] to +/// guarantee (at compile-time) zero-termination of device id tables provided by drivers. +/// +/// # Safety +/// +/// Implementers must ensure that: +/// - `Self` is layout-compatible with [`RawDeviceId::RawType`]; i.e. it's safe to transmute to +/// `RawDeviceId`. +/// +/// This requirement is needed so `IdArray::new` can convert `Self` to `RawType` when building +/// the ID table. +/// +/// Ideally, this should be achieved using a const function that does conversion instead of +/// transmute; however, const trait functions relies on `const_trait_impl` unstable feature, +/// which is broken/gone in Rust 1.73. +/// +/// - `DRIVER_DATA_OFFSET` is the offset of context/data field of the device ID (usually named +/// `driver_data`) of the device ID, the field is suitable sized to write a `usize` value. +/// +/// Similar to the previous requirement, the data should ideally be added during `Self` to +/// `RawType` conversion, but there's currently no way to do it when using traits in const. +pub unsafe trait RawDeviceId { +/// The raw type that holds the device id. +/// +/// Id tables created from [`Self`] are going to hold this type in its zero-terminated array. +type RawType :Copy ; + +/// The offset to the context/data field. +const DRIVER_DATA_OFFSET :usize ;Quoted 8 lines
rust/kernel/device_id.rs line 44
Quoted 4 lines
rust/kernel/device_id.rs new file +4
-0+ +/// The index stored at `DRIVER_DATA_OFFSET` of the implementor of the [`RawDeviceId`] trait. +fn index (&self) ->usize ; +}Very late to the game here, but have a question about the use of OFFSET here. Why is this preferred to a method that returns a pointer to the field?
Quoted 1 line
We need it from const context, trait methods can't be const (yet).
Thanks for explaining!
Danilo Krummrich
maintainerPATCH v7 4/16 rust: add rcu abstraction
In reply to Danilo KrummrichFrom: 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
@@ -19690,6 +19690,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
@@ -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
@@ -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
@@ -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
Gary Guo
maintainerRe: [PATCH v7 04/16] rust: add rcu abstraction
Quoted 9 lines · On Thu, 19 Dec 2024 18:04:06 +0100 Danilo Krummrich <[email protected]> wrote:
From: Wedson Almeida Filho <[email protected]>
Add a simple abstraction to guard critical code sections with an rcu read lock.
Reviewed-by Boqun Feng <[email protected]>Signed-off-by Wedson Almeida Filho <[email protected]>Co-developed-by Danilo Krummrich <[email protected]>Signed-off-by Danilo Krummrich <[email protected]>
Quoted 9 lines
--- 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
[resend to the list]
Quoted 80 lines
MAINTAINERS +1
-0@@ -19690,6 +19690,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*.crust/helpers/helpers.c +1
-0@@ -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@@ -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@@ -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 +18
-0@@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! RCU support. +//! +//! C header: [`include/linux/rcupdate.h`](srctree/include/linux/rcupdate.h) + +use crate ::{bindings, types::NotThreadSafe }; + +/// Evidence that the RCU read side lock is held on the current thread/CPU. +/// +/// The type is explicitly not `Send` because this property is per-thread/CPU. +/// +/// # Invariants +/// +/// The RCU read side lock is actually held while instances of this guard exist. +pub struct Guard (NotThreadSafe ); + +impl Guard {
rust/kernel/sync/rcu.rs line 28
Quoted 10 lines
rust/kernel/sync/rcu.rs new file +10
-0+/// Acquires the RCU read side lock and returns a guard. +pub fn new () ->Self { +// SAFETY: An FFI call with no additional requirements. +unsafe { bindings::rcu_read_lock () }; +// INVARIANT: The RCU read side lock was just acquired above. +Self (NotThreadSafe ) + } + +/// Explicitly releases the RCU read side lock. +pub fn unlock (self) {}
I don't think there's need for this, drop(rcu_guard) is equally clear.
There was a debate in Rust community about explicit lock methods, but the conclusion was to not have it,
see https://github.com/rust-lang/rust/issues/81872.
rust/kernel/sync/rcu.rs line 35
Quoted 7 lines
rust/kernel/sync/rcu.rs new file +7
-0+} + +impl Default for Guard { +fn default () ->Self { +Self ::new () + } +}
I don't think anyone would like to implicit acquire an RCU guard! I believe you included this because clippy is yelling, but in this case you shouldn't listen to clippy. Either suppress the warning or rename new to lock.
Quoted 12 lines
rust/kernel/sync/rcu.rs new file +12
-0+ +impl Drop for Guard { +fn drop (&mut self) { +// SAFETY: By the type invariants, the RCU read side is locked, so it is ok to unlock it. +unsafe { bindings::rcu_read_unlock () }; + } +} + +/// Acquires the RCU read side lock. +pub fn read_lock () ->Guard { +Guard ::new () +}
Danilo Krummrich
maintainerRe: [PATCH v7 04/16] rust: add rcu abstraction
Quoted 109 lines · Gary Guo
Quoted 9 lines · On Thu, 19 Dec 2024 18:04:06 +0100 Danilo Krummrich <[email protected]> wrote:
From: Wedson Almeida Filho <[email protected]>
Add a simple abstraction to guard critical code sections with an rcu read lock.
Reviewed-by Boqun Feng <[email protected]>Signed-off-by Wedson Almeida Filho <[email protected]>Co-developed-by Danilo Krummrich <[email protected]>Signed-off-by Danilo Krummrich <[email protected]>Quoted 9 lines
--- 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[resend to the list]
Quoted 85 lines
MAINTAINERS +1
-0@@ -19690,6 +19690,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*.crust/helpers/helpers.c +1
-0@@ -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@@ -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@@ -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 +23
-0@@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! RCU support. +//! +//! C header: [`include/linux/rcupdate.h`](srctree/include/linux/rcupdate.h) + +use crate ::{bindings, types::NotThreadSafe }; + +/// Evidence that the RCU read side lock is held on the current thread/CPU. +/// +/// The type is explicitly not `Send` because this property is per-thread/CPU. +/// +/// # Invariants +/// +/// The RCU read side lock is actually held while instances of this guard exist. +pub struct Guard (NotThreadSafe ); + +impl Guard { +/// Acquires the RCU read side lock and returns a guard. +pub fn new () ->Self { +// SAFETY: An FFI call with no additional requirements. +unsafe { bindings::rcu_read_lock () }; +// INVARIANT: The RCU read side lock was just acquired above.
Quoted 8 lines
rust/kernel/sync/rcu.rs line 28
Quoted 5 lines
rust/kernel/sync/rcu.rs new file +5
-0+Self (NotThreadSafe ) + } + +/// Explicitly releases the RCU read side lock. +pub fn unlock (self) {}I don't think there's need for this,
drop(rcu_guard)is equally clear.
I don't mind one or the other, feel free to send a patch to remove it. :)
Quoted 5 lines
There was a debate in Rust community about explicit lock methods, but the conclusion was to not have it,
see https://github.com/rust-lang/rust/issues/81872.
Quoted 12 lines
rust/kernel/sync/rcu.rs line 35
Quoted 7 lines
rust/kernel/sync/rcu.rs new file +7
-0+} + +impl Default for Guard { +fn default () ->Self { +Self ::new () + } +}I don't think anyone would like to implicit acquire an RCU guard! I believe you included this because clippy is yelling, but in this case you shouldn't listen to clippy. Either suppress the warning or rename
newtolock.
I picked up this patch from Wedson, so I can't tell for sure. I don't see any other reason for this though, so we could remove it.
Quoted 14 lines
Quoted 13 lines
rust/kernel/sync/rcu.rs new file +12
-0+ +impl Drop for Guard { +fn drop (&mut self) { +// SAFETY: By the type invariants, the RCU read side is locked, so it is ok to unlock it. +unsafe { bindings::rcu_read_unlock () }; + } +} + +/// Acquires the RCU read side lock. +pub fn read_lock () ->Guard { +Guard ::new () +}
Danilo Krummrich
maintainerPATCH v7 5/16 rust: types: add `Opaque::pin_init`
In reply to Danilo KrummrichAnalogous 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
@@ -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
Re: [PATCH v7 05/16] rust: types: add `Opaque::pin_init`
Quoted 6 lines · On Thu, 19 Dec 2024 18:04:07 +0100 Danilo Krummrich <[email protected]> wrote:
Analogous to
Opaque::newaddOpaque::pin_init, which instead of a valueTtakes aPinInit<T>and returns aPinInit<Opaque<T>>.Reviewed-by Alice Ryhl <[email protected]>Suggested-by Alice Ryhl <[email protected]>Signed-off-by Danilo Krummrich <[email protected]>
Quoted 26 lines
--- rust/kernel/types.rs | 11 +++++++++++ 1 file changed, 11 insertions(+)rust/kernel/types.rs +11
-0@@ -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
Danilo Krummrich
maintainerPATCH v7 6/16 rust: add `Revocable` type
In reply to Danilo KrummrichFrom: 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 | 219 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 rust/kernel/revocable.rs
rust/kernel/lib.rs +1 -0
@@ -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 +219 -0
219 lines not on this page · raw message
Signature · 1 line
2.47.1
Danilo Krummrich
maintainerPATCH v7 7/16 rust: add `io::{Io, IoRaw}` base types
In reply to Danilo KrummrichI/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
@@ -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
@@ -79,6 +79,7 @@#[doc(hidden)] pub use bindings; +pub mod io;pub use macros;pub use uapi;
Signature · 1 line
2.47.1
Re: [PATCH v7 07/16] rust: add `io::{Io, IoRaw}` base types
Quoted 373 lines · Danilo Krummrich
I/O memory is typically either mapped through direct calls to
ioremap()or subsystem / bus specific ones such aspci_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::readborIo:try_readb.
Iosupports 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.
IoRawis meant to be embedded into a structure (e.g.pci::Barorio::IoMem) which creates the actual I/O memory mapping and initializesIoRawaccordingly.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 aDevrescontainer, such that it gets revoked once the device is unbound.Reviewed-by Alice Ryhl <[email protected]>Tested-by Daniel Almeida <[email protected]>Reviewed-by Daniel Almeida <[email protected]>Signed-off-by Danilo Krummrich <[email protected]>--- 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.rsrust/helpers/helpers.c +1
-0@@ -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@@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-2.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) +{ +iounmap (addr); +} + +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 +209
-0@@ -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 as _) }; +/// 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(addras _ ) } + } + +/// 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(addras _ ) }) + } + }; +} + +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, addras _ , ) } + } + +/// 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, addras _ ) } +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
rust/kernel/io.rs line 219
Quoted 10 lines
rust/kernel/io.rs new file +10
-0+// 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 + }
Currently reworking the portmem abstractions I wrote for the LED/SE10 driver.
Right now I’m wondering if it would make sense to move the 3 functions above (offset_valid, io_addr and io_addr_assert into IoRaw), as I’m considering reusing the IoRaw in the portmem and then just offer the outb/outw/outl functions on a wraping type around IoRaw. For this I would also use the same functions to check bounds.
Thanks,
Fiona
Quoted 55 lines
rust/kernel/io.rs new file +41
-0+ +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@@ -79,6 +79,7 @@#[doc(hidden)] pub use bindings; +pub mod io;pub use macros;pub use uapi;Signature · 1 line
2.47.1
Danilo Krummrich
maintainerRe: [PATCH v7 07/16] rust: add `io::{Io, IoRaw}` base types
Quoted 50 lines · Fiona Behrens
Quoted 48 lines · Danilo Krummrich
rust/kernel/io.rs new file +48
-0+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 ));
Quoted 8 lines
rust/kernel/io.rs line 219
Quoted 3 lines
rust/kernel/io.rs new file +3
-0+ + self.addr () + offset + }Currently reworking the portmem abstractions I wrote for the LED/SE10 driver.
Right now I’m wondering if it would make sense to move the 3 functions above (offset_valid,io_addrandio_addr_assertinto IoRaw), as I’m considering reusing the IoRaw in the portmem and then just offer the outb/outw/outl functions on a wraping type around IoRaw. For this I would also use the same functions to check bounds.
Sure, feel free to move them. I think I should have moved those functions to IoRaw from the get-go.
- Danilo
Re: [PATCH v7 07/16] rust: add `io::{Io, IoRaw}` base types
In reply to Danilo KrummrichQuoted 25 lines · Danilo Krummrich
I/O memory is typically either mapped through direct calls to
ioremap()or subsystem / bus specific ones such aspci_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::readborIo:try_readb.
Iosupports 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.
IoRawis meant to be embedded into a structure (e.g.pci::Barorio::IoMem) which creates the actual I/O memory mapping and initializesIoRawaccordingly.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 aDevrescontainer, such that it gets revoked once the device is unbound.
[...]
rust/kernel/io.rs line 119
Quoted 10 lines
rust/kernel/io.rs new file +10
-0+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);
I'm rather new to Rust in the kernel but I've been playing
around with the nova-core stub (https://lore.kernel.org/
dri-devel/[email protected]/) a bit and the first thing I tried to do was add a new register access. Of course when doing that I forgot to update the BAR size definition:
const BAR0_SIZE: usize = 8;
pub(crate) type Bar0 = pci::Bar<BAR0_SIZE>;
That lead to this rather cryptic build error message:
RUSTC [M] drivers/gpu/nova-core/nova_core.odrivers/gpu/nova-core/nova_core.o: warning: objtool: _RNvXNtCs3LxSlPxFOnk_9nova_core6driverNtB2_8NovaCoreNtNtCsgupvMsqqUAi_6kernel3pci6Driver5probe() falls through to next function _RNvXs_NtCs3LxSlPxFOnk_9nova_core3gpuNtB4_7ChipsetNtNtCs2wKxHFORdeQ_4core3fmt7Display3fmt()
MODPOST Module.symvers
ERROR: modpost: "rust_build_error" [drivers/gpu/nova-core/nova_core.ko] undefined!
Building it into the kernel instead of as a module provides some more hints:
vmlinux.o: warning: objtool: _RNvXNtCs3LxSlPxFOnk_9nova_core6driverNtB2_8NovaCoreNtNtCsgupvMsqqUAi_6kernel3pci6Driver5probe() falls through to next function _RNvXs_NtCs3LxSlPxFOnk_9nova_core3gpuNtB4_7ChipsetNtNtCs2wKxHFORdeQ_4core3fmt7Display3fmt()
OBJCOPY modules.builtin.modinfo
GEN modules.builtin
MODPOST Module.symvers
UPD include/generated/utsversion.h CC init/version-timestamp.o KSYMS .tmp_vmlinux0.kallsyms.S AS .tmp_vmlinux0.kallsyms.o LD .tmp_vmlinux1
/usr/bin/ld: vmlinux.o: in function `<kernel::io::Io<4>>::io_addr_assert::<u32>':
/data/source/linux/rust/kernel/build_assert.rs:78: undefined reference to `rust_build_error'
That was just enough to let me figure out what I'd done wrong based on the small change I'd made. However the lack of reference to the actual offending line of code that triggered the assert still made it more difficult than needed.
Is this a known issue or limitation? Or is this a bug/rough edge that still needs fixing? Or alternatively am I just doing something wrong? Would appreciate any insights as figuring out what I'd done wrong here was a bit of a rough introduction!
- Alistair
Quoted 157 lines
rust/kernel/io.rs new file +141
-0+ +// 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, addras _ , ) } + } + +/// 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, addras _ ) } +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@@ -79,6 +79,7 @@#[doc(hidden)] pub use bindings; +pub mod io;pub use macros;pub use uapi;Signature · 1 line
2.47.1
Re: [PATCH v7 07/16] rust: add `io::{Io, IoRaw}` base types
Hi Alistair,
Quoted 5 lines · Alistair Popple
Is this a known issue or limitation? Or is this a bug/rough edge that still needs fixing? Or alternatively am I just doing something wrong? Would appreciate any insights as figuring out what I'd done wrong here was a bit of a rough introduction!
Yeah, it is a result of our build_assert! machinery:
https://rust.docs.kernel.org/kernel/macro.build_assert.html
which works by producing a build (link) error rather than the usual compiler error and thus the bad error message.
build_assert! is really the biggest hammer we have to assert something is true at build time, since it may rely on the optimizer. For instance, if static_assert! is usable in that context, it should be instead of build_assert!.
Ideally we would have a way to get the message propagated somehow, because it is indeed confusing.
I hope that helps.
Cheers,
Miguel
Re: [PATCH v7 07/16] rust: add `io::{Io, IoRaw}` base types
Quoted 16 lines · Miguel Ojeda
Hi Alistair,
Quoted 5 lines · Alistair Popple
Is this a known issue or limitation? Or is this a bug/rough edge that still needs fixing? Or alternatively am I just doing something wrong? Would appreciate any insights as figuring out what I'd done wrong here was a bit of a rough introduction!
Yeah, it is a result of our
build_assert!machinery:https://rust.docs.kernel.org/kernel/macro.build_assert.html
which works by producing a build (link) error rather than the usual compiler error and thus the bad error message.
Quoted 7 lines
build_assert!is really the biggest hammer we have to assert something is true at build time, since it may rely on the optimizer. For instance, ifstatic_assert!is usable in that context, it should be instead ofbuild_assert!.Ideally we would have a way to get the message propagated somehow, because it is indeed confusing.
Are there any proposals or ideas for how we could do that?
Quoted 1 line
I hope that helps.
Kind of, but given the current state of build_assert's and the impossiblity of debugging them should we avoid adding them until they can be fixed?
Unless the code absolutely cannot compile without them I think it would be better to turn them into runtime errors that can at least hint at what might have gone wrong. For example I think a run-time check would have been much more appropriate and easy to debug here, rather than having to bisect my changes.
I was hoping I could suggest CONFIG_RUST_BUILD_ASSERT_ALLOW be made default yes, but testing with that also didn't yeild great results - it creates a backtrace but that doesn't seem to point anywhere terribly close to where the bad access was, I'm guessing maybe due to inlining and other optimisations - or is decode_stacktrace.sh not the right tool for this job?
Thanks.
- Alistair
Quoted 2 lines
Cheers,
Miguel
Danilo Krummrich
maintainerRe: [PATCH v7 07/16] rust: add `io::{Io, IoRaw}` base types
Quoted 2 lines · Alistair Popple
Kind of, but given the current state of build_assert's and the impossiblity of debugging them should we avoid adding them until they can be fixed?
If you build the module as built-in the linker gives you some more hints, but I agree it's still not great.
I think build_assert() is not widely used yet and, until the situation improves, we could also keep a list of common pitfalls if that helps?
Quoted 4 lines
Unless the code absolutely cannot compile without them I think it would be better to turn them into runtime errors that can at least hint at what might have gone wrong. For example I think a run-time check would have been much more appropriate and easy to debug here, rather than having to bisect my changes.
No, especially for I/O the whole purpose of the non-try APIs is to ensure that boundary checks happen at compile time.
Quoted 5 lines
I was hoping I could suggest
CONFIG_RUST_BUILD_ASSERT_ALLOWbe made default yes, but testing with that also didn't yeild great results - it creates a backtrace but that doesn't seem to point anywhere terribly close to where the bad access was, I'm guessing maybe due to inlining and other optimisations - or isdecode_stacktrace.shnot the right tool for this job?
I was about to suggest CONFIG_RUST_BUILD_ASSERT_ALLOW=y to you, since this will make the kernel panic when hitting a build_assert().
I gave this a quick try with [1] in qemu and it lead to the following hint, right before the oops:
[ 0.957932] rust_kernel: panicked at /home/danilo/projects/linux/nova/nova-next/rust/kernel/io.rs:216:9:
Seeing this immediately tells me that I'm trying to do out of bound I/O accesses in my driver, which indeed doesn't tell me the exact line (in case things are inlined too much to gather it from the backtrace of the oops), but it should be good enough, no?
[1]
samples/rust/rust_driver_pci.rs +1 -1
@@ -13,7 +13,7 @@ impl Regs {const OFFSET :usize =0x4 ;const DATA :usize =0x8 ;const COUNT :usize =0xC ; -const END :usize =0x10 ; +const END :usize =0x2 ; }type Bar0 = pci::Bar <{Regs ::END }>;
Re: [PATCH v7 07/16] rust: add `io::{Io, IoRaw}` base types
Quoted 6 lines · Danilo Krummrich
Quoted 2 lines · Alistair Popple
Kind of, but given the current state of build_assert's and the impossiblity of debugging them should we avoid adding them until they can be fixed?
If you build the module as built-in the linker gives you some more hints, but I agree it's still not great.
Yeah, that is how I eventually figured it out as a result of trying to resolve the "undefined symbol" build error.
Quoted 2 lines
I think
build_assert()is not widely used yet and, until the situation improves, we could also keep a list of common pitfalls if that helps?
I've asked a few times, but are there any plans/ideas on how to improve the situation? I'm kind of suprised we're building things on top of a fairly broken feature without an idea of how we might make that feature work. I'd love to help, but being new to R4L no immediately useful ideas come to mind.
At the very least if we could produce something more informative in the output than the objtool "falls through to next function" warning and the undefined reference that would help. I'm not sure if there is something we could do in the build system to detect that and print a build-time hint along the lines of "hey, you probably hit a build assert".
Quoted 7 lines
Quoted 4 lines
Unless the code absolutely cannot compile without them I think it would be better to turn them into runtime errors that can at least hint at what might have gone wrong. For example I think a run-time check would have been much more appropriate and easy to debug here, rather than having to bisect my changes.
No, especially for I/O the whole purpose of the non-try APIs is to ensure that boundary checks happen at compile time.
To be honest I don't really understand the utility here because the compile-time check can't be a definitive check. You're always going to have to fallback to a run-time check because at least for PCI (and likely others) you can't know for at compile time if the IO region is big enough or matches the compile-time constraint.
So this seems more like a quiz for developers to check if they really do want to access the given offset. It's not really doing any useful compile-time bounds check that is preventing something bad from happening, becasue that has to happen at run-time. Especially as the whole BAR is mapped anyway.
Hence why I think an obvious run-time error instead of an obtuse and difficult to figure out build error would be better. But maybe I'm missing some usecase here that makes this more useful.
Quoted 12 lines
Quoted 5 lines
I was hoping I could suggest
CONFIG_RUST_BUILD_ASSERT_ALLOWbe made default yes, but testing with that also didn't yeild great results - it creates a backtrace but that doesn't seem to point anywhere terribly close to where the bad access was, I'm guessing maybe due to inlining and other optimisations - or isdecode_stacktrace.shnot the right tool for this job?I was about to suggest
CONFIG_RUST_BUILD_ASSERT_ALLOW=yto you, since this will make the kernel panic when hitting abuild_assert().I gave this a quick try with [1] in qemu and it lead to the following hint, right before the oops:
Quoted 6 lines
[ 0.957932] rust_kernel: panicked at /home/danilo/projects/linux/nova/nova-next/rust/kernel/io.rs:216:9:Seeing this immediately tells me that I'm trying to do out of bound I/O accesses in my driver, which indeed doesn't tell me the exact line (in case things are inlined too much to gather it from the backtrace of the oops), but it should be good enough, no?
*smacks forehead*
Yes. So to answer this question:
Quoted 1 line
or is
decode_stacktrace.shnot the right tool for this job?
No, it isn't. Just reading the kernel logs properly would have been a better option! I guess coming from C I'm just too used to jumping straight to the stack trace in the case of BUG_ON(), etc. Thanks for point that out.
Quoted 15 lines
[1]
samples/rust/rust_driver_pci.rs +1
-1@@ -13,7 +13,7 @@ impl Regs {const OFFSET :usize =0x4 ;const DATA :usize =0x8 ;const COUNT :usize =0xC ; -const END :usize =0x10 ; +const END :usize =0x2 ; }type Bar0 = pci::Bar <{Regs ::END }>;
Danilo Krummrich
maintainerRe: [PATCH v7 07/16] rust: add `io::{Io, IoRaw}` base types
Quoted 8 lines · Alistair Popple
Quoted 4 lines · Danilo Krummrich
On Tue, Feb 25, 2025 at 04:50:05PM +1100, Alistair Popple wrote:
I think
build_assert()is not widely used yet and, until the situation improves, we could also keep a list of common pitfalls if that helps?I've asked a few times, but are there any plans/ideas on how to improve the situation?
I just proposed a few ones. If the limitation can be resolved I don't know.
There are two different cases.
(1) build_assert() is evaluated in const context to false
(2) the compiler can't guarantee that build_assert() is evaluated to true at compile time
For (1) you get a proper backtrace by the compiler. For (2) there's currently only the option to make the linker fail, which doesn't produce the most useful output.
If we wouldn't do (2) we'd cause a kernel panic on runtime, which can be enforced with CONFIG_RUST_BUILD_ASSERT_ALLOW=y.
Quoted 3 lines
I'm kind of suprised we're building things on top of a fairly broken feature without an idea of how we might make that feature work. I'd love to help, but being new to R4L no immediately useful ideas come to mind.
The feature is not broken at all, it works perfectly fine. It's just that for
(2) it has an ergonomic limitation.
Quoted 5 lines
Quoted 5 lines
Quoted 4 lines
Unless the code absolutely cannot compile without them I think it would be better to turn them into runtime errors that can at least hint at what might have gone wrong. For example I think a run-time check would have been much more appropriate and easy to debug here, rather than having to bisect my changes.
Quoted 8 lines
Quoted 2 lines
No, especially for I/O the whole purpose of the non-try APIs is to ensure that boundary checks happen at compile time.
To be honest I don't really understand the utility here because the compile-time check can't be a definitive check. You're always going to have to fallback to a run-time check because at least for PCI (and likely others) you can't know for at compile time if the IO region is big enough or matches the compile-time constraint.
That's not true, let me explain.
When you write a driver, you absolutely have to know the register layout. This means that you also know what the minimum PCI bar size has to be for your driver to work. If it would be smaller than what your driver expects, it can't function anyways. In Rust we make use of this fact.
When you map a PCI bar through pdev.iomap_region_sized you pass in a const generic (SIZE) representing the *expected* PCI bar size. This can indeed fail on run-time, but that's fine, as mentioned, if the bar is smaller than what your driver expect, it's useless anyways.
If the call succeeds, it means that the actual PCI bar size is greater or equal to SIZE. Since SIZE is known at compile time all subsequent I/O operations can be boundary checked against SIZE at compile time, which additionally makes the call infallible. This works for most I/O operations drivers do.
However, sometimes we need to do I/O ops at a PCI bar offset that is only known at run-time. In this case you can use the try_* variants, such as try_read32(). Those do boundary checks against the actual size of the PCI bar, which is only known at run-time and hence they're fallible.
Quoted 5 lines
So this seems more like a quiz for developers to check if they really do want to access the given offset. It's not really doing any useful compile-time bounds check that is preventing something bad from happening, becasue that has to happen at run-time. Especially as the whole BAR is mapped anyway.
See the explanation above.
Quoted 4 lines
Hence why I think an obvious run-time error instead of an obtuse and difficult to figure out build error would be better. But maybe I'm missing some usecase here that makes this more useful.
No, failing the boundary check at compile time (if possible) is always better than failing it at run-time for obvious reasons.
Quoted 22 lines
Quoted 13 lines
Quoted 6 lines
I was hoping I could suggest
CONFIG_RUST_BUILD_ASSERT_ALLOWbe made default yes, but testing with that also didn't yeild great results - it creates a backtrace but that doesn't seem to point anywhere terribly close to where the bad access was, I'm guessing maybe due to inlining and other optimisations - or isdecode_stacktrace.shnot the right tool for this job?I was about to suggest
CONFIG_RUST_BUILD_ASSERT_ALLOW=yto you, since this will make the kernel panic when hitting abuild_assert().I gave this a quick try with [1] in qemu and it lead to the following hint, right before the oops:
Quoted 6 lines
[ 0.957932] rust_kernel: panicked at /home/danilo/projects/linux/nova/nova-next/rust/kernel/io.rs:216:9:Seeing this immediately tells me that I'm trying to do out of bound I/O accesses in my driver, which indeed doesn't tell me the exact line (in case things are inlined too much to gather it from the backtrace of the oops), but it should be good enough, no?
*smacks forehead*
Quoted 7 lines
Yes. So to answer this question:
Quoted 1 line
or is
decode_stacktrace.shnot the right tool for this job?No, it isn't. Just reading the kernel logs properly would have been a better option! I guess coming from C I'm just too used to jumping straight to the stack trace in the case of
BUG_ON(), etc. Thanks for point that out.
Happy I could help.
Re: [PATCH v7 07/16] rust: add `io::{Io, IoRaw}` base types
Quoted 11 lines · Danilo Krummrich
Quoted 8 lines · Alistair Popple
Quoted 4 lines · Danilo Krummrich
On Tue, Feb 25, 2025 at 04:50:05PM +1100, Alistair Popple wrote:
I think
build_assert()is not widely used yet and, until the situation improves, we could also keep a list of common pitfalls if that helps?I've asked a few times, but are there any plans/ideas on how to improve the situation?
I just proposed a few ones. If the limitation can be resolved I don't know.
Thanks. The limitation is what I was asking about. The work around for (2) works, but is not terribly discoverable. I will see if I can come up with something better to help with discoverability that at least.
Quoted 13 lines
There are two different cases.
(1)
build_assert()is evaluated in const context to false
(2) the compiler can't guarantee thatbuild_assert()is evaluated to true at compile timeFor (1) you get a proper backtrace by the compiler. For (2) there's currently only the option to make the linker fail, which doesn't produce the most useful output.
If we wouldn't do (2) we'd cause a kernel panic on runtime, which can be enforced with
CONFIG_RUST_BUILD_ASSERT_ALLOW=y.
Quoted 6 lines
Quoted 3 lines
I'm kind of suprised we're building things on top of a fairly broken feature without an idea of how we might make that feature work. I'd love to help, but being new to R4L no immediately useful ideas come to mind.
The feature is not broken at all, it works perfectly fine. It's just that for
(2) it has an ergonomic limitation.
I'm not sure I agree it works perfectly fine. Developer ergonomics are an important aspect of any build environment, and I'd argue the ergonomic limitation for (2) means it is at least somewhat broken and needs fixing.
Quoted 26 lines
Quoted 5 lines
Quoted 5 lines
Quoted 4 lines
Unless the code absolutely cannot compile without them I think it would be better to turn them into runtime errors that can at least hint at what might have gone wrong. For example I think a run-time check would have been much more appropriate and easy to debug here, rather than having to bisect my changes.
Quoted 8 lines
Quoted 2 lines
No, especially for I/O the whole purpose of the non-try APIs is to ensure that boundary checks happen at compile time.
To be honest I don't really understand the utility here because the compile-time check can't be a definitive check. You're always going to have to fallback to a run-time check because at least for PCI (and likely others) you can't know for at compile time if the IO region is big enough or matches the compile-time constraint.
That's not true, let me explain.
When you write a driver, you absolutely have to know the register layout. This means that you also know what the minimum PCI bar size has to be for your driver to work. If it would be smaller than what your driver expects, it can't function anyways. In Rust we make use of this fact.
When you map a PCI bar through
pdev.iomap_region_sizedyou pass in a const generic (SIZE) representing the *expected* PCI bar size. This can indeed fail on run-time, but that's fine, as mentioned, if the bar is smaller than what your driver expect, it's useless anyways.
Quoted 4 lines
If the call succeeds, it means that the actual PCI bar size is greater or equal to
SIZE. SinceSIZEis known at compile time all subsequent I/O operations can be boundary checked againstSIZEat compile time, which additionally makes the call infallible. This works for most I/O operations drivers do.
Argh! That's the piece I was missing - that this makes the IO call infallible and thus removes the need to write run-time error handling code. Sadly of course that's not actually true, because I/O operations can always fail for reasons other than what can be checked at compile time (eg. in particular PCI devices can fall off the bus and return all 0xF's). But I guess existing drivers don't really handle those cases either.
Anyway thanks for your time and detailed explainations, I really just started this thread as I think reducing friction for existing kernel developers to start looking at Rust in the kernel is important. So I wanted to highlight that the build_assert as linker error is really confusing when coming from C, and that it's an area that I think needs to improve to make Rust more successful in the kernel. Sadly I don't have any immediately ideas but if I do I will post them.
Quoted 52 lines
However, sometimes we need to do I/O ops at a PCI bar offset that is only known at run-time. In this case you can use the
try_*variants, such astry_read32(). Those do boundary checks against the actual size of the PCI bar, which is only known at run-time and hence they're fallible.Quoted 5 lines
So this seems more like a quiz for developers to check if they really do want to access the given offset. It's not really doing any useful compile-time bounds check that is preventing something bad from happening, becasue that has to happen at run-time. Especially as the whole BAR is mapped anyway.
See the explanation above.
Quoted 4 lines
Hence why I think an obvious run-time error instead of an obtuse and difficult to figure out build error would be better. But maybe I'm missing some usecase here that makes this more useful.
No, failing the boundary check at compile time (if possible) is always better than failing it at run-time for obvious reasons.
Quoted 22 lines
Quoted 13 lines
Quoted 6 lines
I was hoping I could suggest
CONFIG_RUST_BUILD_ASSERT_ALLOWbe made default yes, but testing with that also didn't yeild great results - it creates a backtrace but that doesn't seem to point anywhere terribly close to where the bad access was, I'm guessing maybe due to inlining and other optimisations - or isdecode_stacktrace.shnot the right tool for this job?I was about to suggest
CONFIG_RUST_BUILD_ASSERT_ALLOW=yto you, since this will make the kernel panic when hitting abuild_assert().I gave this a quick try with [1] in qemu and it lead to the following hint, right before the oops:
Quoted 6 lines
[ 0.957932] rust_kernel: panicked at /home/danilo/projects/linux/nova/nova-next/rust/kernel/io.rs:216:9:Seeing this immediately tells me that I'm trying to do out of bound I/O accesses in my driver, which indeed doesn't tell me the exact line (in case things are inlined too much to gather it from the backtrace of the oops), but it should be good enough, no?
*smacks forehead*
Quoted 7 lines
Yes. So to answer this question:
Quoted 1 line
or is
decode_stacktrace.shnot the right tool for this job?No, it isn't. Just reading the kernel logs properly would have been a better option! I guess coming from C I'm just too used to jumping straight to the stack trace in the case of
BUG_ON(), etc. Thanks for point that out.Happy I could help.
Danilo Krummrich
maintainerRe: [PATCH v7 07/16] rust: add `io::{Io, IoRaw}` base types
Quoted 26 lines · Alistair Popple
Quoted 20 lines · Danilo Krummrich
Quoted 5 lines · Alistair Popple
To be honest I don't really understand the utility here because the compile-time check can't be a definitive check. You're always going to have to fallback to a run-time check because at least for PCI (and likely others) you can't know for at compile time if the IO region is big enough or matches the compile-time constraint.
That's not true, let me explain.
When you write a driver, you absolutely have to know the register layout. This means that you also know what the minimum PCI bar size has to be for your driver to work. If it would be smaller than what your driver expects, it can't function anyways. In Rust we make use of this fact.
When you map a PCI bar through
pdev.iomap_region_sizedyou pass in a const generic (SIZE) representing the *expected* PCI bar size. This can indeed fail on run-time, but that's fine, as mentioned, if the bar is smaller than what your driver expect, it's useless anyways.Quoted 4 lines
If the call succeeds, it means that the actual PCI bar size is greater or equal to
SIZE. SinceSIZEis known at compile time all subsequent I/O operations can be boundary checked againstSIZEat compile time, which additionally makes the call infallible. This works for most I/O operations drivers do.
Quoted 6 lines
Argh! That's the piece I was missing - that this makes the IO call infallible and thus removes the need to write run-time error handling code. Sadly of course that's not actually true, because I/O operations can always fail for reasons other than what can be checked at compile time (eg. in particular PCI devices can fall off the bus and return all 0xF's). But I guess existing drivers don't really handle those cases either.
We handle this case too by giving out a Devres<pci::Bar> rather than just a pci::Bar. The former gets revoked when the device falls off the bus.
Re: [PATCH v7 07/16] rust: add `io::{Io, IoRaw}` base types
In reply to Alistair PoppleQuoted 8 lines · Alistair Popple
I'm not sure I agree it works perfectly fine. Developer ergonomics are an important aspect of any build environment, and I'd argue the ergonomic limitation for (2) means it is at least somewhat broken and needs fixing.
Anyway thanks for your time and detailed explainations, I really just started this thread as I think reducing friction for existing kernel developers to start looking at Rust in the kernel is important.
+1, it is indeed very, very important.
But, just to clarify, we have been caring about ergonomics and reducing friction for kernel developers since the very beginning, including asking upstream Rust for features and so on when applicable.
In general, it has been a factor in most topics we have discussed in the team since 2020, not just for source code or debugging features, but also docs, KUnit, and so on.
That is why we would like to improve it and why we have it in our upstream Rust wishlist and so on. In other words, it is not that we do not see the issue!
I hope that clarifies.
Cheers,
Miguel
Re: [PATCH v7 07/16] rust: add `io::{Io, IoRaw}` base types
In reply to Alistair PoppleQuoted 5 lines · Alistair Popple
I've asked a few times, but are there any plans/ideas on how to improve the situation? I'm kind of suprised we're building things on top of a fairly broken feature without an idea of how we might make that feature work. I'd love to help, but being new to R4L no immediately useful ideas come to mind.
It is not "broken" -- after all, it works as it was intended/designed when it was introduced, though it is definitely a hack and thus indeed the message could be improved greatly. :)
As for how to improve it, e.g. Gary suggested the other day to use the DWARF information to locate the call site.
I guess another way would be to generate different symbol names per call site, so that we can embed the path and line number into it (more or less), so that the user at least has a hint, though that may have disadvantages.
Cheers,
Miguel
Danilo Krummrich
maintainerPATCH v7 8/16 rust: add devres abstraction
In reply to Danilo KrummrichAdd 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 | 178 +++++++++++++++++++++++++++++++++++++++++ rust/kernel/lib.rs | 1 + 5 files changed, 191 insertions(+) create mode 100644 rust/helpers/device.c create mode 100644 rust/kernel/devres.rs
MAINTAINERS +1 -0
@@ -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
@@ -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
@@ -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 +178 -0
178 lines not on this page · raw message
rust/kernel/lib.rs +1 -0
@@ -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
Gary Guo
maintainerRe: [PATCH v7 08/16] rust: add devres abstraction
Quoted 93 lines · On Thu, 19 Dec 2024 18:04:10 +0100 Danilo Krummrich <[email protected]> wrote:
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().Signed-off-by Danilo Krummrich <[email protected]>--- MAINTAINERS | 1 + rust/helpers/device.c | 10 +++ rust/helpers/helpers.c | 1 + rust/kernel/devres.rs | 178 +++++++++++++++++++++++++++++++++++++++++ rust/kernel/lib.rs | 1 + 5 files changed, 191 insertions(+) create mode 100644 rust/helpers/device.c create mode 100644 rust/kernel/devres.rs<snip>
rust/kernel/devres.rs new file +68
-0+pub struct Devres <T >(Arc <DevresInner <T >>); + +impl <T >DevresInner <T > { +fn new (dev: &Device , data:T , flags:Flags ) ->Result <Arc <DevresInner <T >>> { +let inner =Arc ::pin_init ( +pin_init! (DevresInner { + data <-Revocable ::new (data), + }), + flags, + )?; + +// Convert `Arc<DevresInner>` into a raw pointer and make devres own this reference until +// `Self::devres_callback` is called. +let data = inner.clone ().into_raw (); + +// SAFETY: `devm_add_action` guarantees to call `Self::devres_callback` once `dev` is +// detached. +let ret =unsafe { + bindings::devm_add_action (dev.as_raw (),Some (Self ::devres_callback), dataas _ ) + }; + +if ret !=0 { +// SAFETY: We just created another reference to `inner` in order to pass it to +// `bindings::devm_add_action`. If `bindings::devm_add_action` fails, we have to drop +// this reference accordingly. +let _ =unsafe {Arc ::from_raw (data) }; +return Err (Error ::from_errno (ret)); + } + +Ok (inner) + } + +#[allow(clippy::missing_safety_doc)] +unsafe extern "C" fn devres_callback (ptr: *mut kernel::ffi::c_void ) { +let ptr = ptras *mut DevresInner <T >; +// Devres owned this memory; now that we received the callback, drop the `Arc` and hence the +// reference. +// SAFETY: Safe, since we leaked an `Arc` reference to devm_add_action() in +// `DevresInner::new`. +let inner =unsafe {Arc ::from_raw (ptr) }; + + inner.data.revoke (); + } +} + +impl <T >Devres <T > { +/// Creates a new [`Devres`] instance of the given `data`. The `data` encapsulated within the +/// returned `Devres` instance' `data` will be revoked once the device is detached. +pub fn new (dev: &Device , data:T , flags:Flags ) ->Result <Self > { +let inner =DevresInner ::new (dev, data, flags)?; + +Ok (Devres (inner)) + } + +/// Same as [`Devres::new`], but does not return a `Devres` instance. Instead the given `data` +/// is owned by devres and will be revoked / dropped, once the device is detached. +pub fn new_foreign_owned (dev: &Device , data:T , flags:Flags ) ->Result { +let _ =DevresInner ::new (dev, data, flags)?; + +Ok (()) + } +} + +impl <T >Deref for Devres <T > { +type Target =Revocable <T >; + +fn deref (&self) -> &Self ::Target { + &self.0 .data
rust/kernel/devres.rs line 172
Quoted 10 lines
rust/kernel/devres.rs new file +10
-0+ } +} + +impl <T >Drop for Devres <T > { +fn drop (&mut self) { +// Revoke the data, such that it gets dropped already and the actual resource is freed. +// +// `DevresInner` has to stay alive until the devres callback has been called. This is +// necessary since we don't know when `Devres` is dropped and calling +// `devm_remove_action()` instead could race with `devres_release_all()`.
IIUC, the outcome of that race is the WARN if devres_release_all takes the spinlock first and has already remvoed the action?
Could you do a custom devres_release here that mimick devm_remove_action but omit the WARN? This way it allows the memory behind DevresInner to be freed early without keeping it allocated until the end of device lifetime.
Quoted 18 lines
rust/kernel/devres.rs new file +6
-0+// +// SAFETY: When `drop` runs, it's guaranteed that nobody is accessing the revocable data +// anymore, hence it is safe not to wait for the grace period to finish. +unsafe { self.revoke_nosync () }; + } +}rust/kernel/lib.rs +1
-0@@ -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 )]
Danilo Krummrich
maintainerRe: [PATCH v7 08/16] rust: add devres abstraction
Quoted 102 lines · Gary Guo
Quoted 99 lines · On Thu, 19 Dec 2024 18:04:10 +0100 Danilo Krummrich <[email protected]> wrote:
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().Signed-off-by Danilo Krummrich <[email protected]>--- MAINTAINERS | 1 + rust/helpers/device.c | 10 +++ rust/helpers/helpers.c | 1 + rust/kernel/devres.rs | 178 +++++++++++++++++++++++++++++++++++++++++ rust/kernel/lib.rs | 1 + 5 files changed, 191 insertions(+) create mode 100644 rust/helpers/device.c create mode 100644 rust/kernel/devres.rs<snip>
rust/kernel/devres.rs new file +74
-0+pub struct Devres <T >(Arc <DevresInner <T >>); + +impl <T >DevresInner <T > { +fn new (dev: &Device , data:T , flags:Flags ) ->Result <Arc <DevresInner <T >>> { +let inner =Arc ::pin_init ( +pin_init! (DevresInner { + data <-Revocable ::new (data), + }), + flags, + )?; + +// Convert `Arc<DevresInner>` into a raw pointer and make devres own this reference until +// `Self::devres_callback` is called. +let data = inner.clone ().into_raw (); + +// SAFETY: `devm_add_action` guarantees to call `Self::devres_callback` once `dev` is +// detached. +let ret =unsafe { + bindings::devm_add_action (dev.as_raw (),Some (Self ::devres_callback), dataas _ ) + }; + +if ret !=0 { +// SAFETY: We just created another reference to `inner` in order to pass it to +// `bindings::devm_add_action`. If `bindings::devm_add_action` fails, we have to drop +// this reference accordingly. +let _ =unsafe {Arc ::from_raw (data) }; +return Err (Error ::from_errno (ret)); + } + +Ok (inner) + } + +#[allow(clippy::missing_safety_doc)] +unsafe extern "C" fn devres_callback (ptr: *mut kernel::ffi::c_void ) { +let ptr = ptras *mut DevresInner <T >; +// Devres owned this memory; now that we received the callback, drop the `Arc` and hence the +// reference. +// SAFETY: Safe, since we leaked an `Arc` reference to devm_add_action() in +// `DevresInner::new`. +let inner =unsafe {Arc ::from_raw (ptr) }; + + inner.data.revoke (); + } +} + +impl <T >Devres <T > { +/// Creates a new [`Devres`] instance of the given `data`. The `data` encapsulated within the +/// returned `Devres` instance' `data` will be revoked once the device is detached. +pub fn new (dev: &Device , data:T , flags:Flags ) ->Result <Self > { +let inner =DevresInner ::new (dev, data, flags)?; + +Ok (Devres (inner)) + } + +/// Same as [`Devres::new`], but does not return a `Devres` instance. Instead the given `data` +/// is owned by devres and will be revoked / dropped, once the device is detached. +pub fn new_foreign_owned (dev: &Device , data:T , flags:Flags ) ->Result { +let _ =DevresInner ::new (dev, data, flags)?; + +Ok (()) + } +} + +impl <T >Deref for Devres <T > { +type Target =Revocable <T >; + +fn deref (&self) -> &Self ::Target { + &self.0 .data + } +} + +impl <T >Drop for Devres <T > { +fn drop (&mut self) { +// Revoke the data, such that it gets dropped already and the actual resource is freed.
Quoted 8 lines
rust/kernel/devres.rs line 172
Quoted 4 lines
rust/kernel/devres.rs new file +4
-0+// +// `DevresInner` has to stay alive until the devres callback has been called. This is +// necessary since we don't know when `Devres` is dropped and calling +// `devm_remove_action()` instead could race with `devres_release_all()`. IIUC, the outcome of that race is the
WARNifdevres_release_alltakes the spinlock first and has already remvoed the action?
Yes, this was one issue. But I think there was another when you have a class Registration that is owned by a Devres, which holds private data that is encapsulated in a Devres too.
I have this case in Nova where the DRM device' private data holds the PCI bar, and the DRM device registration has a reference of the corresponding DRM device.
But maybe this also was something else. I will double check and if I can confirm that the WARN_ON() in devm_remove_action() is the only issue, we can certainly change this.
Quoted 25 lines
Could you do a custom
devres_releasehere that mimickdevm_remove_actionbut omit theWARN? This way it allows the memory behind DevresInner to be freed early without keeping it allocated until the end of device lifetime.Quoted 18 lines
rust/kernel/devres.rs new file +6
-0+// +// SAFETY: When `drop` runs, it's guaranteed that nobody is accessing the revocable data +// anymore, hence it is safe not to wait for the grace period to finish. +unsafe { self.revoke_nosync () }; + } +}rust/kernel/lib.rs +1
-0@@ -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 )]
Danilo Krummrich
maintainerRe: [PATCH v7 08/16] rust: add devres abstraction
Quoted 116 lines · Danilo Krummrich
Quoted 102 lines · Gary Guo
Quoted 99 lines · On Thu, 19 Dec 2024 18:04:10 +0100 Danilo Krummrich <[email protected]> wrote:
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().Signed-off-by Danilo Krummrich <[email protected]>--- MAINTAINERS | 1 + rust/helpers/device.c | 10 +++ rust/helpers/helpers.c | 1 + rust/kernel/devres.rs | 178 +++++++++++++++++++++++++++++++++++++++++ rust/kernel/lib.rs | 1 + 5 files changed, 191 insertions(+) create mode 100644 rust/helpers/device.c create mode 100644 rust/kernel/devres.rs<snip>
rust/kernel/devres.rs new file +74
-0+pub struct Devres <T >(Arc <DevresInner <T >>); + +impl <T >DevresInner <T > { +fn new (dev: &Device , data:T , flags:Flags ) ->Result <Arc <DevresInner <T >>> { +let inner =Arc ::pin_init ( +pin_init! (DevresInner { + data <-Revocable ::new (data), + }), + flags, + )?; + +// Convert `Arc<DevresInner>` into a raw pointer and make devres own this reference until +// `Self::devres_callback` is called. +let data = inner.clone ().into_raw (); + +// SAFETY: `devm_add_action` guarantees to call `Self::devres_callback` once `dev` is +// detached. +let ret =unsafe { + bindings::devm_add_action (dev.as_raw (),Some (Self ::devres_callback), dataas _ ) + }; + +if ret !=0 { +// SAFETY: We just created another reference to `inner` in order to pass it to +// `bindings::devm_add_action`. If `bindings::devm_add_action` fails, we have to drop +// this reference accordingly. +let _ =unsafe {Arc ::from_raw (data) }; +return Err (Error ::from_errno (ret)); + } + +Ok (inner) + } + +#[allow(clippy::missing_safety_doc)] +unsafe extern "C" fn devres_callback (ptr: *mut kernel::ffi::c_void ) { +let ptr = ptras *mut DevresInner <T >; +// Devres owned this memory; now that we received the callback, drop the `Arc` and hence the +// reference. +// SAFETY: Safe, since we leaked an `Arc` reference to devm_add_action() in +// `DevresInner::new`. +let inner =unsafe {Arc ::from_raw (ptr) }; + + inner.data.revoke (); + } +} + +impl <T >Devres <T > { +/// Creates a new [`Devres`] instance of the given `data`. The `data` encapsulated within the +/// returned `Devres` instance' `data` will be revoked once the device is detached. +pub fn new (dev: &Device , data:T , flags:Flags ) ->Result <Self > { +let inner =DevresInner ::new (dev, data, flags)?; + +Ok (Devres (inner)) + } + +/// Same as [`Devres::new`], but does not return a `Devres` instance. Instead the given `data` +/// is owned by devres and will be revoked / dropped, once the device is detached. +pub fn new_foreign_owned (dev: &Device , data:T , flags:Flags ) ->Result { +let _ =DevresInner ::new (dev, data, flags)?; + +Ok (()) + } +} + +impl <T >Deref for Devres <T > { +type Target =Revocable <T >; + +fn deref (&self) -> &Self ::Target { + &self.0 .data + } +} + +impl <T >Drop for Devres <T > { +fn drop (&mut self) { +// Revoke the data, such that it gets dropped already and the actual resource is freed. Quoted 8 lines
rust/kernel/devres.rs line 172
Quoted 4 lines
rust/kernel/devres.rs new file +4
-0+// +// `DevresInner` has to stay alive until the devres callback has been called. This is +// necessary since we don't know when `Devres` is dropped and calling +// `devm_remove_action()` instead could race with `devres_release_all()`. IIUC, the outcome of that race is the
WARNifdevres_release_alltakes the spinlock first and has already remvoed the action?Yes, this was one issue. But I think there was another when you have a class
Registrationthat is owned by aDevres, which holds private data that is encapsulated in aDevrestoo.
Quoted 6 lines
I have this case in Nova where the DRM device' private data holds the PCI bar, and the DRM device registration has a reference of the corresponding DRM device.
But maybe this also was something else. I will double check and if I can confirm that the
WARN_ON()indevm_remove_action()is the only issue, we can certainly change this.
Ok, I double checked and this should indeed be the only issue.
I can reproduce the race and can confirm that a devm_remove_action_nowarn() works, as long as we make it return the integer that indicates whether the action has been removed already, such that we can handle it from the Rust side accordingly.
Generally, I don't think it's a big deal, drivers usually hold the resources they request anyways until remove(). But it's still an improvement, so I'll send a patch for that.
Quoted 26 lines
Quoted 26 lines
Could you do a custom
devres_releasehere that mimickdevm_remove_actionbut omit theWARN? This way it allows the memory behind DevresInner to be freed early without keeping it allocated until the end of device lifetime.Quoted 18 lines
rust/kernel/devres.rs new file +6
-0+// +// SAFETY: When `drop` runs, it's guaranteed that nobody is accessing the revocable data +// anymore, hence it is safe not to wait for the grace period to finish. +unsafe { self.revoke_nosync () }; + } +}rust/kernel/lib.rs +1
-0@@ -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 )]
Danilo Krummrich
maintainerRe: [PATCH v7 08/16] rust: add devres abstraction
In reply to Gary GuoQuoted 111 lines · Gary Guo
Quoted 93 lines · On Thu, 19 Dec 2024 18:04:10 +0100 Danilo Krummrich <[email protected]> wrote:
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().Signed-off-by Danilo Krummrich <[email protected]>--- MAINTAINERS | 1 + rust/helpers/device.c | 10 +++ rust/helpers/helpers.c | 1 + rust/kernel/devres.rs | 178 +++++++++++++++++++++++++++++++++++++++++ rust/kernel/lib.rs | 1 + 5 files changed, 191 insertions(+) create mode 100644 rust/helpers/device.c create mode 100644 rust/kernel/devres.rs<snip>
rust/kernel/devres.rs new file +68
-0+pub struct Devres <T >(Arc <DevresInner <T >>); + +impl <T >DevresInner <T > { +fn new (dev: &Device , data:T , flags:Flags ) ->Result <Arc <DevresInner <T >>> { +let inner =Arc ::pin_init ( +pin_init! (DevresInner { + data <-Revocable ::new (data), + }), + flags, + )?; + +// Convert `Arc<DevresInner>` into a raw pointer and make devres own this reference until +// `Self::devres_callback` is called. +let data = inner.clone ().into_raw (); + +// SAFETY: `devm_add_action` guarantees to call `Self::devres_callback` once `dev` is +// detached. +let ret =unsafe { + bindings::devm_add_action (dev.as_raw (),Some (Self ::devres_callback), dataas _ ) + }; + +if ret !=0 { +// SAFETY: We just created another reference to `inner` in order to pass it to +// `bindings::devm_add_action`. If `bindings::devm_add_action` fails, we have to drop +// this reference accordingly. +let _ =unsafe {Arc ::from_raw (data) }; +return Err (Error ::from_errno (ret)); + } + +Ok (inner) + } + +#[allow(clippy::missing_safety_doc)] +unsafe extern "C" fn devres_callback (ptr: *mut kernel::ffi::c_void ) { +let ptr = ptras *mut DevresInner <T >; +// Devres owned this memory; now that we received the callback, drop the `Arc` and hence the +// reference. +// SAFETY: Safe, since we leaked an `Arc` reference to devm_add_action() in +// `DevresInner::new`. +let inner =unsafe {Arc ::from_raw (ptr) }; + + inner.data.revoke (); + } +} + +impl <T >Devres <T > { +/// Creates a new [`Devres`] instance of the given `data`. The `data` encapsulated within the +/// returned `Devres` instance' `data` will be revoked once the device is detached. +pub fn new (dev: &Device , data:T , flags:Flags ) ->Result <Self > { +let inner =DevresInner ::new (dev, data, flags)?; + +Ok (Devres (inner)) + } + +/// Same as [`Devres::new`], but does not return a `Devres` instance. Instead the given `data` +/// is owned by devres and will be revoked / dropped, once the device is detached. +pub fn new_foreign_owned (dev: &Device , data:T , flags:Flags ) ->Result { +let _ =DevresInner ::new (dev, data, flags)?; + +Ok (()) + } +} + +impl <T >Deref for Devres <T > { +type Target =Revocable <T >; + +fn deref (&self) -> &Self ::Target { + &self.0 .datarust/kernel/devres.rs line 172
Quoted 10 lines
rust/kernel/devres.rs new file +10
-0+ } +} + +impl <T >Drop for Devres <T > { +fn drop (&mut self) { +// Revoke the data, such that it gets dropped already and the actual resource is freed. +// +// `DevresInner` has to stay alive until the devres callback has been called. This is +// necessary since we don't know when `Devres` is dropped and calling +// `devm_remove_action()` instead could race with `devres_release_all()`. IIUC, the outcome of that race is the
WARNifdevres_release_alltakes the spinlock first and has already remvoed the action?
Quoted 4 lines
Could you do a custom
devres_releasehere that mimickdevm_remove_actionbut omit theWARN? This way it allows the memory behind DevresInner to be freed early without keeping it allocated until the end of device lifetime.
Better late than never, I now remember what's the *actual* race I was preventing here:
| Task 0 | Task 1
--|----------------------------------------------------------------------------
1 | Devres::drop() { | devres_release_all() {
2 | DevresInner::remove_action() { | spin_lock_irqsave();
3 | | remove_nodes(&todo);
4 | | spin_unlock_irqrestore();
5 | devm_remove_action_nowarn(); |
6 | let _ = Arc::from_raw(); |
7 | } |
8 | } |
9 | | release_nodes(&todo);
10| | }So, if devres_release_all() wins the race it stores the devres action within the temporary todo list. Which means that in 9 we enter DevresInner::devres_callback() even though DevresInner has been freed already.
Unfortunately, this race can happen with [1], but not with this original version of Devres.
I see two ways to fix it:
1) Just revert [1] and stick with the original version.
2) Use devm_release_action() instead of devm_remove_action() and don't drop the reference in DevresInner::remove_action() (6). This way the reference is always dropped from the callback.
With 2) we still have an unnecessary call to revoke() if Devres is dropped before the device is detached from the driver, but that's still better than keeping DevresInner alive until the device is detached from the driver, which the original version did. Hence, I'll go ahead and prepare a patch for this, unless there are any concerns.
- Danilo
[1] https://lore.kernel.org/rust-for-linux/[email protected]/
Quoted 20 lines
Quoted 19 lines
rust/kernel/devres.rs new file +6
-0+// +// SAFETY: When `drop` runs, it's guaranteed that nobody is accessing the revocable data +// anymore, hence it is safe not to wait for the grace period to finish. +unsafe { self.revoke_nosync () }; + } +}rust/kernel/lib.rs +1
-0@@ -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 )]
Danilo Krummrich
maintainerRe: [PATCH v7 08/16] rust: add devres abstraction
Quoted 33 lines · Danilo Krummrich
Quoted 8 lines
rust/kernel/devres.rs line 172
Quoted 7 lines
rust/kernel/devres.rs new file +7
-0+impl <T >Drop for Devres <T > { +fn drop (&mut self) { +// Revoke the data, such that it gets dropped already and the actual resource is freed. +// +// `DevresInner` has to stay alive until the devres callback has been called. This is +// necessary since we don't know when `Devres` is dropped and calling +// `devm_remove_action()` instead could race with `devres_release_all()`. Quoted 8 lines
IIUC, the outcome of that race is the
WARNifdevres_release_alltakes the spinlock first and has already remvoed the action?Could you do a custom
devres_releasehere that mimickdevm_remove_actionbut omit theWARN? This way it allows the memory behind DevresInner to be freed early without keeping it allocated until the end of device lifetime.Better late than never, I now remember what's the *actual* race I was preventing here:
| Task 0 | Task 1 --|---------------------------------------------------------------------------- 1 | Devres::drop() { | devres_release_all() { 2 | DevresInner::remove_action() { | spin_lock_irqsave(); 3 | | remove_nodes(&todo); 4 | | spin_unlock_irqrestore(); 5 | devm_remove_action_nowarn(); | 6 | let _ = Arc::from_raw(); | 7 | } | 8 | } | 9 | | release_nodes(&todo); 10| | }
Quoted 6 lines
So, if
devres_release_all()wins the race it stores the devres action within the temporary todo list. Which means that in 9 we enterDevresInner::devres_callback()even though DevresInner has been freed already.Unfortunately, this race can happen with [1], but not with this original version of Devres.
Well, actually this can't happen, since obviously we know whether Devres:drop removed it or whether it will be released by devres_release_all() and we only free DevresInner conditionally.
So, the code in [1] is fine; I somehow managed to confuse myself.
Sorry for the noise.
- Danilo
Quoted 18 lines
I see two ways to fix it:
1) Just revert [1] and stick with the original version.
2) Use
devm_release_action()instead ofdevm_remove_action()and don't drop the reference inDevresInner::remove_action()(6). This way the reference is always dropped from the callback.With 2) we still have an unnecessary call to
revoke()if Devres is dropped before the device is detached from the driver, but that's still better than keeping DevresInner alive until the device is detached from the driver, which the original version did. Hence, I'll go ahead and prepare a patch for this, unless there are any concerns.- Danilo
[1] https://lore.kernel.org/rust-for-linux/[email protected]/
Danilo Krummrich
maintainerPATCH v7 9/16 rust: pci: add basic PCI device / driver abstractions
In reply to Danilo KrummrichImplement 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 | 292 ++++++++++++++++++++++++++++++++ 6 files changed, 315 insertions(+) create mode 100644 rust/helpers/pci.c create mode 100644 rust/kernel/pci.rs
MAINTAINERS +1 -0
@@ -18104,6 +18104,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
@@ -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
@@ -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
@@ -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
@@ -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 +292 -0
292 lines not on this page · raw message
Signature · 1 line
2.47.1
Danilo Krummrich
maintainerPATCH v7 10/16 rust: pci: implement I/O mappable `pci::Bar`
In reply to Danilo KrummrichImplement 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 | 142 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 141 insertions(+), 1 deletion(-)
rust/kernel/pci.rs +141 -1
@@ -5,15 +5,19 @@//! 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 , }; -use core::ptr::addr_of_mut; +use core::{ops::Deref , ptr::addr_of_mut};use kernel::prelude::*;/// An adapter for the registration of PCI drivers.
@@ -235,9 +239,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, lenas 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 (), ioptras _ ); + 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`. ///
@@ -283,6 +393,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
Danilo Krummrich
maintainerPATCH v7 11/16 samples: rust: add Rust PCI sample driver
In reply to Danilo KrummrichThis 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
@@ -18105,6 +18105,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
@@ -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
@@ -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
Danilo Krummrich
maintainerPATCH v7 12/16 rust: of: add `of::DeviceId` abstraction
In reply to Danilo Krummrichof::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
@@ -17504,6 +17504,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
@@ -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
Danilo Krummrich
maintainerPATCH v7 13/16 rust: driver: implement `Adapter`
In reply to Danilo KrummrichIn 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 | 58 ++++++++++++++++++++++++++++++++- 2 files changed, 58 insertions(+), 1 deletion(-)
rust/bindings/bindings_helper.h +1 -0
@@ -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 +57 -1
67 lines not on this page · raw message
Signature · 1 line
2.47.1
Danilo Krummrich
maintainerPATCH v7 14/16 rust: platform: add basic platform device / driver abstractions
In reply to Danilo KrummrichImplement 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
Danilo Krummrich
maintainerPATCH v7 15/16 samples: rust: add Rust platform sample driver
In reply to Danilo KrummrichAdd 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
@@ -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
@@ -33,6 +33,11 @@ dev@100 { reg = <0x100>; }; }; + + test-device@2 { + compatible = "test,rust-device"; + reg = <0x2>; + }; }; }; };
samples/rust/Kconfig +10 -0
@@ -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
@@ -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
Danilo Krummrich
maintainerPATCH v7 16/16 MAINTAINERS: add Danilo to DRIVER CORE
In reply to Danilo KrummrichLet'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
@@ -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
Quoted 43 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 v7:
==============
- Revocable:
- replacecompare_exchangewithswap- Driver:
- fix warning whenCONFIG_OF=n- I/O:
- remove unnecessary return statement inrust_helper_iounmap()
- fix cast in doctest forbindings::ioremap- Devres:
- fix cast in doctest forbindings::ioremap- PCI:
- removeDerefofpci::DeviceId
- renameDeviceIdconstructors
-new->from_id
-with_class ->from_class`
Quoted 3 lines
- MISC:
- usekernel::ffi::c_*instead ofcore::ffi::c_*
- rebase onto latest rust-next (0c5928deada15a8d075516e6e0d9ee19011bb000)
Not sure if resending is required, but after a quick retest the Tested-by from v6 still stands:
Thanks
Dirk
Greg KH
maintainerQuoted 43 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 v7:
==============
- Revocable:
- replacecompare_exchangewithswap- Driver:
- fix warning whenCONFIG_OF=n- I/O:
- remove unnecessary return statement inrust_helper_iounmap()
- fix cast in doctest forbindings::ioremap- Devres:
- fix cast in doctest forbindings::ioremap- PCI:
- removeDerefofpci::DeviceId
- renameDeviceIdconstructors
-new->from_id
-with_class ->from_class`
Quoted 3 lines
- MISC:
- usekernel::ffi::c_*instead ofcore::ffi::c_*
- rebase onto latest rust-next (0c5928deada15a8d075516e6e0d9ee19011bb000)
Thanks for this last round of changes, looks great to me! I've added them all to my driver-core-testing branch, and if 0-day doesn't complain, will move it to driver-core-next for merging into 6.14-rc1.
Thanks for persisting with this, looks great. Now the real work starts :)
thanks,
greg k-h