mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: "Alexandre Courbot" <acourbot@nvidia.com>
To: "Daniel Almeida" <daniel.almeida@collabora.com>
Cc: "Miguel Ojeda" <ojeda@kernel.org>,
	"Alex Gaynor" <alex.gaynor@gmail.com>,
	"Boqun Feng" <boqun.feng@gmail.com>,
	"Gary Guo" <gary@garyguo.net>,
	"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
	"Benno Lossin" <benno.lossin@proton.me>,
	"Andreas Hindborg" <a.hindborg@kernel.org>,
	"Alice Ryhl" <aliceryhl@google.com>,
	"Trevor Gross" <tmgross@umich.edu>,
	"Danilo Krummrich" <dakr@kernel.org>,
	"Boris Brezillon" <boris.brezillon@collabora.com>,
	"Sebastian Reichel" <sebastian.reichel@collabora.com>,
	"Liam Girdwood" <lgirdwood@gmail.com>,
	"Mark Brown" <broonie@kernel.org>,
	linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org
Subject: Re: [PATCH v3] rust: regulator: add a bare minimum regulator abstraction
Date: Mon, 19 May 2025 23:20:06 +0900	[thread overview]
Message-ID: <DA07BSJPBP55.332DLW59WGDFA@nvidia.com> (raw)
In-Reply-To: <8517D6F0-C1A2-4E38-8E62-57DCCD5E58D4@collabora.com>

Hi Daniel,

On Mon May 19, 2025 at 7:52 PM JST, Daniel Almeida wrote:
> Hi Alex,
>
> I still don’t understand your use case 100% :/

I should clarify that I don't have a use-case for this (yet), nor do I
foresee that Nova will need to use regulators ; it's just a drive-by
review as I am a bit familiar with the regulator consumer API thanks to
past work.

>
>> 
>> I just mean the cases where users will want to enable and disable the
>> regulator more frequently than just enabling it at probe time.
>
> This is already possible through kernel::types::Either. 
>
> i.e.: the current design - or the proposed typestate one - can already switch
> back and forth between Regulator and EnabledRegulator. Using Either makes it
> just work, because you can change the variant at runtime without hassle. This
> lets you consume self in an ergonomic way.
>
> By the way, the reason I'm pushing back slightly here is because you seem
> (IIUC) to be trying to reintroduce the pattern we had to move away from in v1.
>
> i.e.: we explicitly had to move away from trying to match enables and disables
> in Rust, because it was hard to get this right.
>
> The current design is a simplification that apparently works, because at best
> you have +1 on the count and that is encoded in the type itself, so there is
> nothing to actually "track" or "balance" within a given instance. Multiple
> calls to _get() or _enable() on the same instance are simply forbidden.
>
> Can you add some pseudocode that shows how this doesn't work (or is otherwise
> unergonomic) in Nova? I think it will make your point clearer.

So let's say you have this in your device struct:

  regulator: Either<Regulator, EnabledRegulator>,

And you want to set the voltage on it. Now you need to do:

  match &self.regulator {
    Either::Left(regulator) => regulator.set_voltage(...)?,
    Either::Right(regulator) => regulator.set_voltage(...)?,
  }

And you need to do that for every single method that is available on
both types. It's unergonomic and cumbersome.

Conversely, if you have:

  regulator: Regulator<Switch>,

then it's simply a matter of doing

  self.regulator.set_voltage(...)?;

It's more code. More code means more bugs. :) And it's going to be quite
a common pattern, so I think we should make it convenient.

Also it looks like `Either` is going away, which means each user will
now have to implement their own enum type.

>
>> 
>>> 
>>>> 
>>>> It has been proposed earlier to use a typestate, and this would indeed
>>>> provide several benefits, the first one being the ability to have shared
>>>> impl blocks (and shared documentation) between the enabled and disabled
>>>> states for methods like set/get_voltage().
>>>> 
>>>> But the key benefit I see is that it could also address the
>>>> aforementioned dynamic management problem through the introduction of a
>>>> third state.
>>>> 
>>>> Alongside the `Enabled` and `Disabled` states, there would be a third
>>>> state (`Dynamic`?) in which the regulator could either be enabled or
>>>> disabled. This `Dynamic` state is the only one providing `enable` and
>>>> `disable` methods (as well as `is_enabled`) to change its operational
>>>> state without affecting its type.
>>> 
>>> Dynamic is just "Regulator" in the current version of this patch. There is no
>>> "Disabled" because there is no guarantee that someone else won't enable the
>>> regulator, trivially breaking this invariant at any moment.
>> 
>> There is a core difference, which is that in your version of
>> `Regulator`, `enable` takes ownership of `self` and returns a different
>> type, whereas `Dynamic` would take `&mut self` and change its internal
>> state, like the C API does.
>
> I see now, but consuming self is something we're trying after considering the
> &mut self approach, which did not work very well in v1.

Just took a look at the comments on v1 since I arrived late in the
discussion.

I think both approaches have scenarios where they apply. It's a bit like
Rust's borrow checker: whenever possible, you want the rules to be
enforced at compile-time, but sometimes the access patterns become too
complex so you resort to RefCell to enforce these rules dynamically.

So please don't get me wrong, I think having a dedicated type for an
enabled regulator is great design, and its use should be preferred
whenever possible ; most drivers just want to get and enable a regulator
at probe time, and `Regulator::<Enabled>::get()` would be perfect for
that.

But if we only provide that then we also put restrictions on what the C
API allows, and users with more complex use-cases will pay the price by
rewriting code that already exists (a bit more on that later).

The following comment was made on v1:

> It's possible there's a need to split simple and complex consumer APIs
> in Rust?

And it sounds sensible to me.
>
>> 
>>> 
>>> The only thing we can guarantee is "Enabled", through our own call to
>>> "regulator_enable()".
>>> 
>>> In fact, for the typestate solution, I was thinking about "UnknownState" and
>>> "Enabled", or any nomenclature along these lines.
>>> 
>>>> 
>>>> All three states then implement `set_voltage` and `get_voltage` through
>>>> a common impl block, that could be extended with other methods from the
>>>> C API that are independent of the state, as needed.
>>>> 
>>>> To handle typestate transitions:
>>>> 
>>>> - The `Disabled` and `Dynamic` states provide a `try_into_enabled()`
>>>> method to transition the regulator to the `Enabled` state.
>>> 
>>> Why not “enable()” as we currently have?
>> 
>> `enable()` to me sounds like a method that mutates `self` in-place,
>> whereas your version consumes it and turns it into a different type.
>> Such methods are typically named `into_*`, or `try_into_*` when they can
>> fail.
>> 
>> Actually, this could even be a `TryInto` implementation, but in this
>> particular case having methods with the target state in their names may
>> result in clearer code and allow the reader to model the transition
>> graph more easily.
>> 
>>> 
>>> If we go with the "Dynamic" nomenclature, and we agree that there's no
>>> "Disabled",  then we can implement "pub fn enable(self) -> Regulator<Enabled>",
>>> for "Dynamic", which is what we currently have, but with other names.
>> 
>> Not if we want to provide the behavior of the C consumer API, which
>> requires multiple calls to `regulator_enable()` to be matched by an equal
>> number of calls to `regulator_disable()`, which could be useful to some
>> drivers (lest they reimplement their own counter).
>
> This is explicitly not supported, because (given the current code) why should it be?
>
> If you want a given regulator to be enabled, just make sure you have
> Regulator<Enabled> in your kernel::types::Either container.
>
> You don't need a counter either: Regulator<Enabled> has a count of one, and
> when that goes out of scope, it's decremented.

That works very well for simpler scenarios, but won't cover everything
optimally.

A common pattern is a driver (sensors come to mind, and maybe codecs)
that acquire a regulator at probe time, and enable it every time
user-space opens the device (and, conversely, disable it as the user
session closes). The driver wants the regulator to be enabled if there
is at least one user-space session opened, and only disable it when all
user-space sessions are closed. With the proposed scheme, a Rust driver
would have to keep track of the opened sessions count and disable the
regulator itself when it reaches zero - effectively duplicating what the
C API would happily do for it, with the potential of bugs being
introduced in these extra lines of code.

There was also a discussion stating that it is safer (for the hardware)
to leave a regulator on than it is to switch it off when, due to a bug,
it is released while its enable count is not zero, and I think that's a
behavior we should allow if the user expresses a need for it. Safety is
not only memory safety :) (and in this case, it is not affected anyway).

>> - `Regulator<Disabled>` guarantees that the regulator is not enabled on
>>  the consumer side. It could be useful to have for drivers that use
>>  distinct types to store their state depending on their power status:
>>  the powered-on type would store a `Regulator<Enabled>`, the
>>  powered-off type a `Regulator<Disabled>`. That way you cannot even
>>  write code transitioning between the states if you omit the regulator
>>  - which I think is really neat.
>> 
>
> I thought we had agreed that there is no “Disabled”, even in C?

The relevant function is called `regulator_disable()`, and it may or may
not actually disable the regulator, but the consumer behaves as if it
were. I think we should keep the same nomenclature for consistency.

>> These two should cover a large percentage of consumer needs, but for
>> those that need more fine-grained control we should also have one or two
>> policies that follow the C API a bit closer, e.g.:
>> 
>> - `Regulator<Controlled>` (or just `Regulator`): user is granted an API
>>  very close to the C one, and is responsible for balancing calls to
>>  `enable()` and `disable()`. Regulator remains in the state it was in
>>  when dropped.
>> 
>> - `Regulator<Switch>`: calls to `enable()` and `disable()` do not need
>>  to be balanced, and the regulator always transitions to one state if
>>  it was in the other. Regulator gets automatically disabled on drop.
>>  This provides a simpler, safer alternative to `Controlled`.
>> 
>> Note that I am not advocating for including these two policies
>> specifically, these are just examples. My main point is that we should
>> also provide a way to change the regulator's enabled state without
>> requiring a change of type ; which policy(es) to adopt will depend on
>> which restrictions we conclude are adequate to place on the C API, if
>> any.
>
> Can you expand a bit on the issues of changing types? Again, some pseudocode
> will probably help a lot :)

See my example using `Either` above. :) I hope to be forgiven for the
user-space sessions example as I believe it is easy to model.


      parent reply	other threads:[~2025-05-19 14:20 UTC|newest]

Thread overview: 52+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-05-13 15:44 Daniel Almeida
2025-05-13 20:01 ` Benno Lossin
2025-05-14  7:46   ` Mark Brown
2025-05-14  9:37     ` Benno Lossin
2025-05-14 10:16       ` Mark Brown
2025-05-14 10:31         ` Benno Lossin
2025-05-14 11:50           ` Mark Brown
2025-05-14 12:23             ` Benno Lossin
2025-05-14 12:48               ` Mark Brown
2025-05-14 14:06                 ` Benno Lossin
2025-05-14 13:01   ` Daniel Almeida
2025-05-14 13:57     ` Benno Lossin
2025-05-14 14:40       ` Daniel Almeida
2025-05-14 15:38         ` Benno Lossin
2025-05-14 15:50           ` Mark Brown
2025-05-14 16:05             ` Benno Lossin
2025-05-14 16:08               ` Mark Brown
2025-05-14 16:19               ` Daniel Almeida
2025-05-14 17:41                 ` Benno Lossin
2025-05-14 16:10             ` Daniel Almeida
2025-05-15  8:19               ` Mark Brown
2025-05-14 15:48         ` Mark Brown
2025-05-14  8:27 ` Mark Brown
2025-05-18  2:28 ` Alexandre Courbot
2025-05-18  7:19   ` Benno Lossin
2025-05-18  8:14     ` Alexandre Courbot
2025-05-18  8:30       ` Alexandre Courbot
2025-05-18  9:57         ` Benno Lossin
2025-05-18 11:12           ` Alexandre Courbot
2025-05-18 14:05             ` Benno Lossin
2025-05-19  0:29               ` Alexandre Courbot
2025-05-18 12:20       ` Mark Brown
2025-05-18 12:51         ` Alexandre Courbot
2025-05-19  9:55           ` Mark Brown
2025-05-18 14:04         ` Benno Lossin
2025-05-19  9:56           ` Mark Brown
2025-05-19 11:25             ` Benno Lossin
2025-05-19 11:46               ` Mark Brown
2025-05-19 12:30                 ` Benno Lossin
2025-05-19 12:46                   ` Mark Brown
2025-05-18 12:17   ` Mark Brown
2025-05-18 12:49     ` Alexandre Courbot
2025-05-19  9:54       ` Mark Brown
2025-05-18 15:11   ` Daniel Almeida
2025-05-19  1:25     ` Alexandre Courbot
2025-05-19 10:52       ` Daniel Almeida
2025-05-19 11:01         ` Daniel Almeida
2025-05-19 11:54         ` Benno Lossin
2025-05-19 11:59           ` Miguel Ojeda
2025-05-19 14:43           ` Alexandre Courbot
2025-05-20 18:09             ` Benno Lossin
2025-05-19 14:20         ` Alexandre Courbot [this message]

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=DA07BSJPBP55.332DLW59WGDFA@nvidia.com \
    --to=acourbot@nvidia.com \
    --cc=a.hindborg@kernel.org \
    --cc=alex.gaynor@gmail.com \
    --cc=aliceryhl@google.com \
    --cc=benno.lossin@proton.me \
    --cc=bjorn3_gh@protonmail.com \
    --cc=boqun.feng@gmail.com \
    --cc=boris.brezillon@collabora.com \
    --cc=broonie@kernel.org \
    --cc=dakr@kernel.org \
    --cc=daniel.almeida@collabora.com \
    --cc=gary@garyguo.net \
    --cc=lgirdwood@gmail.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=ojeda@kernel.org \
    --cc=rust-for-linux@vger.kernel.org \
    --cc=sebastian.reichel@collabora.com \
    --cc=tmgross@umich.edu \
    /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®