mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Alice Ryhl <aliceryhl@google.com>
To: Beata Michalska <beata.michalska@arm.com>
Cc: Alice Ryhl <aliceryhl@google.com>,
	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, tmgross@umich.edu,
	daniel.almeida@collabora.com,  boris.brezillon@collabora.com,
	work@onurozkan.dev, samitolvanen@google.com,
	 acourbot@nvidia.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 v4 1/3] rust: add runtime PM support
Date: Fri, 16 Oct 2026 12:12:29 +0000	[thread overview]
Message-ID: <20261016121230.3724323-1-aliceryhl@google.com> (raw)
In-Reply-To: <20261005133857.1572243-2-beata.michalska@arm.com>

On Mon, Oct 05, 2026 at 03:37:39PM +0200, Beata Michalska wrote:
> diff --git a/drivers/base/base.h b/drivers/base/base.h
> index a5b7abc10ff0..538cbb104254 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;
>  };

`scripts/kernel-doc -none drivers/base/base.h` warns that `rust_private` is
not described in the kernel-doc comment for `struct device_private`.

> +/// 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 its 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,
> +}

Is `RuntimePMState::Unknown` needed? The only place that takes
`RuntimePMState` is `PMContext::enable()`, which rejects `Unknown` with
`EINVAL`. Removing `Unknown` would avoid having to handle an invalid state in
`enable()`.

> +impl<'a> ResumeScope<'a> {
...
> +    fn release_inner(&self) -> Result {
> +        let scope_mode = self.0.mode & !ModeFlag::Idle;
> +
> +        match self.0.mode {
> +            mode if mode.contains(ModeFlag::Idle) => Request::idle(
> +                self.0.dev,
> +                scope_mode & (ModeFlag::Async | ModeFlag::Nowait),
> +            ),
> +            mode if mode.contains(ModeFlag::Auto) => {
> +                Request::mark_last_busy(self.0.dev);
> +                Request::suspend(self.0.dev, scope_mode)
> +            }
> +            _ => Request::suspend(self.0.dev, scope_mode),
> +        }
> +    }
...
> +impl<'a> AwakeScope<'a> {
> +    fn new(dev: &'a device::Device<device::Bound>, mode: Mode) -> Result<Self> {
> +        if !mode.contains(ModeFlag::Acquire) {
> +            return Err(EINVAL);
> +        }
> +        // ModeFlag::Idle is internal so strip it off before passing further
> +        match Request::resume(dev, mode & !ModeFlag::Idle) {
> +            Ok(()) => {}
> +            // For async/nowait requests, `EINPROGRESS` means the resume is in
> +            // flight and the usage reference already keeps the device active.
> +            Err(e) if e == EINPROGRESS && mode.contains_any(ModeFlag::Async | ModeFlag::Nowait) => {
> +            }
> +            Err(e) => {
> +                Request::put_noidle(dev);
> +                return Err(e);
> +            }
> +        }
> +
> +        Ok(Self(Scope::<Awake> {
> +            dev,
> +            mode,
> +            _tag: PhantomData,
> +        }))
> +    }
> +
> +    fn release_inner(&self) -> Result {
> +        let scope_mode = self.0.mode & !ModeFlag::Idle;
> +        match self.0.mode {
> +            mode if mode.contains(ModeFlag::Idle) => Request::idle(self.0.dev, scope_mode),

In `ResumeScope::release_inner()`, the `ModeFlag::Idle` arm masks out
`ModeFlag::Auto` (`scope_mode & (ModeFlag::Async | ModeFlag::Nowait)`),
whereas here in `AwakeScope::release_inner()` it passes `scope_mode` unmasked.
Should this also strip `ModeFlag::Auto`?

Also, in `AwakeScope::new()`, when `ModeFlag::Nowait` is set without
`ModeFlag::Async`, `__pm_runtime_resume()` returns `-EINPROGRESS` if a
concurrent suspend or resume is in progress without queueing an async resume.
Swallowing `EINPROGRESS` when only `Nowait` is set means `get()` returns
`Ok(AwakeScope)` even though the device is not resumed (and might be
suspending). Should `EINPROGRESS` only be treated as `Ok(())` for
`ModeFlag::Async`?

> +impl<'a> RetainScope<'a> {
...
> +    fn release_inner(&self) {
> +        Request::put_noidle(self.0.dev);
> +    }

In the v3 discussion with Sami [1], you agreed that `RetainScope` should call
`put` rather than `put_noidle` on release so that dropping the last reference
triggers idle/suspend handling, looks like this was missed in v4.

[1]: https://lore.kernel.org/r/apaYNvQSSUCkMTjn@arm.com

> +#[cfg(not(CONFIG_PM))]
> +impl Request {
> +    #[inline]
> +    fn active(_dev: &device::Device<device::Bound>) -> bool {
> +        false
> +    }

`include/linux/pm_runtime.h` already provides `!CONFIG_PM` stub
implementations for all of these functions. If you add `#ifndef CONFIG_PM`
helpers in `rust/helpers/pm_runtime.c` for the functions that are `extern`
under `CONFIG_PM` and `static inline` under `!CONFIG_PM`, you can drop the
`#[cfg(not(CONFIG_PM))]` `Request` block and the `#[cfg(CONFIG_PM)]` guards in
`apply_config()` altogether.

> +struct PMPayload<P> {
> +    in_flight: AtomicFlag,
> +    inner: UnsafeCell<Option<P>>,
> +}

Thanks for removing `Option` from the public `PMOps` trait methods! You can
still use `ManuallyDrop<P>` instead of `Option<P>` inside `PMPayload` too,
which avoids the extra discriminant and the unreachable `None` check in
`transition()`.

> +    /// Enable runtime PM
> +    pub fn enable(&self, state: RuntimePMState) -> Result {
> +        self.inner.state
> +            .cmpxchg(
> +                PMContextState::Disabled,
> +                PMContextState::Enabled,
> +                ordering::Full
> +            )
> +            .map_err(|old| match old {
> +                PMContextState::Dead => ENODEV,
> +                PMContextState::Enabled => EBUSY,
> +                _ => EINVAL, // unreachable
> +            })?;
> +
> +        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),
> +        }.inspect_err(|_| {
> +            self.inner.state.store(PMContextState::Disabled, ordering::Release);
> +        })?;
> +
> +        Request::runtime_enable(self.inner.dev);
> +        Ok(())
> +    }

If `match state` fails (for instance if `mark_active()` or `mark_suspended()`
returns an error), `Self::apply_config()` has already run and its side effects
(such as `pm_runtime_irq_safe()` incrementing the parent's usage count or
`pm_runtime_no_callbacks()`) are not undone. Could `mark_active()` /
`mark_suspended()` be called before `apply_config()`?

> +    /// 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.contains(ModeFlag::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.contains_any(ModeFlag::Async | ModeFlag::Nowait) {
> +            return Err(EINVAL);
> +        }
> +        let _scope = self.get(profile)?;
> +        f()
> +    }

Should `with_resume()` also reject `ModeFlag::Nowait` like `with_get()` does?

> +    fn apply_config(dev: &device::Device<device::Bound>, opts: &[PMConfig]) {
> +        #[cfg(not(CONFIG_PM))]
> +        let _ = opts;
> +        let _ = dev;

`let _ = dev;` is outside `#[cfg(not(CONFIG_PM))]` here (though as noted
above, using C helpers for `!CONFIG_PM` removes the need for `#[cfg]` here
entirely).

> +#[repr(C)]
> +#[pin_data]
> +struct RegistrationData<'a, T: PMOps> {
> +    #[pin]
> +    data: PMPayload<T::RuntimePayloadType>,
> +    _marker: PhantomData<&'a mut T>,
> +}

Does `RegistrationData` need `#[pin_data]`? `PMPayload` moves the payload in
and out by value and doesn't use structural pinning, so a plain `KBox::new()`
should suffice.

> +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 `payload` is stored as a Registration data
> +    /// and is used to service PM transitions.
> +    ///
> +    /// The device must use the callback represented by `ops`, generated
> +    /// for the same bus adapter and driver pair `(D, T)`.
> +    pub fn new(
> +        dev: &'a device::Device<device::Core<'_>>,
> +        ops: &'static DevPMOps<T>,
> +        profiles: Option<KVec<PMProfile>>,
> +        configs: Option<KVec<PMConfig>>,
> +        payload: T::RuntimePayloadType,
> +    ) -> Result<Self> {
...
> +        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 },
> +        })
> +    }

What happens if a driver calls `core::mem::forget(pm_reg)` (which is safe in
Rust)?

If `Registration::drop` never runs when the driver unbinds:
1. Runtime PM remains enabled on the device (and `pm_runtime_reinit()` in
   `device_unbind_cleanup()` is a no-op while runtime PM is enabled).
2. `rust_private` remains non-null, pointing to `RegistrationData<'a, T>`
   after `'a` has ended and after the device has unbound from `T`.

Other registrations tied to `'bound` (such as `drm::Registration::new()` and
`irq::Registration::new()`) make `Registration::new()` an `unsafe fn` with the
safety requirement that the caller must not `mem::forget()` the returned
`Registration` or otherwise prevent its `Drop` implementation from running
before `'a` ends. Doing the same here would also allow `RuntimePayloadType<'a>`
to be non-`'static`, so drivers can hold `'bound`-scoped resources like
`IoMem<'bound>` in the runtime PM payload.

Also, there are a few leftover `#[allow(unused)]` attributes in this file (on
`mark_active`, `mark_suspended`, `runtime_resume_callback`,
`runtime_suspend_callback`, `DevPMOps`, and `PayloadGuard`) that can be
removed.

Alice

  reply	other threads:[~2026-10-16 12:12 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-05 13:37 [PATCH v4 0/3] Rust: " Beata Michalska
2026-10-05 13:37 ` [PATCH v4 1/3] rust: " Beata Michalska
2026-10-16 12:12   ` Alice Ryhl [this message]
2026-10-05 13:37 ` [PATCH v4 2/3] rust: platform: wire runtime PM callbacks Beata Michalska
2026-10-16 12:12   ` Alice Ryhl
2026-10-05 13:37 ` [PATCH v4 3/3 DO NOT MERGE] drm/tyr: enable runtime PM 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=20261016121230.3724323-1-aliceryhl@google.com \
    --to=aliceryhl@google.com \
    --cc=a.hindborg@kernel.org \
    --cc=acourbot@nvidia.com \
    --cc=beata.michalska@arm.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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®