From: Beata Michalska <beata.michalska@arm.com>
To: "Onur Özkan" <work@onurozkan.dev>
Cc: ojeda@kernel.org, dakr@kernel.org, gregkh@linuxfoundation.org,
rafael@kernel.org, boqun@kernel.org, gary@garyguo.net,
bjorn3_gh@protonmail.com, lossin@kernel.org,
a.hindborg@kernel.org, aliceryhl@google.com, tmgross@umich.edu,
daniel.almeida@collabora.com, boris.brezillon@collabora.com,
samitolvanen@google.com, rust-for-linux@vger.kernel.org,
driver-core@lists.linux.dev, linux-kernel@vger.kernel.org,
linux-pm@vger.kernel.org
Subject: Re: [PATCH v2 1/3] rust: add runtime PM support
Date: Wed, 29 Jul 2026 11:07:12 +0200 [thread overview]
Message-ID: <amnCwIMj5dIJevfk@arm.com> (raw)
In-Reply-To: <20260727145622.904965-1-work@onurozkan.dev>
On Mon, Jul 27, 2026 at 05:56:20PM +0300, Onur Özkan wrote:
> 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?
>
For pm_runtime_enable - this will drop a warning.
For pm_runtime_disable - the disable_depth counter will be increased. This
implies that for every extra disable call - enable one needs to be issued.
All handled by PM core.
> > +}
> > +
> > +#[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?
True but that again is handled by PM core.
>
> > + bindings::pm_runtime_barrier(self.ctx.inner.dev.as_raw());
> > + }
>
> You should not combine multiple unsafe calls in single unsafe block I think.
Why?
Do not see much point in separating those.
---
BR
Beata
>
> > + // 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
> >
next prev parent reply other threads:[~2026-07-29 9:07 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
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-29 9:07 ` Beata Michalska [this message]
2026-07-29 13:58 ` 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
2026-07-29 9:13 ` Beata Michalska
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=amnCwIMj5dIJevfk@arm.com \
--to=beata.michalska@arm.com \
--cc=a.hindborg@kernel.org \
--cc=aliceryhl@google.com \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun@kernel.org \
--cc=boris.brezillon@collabora.com \
--cc=dakr@kernel.org \
--cc=daniel.almeida@collabora.com \
--cc=driver-core@lists.linux.dev \
--cc=gary@garyguo.net \
--cc=gregkh@linuxfoundation.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-pm@vger.kernel.org \
--cc=lossin@kernel.org \
--cc=ojeda@kernel.org \
--cc=rafael@kernel.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=samitolvanen@google.com \
--cc=tmgross@umich.edu \
--cc=work@onurozkan.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.