mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: "Danilo Krummrich" <dakr@kernel.org>
To: "Beata Michalska" <beata.michalska@arm.com>
Cc: <ojeda@kernel.org>, <gregkh@linuxfoundation.org>,
	<rafael@kernel.org>, <aliceryhl@google.com>, <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 v5 1/3] rust: add runtime PM support
Date: Thu, 05 Nov 2026 17:47:28 +0100	[thread overview]
Message-ID: <DMNFI31C8X12.3L2J0GKKNJN81@kernel.org> (raw)
In-Reply-To: <20261026120523.526942-2-beata.michalska@arm.com>

On Mon Oct 26, 2026 at 1:05 PM CET, Beata Michalska wrote:
>  drivers/base/base.h             |    4 +
>  rust/bindings/bindings_helper.h |    1 +
>  rust/helpers/helpers.c          |    1 +
>  rust/helpers/pm_runtime.c       |   89 +++
>  rust/kernel/lib.rs              |    1 +
>  rust/kernel/pm.rs               | 1106 +++++++++++++++++++++++++++++++

I think this fits better under rust/kernel/device/pm.rs. This would also avoid
naming like DevPMOps, which can then just be device::PMOps.

We should also add the file to POWER MANAGEMENT CORE in MAINTAINERS.

> diff --git a/drivers/base/base.h b/drivers/base/base.h
> index a5b7abc10ff0..e79108bc754a 100644
> --- a/drivers/base/base.h
> +++ b/drivers/base/base.h
> @@ -103,6 +103,7 @@ struct driver_private {
>   *			   dev_err_probe() for later retrieval via debugfs
>   * @device: pointer back to the struct device that this structure is
>   *	    associated with.
> + * @rust_private: private data used exclusively by the Rust driver core
>   * @dead: This device is currently either in the process of or has been
>   *	  removed from the system. Any asynchronous events scheduled for this
>   *	  device should exit without taking any action.
> @@ -119,6 +120,9 @@ struct device_private {
>  	const struct device_driver *async_driver;
>  	char *deferred_probe_reason;
>  	struct device *device;
> +#ifdef CONFIG_RUST
> +	void *rust_private;

Let's call this pointer pm_registration_data_rust. Or even just add

	struct {
		void *pm_registration_data;
	} rust;
	
> +use core::{cell::UnsafeCell, marker::PhantomData, mem::ManuallyDrop};

NIT: Formatting.

> +impl From<Mode> for core::ffi::c_int {
> +    #[inline]
> +    fn from(mode: Mode) -> core::ffi::c_int {
> +        mode.0 as core::ffi::c_int
> +    }
> +}

You can just use c_int, as it comes from the prelude. Also note that the prelude
takes it from ::ffi, rather than ::core.

> +    /// Explicitly release the scope
> +    /// This should be used in favor of regular drop
> +    /// when error handling is required.

NIT:

	// 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.

I'd rather say a few words about what those helpers are. The fact they require a
&Device<Bound> is rather obvious from the signature; if at all I'd rather
mention why that is.

> +struct Request;
> +
> +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.

This reads like a requirement, but it rather is the justifiation for this
requirement coming from pm_runtime_active().

> +        // The `Device<Bound>` reference provides that guarantee.

Plus that we are in a scope where the device is guaranteed that the device
remains bound to the driver, which is a requirement as well.

> +/// 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.
> +#[cfg(CONFIG_PM)]
> +fn runtime_pm_callback<T, F>(dev: *mut bindings::device, cb: F) -> Result

This function has to be unsafe, given that it takes a raw pointer.

> +where
> +    T: PMOps,
> +    F: FnOnce(
> +        &<T as PMOps>::DeviceType,
> +        <T as PMOps>::RuntimePayloadType,
> +    ) -> PMCallbackResult<<T as PMOps>::RuntimePayloadType>,
> +{
> +    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) };

Please prefer Device::from_raw() plus Device::as_bound().

> +
> +    // 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);
> +    }

How can we ever reach this?

> +/// Typed reference to a driver's runtime PM ops.
> +///
> +/// Instances of this type are generated from static [`DevPMOps`]
> +/// and are used by bus registration code to install the matching
> +/// C `dev_pm_ops` pointer into the driver object.
> +/// The `A` parameter identifies the bus adapter that is allowed
> +/// to install the table.
> +pub struct DevPMOpsRef<'a, A> {
> +    #[cfg_attr(not(CONFIG_PM), allow(unused))]
> +    ops: &'a bindings::dev_pm_ops,
> +    _p: PhantomData<fn() -> *const A>,
> +}

I'd probably rename this to pm::AdapterOpsRef<'a, A> and add a type alias:

	pub type OpsRef<T> = AdapterOpsRef<'static, <T as PMOps>::Adapter>;

so it looks cleaner in drivers:

	 const DEV_PM_OPS: Option<device::pm::OpsRef<Self>> = Some(TYR_PM_OPS.pm_ops());

(I'd probably also rename DevPMOps to device::pm::Ops, as I think that reads
cleaner.)

> +/// Runtime PM callbacks implemented by a driver.
> +///
> +/// Defines the [`PMOps`] trait and its corresponding [`bindings::dev_pm_ops`].
> +///
> +/// The Adapter associated type identifies the bus adapter that installs
> +/// the generated PM ops.
> +/// Implementations should normally be enabled through a
> +/// bus-specific constructor for [`DevPMOps`], which constrains
> +/// [`Self::DeviceType`] to the correct bound device type for that bus.
> +///
> +/// Each 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.

This kind of implementation detail doesn't belong in this doc-comment.

> +#[vtable]
> +pub trait PMOps: Sized {
> +    /// The bus adapter that installs the generated PM ops.
> +    type Adapter: driver::DriverLayout + 'static;
> +
> +    /// Type of the bound bus device passed to runtime PM callbacks.
> +    type DeviceType: AsBusDevice<device::Bound>;
> +
> +    /// Type of the payload moved through runtime PM transitions.
> +    type RuntimePayloadType: Send + 'static;

I got back to this type after writing the below, because at first I thought this
is purely the data type that is cycled through suspend() and resume().

However, reading further this is currently the Registration's private data. This
won't work. The Registration's private data must not require 'static as we need
device resources in this data.

So, there should be at least two separate types:

  (1) The private data of pm::Registration.

  (2) An object that is created and returned by suspend() and passed by value to
      resume(), so it can store e.g. reverse guard types (see below).

This should also get us rid of the PMPayload magic.

> +
> +    /// Runtime resume callback.

It would be good to expand this doc-comment explaining the semantics of
RuntimePayloadType.

> +    fn runtime_resume<'a>(
> +        _dev: &'a Self::DeviceType,
> +        _payload: Self::RuntimePayloadType,
> +    ) -> PMCallbackResult<Self::RuntimePayloadType> {
> +        build_error!(VTABLE_DEFAULT_ERROR)
> +    }
> +
> +    /// Runtime suspend callback.
> +    fn runtime_suspend<'a>(
> +        _dev: &'a Self::DeviceType,
> +        _payload: Self::RuntimePayloadType,
> +    ) -> PMCallbackResult<Self::RuntimePayloadType> {
> +        build_error!(VTABLE_DEFAULT_ERROR)
> +    }

I think for Tyr it so far is sufficient to have a single RuntimePayloadType that
keeps cycling through the callbacks, but there will be use-cases where the types
differ.

For instance, for a PCI driver you will have DeviceDisableGuard stored in
suspend() and then you'll want to drop it in resume() so the device gets enabled
again.

We should also avoid requiring 'static, as reverse guards will borrow the
regular guard, e.g. pci::DeviceDisableGuard will borrow from
pci::DeviceEnableGuard.

For runtime_resume() I think of something like:

	fn runtime_resume<'a>(
	    dev: &Self::DeviceType,
	    suspended: Self::SuspendData<'a>,
	) -> Result<(), (Self::SuspendData<'a>, Error)>;


> +/// Runtime PM context tied to a device.
> +pub struct PMContext<'a, T: PMOps> {
> +    // Preferably, PMContext could be shared via borrowed reference over
> +    // a pm Registration's lifetime but that bears complications on its own
> +    // when the context needs to be shared across different Registration types.
> +    inner: Arc<PMContextInner<'a, T>>,

Since we now have self-referencial pin-init, can we please avoid this Arc?

> +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)`.
> +    ///
> +    /// # Safety
> +    ///
> +    /// The returned [`Registration`] object cannot be mem::forgotten,
> +    /// or it's [`Drop`] implementation cannot be skipped in any way.
> +    ///
> +    pub unsafe fn new(
> +        dev: &'a device::Device<device::Core<'_>>,
> +        ops: &'static DevPMOps<T>,
> +        profiles: Option<KVec<PMProfile>>,
> +        configs: Option<KVec<PMConfig>>,
> +        payload: T::RuntimePayloadType,

This should be impl PinInit<T::PMRegistrationData<'a>, E> + 'a.

> +    ) -> Result<Self> {

This should be impl PinInit<Self, Error> + 'a.

> +        // SAFETY: For the duration of this call, `dev` is a valid `Device<Core>`,
> +        // and so is its raw `struct device` pointer.
> +        unsafe {

Please use separate unsafe blocks per unsafe operation.

> +            let drv = (*dev.as_raw()).driver;
> +            if drv.is_null() || (*drv).pm != ops.as_raw() {

drv.is_null() can't happen as we require Device<Core>.

> +                return Err(EINVAL);
> +            }
> +        }
> +
> +        let payload = KBox::new(
> +            RegistrationData::<T> {
> +                data: PMPayload {
> +                    in_flight: AtomicFlag::new(false),
> +                    inner: UnsafeCell::new(ManuallyDrop::new(payload)),
> +                },
> +                _marker: PhantomData,
> +            },
> +            GFP_KERNEL,
> +        )?;
> +
> +        let inner_ctx = Arc::new(
> +            PMContextInner {
> +                dev,
> +                state: Atomic::new(PMContextState::Disabled),
> +                profiles: profiles.unwrap_or_default(),
> +                configs: configs.unwrap_or_default(),
> +                _marker: PhantomData,
> +            },
> +            GFP_KERNEL,
> +        )?;
> +
> +        // SAFETY: For the duration of this call, `dev` is a valid `Device<Core>`,
> +        // and so is its raw `struct device` pointer.
> +        // The payload allocation is converted into a foreign pointer
> +        // and owned by this `Registration` until `Drop` clears
> +        // `rust_private` and reconstructs the `KBox`.
> +        unsafe {

Same as above.

> +            let ptr = (*(*dev.as_raw()).p).rust_private;

This is accessed a couple of time, let's add a helper for this.

> +            if !ptr.is_null() {
> +                return Err(EBUSY);
> +            }
> +            (*(*dev.as_raw()).p).rust_private = payload.into_foreign();
> +        }
> +
> +        Ok(Self {
> +            ctx: PMContext { inner: inner_ctx },
> +        })
> +    }

  parent reply	other threads:[~2026-11-05 16:47 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-26 12:05 Request::put_noidleFrom 43bd77ceaa3d6a9f61ae8074be13824a94fa76c1 Mon Sep 17 00:00:00 2001 Beata Michalska
2026-10-26 12:05 ` [PATCH v5 1/3] rust: add runtime PM support Beata Michalska
2026-11-05  5:16   ` Onur Özkan
2026-11-05 16:47   ` Danilo Krummrich [this message]
2026-10-26 12:05 ` [PATCH v5 2/3] rust: platform: wire runtime PM callbacks Beata Michalska
2026-10-26 12:05 ` [PATCH v5 3/3 DO NOT MERGE] drm/tyr: enable runtime PM Beata Michalska
2026-10-26 12:15 ` Request::put_noidleFrom 43bd77ceaa3d6a9f61ae8074be13824a94fa76c1 Mon Sep 17 00:00:00 2001 Greg KH
2026-10-26 12:28   ` Beata Michalska
2026-10-26 12:25 ` [PATCH v5 0/3] Rust: add runtime PM support 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=DMNFI31C8X12.3L2J0GKKNJN81@kernel.org \
    --to=dakr@kernel.org \
    --cc=a.hindborg@kernel.org \
    --cc=acourbot@nvidia.com \
    --cc=aliceryhl@google.com \
    --cc=beata.michalska@arm.com \
    --cc=bjorn3_gh@protonmail.com \
    --cc=boqun@kernel.org \
    --cc=boris.brezillon@collabora.com \
    --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®