* [PATCH v2 0/3] Rust: add runtime PM support
@ 2026-07-21 15:34 Beata Michalska
2026-07-21 15:34 ` [PATCH v2 1/3] rust: " Beata Michalska
` (2 more replies)
0 siblings, 3 replies; 6+ messages in thread
From: Beata Michalska @ 2026-07-21 15:34 UTC (permalink / raw)
To: ojeda, dakr, gregkh, rafael
Cc: boqun, gary, bjorn3_gh, lossin, a.hindborg, aliceryhl, tmgross,
daniel.almeida, boris.brezillon, work, samitolvanen,
rust-for-linux, driver-core, linux-kernel, linux-pm
This series adds initial Rust support for Linux runtime PM.
The concept for Rust abstraction gets introduced in the first patch.
It provides callback registration, PM callback dispatch through PMOps trait,
scoped request helpers for common runtime PM operations and configuration
helpers.
The abstraction intends to keep the request semantics aligned with
the C runtime PM core.
Second patch integrates dev_pm_ops into the Rust platform driver abstraction,
enabling drivers to register PM callbacks with the core.
The last in the series is provided for demonstrative purposes
(not intended to be an actual merge material).
This has been lightly tested with the Tyr driver.
It is based on Danilo's drm-lifetime branch [1]
Note: Currently the PM abstraction stores the payload data associated with the
PM transitions within `struct device_private`. Newly introduced member has been
deliberately named 'rust_private', wiht the intention to be extended to cover
data registgry (on the Rust side) for any future cases that might need to take
the same approach as PM abstraction. And those are comming.
The work is ongoing and hopefully will be available soon.
For the time beinng the
`rust_private`
is intended to represent
`rust_pm_private`.
TBC
---
v2:
- Moving away from the old PMContext<T, Dormant/Active> type-state design
- Introducing an explicit Registration<'a, T> object.
- PMContext now carries a lifetime and is derived from the registration
- Callback data is no longer recovered through driver data
- Simplified PMOps
- Runtime payload handling change
- Request helpers slightly extended
---
[1] https://git.kernel.org/pub/scm/linux/kernel/git/dakr/linux.git/log/?h=drm-lifetime
---
Beata Michalska (3):
rust: add runtime PM support
rust: platform: wire runtime PM callbacks
drm/tyr: enable runtime PM
drivers/base/base.h | 3 +
drivers/gpu/drm/tyr/driver.rs | 112 +++-
drivers/gpu/drm/tyr/file.rs | 7 +-
rust/bindings/bindings_helper.h | 1 +
rust/helpers/helpers.c | 1 +
rust/helpers/pm_runtime.c | 44 ++
rust/kernel/lib.rs | 1 +
rust/kernel/platform.rs | 9 +
rust/kernel/pm.rs | 1020 +++++++++++++++++++++++++++++++
9 files changed, 1177 insertions(+), 21 deletions(-)
create mode 100644 rust/helpers/pm_runtime.c
create mode 100644 rust/kernel/pm.rs
--
2.43.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v2 1/3] rust: add runtime PM support
2026-07-21 15:34 [PATCH v2 0/3] Rust: add runtime PM support Beata Michalska
@ 2026-07-21 15:34 ` Beata Michalska
2026-07-27 14:56 ` Onur Özkan
2026-07-21 15:34 ` [PATCH v2 2/3] rust: platform: wire runtime PM callbacks Beata Michalska
2026-07-21 15:34 ` [PATCH v2 3/3] drm/tyr: enable runtime PM Beata Michalska
2 siblings, 1 reply; 6+ messages in thread
From: Beata Michalska @ 2026-07-21 15:34 UTC (permalink / raw)
To: ojeda, dakr, gregkh, rafael
Cc: boqun, gary, bjorn3_gh, lossin, a.hindborg, aliceryhl, tmgross,
daniel.almeida, boris.brezillon, work, samitolvanen,
rust-for-linux, driver-core, linux-kernel, linux-pm
Add a Rust abstraction for Linux runtime PM, allowing Rust drivers to
register runtime PM callbacks and use scoped runtime PM requests while
retaining the behavior and guarantees of the underlying C PM core.
Runtime PM is managed through a registration object tied to the device
lifetime. The registration stores the callback payload and 'ensures' that
the registration, its `PMContext`, and the payload do not outlive the
device. All that while `PMContext` holds a lifetime-annotated device reference
so that runtime PM requests are made only while the device remains bound.
Drivers define runtime PM callbacks through the `PMOps` trait. The
generated `dev_pm_ops` callbacks take the stored payload, pass ownership
to the corresponding Rust callback, and store the returned payload for
the next invocation. Each callback must return a valid payload,
regardless of whether the power transition succeeds or not.
`PMContext` provides scoped helpers for common runtime PM operations:
- `ResumeScope` resumes the device for the lifetime of the scope.
- `AwakeScope` resumes the device and holds a runtime PM usage reference.
- `RetainScope` increments the runtime PM usage count without resuming the
device.
Each scope performs the corresponding inverse operation when dropped,
according to the selected driver-defined profile.
The abstraction does not change the semantics of the C runtime PM API.
Asynchronous requests may only queue work, non-waiting requests may fail
instead of sleeping, and callbacks remain subject to the existing driver
core rules.
The current implementation requires the PM registration and `PMOps`
implementation to use the same type because callback dispatch interprets
the stored registration data using that relationship. This requirement is
not yet fully enforced by the type system.
Signed-off-by: Beata Michalska <beata.michalska@arm.com>
---
drivers/base/base.h | 3 +
rust/bindings/bindings_helper.h | 1 +
rust/helpers/helpers.c | 1 +
rust/helpers/pm_runtime.c | 44 ++
rust/kernel/lib.rs | 1 +
rust/kernel/pm.rs | 1020 +++++++++++++++++++++++++++++++
6 files changed, 1070 insertions(+)
create mode 100644 rust/helpers/pm_runtime.c
create mode 100644 rust/kernel/pm.rs
diff --git a/drivers/base/base.h b/drivers/base/base.h
index a19f4cda2c83..c49303e4a86a 100644
--- a/drivers/base/base.h
+++ b/drivers/base/base.h
@@ -119,6 +119,9 @@ struct device_private {
const struct device_driver *async_driver;
char *deferred_probe_reason;
struct device *device;
+#ifdef CONFIG_RUST
+ void *rust_private;
+#endif
u8 dead:1;
};
#define to_device_private_parent(obj) \
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 1124785e210b..78a9153c6a01 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -77,6 +77,7 @@
#include <linux/pid_namespace.h>
#include <linux/platform_device.h>
#include <linux/pm_opp.h>
+#include <linux/pm_runtime.h>
#include <linux/poll.h>
#include <linux/property.h>
#include <linux/pwm.h>
diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index 4488a87223b9..14d93a5d8b1f 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -76,6 +76,7 @@
#include "pci.c"
#include "pid_namespace.c"
#include "platform.c"
+#include "pm_runtime.c"
#include "poll.c"
#include "processor.c"
#include "property.c"
diff --git a/rust/helpers/pm_runtime.c b/rust/helpers/pm_runtime.c
new file mode 100644
index 000000000000..d0d71fcb0097
--- /dev/null
+++ b/rust/helpers/pm_runtime.c
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/pm_runtime.h>
+
+__rust_helper void rust_helper_pm_runtime_get_noresume(struct device *dev)
+{
+ pm_runtime_get_noresume(dev);
+}
+
+__rust_helper void rust_helper_pm_runtime_put_noidle(struct device *dev)
+{
+ pm_runtime_put_noidle(dev);
+}
+
+__rust_helper void rust_helper_pm_runtime_mark_last_busy(struct device *dev)
+{
+ pm_runtime_mark_last_busy(dev) ;
+}
+
+__rust_helper bool rust_helper_pm_runtime_active(struct device *dev)
+{
+ return pm_runtime_active(dev);
+}
+
+__rust_helper bool rust_helper_pm_runtime_suspended(struct device *dev)
+{
+ return pm_runtime_suspended(dev);
+}
+
+__rust_helper void rust_helper_pm_suspend_ignore_children(struct device *dev,
+ bool enable)
+{
+ pm_suspend_ignore_children(dev, enable);
+}
+
+__rust_helper int rust_helper_pm_runtime_set_active(struct device *dev)
+{
+ return pm_runtime_set_active(dev);
+}
+
+__rust_helper int rust_helper_pm_runtime_set_suspended(struct device *dev)
+{
+ return pm_runtime_set_suspended(dev);
+}
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index b72b2fbe046d..7e51e497e2f0 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -105,6 +105,7 @@
pub mod pci;
pub mod pid_namespace;
pub mod platform;
+pub mod pm;
pub mod prelude;
pub mod print;
pub mod processor;
diff --git a/rust/kernel/pm.rs b/rust/kernel/pm.rs
new file mode 100644
index 000000000000..b6a884fbe1ba
--- /dev/null
+++ b/rust/kernel/pm.rs
@@ -0,0 +1,1020 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Rust Runtime Power Management abstraction.
+//!
+//! C header: [`include/linux/pm_runtime.h`](srctree/include/linux/pm_runtime.h)
+
+use crate::{
+ bindings,
+ device::{
+ self,
+ AsBusDevice, //
+ },
+ error::{
+ to_result,
+ VTABLE_DEFAULT_ERROR, //
+ },
+ macros::paste,
+ prelude::*,
+ sync::atomic::{
+ ordering,
+ Atomic, //
+ },
+ sync::Arc,
+ types::ForeignOwnable, //
+};
+
+use core::{
+ cell::UnsafeCell,
+ marker::PhantomData
+};
+
+/// Runtime Power Management modes that determine how a particular PM
+/// transition is to be carried out.
+/// Corresponds to C Runtime PM flag argument bits:
+/// - `RPM_ASYNC`
+/// - `RPM_NOWAIT`
+/// - `RPM_GET_PUT`
+/// - `RPM_AUTO`
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+struct Mode(u32);
+
+impl Mode {
+ /// Synchronous PM operations - default.
+ const SYNC: Mode = Mode(0);
+ /// Allow asynchronous PM operations.
+ const ASYNC: Mode = Mode(bindings::RPM_ASYNC);
+ /// Do not wait for any pending requests to finish.
+ const NOWAIT: Mode = Mode(bindings::RPM_NOWAIT);
+ /// Acquire a runtime-PM usage reference.
+ const ACQUIRE: Mode = Mode(bindings::RPM_GET_PUT);
+ /// Use autosuspend.
+ const AUTO: Mode = Mode(bindings::RPM_AUTO);
+ /// Additional mode for devices supporting idle states.
+ /// No counterpart.
+ const IDLE: Mode = Mode(1 << 16);
+
+ const fn join(self, other: Self) -> Self {
+ Self(self.0 | other.0)
+ }
+
+ fn includes(self, other: Self) -> bool {
+ (self.0 & other.0) != 0
+ }
+}
+
+impl core::ops::BitOr for Mode {
+ type Output = Self;
+ fn bitor(self, rhs: Self) -> Self::Output {
+ Self(self.0 | rhs.0)
+ }
+}
+
+impl core::ops::BitAnd for Mode {
+ type Output = Self;
+ fn bitand(self, rhs: Self) -> Self::Output {
+ Self(self.0 & rhs.0)
+ }
+}
+
+impl core::ops::Not for Mode {
+ type Output = Self;
+ fn not(self) -> Self::Output {
+ Self(!self.0)
+ }
+}
+
+impl From<Mode> for core::ffi::c_int {
+ #[inline]
+ fn from(mode: Mode) -> core::ffi::c_int {
+ mode.0 as core::ffi::c_int
+ }
+}
+
+/// Utility macro for combining multiple request modes
+macro_rules! mode {
+ ($mode:expr $(, $args:expr)+ $(,)?) => {{
+ let mut new_mode = $mode;
+ $( new_mode = new_mode.join($args); )+
+ new_mode
+ }};
+}
+
+/// Device's runtime power management status
+#[repr(i32)]
+pub enum RuntimePMState {
+ /// Runtime PM has not been initialized for this device yet.
+ UNKNOWN = bindings::rpm_status_RPM_INVALID,
+ /// The device is expected to be runtime active and in it's normal operating state
+ RESUMED = bindings::rpm_status_RPM_ACTIVE,
+ /// The device is expected to be suspended, unavailable for normal operations
+ SUSPENDED = bindings::rpm_status_RPM_SUSPENDED,
+}
+
+/// Runtime power transition scope.
+pub struct Scope<'a, Tag> {
+ dev: &'a device::Device<device::Bound>,
+ mode: Mode,
+ _tag: PhantomData<Tag>,
+}
+
+/// Device resumed without incrementing the device's usage count
+pub struct Resume;
+/// Device resumed with the device's usage count being incremented
+pub struct Awake;
+/// Device with increased usage reference
+pub struct Retain;
+
+/// Resumes the device without acquiring the usage reference.
+/// Note: This does not guarantee the device will be kept active
+/// for the lifetime of the scope due to potential pending/incoming
+/// suspend requests.
+///
+/// On drop:
+/// - If `Mode::IDLE`, calls `__pm_runtime_idle()`:
+/// triggers idle notification before attempting to suspend
+/// - If `Mode::AUTO`, marks last busy then calls `__pm_runtime_suspend()`.
+/// - Otherwise calls `__pm_runtime_suspend()`.
+///
+/// The guard must be dropped from a context matching the requested transition
+/// mode: sync vs async.
+#[must_use = "dropping this guard issues the matching runtime PM release request"]
+pub struct ResumeScope<'a>(Scope<'a, Resume>);
+
+/// Acquires a runtime-PM usage reference and keeps the device powered.
+///
+/// Requires `Mode::ACQUIRE`. Drop behavior matches `ResumeScope`.
+/// The guard must be dropped from a context matching the requested transition
+/// mode: sync vs async.
+#[must_use = "dropping this guard releases its runtime PM hold"]
+pub struct AwakeScope<'a>(Scope<'a, Awake>);
+
+/// Prevents the device from getting suspended by holding the usage reference
+/// count.
+///
+/// On drop, calls `pm_runtime_put_noidle()`.
+#[must_use = "dropping this guard releases its runtime PM hold"]
+pub struct RetainScope<'a>(Scope<'a, Retain>);
+
+impl<'a> ResumeScope<'a> {
+ fn new(dev: &'a device::Device<device::Bound>, mode: Mode) -> Result<Self> {
+ if mode.includes(Mode::ACQUIRE) {
+ // Mode::ACQUIRE is intended to be used with Awake scope
+ // Avoid mixing the modes.
+ return Err(EINVAL);
+ }
+
+ // Mode::IDLE is internal so strip it of before passing further
+ Request::resume(dev, mode & !Mode::IDLE).map(|()| {
+ Self(Scope::<Resume> {
+ dev,
+ mode,
+ _tag: PhantomData,
+ })
+ })
+ }
+
+ fn release_inner(&self) -> Result {
+ let scope_mode = self.0.mode & !Mode::IDLE;
+
+ match self.0.mode {
+ mode if mode.includes(Mode::IDLE) => {
+ Request::idle(self.0.dev, scope_mode & (Mode::ASYNC | Mode::NOWAIT))
+ }
+ mode if mode.includes(Mode::AUTO) => {
+ Request::mark_last_busy(self.0.dev);
+ Request::suspend(self.0.dev, scope_mode)
+ }
+ _ => Request::suspend(self.0.dev, scope_mode),
+ }
+ }
+
+ /// Explicitly release the scope
+ /// This should be used in favor of regular drop
+ /// when error handling is required.
+ pub fn release(self) -> Result {
+ let result = self.release_inner();
+ core::mem::forget(self);
+ result
+ }
+}
+
+impl<'a> Drop for ResumeScope<'a> {
+ fn drop(&mut self) {
+ let _ = self.release_inner();
+ }
+}
+
+impl<'a> AwakeScope<'a> {
+ fn new(dev: &'a device::Device<device::Bound>, mode: Mode) -> Result<Self> {
+ if !mode.includes(Mode::ACQUIRE) {
+ return Err(EINVAL);
+ }
+ // Mode::IDLE is internal so strip it of before passing further
+ Request::resume(dev, mode & !Mode::IDLE)
+ .inspect_err(|_| Request::put_noidle(dev))
+ .map(|()| {
+ Self(Scope::<Awake> {
+ dev,
+ mode,
+ _tag: PhantomData,
+ })
+ })
+ }
+
+ fn release_inner(&self) -> Result {
+ let scope_mode = self.0.mode & !Mode::IDLE;
+ match self.0.mode {
+ mode if mode.includes(Mode::IDLE) => Request::idle(self.0.dev, scope_mode),
+ mode if mode.includes(Mode::AUTO) => {
+ Request::mark_last_busy(self.0.dev);
+ Request::suspend(self.0.dev, scope_mode)
+ }
+ _ => Request::idle(self.0.dev, scope_mode),
+ }
+ }
+
+ /// Explicitly release the scope
+ /// This should be used in favor of regular drop
+ /// when error handling is required.
+ pub fn release(self) -> Result {
+ let result = self.release_inner();
+ core::mem::forget(self);
+ result
+ }
+}
+
+impl<'a> Drop for AwakeScope<'a> {
+ fn drop(&mut self) {
+ let _ = self.release_inner();
+ }
+}
+
+impl<'a> RetainScope<'a> {
+ fn new(dev: &'a device::Device<device::Bound>) -> Result<Self> {
+ Request::get_noresume(dev);
+ Ok(Self(Scope::<Retain> {
+ dev,
+ mode: Mode(0),
+ _tag: PhantomData,
+ }))
+ }
+
+ fn try_new(dev: &'a device::Device<device::Bound>) -> Result<Self> {
+ Request::get_if_active(dev)?;
+ Ok(Self(Scope::<Retain> {
+ dev,
+ mode: Mode(0),
+ _tag: PhantomData,
+ }))
+ }
+
+ fn release_inner(&self) {
+ Request::put_noidle(self.0.dev);
+ }
+
+ /// Explicitly release the scope
+ /// This should be used in favor of regular drop
+ /// when error handling is required.
+ pub fn release(self) -> Result {
+ self.release_inner();
+ core::mem::forget(self);
+ Ok(())
+ }
+}
+
+impl<'a> Drop for RetainScope<'a> {
+ fn drop(&mut self) {
+ self.release_inner();
+ }
+}
+
+/// Runtime PM helpers - wrappers around C runtime PM interface.
+/// All methods require a reference to a bound device.
+struct Request;
+
+#[cfg(CONFIG_PM)]
+impl Request {
+ #[inline]
+ fn active(dev: &device::Device<device::Bound>) -> bool {
+ // SAFETY: `dev.as_raw()` must provide a valid pointer to
+ // `struct device` for the duration of the call.
+ // The `Device<Bound>` reference provides that guarantee.
+ unsafe { bindings::pm_runtime_active(dev.as_raw()) }
+ }
+
+ #[inline]
+ fn suspended(dev: &device::Device<device::Bound>) -> bool {
+ // SAFETY: `dev.as_raw()` must provide a valid pointer to
+ // `struct device` for the duration of the call.
+ // The `Device<Bound>` reference provides that guarantee.
+ unsafe { bindings::pm_runtime_suspended(dev.as_raw()) }
+ }
+
+ #[inline]
+ fn resume(dev: &device::Device<device::Bound>, mode: Mode) -> Result {
+ // SAFETY: `dev.as_raw()` must provide a valid pointer to
+ // `struct device` for the duration of the call.
+ // The `Device<Bound>` reference provides that guarantee.
+ to_result(unsafe { bindings::__pm_runtime_resume(dev.as_raw(), mode.into()) })
+ }
+
+ #[inline]
+ fn idle(dev: &device::Device<device::Bound>, mode: Mode) -> Result {
+ // SAFETY: `dev.as_raw()` must provide a valid pointer to
+ // `struct device` for the duration of the call.
+ // The `Device<Bound>` reference provides that guarantee.
+ to_result(unsafe { bindings::__pm_runtime_idle(dev.as_raw(), mode.into()) })
+ }
+
+ #[inline]
+ fn mark_last_busy(dev: &device::Device<device::Bound>) {
+ // SAFETY: `dev.as_raw()` must provide a valid pointer to
+ // `struct device` for the duration of the call.
+ // The `Device<Bound>` reference provides that guarantee.
+ unsafe {
+ bindings::pm_runtime_mark_last_busy(dev.as_raw());
+ }
+ }
+
+ #[inline]
+ fn suspend(dev: &device::Device<device::Bound>, mode: Mode) -> Result {
+ // SAFETY: `dev.as_raw()` must provide a valid pointer to
+ // `struct device` for the duration of the call.
+ // The `Device<Bound>` reference provides that guarantee.
+ to_result(unsafe { bindings::__pm_runtime_suspend(dev.as_raw(), mode.into()) })
+ }
+
+ #[inline]
+ fn get_if_active(dev: &device::Device<device::Bound>) -> Result {
+ // SAFETY: `dev.as_raw()` must provide a valid pointer to
+ // `struct device` for the duration of the call.
+ // The `Device<Bound>` reference provides that guarantee.
+ match unsafe { bindings::pm_runtime_get_if_active(dev.as_raw()) } {
+ ret if ret < 0 => Err(Error::from_errno(ret)),
+ 0 => Err(EAGAIN),
+ _ => Ok(()),
+ }
+ }
+
+ #[inline]
+ fn runtime_enable(dev: &device::Device<device::Bound>) {
+ // SAFETY: `dev.as_raw()` must provide a valid pointer to
+ // `struct device` for the duration of the call.
+ // The `Device<Bound>` reference provides that guarantee.
+ unsafe { bindings::pm_runtime_enable(dev.as_raw()) }
+ }
+
+ #[inline]
+ fn runtime_disable(dev: &device::Device<device::Bound>) {
+ // SAFETY: `dev.as_raw()` must provide a valid pointer to
+ // `struct device` for the duration of the call.
+ // The `Device<Bound>` reference provides that guarantee.
+ unsafe { bindings::__pm_runtime_disable(dev.as_raw(), true) };
+ }
+}
+
+#[cfg(not(CONFIG_PM))]
+impl Request {
+ #[inline]
+ fn active(dev: &device::Device<device::Bound>) -> bool {
+ true
+ }
+
+ #[inline]
+ fn suspended(dev: &device::Device<device::Bound>) -> bool {
+ false
+ }
+
+ #[inline]
+ fn resume(_dev: &device::Device<device::Bound>, _mode: Mode) -> Result {
+ Ok(())
+ }
+
+ #[inline]
+ fn idle(_dev: &device::Device<device::Bound>, _mode: Mode) -> Result {
+ Err(ENOSYS)
+ }
+
+ #[inline]
+ fn mark_last_busy(_dev: &device::Device<device::Bound>) {}
+
+ #[inline]
+ fn suspend(_dev: &device::Device<device::Bound>, _mode: Mode) -> Result {
+ Err(ENOSYS)
+ }
+
+ #[inline]
+ fn get_if_active(dev: &device::Device<device::Bound>) -> Result {
+ Err(EINVAL)
+ }
+
+ #[inline]
+ fn runtime_enable(_dev: &device::Device<device::Bound>) {}
+
+ #[inline]
+ fn runtime_disable(dev: &device::Device<device::Bound>) {}
+}
+
+impl Request {
+ #[inline]
+ fn get_noresume(dev: &device::Device<device::Bound>) {
+ // SAFETY: `dev.as_raw()` must provide a valid pointer to
+ // `struct device` for the duration of the call.
+ // The `Device<Bound>` reference provides that guarantee.
+ unsafe { bindings::pm_runtime_get_noresume(dev.as_raw()) };
+ }
+
+ #[inline]
+ fn put_noidle(dev: &device::Device<device::Bound>) {
+ // SAFETY: `dev.as_raw()` must provide a valid pointer to
+ // `struct device` for the duration of the call.
+ // The `Device<Bound>` reference provides that guarantee.
+ unsafe { bindings::pm_runtime_put_noidle(dev.as_raw()) };
+ }
+
+ #[allow(unused)]
+ #[inline]
+ fn mark_active(dev: &device::Device<device::Bound>) -> Result {
+ to_result(
+ // SAFETY: `dev.as_raw()` must provide a valid pointer to
+ // `struct device` for the duration of the call.
+ // The `Device<Bound>` reference provides that guarantee.
+ unsafe { bindings::pm_runtime_set_active(dev.as_raw()) },
+ )
+ }
+
+ #[allow(unused)]
+ #[inline]
+ fn mark_suspended(dev: &device::Device<device::Bound>) -> Result {
+ to_result(
+ // SAFETY: `dev.as_raw()` must provide a valid pointer to
+ // `struct device` for the duration of the call.
+ // The `Device<Bound>` reference provides that guarantee.
+ unsafe { bindings::pm_runtime_set_suspended(dev.as_raw()) },
+ )
+ }
+}
+
+/// Common runtime PM callback entry point
+///
+/// The generated extern "C" callbacks call into this helper with the raw
+/// `struct device *` provided by the PM core. It rebuilds the Rust device
+/// reference, retrieves the device's PM registration data and performs
+/// handoff to corresponding driver callback.
+fn runtime_pm_callback<T, F>(dev: *mut bindings::device, cb: F) -> Result
+where
+ T: PMOps,
+ F: FnOnce(
+ &<T as PMOps>::DeviceType,
+ Option<<T as PMOps>::RuntimePayloadType>,
+ ) -> Result<
+ Option<<T as PMOps>::RuntimePayloadType>,
+ (Option<<T as PMOps>::RuntimePayloadType>, Error),
+ >,
+{
+ let dev: &device::Device<device::Bound> =
+ // SAFETY: `dev` is provided by the PM core and remains
+ // valid for the duration of the callback.
+ unsafe { device::Device::from_raw(dev) };
+
+ // SAFETY: `dev` is provided by the PM core and remains
+ // valid for the duration of the callback.
+ let ptr = unsafe { (*(*dev.as_raw()).p).rust_private };
+
+ if ptr.is_null() {
+ return Err(ENODEV);
+ }
+
+ // SAFETY: The runtime PM callback can only be triggered for bound device
+ // and once the runtime PM is enabled.
+ // `rust_private` is guaranteed to be valid and points to
+ // associated RegistrationData<T> type object at least for the duration
+ // of this call.
+ let payload: Pin<&RegistrationData<'_, T>> =
+ unsafe { <Pin<KBox<RegistrationData<'_, T>>> as ForeignOwnable>::borrow(ptr) };
+
+ let pm_dev: &T::DeviceType =
+ // SAFETY: The generated `dev_pm_ops` for `T` is installed on devices whose
+ // bus-specific type is `T::DeviceType`. Therefore the base `Device<Bound>`
+ // passed by the PM core is embedded in a valid `T::DeviceType`; the
+ // `AsBusDevice` implementation supplies the correct offset for this cast.
+ unsafe { T::DeviceType::from_device(dev) };
+
+ payload.data.transition(|payload| cb(pm_dev, payload))
+}
+
+/// Defines generated C runtime PM callback.
+///
+/// This macro generates the corresponding `unsafe extern "C"` function for
+/// requested PM operation and forwards it to [`runtime_pm_callback`].
+///
+/// # Safety
+///
+/// The generated function may only be installed in a `dev_pm_ops` table for a
+/// device whose PM registration data was created by `Registration<T>`. In other
+/// words, the type parameter `T` used for the callback table must match the
+/// type parameter `T` used for the stored `RegistrationData<T>`.
+///
+/// The PM registration must keep the stored registration data alive and pinned
+/// until runtime PM is disabled and all in-flight PM callbacks have completed.
+macro_rules! define_pm_callback {
+ (@parse_desc $name:ident) => { define_pm_callback!(@default $name); };
+ (@default $name:ident) => {
+ paste!(
+ /// Generated runtime PM callback.
+ ///
+ /// # Safety
+ ///
+ /// `dev` must be a valid `struct device *` supplied by the PM core for a device
+ /// whose runtime PM callbacks and PM registration data were both created
+ /// for `T`.
+ unsafe extern "C" fn [<$name _callback>]<'a, T:PMOps>(
+ dev: *mut bindings::device
+ ) -> core::ffi::c_int
+ where
+ <T as PMOps>::DeviceType: 'a
+ {
+ runtime_pm_callback::<T, _>(dev,T::$name).map(|_| 0).unwrap_or_else(|e| e.to_errno())
+ }
+ );
+
+ };
+}
+
+/// SAFETY:
+/// bindings::dev_pm_ops is #[repr(C)], implements Default
+/// and the struct itself is all nullable function pointers.
+/// There is no padding and all zero bit-pattern is valid
+///
+const PMOPS_NONE: bindings::dev_pm_ops =
+ unsafe { core::mem::MaybeUninit::<bindings::dev_pm_ops>::zeroed().assume_init() };
+
+/// Runtime PM callbacks implemented by a driver.
+///
+/// Defines the [`PMOps`] trait and its corresponding [`bindings::dev_pm_ops`].
+///
+/// Each generated C callback recovers the Rust bus device from the raw
+/// `struct device *`, borrows the registered runtime PM payload, and delegates
+/// to the matching [`PMOps`] trait method.
+///
+/// # Safety
+///
+/// `PMContext::<T>::PM_OPS` must only be installed for devices whose concrete
+/// bus type is `T::DeviceType`, and whose runtime PM registration data was
+/// installed by [`Registration::new`] for the same `T`.
+///
+macro_rules! define_pm_ops {
+ ($($name:ident $( : $desc:tt )? ),+ $(,)?) => {
+ $( define_pm_callback!( @parse_desc $name $( $desc )?); )+
+ define_pm_ops!(@common $( $name ), +);
+ };
+
+ (@common $( $name:ident),+ ) => {
+ /// Runtime PM callbacks implemented by a driver
+ #[vtable]
+ pub trait PMOps: Sized
+ {
+ /// Type of a bus device
+ type DeviceType: AsBusDevice<device::Bound>;
+ /// Type of the data associated with a PM transitions:
+ type RuntimePayloadType: Send;
+
+ $(
+ #[allow(missing_docs)]
+ // Callback-specific docs are provided by the generated `dev_pm_ops`
+ // contract on `PMOps`.
+
+ fn $name<'a>(
+ _dev: &'a Self::DeviceType,
+ _payload: Option<Self::RuntimePayloadType>,
+ ) -> Result<Option<Self::RuntimePayloadType>, (Option<Self::RuntimePayloadType>, Error)> {
+ build_error!(VTABLE_DEFAULT_ERROR)
+ }
+ )+
+ }
+ paste!(
+ impl<'a, T:PMOps> PMContext<'a, T> {
+ /// Driver-provided runtime PM operations.
+ ///
+ /// A driver implements this trait to handle runtime PM
+ /// transitions for its device type.
+ ///
+ /// Each callback receives the device and the current payload.
+ /// On success, it returns the payload to keep for the next
+ /// transition. On failure, it returns the payload together
+ /// with the error so the previous, or otherwise sane state
+ /// can be preserved.
+ pub const PM_OPS: bindings::dev_pm_ops = bindings::dev_pm_ops {
+ $( [<$name>]: if T::[<HAS_ $name:upper>] {
+ Some([<$name _callback>]::<T>)
+ } else {
+ None
+ }, )+
+ ..PMOPS_NONE
+ };
+ }
+ );
+ }
+}
+
+/// RAII guard for ongoing runtime PM payload transition.
+///
+/// For most of the callbacks this guard is not necessairly needed as
+/// the callbacks themselves are being serialized by the runtime PM C code.
+/// Still, some like runtime_idle are exempt ftom that.
+#[allow(unused)]
+struct PayloadGuard<'a> {
+ busy: &'a Atomic<bool>,
+}
+
+impl Drop for PayloadGuard<'_> {
+ fn drop(&mut self) {
+ self.busy.store(false, ordering::Release);
+ }
+}
+
+struct PMPayload<P> {
+ in_flight: Atomic<bool>,
+ inner: UnsafeCell<Option<P>>,
+}
+
+impl<P> PMPayload<P> {
+ /// Attempts to acquire exclusive access to the runtime PM payload.
+ ///
+ /// Returns `EBUSY` if another runtime PM callback is already transitioning the
+ /// payload.
+ fn acquire(&self) -> Result<PayloadGuard<'_>> {
+ self.in_flight
+ .cmpxchg(false, true, ordering::Acquire)
+ .map_err(|_| EBUSY)?;
+ Ok(PayloadGuard {
+ busy: &self.in_flight,
+ })
+ }
+
+ /// Runs a runtime PM transition with exclusive access to the stored payload.
+ ///
+ /// This method acquires the in-flight guard, temporarily takes the payload out
+ /// of storage The closure must return the payload that should be stored
+ /// for the next transition.
+ ///
+ /// On success, the returned payload replaces the previous payload. On failure,
+ /// the closure returns the payload together with the error, and that payload is
+ /// restored before the error is propagated.
+ ///
+ /// Returns `EBUSY` if another runtime PM transition is already in progress.
+ fn transition(
+ &self,
+ f: impl FnOnce(Option<P>) -> Result<Option<P>, (Option<P>, Error)>,
+ ) -> Result {
+ let _guard = self.acquire()?;
+ // SAFETY: Holding `_guard` means this callback successfully changed
+ // `in_flight` from false to true. No other caller can hold a `PayloadGuard`
+ // until `_guard` is dropped, so this function has exclusive access to `inner`.
+ let slot = unsafe { &mut *self.inner.get() };
+
+ let payload = slot.take();
+
+ match f(payload) {
+ Ok(new_payload) => {
+ *slot = new_payload;
+ Ok(())
+ }
+ Err((old_payload, err)) => {
+ *slot = old_payload;
+ Err(err)
+ }
+ }
+ }
+}
+
+// SAFETY: Although PMPayload's `inner` is an `UnsafeCell`, it is only accessed
+// after `in_flight` has been acquired. The atomic flag serializes all mutable
+// access to the payload, and `PayloadGuard` clears the flag when the access ends.
+unsafe impl<P: Send> Sync for PMPayload<P> {}
+
+struct PMContextInner<'a, T: PMOps> {
+ dev: &'a device::Device<device::Bound>,
+ /// Optional driver-selected runtime PM request PMProfiles.
+ ///
+ /// Set of runtime PM predefined PMProfiles that can be used by the driver
+ /// when requesting a PM transition. This might be useful when a driver
+ /// has several different PM usage patterns.
+ /// See [PMProfile] for more details.
+ profiles: KVec<PMProfile>,
+ /// Set of PM config options applied for associated device.
+ configs: KVec<PMConfig>,
+ _marker: PhantomData<T>,
+}
+
+/// Runtime PM context tied to a device.
+pub struct PMContext<'a, T: PMOps> {
+ // Preferably, PMContext could be shared via borrowed reference over
+ // a pm Registraion's lifetime but that bares complications on its own
+ // when the context needs to be shared across different Registration types.
+ inner: Arc<PMContextInner<'a, T>>,
+}
+
+impl<'a, T: PMOps> PMContext<'a, T> {
+ /// Enable runtime PM
+ pub fn enable(&self, state: RuntimePMState) -> Result {
+ Self::apply_config(self.inner.dev, &self.inner.configs);
+ match state {
+ RuntimePMState::RESUMED => Request::mark_active(self.inner.dev),
+ RuntimePMState::SUSPENDED => Request::mark_suspended(self.inner.dev),
+ _ => Err(EINVAL),
+ }?;
+ Request::runtime_enable(self.inner.dev);
+ Ok(())
+ }
+ /// Disable runtime PM
+ pub fn disable(&self) -> Result {
+ Self::apply_config(self.inner.dev, &[PMConfig::AutoSuspend(false)]);
+ Request::runtime_disable(self.inner.dev);
+ Ok(())
+ }
+
+ /// Returns whether the runtime PM state is active.
+ #[inline]
+ pub fn active(&self) -> bool {
+ Request::active(self.inner.dev)
+ }
+
+ /// Returns whether the runtime PM state is suspended.
+ #[inline]
+ pub fn suspended(&self) -> bool {
+ Request::suspended(self.inner.dev)
+ }
+
+ /// Creates a `ResumeScope` for the given PMProfile.
+ #[inline]
+ pub fn resume(&self, profile: PMProfile) -> Result<ResumeScope<'a>> {
+ ResumeScope::new(self.inner.dev, profile.0)
+ }
+
+ /// Creates an `AwakeScope` for the given PMProfile.
+ /// Note that for ASYNC request this does not guarantee
+ /// the device has been resumed at the time this funtion returns.
+ #[inline]
+ pub fn get(&self, profile: PMProfile) -> Result<AwakeScope<'a>> {
+ AwakeScope::new(self.inner.dev, profile.0 | Mode::ACQUIRE)
+ }
+
+ /// Creates a `RetainScope` for this device.
+ pub fn hold(&self) -> Result<RetainScope<'a>> {
+ RetainScope::new(self.inner.dev)
+ }
+
+ /// Creates a `RetainScope` for an active device.
+ pub fn try_hold_active(&self) -> Result<RetainScope<'a>> {
+ RetainScope::try_new(self.inner.dev)
+ }
+
+ /// Runs a closure while holding a `ResumeScope`.
+ pub fn with_resume<R>(&self, profile: PMProfile, f: impl FnOnce() -> Result<R>) -> Result<R> {
+ if profile.0.includes(Mode::ASYNC) {
+ return Err(EINVAL);
+ }
+ let _scope = self.resume(profile)?;
+ f()
+ }
+ /// Runs a closure while holding an `AwakeScope`.
+ pub fn with_get<R>(&self, profile: PMProfile, f: impl FnOnce() -> Result<R>) -> Result<R> {
+ if profile.0.includes(Mode::ASYNC) {
+ return Err(EINVAL);
+ }
+ let _scope = self.get(profile)?;
+ f()
+ }
+
+ /// Runs a closure while holding a `RetainScope`.
+ pub fn with_hold<R>(&self, f: impl FnOnce() -> Result<R>) -> Result<R> {
+ let _scope = self.hold()?;
+ f()
+ }
+
+ /// Applies runtime PM configuration options.
+ ///
+ /// Options are applied in the order provided. The currently supported
+ /// options do not report per-option failures.
+ fn apply_config(dev: &device::Device<device::Bound>, opts: &[PMConfig]) {
+ #[cfg(not(CONFIG_PM))]
+ let _ = opts;
+ let _ = dev;
+ #[cfg(CONFIG_PM)]
+ for opt in opts {
+ match opt {
+ // SAFETY: `self.dev` is a valid `&ARef<Device>`, so the underlying `Device` is
+ // guaranteed to be alive and `as_raw()` yields a valid pointer for the
+ // duration of this call.
+ PMConfig::IgnoreChildren(v) => unsafe {
+ bindings::pm_suspend_ignore_children(dev.as_raw(), *v)
+ },
+ // SAFETY: `self.dev` is a valid `&ARef<Device>`, so the underlying `Device` is
+ // guaranteed to be alive and `as_raw()` yields a valid pointer for the
+ // duration of this call.
+ PMConfig::NoCallbacks => unsafe { bindings::pm_runtime_no_callbacks(dev.as_raw()) },
+ // SAFETY: `self.dev` is a valid `&ARef<Device>`, so the underlying `Device` is
+ // guaranteed to be alive and `as_raw()` yields a valid pointer for the
+ // duration of this call.
+ PMConfig::IrqSafe => unsafe { bindings::pm_runtime_irq_safe(dev.as_raw()) },
+ // SAFETY: `self.dev` is a valid `&ARef<Device>`, so the underlying `Device` is
+ // guaranteed to be alive and `as_raw()` yields a valid pointer for the
+ // duration of this call.
+ PMConfig::AutoSuspend(v) => unsafe {
+ bindings::__pm_runtime_use_autosuspend(dev.as_raw(), *v);
+ },
+ // SAFETY: `self.dev` is a valid `&ARef<Device>`, so the underlying `Device` is
+ // guaranteed to be alive and `as_raw()` yields a valid pointer for the
+ // duration of this call.
+ PMConfig::AutoSuspendDelay(v) => unsafe {
+ bindings::pm_runtime_set_autosuspend_delay(dev.as_raw(), *v as i32);
+ bindings::__pm_runtime_use_autosuspend(dev.as_raw(), true);
+ },
+ }
+ }
+ }
+
+ /// Get a borrowed reference to PM profiles asociated wiht the PM context
+ pub fn profiles(&self) -> &[PMProfile] {
+ &self.inner.profiles
+ }
+
+ /// Get a borrowed reference to PM configs asociated wiht the PM context
+ pub fn configs(&self) -> &[PMConfig] {
+ &self.inner.configs
+ }
+}
+
+// Preferably, PMContext could be shared via borrowed reference over
+// a pm Registraion's lifetime but that bares complications on its own
+// when the context needs to be shared across different Registration types.
+impl<T: PMOps> Clone for PMContext<'_, T> {
+ fn clone(&self) -> Self {
+ Self {
+ inner: self.inner.clone(),
+ }
+ }
+}
+
+define_pm_ops!(
+ // PM state change
+ runtime_suspend,
+ runtime_resume,
+);
+
+/// Runtime PM request PMProfile.
+pub struct PMProfile(Mode);
+
+impl PMProfile {
+ /// Creates a PMProfile with default SYNC mode set.
+ pub const fn new() -> Self {
+ Self(Mode::SYNC)
+ }
+ /// /Enables async PM operations for this PMProfile.
+ pub const fn r#async(self) -> Self {
+ Self(mode!(self.0, Mode::ASYNC))
+ }
+ /// Use autosuspend
+ pub const fn auto(self) -> Self {
+ Self(mode!(self.0, Mode::AUTO))
+ }
+ /// Requests idle handling for this PMProfile.
+ pub const fn idle(self) -> Self {
+ Self(mode!(self.0, Mode::IDLE))
+ }
+ /// Do not wait for concurrent requests to finish.
+ pub const fn nowait(self) -> Self {
+ Self(mode!(self.0, Mode::NOWAIT))
+ }
+}
+
+impl Default for PMProfile {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+/// Configuration knobs for runtime PM.
+pub enum PMConfig {
+ /// Ignore child devices when suspending.
+ IgnoreChildren(bool),
+ /// Disable runtime PM callbacks.
+ NoCallbacks,
+ /// Mark device as IRQ-safe for runtime PM.
+ IrqSafe,
+ /// Enable or disable autosuspend.
+ AutoSuspend(bool),
+ /// Set autosuspend delay (milliseconds).
+ AutoSuspendDelay(u32),
+}
+
+/// Runtime PM data stored within the `struct device_private' during
+/// runtime PM registration.
+///
+/// The data is associated with PM transitions and it's conceptually owned
+/// by the Registration itself.
+///
+#[repr(C)]
+#[pin_data]
+struct RegistrationData<'a, T: PMOps> {
+ #[pin]
+ data: PMPayload<T::RuntimePayloadType>,
+ _marker: PhantomData<&'a mut ()>,
+}
+
+/// Runtime PM registration for a device.
+///
+/// A `Registration` installs the runtime PM payload used by the
+/// generated [`PMOps`] callbacks and owns the corresponding teardown.
+///
+/// Dropping the registration disables runtime PM, waits for in-flight runtime PM
+/// callbacks to complete, and then removes the stored registration data.
+pub struct Registration<'a, T: PMOps> {
+ ctx: PMContext<'a, T>,
+}
+
+impl<'a, T: PMOps> Registration<'a, T> {
+ /// Creates a runtime PM registration for `dev`.
+ ///
+ /// The provided profiles and configuration are stored in the associated
+ /// [`PMContext`]. The optional `payload` is stored as a Registration data
+ /// and is used to service PM transitions.
+ ///
+ /// The device must use the callback table generated for the same `T`.
+ pub fn new(
+ dev: &'a device::Device<device::Core<'_>>,
+ profiles: Option<KVec<PMProfile>>,
+ configs: Option<KVec<PMConfig>>,
+ payload: Option<T::RuntimePayloadType>,
+ ) -> Result<Self> {
+ let payload = KBox::pin_init(
+ RegistrationData::<T> {
+ data: PMPayload {
+ in_flight: Atomic::new(false),
+ inner: UnsafeCell::new(payload),
+ },
+ _marker: PhantomData,
+ },
+ GFP_KERNEL,
+ )?;
+
+ let inner_ctx = Arc::new(
+ PMContextInner {
+ dev,
+ profiles: profiles.unwrap_or_default(),
+ configs: configs.unwrap_or_default(),
+ _marker: PhantomData,
+ },
+ GFP_KERNEL,
+ )?;
+
+ // SAFETY: `dev` is a live `Device<Core>`, so its raw `struct device` pointer is
+ // valid for the duration of this call. The payload allocation is converted into
+ // a foreign pointer and owned by this `Registration` until `Drop` clears
+ // `rust_private` and reconstructs the `KBox`.
+ unsafe {
+ let ptr = (*(*dev.as_raw()).p).rust_private;
+ if !ptr.is_null() {
+ return Err(EBUSY);
+ }
+ (*(*dev.as_raw()).p).rust_private = payload.into_foreign();
+ }
+
+ Ok(Self {
+ ctx: PMContext { inner: inner_ctx },
+ })
+ }
+ /// Returns the runtime PM context associated with this registration.
+ pub fn ctx(&self) -> &PMContext<'a, T> {
+ &self.ctx
+ }
+}
+
+impl<'a, T: PMOps> Drop for Registration<'a, T> {
+ fn drop(&mut self) {
+ // SAFETY: `self.ctx.inner.dev` is the device this registration was
+ // created for. Runtime PM is disabled first, and `pm_runtime_barrier`
+ // waits for pending runtime PM work/callbacks before the callback data
+ // is removed below.
+
+ unsafe {
+ bindings::__pm_runtime_disable(self.ctx.inner.dev.as_raw(), true);
+ bindings::pm_runtime_barrier(self.ctx.inner.dev.as_raw());
+ }
+ // SAFETY: The pointer, if non-null, was stored by `Registration::new`
+ // using `Pin<KBox<RegistrationData<T>>>::into_foreign`. Runtime PM has
+ // been disabled and drained above, so generated callbacks can no longer
+ // borrow this data. Clearing `rust_private` prevents later lookup, and
+ // `from_foreign` reconstructs the owning allocation so it is dropped.
+ unsafe {
+ let ptr = (*(*self.ctx.inner.dev.as_raw()).p).rust_private;
+
+ if !ptr.is_null() {
+ (*(*self.ctx.inner.dev.as_raw()).p).rust_private = core::ptr::null_mut();
+ Pin::<KBox<RegistrationData<'_, T>>>::from_foreign(ptr);
+ }
+ }
+ }
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v2 2/3] rust: platform: wire runtime PM callbacks
2026-07-21 15:34 [PATCH v2 0/3] Rust: add runtime PM support Beata Michalska
2026-07-21 15:34 ` [PATCH v2 1/3] rust: " Beata Michalska
@ 2026-07-21 15:34 ` Beata Michalska
2026-07-21 15:34 ` [PATCH v2 3/3] drm/tyr: enable runtime PM Beata Michalska
2 siblings, 0 replies; 6+ messages in thread
From: Beata Michalska @ 2026-07-21 15:34 UTC (permalink / raw)
To: ojeda, dakr, gregkh, rafael
Cc: boqun, gary, bjorn3_gh, lossin, a.hindborg, aliceryhl, tmgross,
daniel.almeida, boris.brezillon, work, samitolvanen,
rust-for-linux, driver-core, linux-kernel, linux-pm
Allow Rust platform drivers to expose runtime PM callbacks to the driver core.
The runtime PM abstraction builds a dev_pm_ops table for the concrete driver
implementation, but the platform bus still needs to receive that table through
struct platform_driver. Add an optional PM_OPS associated constant to
platform::Driver and initialize the coresponding C device_driver struct
accordingly during registration. The platform glue only wires the callback
table into the C driver model; ownership of the callback payload and
runtime PM teardown remain with the pm module.
Signed-off-by: Beata Michalska <beata.michalska@arm.com>
---
rust/kernel/platform.rs | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/rust/kernel/platform.rs b/rust/kernel/platform.rs
index d8d48f60b0b9..b7e422388634 100644
--- a/rust/kernel/platform.rs
+++ b/rust/kernel/platform.rs
@@ -72,6 +72,11 @@ unsafe fn register(
None => core::ptr::null(),
};
+ let pm_ops = match T::PM_OPS {
+ Some(ops) => ops,
+ None => core::ptr::null(),
+ };
+
// SAFETY: It's safe to set the fields of `struct platform_driver` on initialization.
unsafe {
(*pdrv.get()).driver.name = name.as_char_ptr();
@@ -79,6 +84,7 @@ unsafe fn register(
(*pdrv.get()).remove = Some(Self::remove_callback);
(*pdrv.get()).driver.of_match_table = of_table;
(*pdrv.get()).driver.acpi_match_table = acpi_table;
+ (*pdrv.get()).driver.pm = pm_ops;
}
// SAFETY: `pdrv` is guaranteed to be a valid `DriverType`.
@@ -222,6 +228,9 @@ pub trait Driver {
/// The table of ACPI device ids supported by the driver.
const ACPI_ID_TABLE: Option<acpi::IdTable<Self::IdInfo>> = None;
+ /// Runtime PM callbacks
+ const PM_OPS: Option<&'static bindings::dev_pm_ops> = None;
+
/// Platform driver probe.
///
/// Called when a new platform device is added or discovered.
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v2 3/3] drm/tyr: enable runtime PM
2026-07-21 15:34 [PATCH v2 0/3] Rust: add runtime PM support Beata Michalska
2026-07-21 15:34 ` [PATCH v2 1/3] rust: " Beata Michalska
2026-07-21 15:34 ` [PATCH v2 2/3] rust: platform: wire runtime PM callbacks Beata Michalska
@ 2026-07-21 15:34 ` Beata Michalska
2026-07-27 15:00 ` Onur Özkan
2 siblings, 1 reply; 6+ messages in thread
From: Beata Michalska @ 2026-07-21 15:34 UTC (permalink / raw)
To: ojeda, dakr, gregkh, rafael
Cc: boqun, gary, bjorn3_gh, lossin, a.hindborg, aliceryhl, tmgross,
daniel.almeida, boris.brezillon, work, samitolvanen,
rust-for-linux, driver-core, linux-kernel, linux-pm
Add runtime PM support to the Tyr platform driver. Move the clocks and
regulators used by runtime suspend and resume into the PM payload, register the
PM callbacks, configure autosuspend, and let DRM paths take a PM usage
reference while querying device state.
Signed-off-by: Beata Michalska <beata.michalska@arm.com>
---
drivers/gpu/drm/tyr/driver.rs | 112 ++++++++++++++++++++++++++++------
drivers/gpu/drm/tyr/file.rs | 7 ++-
2 files changed, 98 insertions(+), 21 deletions(-)
diff --git a/drivers/gpu/drm/tyr/driver.rs b/drivers/gpu/drm/tyr/driver.rs
index 8348c6cd3929..89fe216be0de 100644
--- a/drivers/gpu/drm/tyr/driver.rs
+++ b/drivers/gpu/drm/tyr/driver.rs
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0 or MIT
use kernel::{
+ bindings,
clk::{
Clk,
OptionalClk, //
@@ -20,16 +21,16 @@
poll,
Io, //
},
- new_mutex,
of,
platform,
+ pm,
+ pm::*,
prelude::*,
regulator,
regulator::Regulator,
sizes::SZ_2M,
sync::{
aref::ARef,
- Mutex, //
},
time, //
};
@@ -55,18 +56,21 @@
pub(crate) struct TyrPlatformDriverData<'bound> {
_device: ARef<TyrDrmDevice>,
_reg: drm::Registration<'bound, TyrDrmDriver>,
+ // This needs to be dropped after drm::Registration as this one borrows
+ // borrows PMContext.
+ pub(crate) pm: pm::Registration<'bound, TyrPlatformDriver>,
+
+}
+
+#[pin_data]
+pub(crate) struct TyrRuntimePM<'bound> {
+ pub(crate) pm: PMContext<'bound, TyrPlatformDriver>,
}
#[pin_data]
pub(crate) struct TyrDrmDeviceData {
pub(crate) pdev: ARef<platform::Device>,
- #[pin]
- clks: Mutex<Clocks>,
-
- #[pin]
- regulators: Mutex<Regulators>,
-
/// Some information on the GPU.
///
/// This is mainly queried by userspace, i.e.: Mesa.
@@ -101,6 +105,7 @@ impl platform::Driver for TyrPlatformDriver {
type IdInfo = ();
type Data<'bound> = TyrPlatformDriverData<'bound>;
const OF_ID_TABLE: Option<of::IdTable<Self::IdInfo>> = Some(&OF_TABLE);
+ const PM_OPS: Option<&'static bindings::dev_pm_ops> = Some(&PMContext::<Self>::PM_OPS);
fn probe<'bound>(
pdev: &'bound platform::Device<Core<'_>>,
@@ -117,6 +122,25 @@ fn probe<'bound>(
let mali_regulator = Regulator::<regulator::Enabled>::get(pdev.as_ref(), c"mali")?;
let sram_regulator = Regulator::<regulator::Enabled>::get(pdev.as_ref(), c"sram")?;
+ let runtime_payload = TyrRuntimePMPayload {
+ clks: Clocks {
+ core: core_clk,
+ stacks: stacks_clk,
+ coregroup: coregroup_clk,
+ },
+ _regulators: Regulators {
+ _mali: mali_regulator,
+ _sram: sram_regulator,
+ }
+ };
+
+ let mut pm_configs = KVec::<PMConfig>::with_capacity(2, GFP_KERNEL)?;
+ pm_configs.push(PMConfig::AutoSuspend(true), GFP_KERNEL)?;
+ pm_configs.push(PMConfig::AutoSuspendDelay(300), GFP_KERNEL)?;
+
+ let pm_registration = pm::Registration::new(pdev.as_ref(), None, Some(pm_configs), Some(runtime_payload))?;
+ let pm_context = pm_registration.ctx().clone();
+
let request = pdev.io_request_by_index(0).ok_or(ENODEV)?;
let iomem = request.iomap_sized::<SZ_2M>()?;
@@ -138,28 +162,26 @@ fn probe<'bound>(
let data = try_pin_init!(TyrDrmDeviceData {
pdev: platform.clone(),
- clks <- new_mutex!(Clocks {
- core: core_clk,
- stacks: stacks_clk,
- coregroup: coregroup_clk,
- }),
- regulators <- new_mutex!(Regulators {
- _mali: mali_regulator,
- _sram: sram_regulator,
- }),
gpu_info,
});
let tdev = drm::UnregisteredDevice::<TyrDrmDriver>::new(pdev, data)?;
// SAFETY: `reg` is stored in `TyrPlatformDriverData` and dropped when the driver is
// unbound; it is never forgotten.
- let reg = unsafe { drm::Registration::new(pdev.as_ref(), tdev, (), 0)? };
+ let reg = unsafe { drm::Registration::new(
+ pdev.as_ref(),
+ tdev,
+ pin_init!(TyrRuntimePM { pm: pm_context, }),
+ 0
+ )? };
let driver = TyrPlatformDriverData {
_device: reg.device().into(),
_reg: reg,
+ pm: pm_registration,
};
+ driver.pm.ctx().enable(RuntimePMState::RESUMED)?;
// We need this to be dev_info!() because dev_dbg!() does not work at
// all in Rust for now, and we need to see whether probe succeeded.
dev_info!(pdev, "Tyr initialized correctly.\n");
@@ -185,7 +207,7 @@ fn drop(self: Pin<&mut Self>) {}
#[vtable]
impl drm::Driver for TyrDrmDriver {
type Data = TyrDrmDeviceData;
- type RegistrationData<'a> = ();
+ type RegistrationData<'a> = TyrRuntimePM<'a>;
type File = TyrDrmFileData;
type Object = drm::gem::shmem::Object<BoData>;
type ParentDevice<Ctx: DeviceContext> = platform::Device<Ctx>;
@@ -216,3 +238,55 @@ struct Regulators {
_mali: Regulator<regulator::Enabled>,
_sram: Regulator<regulator::Enabled>,
}
+
+pub(crate) struct TyrRuntimePMPayload {
+ clks: Clocks,
+ _regulators: Regulators,
+}
+
+#[vtable]
+impl PMOps for TyrPlatformDriver {
+ type DeviceType = platform::Device<kernel::device::Bound>;
+ type RuntimePayloadType = TyrRuntimePMPayload;
+
+ fn runtime_suspend<'a>(
+ _dev: &'a Self::DeviceType,
+ payload: Option<TyrRuntimePMPayload>,
+ ) -> Result<Option<TyrRuntimePMPayload>, (Option<TyrRuntimePMPayload>, Error)> {
+
+ let Some(payload) = payload else {
+ return Err((None, EINVAL));
+ };
+
+ payload.clks.coregroup.disable_unprepare();
+ payload.clks.stacks.disable_unprepare();
+ payload.clks.core.disable_unprepare();
+ Ok(Some(payload))
+ }
+ fn runtime_resume<'a>(
+ _dev: &'a Self::DeviceType,
+ payload: Option<TyrRuntimePMPayload>,
+ ) -> Result<Option<TyrRuntimePMPayload>, (Option<TyrRuntimePMPayload>, Error)> {
+
+ let Some(payload) = payload else {
+ return Err((None, EINVAL));
+ };
+
+ if let Err(e) = payload.clks.core.prepare_enable() {
+ return Err((Some(payload), e));
+ }
+
+ if let Err(e) = payload.clks.stacks.prepare_enable() {
+ payload.clks.core.disable_unprepare();
+ return Err((Some(payload), e));
+ }
+
+ if let Err(e) = payload.clks.coregroup.prepare_enable() {
+ payload.clks.stacks.disable_unprepare();
+ payload.clks.core.disable_unprepare();
+ return Err((Some(payload), e));
+ }
+
+ Ok(Some(payload))
+ }
+}
diff --git a/drivers/gpu/drm/tyr/file.rs b/drivers/gpu/drm/tyr/file.rs
index b686041d5d6b..d9371ddfb5f3 100644
--- a/drivers/gpu/drm/tyr/file.rs
+++ b/drivers/gpu/drm/tyr/file.rs
@@ -5,6 +5,7 @@
self,
Registered, //
},
+ pm::PMProfile,
prelude::*,
uaccess::UserSlice,
uapi, //
@@ -12,7 +13,8 @@
use crate::driver::{
TyrDrmDevice,
- TyrDrmDriver, //
+ TyrDrmDriver,
+ TyrRuntimePM,//
};
#[pin_data]
@@ -32,10 +34,11 @@ fn open(_dev: &drm::Device<Self::Driver>) -> Result<Pin<KBox<Self>>> {
impl TyrDrmFileData {
pub(crate) fn dev_query(
ddev: &TyrDrmDevice<Registered>,
- _reg_data: &(),
+ reg_data: &TyrRuntimePM<'_>,
devquery: &mut uapi::drm_panthor_dev_query,
_file: &TyrDrmFile,
) -> Result<u32> {
+ let _pm_scope = reg_data.pm.get(PMProfile::new())?;
if devquery.pointer == 0 {
match devquery.type_ {
uapi::drm_panthor_dev_query_type_DRM_PANTHOR_DEV_QUERY_GPU_INFO => {
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH v2 1/3] rust: add runtime PM support
2026-07-21 15:34 ` [PATCH v2 1/3] rust: " Beata Michalska
@ 2026-07-27 14:56 ` Onur Özkan
0 siblings, 0 replies; 6+ messages in thread
From: Onur Özkan @ 2026-07-27 14:56 UTC (permalink / raw)
To: Beata Michalska
Cc: ojeda, dakr, gregkh, rafael, boqun, gary, bjorn3_gh, lossin,
a.hindborg, aliceryhl, tmgross, daniel.almeida, boris.brezillon,
samitolvanen, rust-for-linux, driver-core, linux-kernel, linux-pm
On Tue, 21 Jul 2026 17:34:02 +0200
Beata Michalska <beata.michalska@arm.com> wrote:
> Add a Rust abstraction for Linux runtime PM, allowing Rust drivers to
> register runtime PM callbacks and use scoped runtime PM requests while
> retaining the behavior and guarantees of the underlying C PM core.
>
> Runtime PM is managed through a registration object tied to the device
> lifetime. The registration stores the callback payload and 'ensures' that
> the registration, its `PMContext`, and the payload do not outlive the
> device. All that while `PMContext` holds a lifetime-annotated device reference
> so that runtime PM requests are made only while the device remains bound.
>
> Drivers define runtime PM callbacks through the `PMOps` trait. The
> generated `dev_pm_ops` callbacks take the stored payload, pass ownership
> to the corresponding Rust callback, and store the returned payload for
> the next invocation. Each callback must return a valid payload,
> regardless of whether the power transition succeeds or not.
>
> `PMContext` provides scoped helpers for common runtime PM operations:
>
> - `ResumeScope` resumes the device for the lifetime of the scope.
> - `AwakeScope` resumes the device and holds a runtime PM usage reference.
> - `RetainScope` increments the runtime PM usage count without resuming the
> device.
>
> Each scope performs the corresponding inverse operation when dropped,
> according to the selected driver-defined profile.
>
> The abstraction does not change the semantics of the C runtime PM API.
> Asynchronous requests may only queue work, non-waiting requests may fail
> instead of sleeping, and callbacks remain subject to the existing driver
> core rules.
>
> The current implementation requires the PM registration and `PMOps`
> implementation to use the same type because callback dispatch interprets
> the stored registration data using that relationship. This requirement is
> not yet fully enforced by the type system.
>
> Signed-off-by: Beata Michalska <beata.michalska@arm.com>
> ---
> drivers/base/base.h | 3 +
> rust/bindings/bindings_helper.h | 1 +
> rust/helpers/helpers.c | 1 +
> rust/helpers/pm_runtime.c | 44 ++
> rust/kernel/lib.rs | 1 +
> rust/kernel/pm.rs | 1020 +++++++++++++++++++++++++++++++
> 6 files changed, 1070 insertions(+)
> create mode 100644 rust/helpers/pm_runtime.c
> create mode 100644 rust/kernel/pm.rs
>
> diff --git a/drivers/base/base.h b/drivers/base/base.h
> index a19f4cda2c83..c49303e4a86a 100644
> --- a/drivers/base/base.h
> +++ b/drivers/base/base.h
> @@ -119,6 +119,9 @@ struct device_private {
> const struct device_driver *async_driver;
> char *deferred_probe_reason;
> struct device *device;
> +#ifdef CONFIG_RUST
> + void *rust_private;
> +#endif
> u8 dead:1;
> };
> #define to_device_private_parent(obj) \
> diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
> index 1124785e210b..78a9153c6a01 100644
> --- a/rust/bindings/bindings_helper.h
> +++ b/rust/bindings/bindings_helper.h
> @@ -77,6 +77,7 @@
> #include <linux/pid_namespace.h>
> #include <linux/platform_device.h>
> #include <linux/pm_opp.h>
> +#include <linux/pm_runtime.h>
> #include <linux/poll.h>
> #include <linux/property.h>
> #include <linux/pwm.h>
> diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
> index 4488a87223b9..14d93a5d8b1f 100644
> --- a/rust/helpers/helpers.c
> +++ b/rust/helpers/helpers.c
> @@ -76,6 +76,7 @@
> #include "pci.c"
> #include "pid_namespace.c"
> #include "platform.c"
> +#include "pm_runtime.c"
> #include "poll.c"
> #include "processor.c"
> #include "property.c"
> diff --git a/rust/helpers/pm_runtime.c b/rust/helpers/pm_runtime.c
> new file mode 100644
> index 000000000000..d0d71fcb0097
> --- /dev/null
> +++ b/rust/helpers/pm_runtime.c
> @@ -0,0 +1,44 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +#include <linux/pm_runtime.h>
> +
> +__rust_helper void rust_helper_pm_runtime_get_noresume(struct device *dev)
> +{
> + pm_runtime_get_noresume(dev);
> +}
> +
> +__rust_helper void rust_helper_pm_runtime_put_noidle(struct device *dev)
> +{
> + pm_runtime_put_noidle(dev);
> +}
> +
> +__rust_helper void rust_helper_pm_runtime_mark_last_busy(struct device *dev)
> +{
> + pm_runtime_mark_last_busy(dev) ;
> +}
> +
> +__rust_helper bool rust_helper_pm_runtime_active(struct device *dev)
> +{
> + return pm_runtime_active(dev);
> +}
> +
> +__rust_helper bool rust_helper_pm_runtime_suspended(struct device *dev)
> +{
> + return pm_runtime_suspended(dev);
> +}
> +
> +__rust_helper void rust_helper_pm_suspend_ignore_children(struct device *dev,
> + bool enable)
> +{
> + pm_suspend_ignore_children(dev, enable);
> +}
> +
> +__rust_helper int rust_helper_pm_runtime_set_active(struct device *dev)
> +{
> + return pm_runtime_set_active(dev);
> +}
> +
> +__rust_helper int rust_helper_pm_runtime_set_suspended(struct device *dev)
> +{
> + return pm_runtime_set_suspended(dev);
> +}
> diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
> index b72b2fbe046d..7e51e497e2f0 100644
> --- a/rust/kernel/lib.rs
> +++ b/rust/kernel/lib.rs
> @@ -105,6 +105,7 @@
> pub mod pci;
> pub mod pid_namespace;
> pub mod platform;
> +pub mod pm;
> pub mod prelude;
> pub mod print;
> pub mod processor;
> diff --git a/rust/kernel/pm.rs b/rust/kernel/pm.rs
> new file mode 100644
> index 000000000000..b6a884fbe1ba
> --- /dev/null
> +++ b/rust/kernel/pm.rs
> @@ -0,0 +1,1020 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +//! Rust Runtime Power Management abstraction.
> +//!
> +//! C header: [`include/linux/pm_runtime.h`](srctree/include/linux/pm_runtime.h)
> +
> +use crate::{
> + bindings,
> + device::{
> + self,
> + AsBusDevice, //
> + },
> + error::{
> + to_result,
> + VTABLE_DEFAULT_ERROR, //
> + },
> + macros::paste,
> + prelude::*,
> + sync::atomic::{
> + ordering,
> + Atomic, //
> + },
> + sync::Arc,
> + types::ForeignOwnable, //
> +};
> +
> +use core::{
> + cell::UnsafeCell,
> + marker::PhantomData
> +};
> +
> +/// Runtime Power Management modes that determine how a particular PM
> +/// transition is to be carried out.
> +/// Corresponds to C Runtime PM flag argument bits:
> +/// - `RPM_ASYNC`
> +/// - `RPM_NOWAIT`
> +/// - `RPM_GET_PUT`
> +/// - `RPM_AUTO`
> +#[derive(Clone, Copy, PartialEq, Eq, Debug)]
> +struct Mode(u32);
> +
> +impl Mode {
> + /// Synchronous PM operations - default.
> + const SYNC: Mode = Mode(0);
> + /// Allow asynchronous PM operations.
> + const ASYNC: Mode = Mode(bindings::RPM_ASYNC);
> + /// Do not wait for any pending requests to finish.
> + const NOWAIT: Mode = Mode(bindings::RPM_NOWAIT);
> + /// Acquire a runtime-PM usage reference.
> + const ACQUIRE: Mode = Mode(bindings::RPM_GET_PUT);
> + /// Use autosuspend.
> + const AUTO: Mode = Mode(bindings::RPM_AUTO);
> + /// Additional mode for devices supporting idle states.
> + /// No counterpart.
> + const IDLE: Mode = Mode(1 << 16);
> +
> + const fn join(self, other: Self) -> Self {
> + Self(self.0 | other.0)
> + }
> +
> + fn includes(self, other: Self) -> bool {
> + (self.0 & other.0) != 0
> + }
> +}
> +
> +impl core::ops::BitOr for Mode {
> + type Output = Self;
> + fn bitor(self, rhs: Self) -> Self::Output {
> + Self(self.0 | rhs.0)
> + }
> +}
> +
> +impl core::ops::BitAnd for Mode {
> + type Output = Self;
> + fn bitand(self, rhs: Self) -> Self::Output {
> + Self(self.0 & rhs.0)
> + }
> +}
> +
> +impl core::ops::Not for Mode {
> + type Output = Self;
> + fn not(self) -> Self::Output {
> + Self(!self.0)
> + }
> +}
> +
> +impl From<Mode> for core::ffi::c_int {
> + #[inline]
> + fn from(mode: Mode) -> core::ffi::c_int {
> + mode.0 as core::ffi::c_int
> + }
> +}
> +
> +/// Utility macro for combining multiple request modes
> +macro_rules! mode {
> + ($mode:expr $(, $args:expr)+ $(,)?) => {{
> + let mut new_mode = $mode;
> + $( new_mode = new_mode.join($args); )+
> + new_mode
> + }};
> +}
> +
> +/// Device's runtime power management status
> +#[repr(i32)]
> +pub enum RuntimePMState {
> + /// Runtime PM has not been initialized for this device yet.
> + UNKNOWN = bindings::rpm_status_RPM_INVALID,
> + /// The device is expected to be runtime active and in it's normal operating state
> + RESUMED = bindings::rpm_status_RPM_ACTIVE,
> + /// The device is expected to be suspended, unavailable for normal operations
> + SUSPENDED = bindings::rpm_status_RPM_SUSPENDED,
> +}
> +
> +/// Runtime power transition scope.
> +pub struct Scope<'a, Tag> {
> + dev: &'a device::Device<device::Bound>,
> + mode: Mode,
> + _tag: PhantomData<Tag>,
> +}
> +
> +/// Device resumed without incrementing the device's usage count
> +pub struct Resume;
> +/// Device resumed with the device's usage count being incremented
> +pub struct Awake;
> +/// Device with increased usage reference
> +pub struct Retain;
> +
> +/// Resumes the device without acquiring the usage reference.
> +/// Note: This does not guarantee the device will be kept active
> +/// for the lifetime of the scope due to potential pending/incoming
> +/// suspend requests.
> +///
> +/// On drop:
> +/// - If `Mode::IDLE`, calls `__pm_runtime_idle()`:
> +/// triggers idle notification before attempting to suspend
> +/// - If `Mode::AUTO`, marks last busy then calls `__pm_runtime_suspend()`.
> +/// - Otherwise calls `__pm_runtime_suspend()`.
> +///
> +/// The guard must be dropped from a context matching the requested transition
> +/// mode: sync vs async.
> +#[must_use = "dropping this guard issues the matching runtime PM release request"]
> +pub struct ResumeScope<'a>(Scope<'a, Resume>);
> +
> +/// Acquires a runtime-PM usage reference and keeps the device powered.
> +///
> +/// Requires `Mode::ACQUIRE`. Drop behavior matches `ResumeScope`.
> +/// The guard must be dropped from a context matching the requested transition
> +/// mode: sync vs async.
> +#[must_use = "dropping this guard releases its runtime PM hold"]
> +pub struct AwakeScope<'a>(Scope<'a, Awake>);
> +
> +/// Prevents the device from getting suspended by holding the usage reference
> +/// count.
> +///
> +/// On drop, calls `pm_runtime_put_noidle()`.
> +#[must_use = "dropping this guard releases its runtime PM hold"]
> +pub struct RetainScope<'a>(Scope<'a, Retain>);
> +
> +impl<'a> ResumeScope<'a> {
> + fn new(dev: &'a device::Device<device::Bound>, mode: Mode) -> Result<Self> {
> + if mode.includes(Mode::ACQUIRE) {
> + // Mode::ACQUIRE is intended to be used with Awake scope
> + // Avoid mixing the modes.
> + return Err(EINVAL);
> + }
> +
> + // Mode::IDLE is internal so strip it of before passing further
> + Request::resume(dev, mode & !Mode::IDLE).map(|()| {
> + Self(Scope::<Resume> {
> + dev,
> + mode,
> + _tag: PhantomData,
> + })
> + })
> + }
> +
> + fn release_inner(&self) -> Result {
> + let scope_mode = self.0.mode & !Mode::IDLE;
> +
> + match self.0.mode {
> + mode if mode.includes(Mode::IDLE) => {
> + Request::idle(self.0.dev, scope_mode & (Mode::ASYNC | Mode::NOWAIT))
> + }
> + mode if mode.includes(Mode::AUTO) => {
> + Request::mark_last_busy(self.0.dev);
> + Request::suspend(self.0.dev, scope_mode)
> + }
> + _ => Request::suspend(self.0.dev, scope_mode),
> + }
> + }
> +
> + /// Explicitly release the scope
> + /// This should be used in favor of regular drop
> + /// when error handling is required.
> + pub fn release(self) -> Result {
> + let result = self.release_inner();
> + core::mem::forget(self);
> + result
> + }
> +}
> +
> +impl<'a> Drop for ResumeScope<'a> {
> + fn drop(&mut self) {
> + let _ = self.release_inner();
> + }
> +}
> +
> +impl<'a> AwakeScope<'a> {
> + fn new(dev: &'a device::Device<device::Bound>, mode: Mode) -> Result<Self> {
> + if !mode.includes(Mode::ACQUIRE) {
> + return Err(EINVAL);
> + }
> + // Mode::IDLE is internal so strip it of before passing further
> + Request::resume(dev, mode & !Mode::IDLE)
> + .inspect_err(|_| Request::put_noidle(dev))
> + .map(|()| {
> + Self(Scope::<Awake> {
> + dev,
> + mode,
> + _tag: PhantomData,
> + })
> + })
> + }
> +
> + fn release_inner(&self) -> Result {
> + let scope_mode = self.0.mode & !Mode::IDLE;
> + match self.0.mode {
> + mode if mode.includes(Mode::IDLE) => Request::idle(self.0.dev, scope_mode),
> + mode if mode.includes(Mode::AUTO) => {
> + Request::mark_last_busy(self.0.dev);
> + Request::suspend(self.0.dev, scope_mode)
> + }
> + _ => Request::idle(self.0.dev, scope_mode),
> + }
> + }
> +
> + /// Explicitly release the scope
> + /// This should be used in favor of regular drop
> + /// when error handling is required.
> + pub fn release(self) -> Result {
> + let result = self.release_inner();
> + core::mem::forget(self);
> + result
> + }
> +}
> +
> +impl<'a> Drop for AwakeScope<'a> {
> + fn drop(&mut self) {
> + let _ = self.release_inner();
> + }
> +}
> +
> +impl<'a> RetainScope<'a> {
> + fn new(dev: &'a device::Device<device::Bound>) -> Result<Self> {
> + Request::get_noresume(dev);
> + Ok(Self(Scope::<Retain> {
> + dev,
> + mode: Mode(0),
> + _tag: PhantomData,
> + }))
> + }
> +
> + fn try_new(dev: &'a device::Device<device::Bound>) -> Result<Self> {
> + Request::get_if_active(dev)?;
> + Ok(Self(Scope::<Retain> {
> + dev,
> + mode: Mode(0),
> + _tag: PhantomData,
> + }))
> + }
> +
> + fn release_inner(&self) {
> + Request::put_noidle(self.0.dev);
> + }
> +
> + /// Explicitly release the scope
> + /// This should be used in favor of regular drop
> + /// when error handling is required.
> + pub fn release(self) -> Result {
> + self.release_inner();
> + core::mem::forget(self);
> + Ok(())
> + }
> +}
> +
> +impl<'a> Drop for RetainScope<'a> {
> + fn drop(&mut self) {
> + self.release_inner();
> + }
> +}
> +
> +/// Runtime PM helpers - wrappers around C runtime PM interface.
> +/// All methods require a reference to a bound device.
> +struct Request;
> +
> +#[cfg(CONFIG_PM)]
> +impl Request {
> + #[inline]
> + fn active(dev: &device::Device<device::Bound>) -> bool {
> + // SAFETY: `dev.as_raw()` must provide a valid pointer to
> + // `struct device` for the duration of the call.
> + // The `Device<Bound>` reference provides that guarantee.
> + unsafe { bindings::pm_runtime_active(dev.as_raw()) }
> + }
> +
> + #[inline]
> + fn suspended(dev: &device::Device<device::Bound>) -> bool {
> + // SAFETY: `dev.as_raw()` must provide a valid pointer to
> + // `struct device` for the duration of the call.
> + // The `Device<Bound>` reference provides that guarantee.
> + unsafe { bindings::pm_runtime_suspended(dev.as_raw()) }
> + }
> +
> + #[inline]
> + fn resume(dev: &device::Device<device::Bound>, mode: Mode) -> Result {
> + // SAFETY: `dev.as_raw()` must provide a valid pointer to
> + // `struct device` for the duration of the call.
> + // The `Device<Bound>` reference provides that guarantee.
> + to_result(unsafe { bindings::__pm_runtime_resume(dev.as_raw(), mode.into()) })
> + }
> +
> + #[inline]
> + fn idle(dev: &device::Device<device::Bound>, mode: Mode) -> Result {
> + // SAFETY: `dev.as_raw()` must provide a valid pointer to
> + // `struct device` for the duration of the call.
> + // The `Device<Bound>` reference provides that guarantee.
> + to_result(unsafe { bindings::__pm_runtime_idle(dev.as_raw(), mode.into()) })
> + }
> +
> + #[inline]
> + fn mark_last_busy(dev: &device::Device<device::Bound>) {
> + // SAFETY: `dev.as_raw()` must provide a valid pointer to
> + // `struct device` for the duration of the call.
> + // The `Device<Bound>` reference provides that guarantee.
> + unsafe {
> + bindings::pm_runtime_mark_last_busy(dev.as_raw());
> + }
> + }
> +
> + #[inline]
> + fn suspend(dev: &device::Device<device::Bound>, mode: Mode) -> Result {
> + // SAFETY: `dev.as_raw()` must provide a valid pointer to
> + // `struct device` for the duration of the call.
> + // The `Device<Bound>` reference provides that guarantee.
> + to_result(unsafe { bindings::__pm_runtime_suspend(dev.as_raw(), mode.into()) })
> + }
> +
> + #[inline]
> + fn get_if_active(dev: &device::Device<device::Bound>) -> Result {
> + // SAFETY: `dev.as_raw()` must provide a valid pointer to
> + // `struct device` for the duration of the call.
> + // The `Device<Bound>` reference provides that guarantee.
> + match unsafe { bindings::pm_runtime_get_if_active(dev.as_raw()) } {
> + ret if ret < 0 => Err(Error::from_errno(ret)),
> + 0 => Err(EAGAIN),
> + _ => Ok(()),
> + }
> + }
> +
> + #[inline]
> + fn runtime_enable(dev: &device::Device<device::Bound>) {
> + // SAFETY: `dev.as_raw()` must provide a valid pointer to
> + // `struct device` for the duration of the call.
> + // The `Device<Bound>` reference provides that guarantee.
> + unsafe { bindings::pm_runtime_enable(dev.as_raw()) }
> + }
> +
> + #[inline]
> + fn runtime_disable(dev: &device::Device<device::Bound>) {
> + // SAFETY: `dev.as_raw()` must provide a valid pointer to
> + // `struct device` for the duration of the call.
> + // The `Device<Bound>` reference provides that guarantee.
> + unsafe { bindings::__pm_runtime_disable(dev.as_raw(), true) };
> + }
What happens when you call these twice i.e. runtime_enable() when it's already
enabled or runtime_disable() when it's already disabled?
> +}
> +
> +#[cfg(not(CONFIG_PM))]
> +impl Request {
> + #[inline]
> + fn active(dev: &device::Device<device::Bound>) -> bool {
> + true
> + }
> +
> + #[inline]
> + fn suspended(dev: &device::Device<device::Bound>) -> bool {
> + false
> + }
> +
> + #[inline]
> + fn resume(_dev: &device::Device<device::Bound>, _mode: Mode) -> Result {
> + Ok(())
> + }
> +
> + #[inline]
> + fn idle(_dev: &device::Device<device::Bound>, _mode: Mode) -> Result {
> + Err(ENOSYS)
> + }
> +
> + #[inline]
> + fn mark_last_busy(_dev: &device::Device<device::Bound>) {}
> +
> + #[inline]
> + fn suspend(_dev: &device::Device<device::Bound>, _mode: Mode) -> Result {
> + Err(ENOSYS)
> + }
> +
> + #[inline]
> + fn get_if_active(dev: &device::Device<device::Bound>) -> Result {
> + Err(EINVAL)
> + }
> +
> + #[inline]
> + fn runtime_enable(_dev: &device::Device<device::Bound>) {}
> +
> + #[inline]
> + fn runtime_disable(dev: &device::Device<device::Bound>) {}
> +}
> +
> +impl Request {
> + #[inline]
> + fn get_noresume(dev: &device::Device<device::Bound>) {
> + // SAFETY: `dev.as_raw()` must provide a valid pointer to
> + // `struct device` for the duration of the call.
> + // The `Device<Bound>` reference provides that guarantee.
> + unsafe { bindings::pm_runtime_get_noresume(dev.as_raw()) };
> + }
> +
> + #[inline]
> + fn put_noidle(dev: &device::Device<device::Bound>) {
> + // SAFETY: `dev.as_raw()` must provide a valid pointer to
> + // `struct device` for the duration of the call.
> + // The `Device<Bound>` reference provides that guarantee.
> + unsafe { bindings::pm_runtime_put_noidle(dev.as_raw()) };
> + }
> +
> + #[allow(unused)]
> + #[inline]
> + fn mark_active(dev: &device::Device<device::Bound>) -> Result {
> + to_result(
> + // SAFETY: `dev.as_raw()` must provide a valid pointer to
> + // `struct device` for the duration of the call.
> + // The `Device<Bound>` reference provides that guarantee.
> + unsafe { bindings::pm_runtime_set_active(dev.as_raw()) },
> + )
> + }
> +
> + #[allow(unused)]
> + #[inline]
> + fn mark_suspended(dev: &device::Device<device::Bound>) -> Result {
> + to_result(
> + // SAFETY: `dev.as_raw()` must provide a valid pointer to
> + // `struct device` for the duration of the call.
> + // The `Device<Bound>` reference provides that guarantee.
> + unsafe { bindings::pm_runtime_set_suspended(dev.as_raw()) },
> + )
> + }
> +}
> +
> +/// Common runtime PM callback entry point
> +///
> +/// The generated extern "C" callbacks call into this helper with the raw
> +/// `struct device *` provided by the PM core. It rebuilds the Rust device
> +/// reference, retrieves the device's PM registration data and performs
> +/// handoff to corresponding driver callback.
> +fn runtime_pm_callback<T, F>(dev: *mut bindings::device, cb: F) -> Result
> +where
> + T: PMOps,
> + F: FnOnce(
> + &<T as PMOps>::DeviceType,
> + Option<<T as PMOps>::RuntimePayloadType>,
> + ) -> Result<
> + Option<<T as PMOps>::RuntimePayloadType>,
> + (Option<<T as PMOps>::RuntimePayloadType>, Error),
> + >,
> +{
> + let dev: &device::Device<device::Bound> =
> + // SAFETY: `dev` is provided by the PM core and remains
> + // valid for the duration of the callback.
> + unsafe { device::Device::from_raw(dev) };
> +
> + // SAFETY: `dev` is provided by the PM core and remains
> + // valid for the duration of the callback.
> + let ptr = unsafe { (*(*dev.as_raw()).p).rust_private };
> +
> + if ptr.is_null() {
> + return Err(ENODEV);
> + }
> +
> + // SAFETY: The runtime PM callback can only be triggered for bound device
> + // and once the runtime PM is enabled.
> + // `rust_private` is guaranteed to be valid and points to
> + // associated RegistrationData<T> type object at least for the duration
> + // of this call.
> + let payload: Pin<&RegistrationData<'_, T>> =
> + unsafe { <Pin<KBox<RegistrationData<'_, T>>> as ForeignOwnable>::borrow(ptr) };
> +
> + let pm_dev: &T::DeviceType =
> + // SAFETY: The generated `dev_pm_ops` for `T` is installed on devices whose
> + // bus-specific type is `T::DeviceType`. Therefore the base `Device<Bound>`
> + // passed by the PM core is embedded in a valid `T::DeviceType`; the
> + // `AsBusDevice` implementation supplies the correct offset for this cast.
> + unsafe { T::DeviceType::from_device(dev) };
> +
> + payload.data.transition(|payload| cb(pm_dev, payload))
> +}
> +
> +/// Defines generated C runtime PM callback.
> +///
> +/// This macro generates the corresponding `unsafe extern "C"` function for
> +/// requested PM operation and forwards it to [`runtime_pm_callback`].
> +///
> +/// # Safety
> +///
> +/// The generated function may only be installed in a `dev_pm_ops` table for a
> +/// device whose PM registration data was created by `Registration<T>`. In other
> +/// words, the type parameter `T` used for the callback table must match the
> +/// type parameter `T` used for the stored `RegistrationData<T>`.
> +///
> +/// The PM registration must keep the stored registration data alive and pinned
> +/// until runtime PM is disabled and all in-flight PM callbacks have completed.
> +macro_rules! define_pm_callback {
> + (@parse_desc $name:ident) => { define_pm_callback!(@default $name); };
> + (@default $name:ident) => {
> + paste!(
> + /// Generated runtime PM callback.
> + ///
> + /// # Safety
> + ///
> + /// `dev` must be a valid `struct device *` supplied by the PM core for a device
> + /// whose runtime PM callbacks and PM registration data were both created
> + /// for `T`.
> + unsafe extern "C" fn [<$name _callback>]<'a, T:PMOps>(
> + dev: *mut bindings::device
> + ) -> core::ffi::c_int
> + where
> + <T as PMOps>::DeviceType: 'a
> + {
> + runtime_pm_callback::<T, _>(dev,T::$name).map(|_| 0).unwrap_or_else(|e| e.to_errno())
> + }
> + );
> +
> + };
> +}
> +
> +/// SAFETY:
> +/// bindings::dev_pm_ops is #[repr(C)], implements Default
> +/// and the struct itself is all nullable function pointers.
> +/// There is no padding and all zero bit-pattern is valid
> +///
> +const PMOPS_NONE: bindings::dev_pm_ops =
> + unsafe { core::mem::MaybeUninit::<bindings::dev_pm_ops>::zeroed().assume_init() };
> +
> +/// Runtime PM callbacks implemented by a driver.
> +///
> +/// Defines the [`PMOps`] trait and its corresponding [`bindings::dev_pm_ops`].
> +///
> +/// Each generated C callback recovers the Rust bus device from the raw
> +/// `struct device *`, borrows the registered runtime PM payload, and delegates
> +/// to the matching [`PMOps`] trait method.
> +///
> +/// # Safety
> +///
> +/// `PMContext::<T>::PM_OPS` must only be installed for devices whose concrete
> +/// bus type is `T::DeviceType`, and whose runtime PM registration data was
> +/// installed by [`Registration::new`] for the same `T`.
> +///
> +macro_rules! define_pm_ops {
> + ($($name:ident $( : $desc:tt )? ),+ $(,)?) => {
> + $( define_pm_callback!( @parse_desc $name $( $desc )?); )+
> + define_pm_ops!(@common $( $name ), +);
> + };
> +
> + (@common $( $name:ident),+ ) => {
> + /// Runtime PM callbacks implemented by a driver
> + #[vtable]
> + pub trait PMOps: Sized
> + {
> + /// Type of a bus device
> + type DeviceType: AsBusDevice<device::Bound>;
> + /// Type of the data associated with a PM transitions:
> + type RuntimePayloadType: Send;
> +
> + $(
> + #[allow(missing_docs)]
> + // Callback-specific docs are provided by the generated `dev_pm_ops`
> + // contract on `PMOps`.
> +
> + fn $name<'a>(
> + _dev: &'a Self::DeviceType,
> + _payload: Option<Self::RuntimePayloadType>,
> + ) -> Result<Option<Self::RuntimePayloadType>, (Option<Self::RuntimePayloadType>, Error)> {
> + build_error!(VTABLE_DEFAULT_ERROR)
> + }
> + )+
> + }
> + paste!(
> + impl<'a, T:PMOps> PMContext<'a, T> {
> + /// Driver-provided runtime PM operations.
> + ///
> + /// A driver implements this trait to handle runtime PM
> + /// transitions for its device type.
> + ///
> + /// Each callback receives the device and the current payload.
> + /// On success, it returns the payload to keep for the next
> + /// transition. On failure, it returns the payload together
> + /// with the error so the previous, or otherwise sane state
> + /// can be preserved.
> + pub const PM_OPS: bindings::dev_pm_ops = bindings::dev_pm_ops {
> + $( [<$name>]: if T::[<HAS_ $name:upper>] {
> + Some([<$name _callback>]::<T>)
> + } else {
> + None
> + }, )+
> + ..PMOPS_NONE
> + };
> + }
> + );
> + }
> +}
> +
> +/// RAII guard for ongoing runtime PM payload transition.
> +///
> +/// For most of the callbacks this guard is not necessairly needed as
> +/// the callbacks themselves are being serialized by the runtime PM C code.
> +/// Still, some like runtime_idle are exempt ftom that.
> +#[allow(unused)]
> +struct PayloadGuard<'a> {
> + busy: &'a Atomic<bool>,
> +}
> +
> +impl Drop for PayloadGuard<'_> {
> + fn drop(&mut self) {
> + self.busy.store(false, ordering::Release);
> + }
> +}
> +
> +struct PMPayload<P> {
> + in_flight: Atomic<bool>,
> + inner: UnsafeCell<Option<P>>,
> +}
> +
> +impl<P> PMPayload<P> {
> + /// Attempts to acquire exclusive access to the runtime PM payload.
> + ///
> + /// Returns `EBUSY` if another runtime PM callback is already transitioning the
> + /// payload.
> + fn acquire(&self) -> Result<PayloadGuard<'_>> {
> + self.in_flight
> + .cmpxchg(false, true, ordering::Acquire)
> + .map_err(|_| EBUSY)?;
> + Ok(PayloadGuard {
> + busy: &self.in_flight,
> + })
> + }
> +
> + /// Runs a runtime PM transition with exclusive access to the stored payload.
> + ///
> + /// This method acquires the in-flight guard, temporarily takes the payload out
> + /// of storage The closure must return the payload that should be stored
> + /// for the next transition.
> + ///
> + /// On success, the returned payload replaces the previous payload. On failure,
> + /// the closure returns the payload together with the error, and that payload is
> + /// restored before the error is propagated.
> + ///
> + /// Returns `EBUSY` if another runtime PM transition is already in progress.
> + fn transition(
> + &self,
> + f: impl FnOnce(Option<P>) -> Result<Option<P>, (Option<P>, Error)>,
> + ) -> Result {
> + let _guard = self.acquire()?;
> + // SAFETY: Holding `_guard` means this callback successfully changed
> + // `in_flight` from false to true. No other caller can hold a `PayloadGuard`
> + // until `_guard` is dropped, so this function has exclusive access to `inner`.
> + let slot = unsafe { &mut *self.inner.get() };
> +
> + let payload = slot.take();
> +
> + match f(payload) {
> + Ok(new_payload) => {
> + *slot = new_payload;
> + Ok(())
> + }
> + Err((old_payload, err)) => {
> + *slot = old_payload;
> + Err(err)
> + }
> + }
> + }
> +}
> +
> +// SAFETY: Although PMPayload's `inner` is an `UnsafeCell`, it is only accessed
> +// after `in_flight` has been acquired. The atomic flag serializes all mutable
> +// access to the payload, and `PayloadGuard` clears the flag when the access ends.
> +unsafe impl<P: Send> Sync for PMPayload<P> {}
> +
> +struct PMContextInner<'a, T: PMOps> {
> + dev: &'a device::Device<device::Bound>,
> + /// Optional driver-selected runtime PM request PMProfiles.
> + ///
> + /// Set of runtime PM predefined PMProfiles that can be used by the driver
> + /// when requesting a PM transition. This might be useful when a driver
> + /// has several different PM usage patterns.
> + /// See [PMProfile] for more details.
> + profiles: KVec<PMProfile>,
> + /// Set of PM config options applied for associated device.
> + configs: KVec<PMConfig>,
> + _marker: PhantomData<T>,
> +}
> +
> +/// Runtime PM context tied to a device.
> +pub struct PMContext<'a, T: PMOps> {
> + // Preferably, PMContext could be shared via borrowed reference over
> + // a pm Registraion's lifetime but that bares complications on its own
> + // when the context needs to be shared across different Registration types.
> + inner: Arc<PMContextInner<'a, T>>,
> +}
> +
> +impl<'a, T: PMOps> PMContext<'a, T> {
> + /// Enable runtime PM
> + pub fn enable(&self, state: RuntimePMState) -> Result {
> + Self::apply_config(self.inner.dev, &self.inner.configs);
> + match state {
> + RuntimePMState::RESUMED => Request::mark_active(self.inner.dev),
> + RuntimePMState::SUSPENDED => Request::mark_suspended(self.inner.dev),
> + _ => Err(EINVAL),
> + }?;
> + Request::runtime_enable(self.inner.dev);
> + Ok(())
> + }
> + /// Disable runtime PM
> + pub fn disable(&self) -> Result {
> + Self::apply_config(self.inner.dev, &[PMConfig::AutoSuspend(false)]);
> + Request::runtime_disable(self.inner.dev);
> + Ok(())
> + }
> +
> + /// Returns whether the runtime PM state is active.
> + #[inline]
> + pub fn active(&self) -> bool {
> + Request::active(self.inner.dev)
> + }
> +
> + /// Returns whether the runtime PM state is suspended.
> + #[inline]
> + pub fn suspended(&self) -> bool {
> + Request::suspended(self.inner.dev)
> + }
> +
> + /// Creates a `ResumeScope` for the given PMProfile.
> + #[inline]
> + pub fn resume(&self, profile: PMProfile) -> Result<ResumeScope<'a>> {
> + ResumeScope::new(self.inner.dev, profile.0)
> + }
> +
> + /// Creates an `AwakeScope` for the given PMProfile.
> + /// Note that for ASYNC request this does not guarantee
> + /// the device has been resumed at the time this funtion returns.
> + #[inline]
> + pub fn get(&self, profile: PMProfile) -> Result<AwakeScope<'a>> {
> + AwakeScope::new(self.inner.dev, profile.0 | Mode::ACQUIRE)
> + }
> +
> + /// Creates a `RetainScope` for this device.
> + pub fn hold(&self) -> Result<RetainScope<'a>> {
> + RetainScope::new(self.inner.dev)
> + }
> +
> + /// Creates a `RetainScope` for an active device.
> + pub fn try_hold_active(&self) -> Result<RetainScope<'a>> {
> + RetainScope::try_new(self.inner.dev)
> + }
> +
> + /// Runs a closure while holding a `ResumeScope`.
> + pub fn with_resume<R>(&self, profile: PMProfile, f: impl FnOnce() -> Result<R>) -> Result<R> {
> + if profile.0.includes(Mode::ASYNC) {
> + return Err(EINVAL);
> + }
> + let _scope = self.resume(profile)?;
> + f()
> + }
> + /// Runs a closure while holding an `AwakeScope`.
> + pub fn with_get<R>(&self, profile: PMProfile, f: impl FnOnce() -> Result<R>) -> Result<R> {
> + if profile.0.includes(Mode::ASYNC) {
> + return Err(EINVAL);
> + }
> + let _scope = self.get(profile)?;
> + f()
> + }
> +
> + /// Runs a closure while holding a `RetainScope`.
> + pub fn with_hold<R>(&self, f: impl FnOnce() -> Result<R>) -> Result<R> {
> + let _scope = self.hold()?;
> + f()
> + }
> +
> + /// Applies runtime PM configuration options.
> + ///
> + /// Options are applied in the order provided. The currently supported
> + /// options do not report per-option failures.
> + fn apply_config(dev: &device::Device<device::Bound>, opts: &[PMConfig]) {
> + #[cfg(not(CONFIG_PM))]
> + let _ = opts;
> + let _ = dev;
> + #[cfg(CONFIG_PM)]
> + for opt in opts {
> + match opt {
> + // SAFETY: `self.dev` is a valid `&ARef<Device>`, so the underlying `Device` is
> + // guaranteed to be alive and `as_raw()` yields a valid pointer for the
> + // duration of this call.
> + PMConfig::IgnoreChildren(v) => unsafe {
> + bindings::pm_suspend_ignore_children(dev.as_raw(), *v)
> + },
> + // SAFETY: `self.dev` is a valid `&ARef<Device>`, so the underlying `Device` is
> + // guaranteed to be alive and `as_raw()` yields a valid pointer for the
> + // duration of this call.
> + PMConfig::NoCallbacks => unsafe { bindings::pm_runtime_no_callbacks(dev.as_raw()) },
> + // SAFETY: `self.dev` is a valid `&ARef<Device>`, so the underlying `Device` is
> + // guaranteed to be alive and `as_raw()` yields a valid pointer for the
> + // duration of this call.
> + PMConfig::IrqSafe => unsafe { bindings::pm_runtime_irq_safe(dev.as_raw()) },
> + // SAFETY: `self.dev` is a valid `&ARef<Device>`, so the underlying `Device` is
> + // guaranteed to be alive and `as_raw()` yields a valid pointer for the
> + // duration of this call.
> + PMConfig::AutoSuspend(v) => unsafe {
> + bindings::__pm_runtime_use_autosuspend(dev.as_raw(), *v);
> + },
> + // SAFETY: `self.dev` is a valid `&ARef<Device>`, so the underlying `Device` is
> + // guaranteed to be alive and `as_raw()` yields a valid pointer for the
> + // duration of this call.
> + PMConfig::AutoSuspendDelay(v) => unsafe {
> + bindings::pm_runtime_set_autosuspend_delay(dev.as_raw(), *v as i32);
> + bindings::__pm_runtime_use_autosuspend(dev.as_raw(), true);
> + },
> + }
> + }
> + }
> +
> + /// Get a borrowed reference to PM profiles asociated wiht the PM context
> + pub fn profiles(&self) -> &[PMProfile] {
> + &self.inner.profiles
> + }
> +
> + /// Get a borrowed reference to PM configs asociated wiht the PM context
> + pub fn configs(&self) -> &[PMConfig] {
> + &self.inner.configs
> + }
> +}
> +
> +// Preferably, PMContext could be shared via borrowed reference over
> +// a pm Registraion's lifetime but that bares complications on its own
> +// when the context needs to be shared across different Registration types.
> +impl<T: PMOps> Clone for PMContext<'_, T> {
> + fn clone(&self) -> Self {
> + Self {
> + inner: self.inner.clone(),
> + }
> + }
> +}
> +
> +define_pm_ops!(
> + // PM state change
> + runtime_suspend,
> + runtime_resume,
> +);
> +
> +/// Runtime PM request PMProfile.
> +pub struct PMProfile(Mode);
> +
> +impl PMProfile {
> + /// Creates a PMProfile with default SYNC mode set.
> + pub const fn new() -> Self {
> + Self(Mode::SYNC)
> + }
> + /// /Enables async PM operations for this PMProfile.
> + pub const fn r#async(self) -> Self {
> + Self(mode!(self.0, Mode::ASYNC))
> + }
> + /// Use autosuspend
> + pub const fn auto(self) -> Self {
> + Self(mode!(self.0, Mode::AUTO))
> + }
> + /// Requests idle handling for this PMProfile.
> + pub const fn idle(self) -> Self {
> + Self(mode!(self.0, Mode::IDLE))
> + }
> + /// Do not wait for concurrent requests to finish.
> + pub const fn nowait(self) -> Self {
> + Self(mode!(self.0, Mode::NOWAIT))
> + }
> +}
> +
> +impl Default for PMProfile {
> + fn default() -> Self {
> + Self::new()
> + }
> +}
> +
> +/// Configuration knobs for runtime PM.
> +pub enum PMConfig {
> + /// Ignore child devices when suspending.
> + IgnoreChildren(bool),
> + /// Disable runtime PM callbacks.
> + NoCallbacks,
> + /// Mark device as IRQ-safe for runtime PM.
> + IrqSafe,
> + /// Enable or disable autosuspend.
> + AutoSuspend(bool),
> + /// Set autosuspend delay (milliseconds).
> + AutoSuspendDelay(u32),
> +}
> +
> +/// Runtime PM data stored within the `struct device_private' during
> +/// runtime PM registration.
> +///
> +/// The data is associated with PM transitions and it's conceptually owned
> +/// by the Registration itself.
> +///
> +#[repr(C)]
> +#[pin_data]
> +struct RegistrationData<'a, T: PMOps> {
> + #[pin]
> + data: PMPayload<T::RuntimePayloadType>,
> + _marker: PhantomData<&'a mut ()>,
> +}
> +
> +/// Runtime PM registration for a device.
> +///
> +/// A `Registration` installs the runtime PM payload used by the
> +/// generated [`PMOps`] callbacks and owns the corresponding teardown.
> +///
> +/// Dropping the registration disables runtime PM, waits for in-flight runtime PM
> +/// callbacks to complete, and then removes the stored registration data.
> +pub struct Registration<'a, T: PMOps> {
> + ctx: PMContext<'a, T>,
> +}
> +
> +impl<'a, T: PMOps> Registration<'a, T> {
> + /// Creates a runtime PM registration for `dev`.
> + ///
> + /// The provided profiles and configuration are stored in the associated
> + /// [`PMContext`]. The optional `payload` is stored as a Registration data
> + /// and is used to service PM transitions.
> + ///
> + /// The device must use the callback table generated for the same `T`.
> + pub fn new(
> + dev: &'a device::Device<device::Core<'_>>,
> + profiles: Option<KVec<PMProfile>>,
> + configs: Option<KVec<PMConfig>>,
> + payload: Option<T::RuntimePayloadType>,
> + ) -> Result<Self> {
> + let payload = KBox::pin_init(
> + RegistrationData::<T> {
> + data: PMPayload {
> + in_flight: Atomic::new(false),
> + inner: UnsafeCell::new(payload),
> + },
> + _marker: PhantomData,
> + },
> + GFP_KERNEL,
> + )?;
> +
> + let inner_ctx = Arc::new(
> + PMContextInner {
> + dev,
> + profiles: profiles.unwrap_or_default(),
> + configs: configs.unwrap_or_default(),
> + _marker: PhantomData,
> + },
> + GFP_KERNEL,
> + )?;
> +
> + // SAFETY: `dev` is a live `Device<Core>`, so its raw `struct device` pointer is
> + // valid for the duration of this call. The payload allocation is converted into
> + // a foreign pointer and owned by this `Registration` until `Drop` clears
> + // `rust_private` and reconstructs the `KBox`.
> + unsafe {
> + let ptr = (*(*dev.as_raw()).p).rust_private;
> + if !ptr.is_null() {
> + return Err(EBUSY);
> + }
> + (*(*dev.as_raw()).p).rust_private = payload.into_foreign();
> + }
> +
> + Ok(Self {
> + ctx: PMContext { inner: inner_ctx },
> + })
> + }
> + /// Returns the runtime PM context associated with this registration.
> + pub fn ctx(&self) -> &PMContext<'a, T> {
> + &self.ctx
> + }
> +}
> +
> +impl<'a, T: PMOps> Drop for Registration<'a, T> {
> + fn drop(&mut self) {
> + // SAFETY: `self.ctx.inner.dev` is the device this registration was
> + // created for. Runtime PM is disabled first, and `pm_runtime_barrier`
> + // waits for pending runtime PM work/callbacks before the callback data
> + // is removed below.
> +
> + unsafe {
> + bindings::__pm_runtime_disable(self.ctx.inner.dev.as_raw(), true);
Same here, is this safe to call even if it's not enabled? Registration can drop
even when pm runtime is not enabled, right?
> + bindings::pm_runtime_barrier(self.ctx.inner.dev.as_raw());
> + }
You should not combine multiple unsafe calls in single unsafe block I think.
> + // SAFETY: The pointer, if non-null, was stored by `Registration::new`
> + // using `Pin<KBox<RegistrationData<T>>>::into_foreign`. Runtime PM has
> + // been disabled and drained above, so generated callbacks can no longer
> + // borrow this data. Clearing `rust_private` prevents later lookup, and
> + // `from_foreign` reconstructs the owning allocation so it is dropped.
> + unsafe {
> + let ptr = (*(*self.ctx.inner.dev.as_raw()).p).rust_private;
> +
> + if !ptr.is_null() {
> + (*(*self.ctx.inner.dev.as_raw()).p).rust_private = core::ptr::null_mut();
> + Pin::<KBox<RegistrationData<'_, T>>>::from_foreign(ptr);
> + }
> + }
> + }
> +}
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v2 3/3] drm/tyr: enable runtime PM
2026-07-21 15:34 ` [PATCH v2 3/3] drm/tyr: enable runtime PM Beata Michalska
@ 2026-07-27 15:00 ` Onur Özkan
0 siblings, 0 replies; 6+ messages in thread
From: Onur Özkan @ 2026-07-27 15:00 UTC (permalink / raw)
To: Beata Michalska
Cc: ojeda, dakr, gregkh, rafael, boqun, gary, bjorn3_gh, lossin,
a.hindborg, aliceryhl, tmgross, daniel.almeida, boris.brezillon,
samitolvanen, rust-for-linux, driver-core, linux-kernel, linux-pm
On Tue, 21 Jul 2026 17:34:04 +0200
Beata Michalska <beata.michalska@arm.com> wrote:
> Add runtime PM support to the Tyr platform driver. Move the clocks and
> regulators used by runtime suspend and resume into the PM payload, register the
> PM callbacks, configure autosuspend, and let DRM paths take a PM usage
> reference while querying device state.
>
> Signed-off-by: Beata Michalska <beata.michalska@arm.com>
> ---
> drivers/gpu/drm/tyr/driver.rs | 112 ++++++++++++++++++++++++++++------
> drivers/gpu/drm/tyr/file.rs | 7 ++-
> 2 files changed, 98 insertions(+), 21 deletions(-)
>
> diff --git a/drivers/gpu/drm/tyr/driver.rs b/drivers/gpu/drm/tyr/driver.rs
> index 8348c6cd3929..89fe216be0de 100644
> --- a/drivers/gpu/drm/tyr/driver.rs
> +++ b/drivers/gpu/drm/tyr/driver.rs
> @@ -1,6 +1,7 @@
> // SPDX-License-Identifier: GPL-2.0 or MIT
>
> use kernel::{
> + bindings,
> clk::{
> Clk,
> OptionalClk, //
> @@ -20,16 +21,16 @@
> poll,
> Io, //
> },
> - new_mutex,
> of,
> platform,
> + pm,
> + pm::*,
> prelude::*,
> regulator,
> regulator::Regulator,
> sizes::SZ_2M,
> sync::{
> aref::ARef,
> - Mutex, //
> },
> time, //
> };
> @@ -55,18 +56,21 @@
> pub(crate) struct TyrPlatformDriverData<'bound> {
> _device: ARef<TyrDrmDevice>,
> _reg: drm::Registration<'bound, TyrDrmDriver>,
> + // This needs to be dropped after drm::Registration as this one borrows
> + // borrows PMContext.
> + pub(crate) pm: pm::Registration<'bound, TyrPlatformDriver>,
> +
> +}
> +
> +#[pin_data]
> +pub(crate) struct TyrRuntimePM<'bound> {
> + pub(crate) pm: PMContext<'bound, TyrPlatformDriver>,
> }
>
> #[pin_data]
> pub(crate) struct TyrDrmDeviceData {
> pub(crate) pdev: ARef<platform::Device>,
>
> - #[pin]
> - clks: Mutex<Clocks>,
> -
> - #[pin]
> - regulators: Mutex<Regulators>,
> -
> /// Some information on the GPU.
> ///
> /// This is mainly queried by userspace, i.e.: Mesa.
> @@ -101,6 +105,7 @@ impl platform::Driver for TyrPlatformDriver {
> type IdInfo = ();
> type Data<'bound> = TyrPlatformDriverData<'bound>;
> const OF_ID_TABLE: Option<of::IdTable<Self::IdInfo>> = Some(&OF_TABLE);
> + const PM_OPS: Option<&'static bindings::dev_pm_ops> = Some(&PMContext::<Self>::PM_OPS);
>
> fn probe<'bound>(
> pdev: &'bound platform::Device<Core<'_>>,
> @@ -117,6 +122,25 @@ fn probe<'bound>(
> let mali_regulator = Regulator::<regulator::Enabled>::get(pdev.as_ref(), c"mali")?;
> let sram_regulator = Regulator::<regulator::Enabled>::get(pdev.as_ref(), c"sram")?;
>
> + let runtime_payload = TyrRuntimePMPayload {
> + clks: Clocks {
> + core: core_clk,
> + stacks: stacks_clk,
> + coregroup: coregroup_clk,
> + },
> + _regulators: Regulators {
> + _mali: mali_regulator,
> + _sram: sram_regulator,
> + }
> + };
> +
> + let mut pm_configs = KVec::<PMConfig>::with_capacity(2, GFP_KERNEL)?;
> + pm_configs.push(PMConfig::AutoSuspend(true), GFP_KERNEL)?;
> + pm_configs.push(PMConfig::AutoSuspendDelay(300), GFP_KERNEL)?;
> +
> + let pm_registration = pm::Registration::new(pdev.as_ref(), None, Some(pm_configs), Some(runtime_payload))?;
> + let pm_context = pm_registration.ctx().clone();
> +
> let request = pdev.io_request_by_index(0).ok_or(ENODEV)?;
> let iomem = request.iomap_sized::<SZ_2M>()?;
>
> @@ -138,28 +162,26 @@ fn probe<'bound>(
>
> let data = try_pin_init!(TyrDrmDeviceData {
> pdev: platform.clone(),
> - clks <- new_mutex!(Clocks {
> - core: core_clk,
> - stacks: stacks_clk,
> - coregroup: coregroup_clk,
> - }),
> - regulators <- new_mutex!(Regulators {
> - _mali: mali_regulator,
> - _sram: sram_regulator,
> - }),
> gpu_info,
> });
>
> let tdev = drm::UnregisteredDevice::<TyrDrmDriver>::new(pdev, data)?;
> // SAFETY: `reg` is stored in `TyrPlatformDriverData` and dropped when the driver is
> // unbound; it is never forgotten.
> - let reg = unsafe { drm::Registration::new(pdev.as_ref(), tdev, (), 0)? };
> + let reg = unsafe { drm::Registration::new(
> + pdev.as_ref(),
> + tdev,
> + pin_init!(TyrRuntimePM { pm: pm_context, }),
> + 0
> + )? };
>
> let driver = TyrPlatformDriverData {
> _device: reg.device().into(),
> _reg: reg,
> + pm: pm_registration,
> };
>
> + driver.pm.ctx().enable(RuntimePMState::RESUMED)?;
> // We need this to be dev_info!() because dev_dbg!() does not work at
> // all in Rust for now, and we need to see whether probe succeeded.
> dev_info!(pdev, "Tyr initialized correctly.\n");
> @@ -185,7 +207,7 @@ fn drop(self: Pin<&mut Self>) {}
> #[vtable]
> impl drm::Driver for TyrDrmDriver {
> type Data = TyrDrmDeviceData;
> - type RegistrationData<'a> = ();
> + type RegistrationData<'a> = TyrRuntimePM<'a>;
> type File = TyrDrmFileData;
> type Object = drm::gem::shmem::Object<BoData>;
> type ParentDevice<Ctx: DeviceContext> = platform::Device<Ctx>;
> @@ -216,3 +238,55 @@ struct Regulators {
> _mali: Regulator<regulator::Enabled>,
> _sram: Regulator<regulator::Enabled>,
> }
> +
> +pub(crate) struct TyrRuntimePMPayload {
> + clks: Clocks,
> + _regulators: Regulators,
> +}
> +
> +#[vtable]
> +impl PMOps for TyrPlatformDriver {
> + type DeviceType = platform::Device<kernel::device::Bound>;
> + type RuntimePayloadType = TyrRuntimePMPayload;
> +
> + fn runtime_suspend<'a>(
> + _dev: &'a Self::DeviceType,
> + payload: Option<TyrRuntimePMPayload>,
> + ) -> Result<Option<TyrRuntimePMPayload>, (Option<TyrRuntimePMPayload>, Error)> {
> +
> + let Some(payload) = payload else {
> + return Err((None, EINVAL));
> + };
> +
> + payload.clks.coregroup.disable_unprepare();
> + payload.clks.stacks.disable_unprepare();
> + payload.clks.core.disable_unprepare();
"impl Drop for Clocks" calls these as well, are they safe to re-call
when they already called?
> + Ok(Some(payload))
> + }
> + fn runtime_resume<'a>(
Missing newline before new function (this appears at multiple lines in this
series).
> + _dev: &'a Self::DeviceType,
> + payload: Option<TyrRuntimePMPayload>,
> + ) -> Result<Option<TyrRuntimePMPayload>, (Option<TyrRuntimePMPayload>, Error)> {
> +
> + let Some(payload) = payload else {
> + return Err((None, EINVAL));
> + };
> +
> + if let Err(e) = payload.clks.core.prepare_enable() {
> + return Err((Some(payload), e));
> + }
> +
> + if let Err(e) = payload.clks.stacks.prepare_enable() {
> + payload.clks.core.disable_unprepare();
> + return Err((Some(payload), e));
> + }
> +
> + if let Err(e) = payload.clks.coregroup.prepare_enable() {
> + payload.clks.stacks.disable_unprepare();
> + payload.clks.core.disable_unprepare();
> + return Err((Some(payload), e));
> + }
> +
> + Ok(Some(payload))
> + }
> +}
> diff --git a/drivers/gpu/drm/tyr/file.rs b/drivers/gpu/drm/tyr/file.rs
> index b686041d5d6b..d9371ddfb5f3 100644
> --- a/drivers/gpu/drm/tyr/file.rs
> +++ b/drivers/gpu/drm/tyr/file.rs
> @@ -5,6 +5,7 @@
> self,
> Registered, //
> },
> + pm::PMProfile,
> prelude::*,
> uaccess::UserSlice,
> uapi, //
> @@ -12,7 +13,8 @@
>
> use crate::driver::{
> TyrDrmDevice,
> - TyrDrmDriver, //
> + TyrDrmDriver,
> + TyrRuntimePM,//
> };
>
> #[pin_data]
> @@ -32,10 +34,11 @@ fn open(_dev: &drm::Device<Self::Driver>) -> Result<Pin<KBox<Self>>> {
> impl TyrDrmFileData {
> pub(crate) fn dev_query(
> ddev: &TyrDrmDevice<Registered>,
> - _reg_data: &(),
> + reg_data: &TyrRuntimePM<'_>,
> devquery: &mut uapi::drm_panthor_dev_query,
> _file: &TyrDrmFile,
> ) -> Result<u32> {
> + let _pm_scope = reg_data.pm.get(PMProfile::new())?;
> if devquery.pointer == 0 {
> match devquery.type_ {
> uapi::drm_panthor_dev_query_type_DRM_PANTHOR_DEV_QUERY_GPU_INFO => {
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-07-27 15:01 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-21 15:34 [PATCH v2 0/3] Rust: add runtime PM support Beata Michalska
2026-07-21 15:34 ` [PATCH v2 1/3] rust: " Beata Michalska
2026-07-27 14:56 ` Onur Özkan
2026-07-21 15:34 ` [PATCH v2 2/3] rust: platform: wire runtime PM callbacks Beata Michalska
2026-07-21 15:34 ` [PATCH v2 3/3] drm/tyr: enable runtime PM Beata Michalska
2026-07-27 15:00 ` Onur Özkan
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox