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.
prev 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®