* [PATCH 1/3] rust: gpio: add GPIO module with common definitions
2026-09-06 8:45 [PATCH 0/3] rust: Add basic GPIO consumer abstractions Kohei Ito
@ 2026-09-06 8:45 ` Kohei Ito
2026-09-06 9:56 ` Miguel Ojeda
2026-09-06 8:45 ` [PATCH 2/3] rust: gpio: Add basic consumer abstractions Kohei Ito
2026-09-06 8:45 ` [PATCH 3/3] sample: rust: Add GPIO consumer sample driver Kohei Ito
2 siblings, 1 reply; 11+ messages in thread
From: Kohei Ito @ 2026-09-06 8:45 UTC (permalink / raw)
To: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan
Cc: linux-kernel, rust-for-linux, linux-gpio, Kohei Ito
Add the top-level GPIO module with minimal common definitions. This
module is the basis for future Rust GPIO extensions.
Signed-off-by: Kohei Ito <koheiito.dev@gmail.com>
---
rust/bindings/bindings_helper.h | 1 +
rust/kernel/gpio.rs | 152 ++++++++++++++++++++++++++++++++++++++++
rust/kernel/lib.rs | 2 +
3 files changed, 155 insertions(+)
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 1124785e210b..98b048b36771 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -61,6 +61,7 @@
#include <linux/file.h>
#include <linux/firmware.h>
#include <linux/fs.h>
+#include <linux/gpio/defs.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/io-pgtable.h>
diff --git a/rust/kernel/gpio.rs b/rust/kernel/gpio.rs
new file mode 100644
index 000000000000..819efc8a0c05
--- /dev/null
+++ b/rust/kernel/gpio.rs
@@ -0,0 +1,152 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! GPIO abstractions.
+
+use crate::{
+ error::{
+ Error,
+ Result, //
+ },
+ fmt,
+ prelude::*, //
+};
+
+/// Describes GPIO direction.
+#[derive(Clone, Copy, PartialEq, Eq)]
+#[repr(u32)]
+pub enum LineDirection {
+ /// Represent the output direction.
+ Out = bindings::GPIO_LINE_DIRECTION_OUT,
+
+ /// Represent the input direction.
+ In = bindings::GPIO_LINE_DIRECTION_IN,
+}
+
+impl core::ops::Not for LineDirection {
+ type Output = Self;
+ fn not(self) -> Self::Output {
+ match self {
+ Self::Out => Self::In,
+ Self::In => Self::Out,
+ }
+ }
+}
+
+impl TryFrom<c_int> for LineDirection {
+ type Error = Error;
+ fn try_from(value: c_int) -> Result<Self> {
+ match value {
+ v if v == Self::Out as c_int => Ok(Self::Out),
+ v if v == Self::In as c_int => Ok(Self::In),
+ _ => Err(EINVAL),
+ }
+ }
+}
+
+impl fmt::Display for LineDirection {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::In => f.pad("In"),
+ Self::Out => f.pad("Out"),
+ }
+ }
+}
+
+/// Describes the logical GPIO level, i.e. taking the ACTIVE_LOW status into account.
+#[derive(Clone, Copy, PartialEq, Eq)]
+pub enum LogicalLineLevel {
+ /// Represent the logical inactive level.
+ Inactive,
+
+ /// Represent the logical active level.
+ Active,
+}
+
+impl core::ops::Not for LogicalLineLevel {
+ type Output = Self;
+ fn not(self) -> Self::Output {
+ match self {
+ Self::Inactive => Self::Active,
+ Self::Active => Self::Inactive,
+ }
+ }
+}
+
+impl LogicalLineLevel {
+ fn as_c_int(&self) -> c_int {
+ match self {
+ Self::Inactive => 0,
+ Self::Active => 1,
+ }
+ }
+}
+
+impl TryFrom<c_int> for LogicalLineLevel {
+ type Error = Error;
+ fn try_from(value: c_int) -> Result<Self> {
+ match value {
+ 0 => Ok(Self::Inactive),
+ 1 => Ok(Self::Active),
+ _ => Err(EINVAL),
+ }
+ }
+}
+
+impl fmt::Display for LogicalLineLevel {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Inactive => f.pad("Inactive"),
+ Self::Active => f.pad("Active"),
+ }
+ }
+}
+
+/// Describes the raw GPIO level, i.e. the value of its physical line without regard for its
+/// ACTIVE_LOW status.
+#[derive(Clone, Copy, PartialEq, Eq)]
+pub enum PhysicalLineLevel {
+ /// Represent the physical LOW level.
+ Low,
+
+ /// Represent the physical HIGH level.
+ High,
+}
+
+impl core::ops::Not for PhysicalLineLevel {
+ type Output = Self;
+ fn not(self) -> Self::Output {
+ match self {
+ Self::Low => Self::High,
+ Self::High => Self::Low,
+ }
+ }
+}
+
+impl PhysicalLineLevel {
+ fn as_c_int(&self) -> c_int {
+ match self {
+ Self::Low => 0,
+ Self::High => 1,
+ }
+ }
+}
+
+impl TryFrom<c_int> for PhysicalLineLevel {
+ type Error = Error;
+ fn try_from(value: c_int) -> Result<Self> {
+ match value {
+ 0 => Ok(Self::Low),
+ 1 => Ok(Self::High),
+ _ => Err(EINVAL),
+ }
+ }
+}
+
+impl fmt::Display for PhysicalLineLevel {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Low => f.pad("Low"),
+ Self::High => f.pad("High"),
+ }
+ }
+}
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 9512af7156df..6c96c1269249 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -73,6 +73,8 @@
pub mod firmware;
pub mod fmt;
pub mod fs;
+#[cfg(CONFIG_GPIOLIB)]
+pub mod gpio;
#[cfg(CONFIG_GPU_BUDDY = "y")]
pub mod gpu;
#[cfg(CONFIG_I2C = "y")]
--
2.50.1
^ permalink raw reply related [flat|nested] 11+ messages in thread* Re: [PATCH 1/3] rust: gpio: add GPIO module with common definitions
2026-09-06 8:45 ` [PATCH 1/3] rust: gpio: add GPIO module with common definitions Kohei Ito
@ 2026-09-06 9:56 ` Miguel Ojeda
2026-09-06 13:09 ` Gary Guo
0 siblings, 1 reply; 11+ messages in thread
From: Miguel Ojeda @ 2026-09-06 9:56 UTC (permalink / raw)
To: Kohei Ito, Linus Walleij, Bartosz Golaszewski
Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, linux-kernel, rust-for-linux,
linux-gpio
On Sun, Sep 6, 2026 at 10:46 AM Kohei Ito <koheiito.dev@gmail.com> wrote:
>
> Add the top-level GPIO module with minimal common definitions. This
> module is the basis for future Rust GPIO extensions.
Thanks & welcome!
I see you Cc'd linux-gpio, which is good, but the GPIO maintainers
should be Cc'd too, since they are the ones that will be deciding on
this -- doing it for you in this one so that they are in the loop.
By the way, it is nice to see GPIO again :) Now that some years have
passed, how does it compare to
https://lwn.net/Articles/863459/
?
Cheers,
Miguel
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH 1/3] rust: gpio: add GPIO module with common definitions
2026-09-06 9:56 ` Miguel Ojeda
@ 2026-09-06 13:09 ` Gary Guo
2026-09-06 15:53 ` Kohei Ito
0 siblings, 1 reply; 11+ messages in thread
From: Gary Guo @ 2026-09-06 13:09 UTC (permalink / raw)
To: Miguel Ojeda, Kohei Ito, Linus Walleij, Bartosz Golaszewski
Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, linux-kernel, rust-for-linux,
linux-gpio
On Sun Sep 6, 2026 at 10:56 AM BST, Miguel Ojeda wrote:
> On Sun, Sep 6, 2026 at 10:46 AM Kohei Ito <koheiito.dev@gmail.com> wrote:
>>
>> Add the top-level GPIO module with minimal common definitions. This
>> module is the basis for future Rust GPIO extensions.
>
> Thanks & welcome!
>
> I see you Cc'd linux-gpio, which is good, but the GPIO maintainers
> should be Cc'd too, since they are the ones that will be deciding on
> this -- doing it for you in this one so that they are in the loop.
>
> By the way, it is nice to see GPIO again :) Now that some years have
> passed, how does it compare to
>
> https://lwn.net/Articles/863459/
>
> ?
This is possibily also a show case for all our I/O projection and register macro
work :)
Best,
Gary
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH 1/3] rust: gpio: add GPIO module with common definitions
2026-09-06 13:09 ` Gary Guo
@ 2026-09-06 15:53 ` Kohei Ito
0 siblings, 0 replies; 11+ messages in thread
From: Kohei Ito @ 2026-09-06 15:53 UTC (permalink / raw)
To: Miguel Ojeda, Gary Guo
Cc: Miguel Ojeda, Linus Walleij, Bartosz Golaszewski, Miguel Ojeda,
Boqun Feng, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Alice Ryhl, Trevor Gross, Danilo Krummrich, Daniel Almeida,
Tamir Duberstein, Alexandre Courbot, Onur Özkan,
linux-kernel, rust-for-linux, linux-gpio
Hi, Miguel and Gary,
On Sun, Sep 06, 2026 at 02:09:30PM +0100, Gary Guo wrote:
> On Sun Sep 6, 2026 at 10:56 AM BST, Miguel Ojeda wrote:
> > On Sun, Sep 6, 2026 at 10:46 AM Kohei Ito <koheiito.dev@gmail.com> wrote:
> >>
> >> Add the top-level GPIO module with minimal common definitions. This
> >> module is the basis for future Rust GPIO extensions.
> >
> > Thanks & welcome!
> >
> > I see you Cc'd linux-gpio, which is good, but the GPIO maintainers
> > should be Cc'd too, since they are the ones that will be deciding on
> > this -- doing it for you in this one so that they are in the loop.
Thank you for adding the GPIO maintainers. I'll make sure to Cc them in
future revisions.
> > By the way, it is nice to see GPIO again :) Now that some years have
> > passed, how does it compare to
> >
> > https://lwn.net/Articles/863459/
> >
> > ?
>
> This is possibily also a show case for all our I/O projection and register macro
> work :)
Thank you for the pointer. I was not aware of the past GPIO abstractions
[1].
As I understand it, the past GPIO work focused on the GPIO driver APIs
(`include/linux/gpio/driver.h`), i.e., for the drivers that provide GPIO
functionality, mostly present in `drivers/gpio`.
On the other hand, this series implements GPIO consumer APIs
(`include/linux/gpio/consumer.h`), i.e., for the drivers that use GPIOs,
such as `drivers/leds/leds-gpio.c` and `drivers/reset/reset-gpio.c`.
I assume that the GPIO driver abstractions will be implemented later in
`rust/kernel/gpio/driver.rs`.
[1] https://github.com/Rust-for-Linux/linux/blob/rust/rust/kernel/gpio.rs
(Current hash: 18b7491480025420896e0c8b73c98475c3806c6f)
Best regards,
Kohei Ito
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 2/3] rust: gpio: Add basic consumer abstractions
2026-09-06 8:45 [PATCH 0/3] rust: Add basic GPIO consumer abstractions Kohei Ito
2026-09-06 8:45 ` [PATCH 1/3] rust: gpio: add GPIO module with common definitions Kohei Ito
@ 2026-09-06 8:45 ` Kohei Ito
2026-09-10 7:38 ` Bartosz Golaszewski
2026-09-13 8:58 ` Alexandre Courbot
2026-09-06 8:45 ` [PATCH 3/3] sample: rust: Add GPIO consumer sample driver Kohei Ito
2 siblings, 2 replies; 11+ messages in thread
From: Kohei Ito @ 2026-09-06 8:45 UTC (permalink / raw)
To: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan
Cc: linux-kernel, rust-for-linux, linux-gpio, Kohei Ito
Add basic abstractions for GPIO consumer APIs.
Due to a bindgen issue that may generate the wrong type for enum types,
`gpio/consumer.h` is included at the top of `bindings_helper.h` as a
temporary workaround. Once the issue is resolved, it can be moved back
to its proper alphabetical position.
Signed-off-by: Kohei Ito <koheiito.dev@gmail.com>
---
rust/bindings/bindings_helper.h | 1 +
rust/kernel/gpio.rs | 2 +
rust/kernel/gpio/consumer.rs | 437 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 440 insertions(+)
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 98b048b36771..30985c102c70 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -26,6 +26,7 @@
* This workaround may not be possible in some cases, depending on how the C
* headers are set up.
*/
+#include <linux/gpio/consumer.h>
#include <linux/hrtimer_types.h>
#include <linux/acpi.h>
diff --git a/rust/kernel/gpio.rs b/rust/kernel/gpio.rs
index 819efc8a0c05..40b6c64e8f5e 100644
--- a/rust/kernel/gpio.rs
+++ b/rust/kernel/gpio.rs
@@ -11,6 +11,8 @@
prelude::*, //
};
+pub mod consumer;
+
/// Describes GPIO direction.
#[derive(Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
diff --git a/rust/kernel/gpio/consumer.rs b/rust/kernel/gpio/consumer.rs
new file mode 100644
index 000000000000..f81c7381c075
--- /dev/null
+++ b/rust/kernel/gpio/consumer.rs
@@ -0,0 +1,437 @@
+// SPDX-License-Identifier: GPL-2.0
+// This file is based on rust/kernel/clk.rs.
+
+//! GPIO consumer abstractions.
+//!
+//! C header: [`include/linux/gpio/consumer.h`](srctree/include/linux/gpio/consumer.h)
+//!
+//! Reference: <https://docs.kernel.org/driver-api/gpio/consumer.html>
+
+use crate::{
+ device::Device,
+ error::{
+ from_err_ptr,
+ to_result,
+ Error,
+ Result, //
+ },
+ gpio::{
+ LineDirection,
+ LogicalLineLevel,
+ PhysicalLineLevel, //
+ },
+ prelude::*, //
+};
+
+use core::{ops::Deref, ptr};
+
+/// The GPIO descriptor flags to configure its direction and output value.
+///
+/// Rust abstraction for the C [`enum gpiod_flags`].
+///
+/// They can be combined with the operators `|`, and `&`.
+///
+/// Values can be used from the associated constants such as
+/// [`Flags::GPIOD_ASIS`].
+#[derive(Clone, Copy, PartialEq)]
+pub struct GpiodFlags(bindings::gpiod_flags);
+
+impl GpiodFlags {
+ /// Don't change anything.
+ pub const ASIS: Self = Self::new(bindings::gpiod_flags_GPIOD_ASIS);
+
+ /// Set lines to input mode.
+ pub const IN: Self = Self::new(bindings::gpiod_flags_GPIOD_IN);
+
+ /// Set lines to output and drive them low.
+ pub const OUT_LOW: Self = Self::new(bindings::gpiod_flags_GPIOD_OUT_LOW);
+
+ /// Set lines to output and drive them high.
+ pub const OUT_HIGH: Self = Self::new(bindings::gpiod_flags_GPIOD_OUT_HIGH);
+
+ /// Set lines to open-drain output and drive them low.
+ pub const OUT_LOW_OPEN_DRAIN: Self = Self::new(bindings::gpiod_flags_GPIOD_OUT_LOW_OPEN_DRAIN);
+
+ /// Set lines to open-drain output and drive them high.
+ pub const OUT_HIGH_OPEN_DRAIN: Self =
+ Self::new(bindings::gpiod_flags_GPIOD_OUT_HIGH_OPEN_DRAIN);
+
+ fn into_inner(self) -> bindings::gpiod_flags {
+ self.0
+ }
+
+ // Always inline to optimize out error path of `build_assert`.
+ #[inline(always)]
+ const fn new(value: bindings::gpiod_flags) -> Self {
+ build_assert!(value as u64 <= bindings::gpiod_flags::MAX as u64);
+ Self(value)
+ }
+}
+
+/// A reference-counted gpio descriptor.
+///
+/// Rust abstraction for the C [`struct gpio_desc`].
+///
+/// # Invariants
+///
+/// A [`GpioDesc`] instance holds either a pointer to a valid [`struct gpio_desc`] created by the C
+/// portion of the kernel or a `NULL` pointer.
+///
+/// Instances of this type are reference-counted. Calling [`GpioDesc::get`] ensures that the
+/// allocation remains valid for the lifetime of the [`GpioDesc`].
+///
+/// # Examples
+///
+/// The following example demonstrates how to obtain a GPIO line for a device.
+///
+/// ```
+/// use crate::{
+/// device::Device,
+/// error::Result,
+/// gpio::{
+/// consumer::{
+/// GpioDesc,
+/// GpiodFlags, //
+/// },
+/// LogicalLineLevel, //
+/// }, //
+/// };
+///
+/// fn examine_gpio(dev: &Device) -> Result {
+/// let gpiod = GpioDesc::get(dev, Some(c"reset"), GpiodFlags::ASIS)?;
+///
+/// gpiod.set_value(LogicalLineLevel::Inactive)?;
+///
+/// gpiod.set_value(LogicalLineLevel::Active)?;
+///
+/// Ok(())
+/// }
+/// ```
+///
+/// [`struct gpio_desc`]: https://docs.kernel.org/driver-api/gpio/consumer.html
+#[repr(transparent)]
+pub struct GpioDesc(*mut bindings::gpio_desc);
+
+// SAFETY: It is safe to call `gpiod_put` on another thread than where `gpiod_get` was called.
+unsafe impl Send for GpioDesc {}
+
+impl GpioDesc {
+ /// Gets [`GpioDesc`] corresponding to a [`Device`] and a connection id.
+ ///
+ /// Equivalent to the kernel's [`gpiod_get`] API.
+ ///
+ /// [`gpiod_get`]: https://docs.kernel.org/driver-api/gpio/index.html#c.gpiod_get
+ pub fn get(dev: &Device, name: Option<&CStr>, flags: GpiodFlags) -> Result<Self> {
+ let con_id = name.map_or(ptr::null(), |n| n.as_char_ptr());
+
+ // SAFETY: It is safe to call [`gpiod_get`] for a valid device pointer.
+ //
+ // INVARIANT: The reference-count is decremented when [`GpioDesc`] goes out of scope.
+ Ok(Self(from_err_ptr(unsafe {
+ bindings::gpiod_get(dev.as_raw(), con_id, flags.into_inner())
+ })?))
+ }
+
+ /// Obtain the raw [`struct gpio_desc`] pointer.
+ #[inline]
+ fn as_raw(&self) -> *mut bindings::gpio_desc {
+ self.0
+ }
+
+ /// Get the direction.
+ ///
+ /// Equivalent to the kernel's [`gpiod_get_direction`] API.
+ ///
+ /// [`gpiod_get_direction`]:
+ /// https://docs.kernel.org/driver-api/gpio/index.html#c.gpiod_get_direction
+ #[inline]
+ pub fn get_direction(&self) -> Result<LineDirection> {
+ // SAFETY: By the type invariants, self.as_raw() is a valid argument for
+ // [`gpiod_get_direction`].
+ let ret = unsafe { bindings::gpiod_get_direction(self.as_raw()) };
+ if ret < 0 {
+ Err(Error::from_errno(ret))
+ } else {
+ LineDirection::try_from(ret)
+ }
+ }
+
+ /// Set the GPIO direction to input.
+ ///
+ /// Equivalent to the kernel's [`gpiod_direction_input`] API.
+ ///
+ /// [`gpiod_direction_input`]:
+ /// https://docs.kernel.org/driver-api/gpio/index.html#c.gpiod_direction_input
+ #[inline]
+ pub fn direction_input(&self) -> Result {
+ // SAFETY: By the type invariants, self.as_raw() is a valid argument for
+ // [`gpiod_direction_input`].
+ to_result(unsafe { bindings::gpiod_direction_input(self.as_raw()) })
+ }
+
+ /// Set the GPIO direction to output and assign the logical value.
+ ///
+ /// Equivalent to the kernel's [`gpiod_direction_output`] API.
+ ///
+ /// [`gpiod_direction_output`]:
+ /// https://docs.kernel.org/driver-api/gpio/index.html#c.gpiod_direction_output
+ #[inline]
+ pub fn direction_output(&self, value: LogicalLineLevel) -> Result {
+ // SAFETY: By the type invariants, self.as_raw() is a valid argument for
+ // [`gpiod_direction_output`].
+ to_result(unsafe { bindings::gpiod_direction_output(self.as_raw(), value.as_c_int()) })
+ }
+
+ /// Set the GPIO direction to output and assign the physical value.
+ ///
+ /// Equivalent to the kernel's [`gpiod_direction_output_raw`] API.
+ ///
+ /// [`gpiod_direction_output_raw`]:
+ /// https://docs.kernel.org/driver-api/gpio/index.html#c.gpiod_direction_output_raw
+ #[inline]
+ pub fn direction_output_raw(&self, value: PhysicalLineLevel) -> Result {
+ // SAFETY: By the type invariants, self.as_raw() is a valid argument for
+ // [`gpiod_direction_output_raw`].
+ to_result(unsafe { bindings::gpiod_direction_output_raw(self.as_raw(), value.as_c_int()) })
+ }
+
+ /// Get the logical GPIO value.
+ ///
+ /// Equivalent to the kernel's [`gpiod_get_value`] API.
+ ///
+ /// [`gpiod_get_value`]: https://docs.kernel.org/driver-api/gpio/index.html#c.gpiod_get_value
+ #[inline]
+ pub fn get_value(&self) -> Result<LogicalLineLevel> {
+ // SAFETY: By the type invariants, self.as_raw() is a valid argument for
+ // [`gpiod_get_value`].
+ let ret = unsafe { bindings::gpiod_get_value(self.as_raw()) };
+ if ret < 0 {
+ Err(Error::from_errno(ret))
+ } else {
+ LogicalLineLevel::try_from(ret)
+ }
+ }
+
+ /// Assign the logical value.
+ ///
+ /// Equivalent to the kernel's [`gpiod_set_value`] API.
+ ///
+ /// [`gpiod_set_value`]: https://docs.kernel.org/driver-api/gpio/index.html#c.gpiod_set_value
+ #[inline]
+ pub fn set_value(&self, value: LogicalLineLevel) -> Result {
+ // SAFETY: By the type invariants, self.as_raw() is a valid argument for
+ // [`gpiod_set_value`].
+ to_result(unsafe { bindings::gpiod_set_value(self.as_raw(), value.as_c_int()) })
+ }
+
+ /// Get the physical GPIO value.
+ ///
+ /// Equivalent to the kernel's [`gpiod_get_raw_value`] API.
+ ///
+ /// [`gpiod_get_raw_value`]:
+ /// https://docs.kernel.org/driver-api/gpio/index.html#c.gpiod_get_raw_value
+ #[inline]
+ pub fn get_raw_value(&self) -> Result<PhysicalLineLevel> {
+ // SAFETY: By the type invariants, self.as_raw() is a valid argument for
+ // [`gpiod_get_raw_value`].
+ let ret = unsafe { bindings::gpiod_get_raw_value(self.as_raw()) };
+ if ret < 0 {
+ Err(Error::from_errno(ret))
+ } else {
+ PhysicalLineLevel::try_from(ret)
+ }
+ }
+
+ /// Assign the physical value.
+ ///
+ /// Equivalent to the kernel's [`gpiod_set_raw_value`] API.
+ ///
+ /// [`gpiod_set_raw_value`]:
+ /// https://docs.kernel.org/driver-api/gpio/index.html#c.gpiod_set_raw_value
+ #[inline]
+ pub fn set_raw_value(&self, value: PhysicalLineLevel) -> Result {
+ // SAFETY: By the type invariants, self.as_raw() is a valid argument for
+ // [`gpiod_set_raw_value`].
+ to_result(unsafe { bindings::gpiod_set_raw_value(self.as_raw(), value.as_c_int()) })
+ }
+
+ /// Get the logical GPIO value.
+ ///
+ /// Equivalent to the kernel's [`gpiod_get_value_cansleep`] API.
+ ///
+ /// [`gpiod_get_value_cansleep`]:
+ /// https://docs.kernel.org/driver-api/gpio/index.html#c.gpiod_get_value_cansleep
+ #[inline]
+ pub fn get_value_cansleep(&self) -> Result<LogicalLineLevel> {
+ // SAFETY: By the type invariants, self.as_raw() is a valid argument for
+ // [`gpiod_get_value_cansleep`].
+ let ret = unsafe { bindings::gpiod_get_value_cansleep(self.as_raw()) };
+ if ret < 0 {
+ Err(Error::from_errno(ret))
+ } else {
+ LogicalLineLevel::try_from(ret)
+ }
+ }
+
+ /// Assign the logical value.
+ ///
+ /// Equivalent to the kernel's [`gpiod_set_value_cansleep`] API.
+ ///
+ /// [`gpiod_set_value_cansleep`]:
+ /// https://docs.kernel.org/driver-api/gpio/index.html#c.gpiod_set_value_cansleep
+ #[inline]
+ pub fn set_value_cansleep(&self, value: LogicalLineLevel) -> Result {
+ // SAFETY: By the type invariants, self.as_raw() is a valid argument for
+ // [`gpiod_set_value_cansleep`].
+ to_result(unsafe { bindings::gpiod_set_value_cansleep(self.as_raw(), value.as_c_int()) })
+ }
+
+ /// Get the physical GPIO value.
+ ///
+ /// Equivalent to the kernel's [`gpiod_get_raw_value_cansleep`] API.
+ ///
+ /// [`gpiod_get_raw_value_cansleep`]:
+ /// https://docs.kernel.org/driver-api/gpio/index.html#c.gpiod_get_raw_value_cansleep
+ #[inline]
+ pub fn get_raw_value_cansleep(&self) -> Result<PhysicalLineLevel> {
+ // SAFETY: By the type invariants, self.as_raw() is a valid argument for
+ // [`gpiod_get_raw_value_cansleep`].
+ let ret = unsafe { bindings::gpiod_get_raw_value_cansleep(self.as_raw()) };
+ if ret < 0 {
+ Err(Error::from_errno(ret))
+ } else {
+ PhysicalLineLevel::try_from(ret)
+ }
+ }
+
+ /// Assign the physical value.
+ ///
+ /// Equivalent to the kernel's [`gpiod_set_raw_value_cansleep`] API.
+ ///
+ /// [`gpiod_set_raw_value_cansleep`]:
+ /// https://docs.kernel.org/driver-api/gpio/index.html#c.gpiod_set_raw_value_cansleep
+ #[inline]
+ pub fn set_raw_value_cansleep(&self, value: PhysicalLineLevel) -> Result {
+ // SAFETY: By the type invariants, self.as_raw() is a valid argument for
+ // [`gpiod_set_raw_value_cansleep`].
+ to_result(unsafe {
+ bindings::gpiod_set_raw_value_cansleep(self.as_raw(), value.as_c_int())
+ })
+ }
+
+ /// Test whether the GPIO is active-low or not.
+ ///
+ /// Equivalent to the kernel's [`gpiod_is_active_low`] API.
+ ///
+ /// [`gpiod_is_active_low`]:
+ /// https://docs.kernel.org/driver-api/gpio/index.html#c.gpiod_is_active_low
+ #[inline]
+ pub fn is_active_low(&self) -> Result<bool> {
+ // SAFETY: By the type invariants, self.as_raw() is a valid argument for
+ // [`gpiod_is_active_low`].
+ match unsafe { bindings::gpiod_is_active_low(self.as_raw()) } {
+ 0 => Ok(false),
+ 1 => Ok(true),
+ err => Err(Error::from_errno(err)),
+ }
+ }
+
+ /// Report whether gpio value access may sleep or not.
+ ///
+ /// Equivalent to the kernel's [`gpiod_cansleep`] API.
+ ///
+ /// [`gpiod_cansleep`]:
+ /// https://docs.kernel.org/driver-api/gpio/index.html#c.gpiod_cansleep
+ #[inline]
+ pub fn cansleep(&self) -> Result<bool> {
+ // SAFETY: By the type invariants, self.as_raw() is a valid argument for
+ // [`gpiod_cansleep`].
+ match unsafe { bindings::gpiod_cansleep(self.as_raw()) } {
+ 0 => Ok(false),
+ 1 => Ok(true),
+ err => Err(Error::from_errno(err)),
+ }
+ }
+}
+
+impl Drop for GpioDesc {
+ fn drop(&mut self) {
+ // SAFETY: By the type invariants, self.as_raw() is a valid argument for [`gpiod_put`].
+ unsafe { bindings::gpiod_put(self.as_raw()) };
+ }
+}
+
+/// A reference-counted optional gpio descriptor.
+///
+/// A lightweight wrapper around an optional [`GpioDesc`]. An [`OptionalGpioDesc`] represents
+/// a [`GpioDesc`] that a driver can function without but may improve performance or enable
+/// additional features when available.
+///
+/// # Invariants
+///
+/// An [`OptionalGpioDesc`] instance encapsulates a [`GpioDesc`] with either a valid
+/// [`struct gpio_desc`] or `NULL` pointer.
+///
+/// Instances of this type are reference-counted. Calling [`OptionalGpioDesc::get`] ensures that
+/// the allocation remains valid for the lifetime of the [`OptionalGpioDesc`].
+///
+/// # Examples
+///
+/// The following example demonstrates how to obtain and configure an optional GPIO for a
+/// device. The code functions correctly whether or not the GPIO is available.
+///
+/// ```
+/// use crate::{
+/// device::Device,
+/// error::Result,
+/// gpio::{
+/// consumer::{
+/// OptionalGpioDesc,
+/// GpiodFlags, //
+/// },
+/// LogicalLineLevel, //
+/// }, //
+/// };
+///
+/// fn examine_gpio(dev: &Device) -> Result {
+/// let gpiod = OptionalGpioDesc::get(dev, Some(c"reset"), GpiodFlags::ASIS)?;
+///
+/// gpiod.set_value(LogicalLineLevel::Inactive)?;
+///
+/// gpiod.set_value(LogicalLineLevel::Active)?;
+///
+/// Ok(())
+/// }
+/// ```
+///
+/// [`struct gpio_desc`]: https://docs.kernel.org/driver-api/gpio/consumer.html
+pub struct OptionalGpioDesc(GpioDesc);
+
+impl OptionalGpioDesc {
+ /// Gets [`OptionalGpioDesc`] corresponding to a [`Device`] and a connection id.
+ ///
+ /// Equivalent to the kernel's [`gpiod_get_optional`] API.
+ ///
+ /// [`gpiod_get_optional`]:
+ /// https://docs.kernel.org/driver-api/gpio/index.html#c.gpiod_get_optional
+ pub fn get(dev: &Device, name: Option<&CStr>, flags: GpiodFlags) -> Result<Self> {
+ let con_id = name.map_or(ptr::null(), |n| n.as_char_ptr());
+
+ // SAFETY: It is safe to call [`gpiod_get_optional`] for a valid device pointer.
+ //
+ // INVARIANT: The reference-count is decremented when [`OptionalGpioDesc`] goes out of
+ // scope.
+ Ok(Self(GpioDesc(from_err_ptr(unsafe {
+ bindings::gpiod_get_optional(dev.as_raw(), con_id, flags.into_inner())
+ })?)))
+ }
+}
+
+// Make [`OptionalGpioDesc`] behave like [`GpioDesc`].
+impl Deref for OptionalGpioDesc {
+ type Target = GpioDesc;
+
+ fn deref(&self) -> &GpioDesc {
+ &self.0
+ }
+}
--
2.50.1
^ permalink raw reply related [flat|nested] 11+ messages in thread* Re: [PATCH 2/3] rust: gpio: Add basic consumer abstractions
2026-09-06 8:45 ` [PATCH 2/3] rust: gpio: Add basic consumer abstractions Kohei Ito
@ 2026-09-10 7:38 ` Bartosz Golaszewski
2026-09-13 8:58 ` Alexandre Courbot
1 sibling, 0 replies; 11+ messages in thread
From: Bartosz Golaszewski @ 2026-09-10 7:38 UTC (permalink / raw)
To: Kohei Ito
Cc: linux-kernel, rust-for-linux, linux-gpio, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Daniel Almeida, Tamir Duberstein, Alexandre Courbot,
Onur Özkan
On Sun, 6 Sep 2026 10:45:50 +0200, Kohei Ito <koheiito.dev@gmail.com> said:
> Add basic abstractions for GPIO consumer APIs.
>
> Due to a bindgen issue that may generate the wrong type for enum types,
> `gpio/consumer.h` is included at the top of `bindings_helper.h` as a
> temporary workaround. Once the issue is resolved, it can be moved back
> to its proper alphabetical position.
>
> Signed-off-by: Kohei Ito <koheiito.dev@gmail.com>
> ---
My rust skills are still quite limited. This doesn't look bad but I'd also
prefer someone more well versed have a look at tell me if these make sense.
Bartosz
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH 2/3] rust: gpio: Add basic consumer abstractions
2026-09-06 8:45 ` [PATCH 2/3] rust: gpio: Add basic consumer abstractions Kohei Ito
2026-09-10 7:38 ` Bartosz Golaszewski
@ 2026-09-13 8:58 ` Alexandre Courbot
1 sibling, 0 replies; 11+ messages in thread
From: Alexandre Courbot @ 2026-09-13 8:58 UTC (permalink / raw)
To: Kohei Ito
Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
Onur Özkan, linux-kernel, rust-for-linux, linux-gpio
On Sun Sep 6, 2026 at 5:45 PM JST, Kohei Ito wrote:
> Add basic abstractions for GPIO consumer APIs.
Wow, GPIO! That brings some good memories back. :_)
>
> Due to a bindgen issue that may generate the wrong type for enum types,
> `gpio/consumer.h` is included at the top of `bindings_helper.h` as a
> temporary workaround. Once the issue is resolved, it can be moved back
> to its proper alphabetical position.
Can you describe what the issue is, and share any relevant link?
>
> Signed-off-by: Kohei Ito <koheiito.dev@gmail.com>
> ---
> rust/bindings/bindings_helper.h | 1 +
> rust/kernel/gpio.rs | 2 +
> rust/kernel/gpio/consumer.rs | 437 ++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 440 insertions(+)
>
> diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
> index 98b048b36771..30985c102c70 100644
> --- a/rust/bindings/bindings_helper.h
> +++ b/rust/bindings/bindings_helper.h
> @@ -26,6 +26,7 @@
> * This workaround may not be possible in some cases, depending on how the C
> * headers are set up.
> */
> +#include <linux/gpio/consumer.h>
> #include <linux/hrtimer_types.h>
>
> #include <linux/acpi.h>
> diff --git a/rust/kernel/gpio.rs b/rust/kernel/gpio.rs
> index 819efc8a0c05..40b6c64e8f5e 100644
> --- a/rust/kernel/gpio.rs
> +++ b/rust/kernel/gpio.rs
> @@ -11,6 +11,8 @@
> prelude::*, //
> };
>
> +pub mod consumer;
> +
> /// Describes GPIO direction.
> #[derive(Clone, Copy, PartialEq, Eq)]
> #[repr(u32)]
> diff --git a/rust/kernel/gpio/consumer.rs b/rust/kernel/gpio/consumer.rs
> new file mode 100644
> index 000000000000..f81c7381c075
> --- /dev/null
> +++ b/rust/kernel/gpio/consumer.rs
> @@ -0,0 +1,437 @@
> +// SPDX-License-Identifier: GPL-2.0
> +// This file is based on rust/kernel/clk.rs.
> +
> +//! GPIO consumer abstractions.
> +//!
> +//! C header: [`include/linux/gpio/consumer.h`](srctree/include/linux/gpio/consumer.h)
> +//!
> +//! Reference: <https://docs.kernel.org/driver-api/gpio/consumer.html>
> +
> +use crate::{
> + device::Device,
> + error::{
> + from_err_ptr,
> + to_result,
> + Error,
> + Result, //
> + },
> + gpio::{
> + LineDirection,
> + LogicalLineLevel,
> + PhysicalLineLevel, //
> + },
> + prelude::*, //
> +};
> +
> +use core::{ops::Deref, ptr};
> +
> +/// The GPIO descriptor flags to configure its direction and output value.
> +///
> +/// Rust abstraction for the C [`enum gpiod_flags`].
> +///
> +/// They can be combined with the operators `|`, and `&`.
The C comment for `gpiod_flags` says "these values cannot be OR'd" so I
guess this comment isn't true. Besides, there is no `BitOr` impl for
`GpiodFlags` in the patch so it actually cannot be done.
> +///
> +/// Values can be used from the associated constants such as
> +/// [`Flags::GPIOD_ASIS`].
> +#[derive(Clone, Copy, PartialEq)]
> +pub struct GpiodFlags(bindings::gpiod_flags);
> +
> +impl GpiodFlags {
> + /// Don't change anything.
> + pub const ASIS: Self = Self::new(bindings::gpiod_flags_GPIOD_ASIS);
> +
> + /// Set lines to input mode.
> + pub const IN: Self = Self::new(bindings::gpiod_flags_GPIOD_IN);
> +
> + /// Set lines to output and drive them low.
> + pub const OUT_LOW: Self = Self::new(bindings::gpiod_flags_GPIOD_OUT_LOW);
> +
> + /// Set lines to output and drive them high.
> + pub const OUT_HIGH: Self = Self::new(bindings::gpiod_flags_GPIOD_OUT_HIGH);
> +
> + /// Set lines to open-drain output and drive them low.
> + pub const OUT_LOW_OPEN_DRAIN: Self = Self::new(bindings::gpiod_flags_GPIOD_OUT_LOW_OPEN_DRAIN);
> +
> + /// Set lines to open-drain output and drive them high.
> + pub const OUT_HIGH_OPEN_DRAIN: Self =
> + Self::new(bindings::gpiod_flags_GPIOD_OUT_HIGH_OPEN_DRAIN);
> +
> + fn into_inner(self) -> bindings::gpiod_flags {
> + self.0
> + }
> +
> + // Always inline to optimize out error path of `build_assert`.
> + #[inline(always)]
> + const fn new(value: bindings::gpiod_flags) -> Self {
> + build_assert!(value as u64 <= bindings::gpiod_flags::MAX as u64);
Better to not use `build_assert` here as it inserts build-time
landmines.
Since you are only using this to build the constants above, you can just
do `Self(bindings::gpiod_flags_*)` on them. Adding an extra assert for
an bounded enum type doesn't add any extra protection.
> + Self(value)
> + }
> +}
> +
> +/// A reference-counted gpio descriptor.
Not really - the GPIO device is reference-counted, but descriptors are
not. Calling `gpiod_get` a second time returns `EBUSY`.
> +///
> +/// Rust abstraction for the C [`struct gpio_desc`].
> +///
> +/// # Invariants
> +///
> +/// A [`GpioDesc`] instance holds either a pointer to a valid [`struct gpio_desc`] created by the C
> +/// portion of the kernel or a `NULL` pointer.
> +///
> +/// Instances of this type are reference-counted. Calling [`GpioDesc::get`] ensures that the
> +/// allocation remains valid for the lifetime of the [`GpioDesc`].
> +///
> +/// # Examples
> +///
> +/// The following example demonstrates how to obtain a GPIO line for a device.
> +///
> +/// ```
> +/// use crate::{
These doctests won't compile as they are supposed to use `kernel::`, not
`crate::`.
Please make sure to include the doctests when building
(`CONFIG_RUST_KERNEL_DOCTESTS` build option), and to also build the
`rustdoc` target as per the checklist [1].
[1] https://rust-for-linux.com/contributing#submit-checklist-addendum
> +/// device::Device,
> +/// error::Result,
> +/// gpio::{
> +/// consumer::{
> +/// GpioDesc,
> +/// GpiodFlags, //
> +/// },
> +/// LogicalLineLevel, //
> +/// }, //
> +/// };
> +///
> +/// fn examine_gpio(dev: &Device) -> Result {
> +/// let gpiod = GpioDesc::get(dev, Some(c"reset"), GpiodFlags::ASIS)?;
> +///
> +/// gpiod.set_value(LogicalLineLevel::Inactive)?;
> +///
> +/// gpiod.set_value(LogicalLineLevel::Active)?;
> +///
> +/// Ok(())
> +/// }
> +/// ```
> +///
> +/// [`struct gpio_desc`]: https://docs.kernel.org/driver-api/gpio/consumer.html
> +#[repr(transparent)]
> +pub struct GpioDesc(*mut bindings::gpio_desc);
> +
> +// SAFETY: It is safe to call `gpiod_put` on another thread than where `gpiod_get` was called.
> +unsafe impl Send for GpioDesc {}
We should probably also implement `Sync` so GPIOs can be used in
interrupt context.
> +
> +impl GpioDesc {
> + /// Gets [`GpioDesc`] corresponding to a [`Device`] and a connection id.
> + ///
> + /// Equivalent to the kernel's [`gpiod_get`] API.
> + ///
> + /// [`gpiod_get`]: https://docs.kernel.org/driver-api/gpio/index.html#c.gpiod_get
> + pub fn get(dev: &Device, name: Option<&CStr>, flags: GpiodFlags) -> Result<Self> {
`dev` here is only used as a lookup key, and the GPIO descriptor can
outlive the device being unbound (the GPIO can actually even be obtained
while the device is unbound!). This is because `dev` is not the provider
of the GPIO, but as the API name implies its consumer - i.e. the device
on which the GPIO is expected to have an effect.
This is what the GPIO API expects, but it looks a bit counterintuitive
when compared to most other Rust subsystems, where an obtained resource
is typically tied to the device given as parameter being bound. I think
it's worth mentioning in the comment.
> + let con_id = name.map_or(ptr::null(), |n| n.as_char_ptr());
> +
> + // SAFETY: It is safe to call [`gpiod_get`] for a valid device pointer.
> + //
> + // INVARIANT: The reference-count is decremented when [`GpioDesc`] goes out of scope.
> + Ok(Self(from_err_ptr(unsafe {
> + bindings::gpiod_get(dev.as_raw(), con_id, flags.into_inner())
> + })?))
> + }
> +
> + /// Obtain the raw [`struct gpio_desc`] pointer.
> + #[inline]
> + fn as_raw(&self) -> *mut bindings::gpio_desc {
> + self.0
> + }
> +
> + /// Get the direction.
> + ///
> + /// Equivalent to the kernel's [`gpiod_get_direction`] API.
> + ///
> + /// [`gpiod_get_direction`]:
> + /// https://docs.kernel.org/driver-api/gpio/index.html#c.gpiod_get_direction
> + #[inline]
> + pub fn get_direction(&self) -> Result<LineDirection> {
> + // SAFETY: By the type invariants, self.as_raw() is a valid argument for
> + // [`gpiod_get_direction`].
> + let ret = unsafe { bindings::gpiod_get_direction(self.as_raw()) };
> + if ret < 0 {
> + Err(Error::from_errno(ret))
> + } else {
> + LineDirection::try_from(ret)
> + }
> + }
IIUC the direction of a GPIO at a given point in the code is always
statically known, and only a subset of the API really make sense for a
given direction (e.g. `gpiod_set_raw_value_commit` returns `EPERM` if
the direction is not output). So this is a prime candidate for using the
typestate pattern to store the direction in the type.
I.e. you would have `GpioDesc<Input>`, `GpioDesc<Output>`, and changing
the direction would consume the descriptor and return the new one with
the requested direction.
The regulator Rust API makes use of this pattern, you can check it out
for an example if needed.
> +
> + /// Set the GPIO direction to input.
> + ///
> + /// Equivalent to the kernel's [`gpiod_direction_input`] API.
> + ///
> + /// [`gpiod_direction_input`]:
> + /// https://docs.kernel.org/driver-api/gpio/index.html#c.gpiod_direction_input
> + #[inline]
> + pub fn direction_input(&self) -> Result {
> + // SAFETY: By the type invariants, self.as_raw() is a valid argument for
> + // [`gpiod_direction_input`].
> + to_result(unsafe { bindings::gpiod_direction_input(self.as_raw()) })
> + }
> +
> + /// Set the GPIO direction to output and assign the logical value.
> + ///
> + /// Equivalent to the kernel's [`gpiod_direction_output`] API.
> + ///
> + /// [`gpiod_direction_output`]:
> + /// https://docs.kernel.org/driver-api/gpio/index.html#c.gpiod_direction_output
> + #[inline]
> + pub fn direction_output(&self, value: LogicalLineLevel) -> Result {
> + // SAFETY: By the type invariants, self.as_raw() is a valid argument for
> + // [`gpiod_direction_output`].
> + to_result(unsafe { bindings::gpiod_direction_output(self.as_raw(), value.as_c_int()) })
> + }
> +
> + /// Set the GPIO direction to output and assign the physical value.
> + ///
> + /// Equivalent to the kernel's [`gpiod_direction_output_raw`] API.
> + ///
> + /// [`gpiod_direction_output_raw`]:
> + /// https://docs.kernel.org/driver-api/gpio/index.html#c.gpiod_direction_output_raw
> + #[inline]
> + pub fn direction_output_raw(&self, value: PhysicalLineLevel) -> Result {
> + // SAFETY: By the type invariants, self.as_raw() is a valid argument for
> + // [`gpiod_direction_output_raw`].
> + to_result(unsafe { bindings::gpiod_direction_output_raw(self.as_raw(), value.as_c_int()) })
> + }
> +
> + /// Get the logical GPIO value.
> + ///
> + /// Equivalent to the kernel's [`gpiod_get_value`] API.
> + ///
> + /// [`gpiod_get_value`]: https://docs.kernel.org/driver-api/gpio/index.html#c.gpiod_get_value
> + #[inline]
> + pub fn get_value(&self) -> Result<LogicalLineLevel> {
> + // SAFETY: By the type invariants, self.as_raw() is a valid argument for
> + // [`gpiod_get_value`].
> + let ret = unsafe { bindings::gpiod_get_value(self.as_raw()) };
> + if ret < 0 {
> + Err(Error::from_errno(ret))
> + } else {
> + LogicalLineLevel::try_from(ret)
> + }
> + }
> +
> + /// Assign the logical value.
> + ///
> + /// Equivalent to the kernel's [`gpiod_set_value`] API.
> + ///
> + /// [`gpiod_set_value`]: https://docs.kernel.org/driver-api/gpio/index.html#c.gpiod_set_value
> + #[inline]
> + pub fn set_value(&self, value: LogicalLineLevel) -> Result {
> + // SAFETY: By the type invariants, self.as_raw() is a valid argument for
> + // [`gpiod_set_value`].
> + to_result(unsafe { bindings::gpiod_set_value(self.as_raw(), value.as_c_int()) })
> + }
> +
> + /// Get the physical GPIO value.
> + ///
> + /// Equivalent to the kernel's [`gpiod_get_raw_value`] API.
> + ///
> + /// [`gpiod_get_raw_value`]:
> + /// https://docs.kernel.org/driver-api/gpio/index.html#c.gpiod_get_raw_value
> + #[inline]
> + pub fn get_raw_value(&self) -> Result<PhysicalLineLevel> {
> + // SAFETY: By the type invariants, self.as_raw() is a valid argument for
> + // [`gpiod_get_raw_value`].
> + let ret = unsafe { bindings::gpiod_get_raw_value(self.as_raw()) };
> + if ret < 0 {
> + Err(Error::from_errno(ret))
> + } else {
> + PhysicalLineLevel::try_from(ret)
> + }
> + }
> +
> + /// Assign the physical value.
> + ///
> + /// Equivalent to the kernel's [`gpiod_set_raw_value`] API.
> + ///
> + /// [`gpiod_set_raw_value`]:
> + /// https://docs.kernel.org/driver-api/gpio/index.html#c.gpiod_set_raw_value
> + #[inline]
> + pub fn set_raw_value(&self, value: PhysicalLineLevel) -> Result {
> + // SAFETY: By the type invariants, self.as_raw() is a valid argument for
> + // [`gpiod_set_raw_value`].
> + to_result(unsafe { bindings::gpiod_set_raw_value(self.as_raw(), value.as_c_int()) })
> + }
> +
> + /// Get the logical GPIO value.
> + ///
> + /// Equivalent to the kernel's [`gpiod_get_value_cansleep`] API.
> + ///
> + /// [`gpiod_get_value_cansleep`]:
> + /// https://docs.kernel.org/driver-api/gpio/index.html#c.gpiod_get_value_cansleep
> + #[inline]
> + pub fn get_value_cansleep(&self) -> Result<LogicalLineLevel> {
> + // SAFETY: By the type invariants, self.as_raw() is a valid argument for
> + // [`gpiod_get_value_cansleep`].
> + let ret = unsafe { bindings::gpiod_get_value_cansleep(self.as_raw()) };
> + if ret < 0 {
> + Err(Error::from_errno(ret))
> + } else {
> + LogicalLineLevel::try_from(ret)
> + }
> + }
Here as well it would have been nice if we could avoid having
`_cansleep` variants, but I am not sure there is anything we can do for
that so I guess we'll need to keep all the variants.
> +
> + /// Assign the logical value.
> + ///
> + /// Equivalent to the kernel's [`gpiod_set_value_cansleep`] API.
> + ///
> + /// [`gpiod_set_value_cansleep`]:
> + /// https://docs.kernel.org/driver-api/gpio/index.html#c.gpiod_set_value_cansleep
> + #[inline]
> + pub fn set_value_cansleep(&self, value: LogicalLineLevel) -> Result {
> + // SAFETY: By the type invariants, self.as_raw() is a valid argument for
> + // [`gpiod_set_value_cansleep`].
> + to_result(unsafe { bindings::gpiod_set_value_cansleep(self.as_raw(), value.as_c_int()) })
> + }
> +
> + /// Get the physical GPIO value.
> + ///
> + /// Equivalent to the kernel's [`gpiod_get_raw_value_cansleep`] API.
> + ///
> + /// [`gpiod_get_raw_value_cansleep`]:
> + /// https://docs.kernel.org/driver-api/gpio/index.html#c.gpiod_get_raw_value_cansleep
> + #[inline]
> + pub fn get_raw_value_cansleep(&self) -> Result<PhysicalLineLevel> {
> + // SAFETY: By the type invariants, self.as_raw() is a valid argument for
> + // [`gpiod_get_raw_value_cansleep`].
> + let ret = unsafe { bindings::gpiod_get_raw_value_cansleep(self.as_raw()) };
> + if ret < 0 {
> + Err(Error::from_errno(ret))
> + } else {
> + PhysicalLineLevel::try_from(ret)
> + }
> + }
> +
> + /// Assign the physical value.
> + ///
> + /// Equivalent to the kernel's [`gpiod_set_raw_value_cansleep`] API.
> + ///
> + /// [`gpiod_set_raw_value_cansleep`]:
> + /// https://docs.kernel.org/driver-api/gpio/index.html#c.gpiod_set_raw_value_cansleep
> + #[inline]
> + pub fn set_raw_value_cansleep(&self, value: PhysicalLineLevel) -> Result {
> + // SAFETY: By the type invariants, self.as_raw() is a valid argument for
> + // [`gpiod_set_raw_value_cansleep`].
> + to_result(unsafe {
> + bindings::gpiod_set_raw_value_cansleep(self.as_raw(), value.as_c_int())
> + })
> + }
> +
> + /// Test whether the GPIO is active-low or not.
> + ///
> + /// Equivalent to the kernel's [`gpiod_is_active_low`] API.
> + ///
> + /// [`gpiod_is_active_low`]:
> + /// https://docs.kernel.org/driver-api/gpio/index.html#c.gpiod_is_active_low
> + #[inline]
> + pub fn is_active_low(&self) -> Result<bool> {
> + // SAFETY: By the type invariants, self.as_raw() is a valid argument for
> + // [`gpiod_is_active_low`].
> + match unsafe { bindings::gpiod_is_active_low(self.as_raw()) } {
> + 0 => Ok(false),
> + 1 => Ok(true),
> + err => Err(Error::from_errno(err)),
> + }
In C this function cannot fail for a valid descriptor, so the Rust one
shouldn't either. Anything != 0 can be considered `true`.
> + }
> +
> + /// Report whether gpio value access may sleep or not.
> + ///
> + /// Equivalent to the kernel's [`gpiod_cansleep`] API.
> + ///
> + /// [`gpiod_cansleep`]:
> + /// https://docs.kernel.org/driver-api/gpio/index.html#c.gpiod_cansleep
> + #[inline]
> + pub fn cansleep(&self) -> Result<bool> {
> + // SAFETY: By the type invariants, self.as_raw() is a valid argument for
> + // [`gpiod_cansleep`].
> + match unsafe { bindings::gpiod_cansleep(self.as_raw()) } {
> + 0 => Ok(false),
> + 1 => Ok(true),
> + err => Err(Error::from_errno(err)),
> + }
> + }
Same here.
Also, as a general guideline, it is good to have a concrete user for new
Rust abstractions. Do you have a project that will make use of this?
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 3/3] sample: rust: Add GPIO consumer sample driver
2026-09-06 8:45 [PATCH 0/3] rust: Add basic GPIO consumer abstractions Kohei Ito
2026-09-06 8:45 ` [PATCH 1/3] rust: gpio: add GPIO module with common definitions Kohei Ito
2026-09-06 8:45 ` [PATCH 2/3] rust: gpio: Add basic consumer abstractions Kohei Ito
@ 2026-09-06 8:45 ` Kohei Ito
2026-09-10 7:37 ` Bartosz Golaszewski
2 siblings, 1 reply; 11+ messages in thread
From: Kohei Ito @ 2026-09-06 8:45 UTC (permalink / raw)
To: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan
Cc: linux-kernel, rust-for-linux, linux-gpio, Kohei Ito
Add a sample driver to demonstrate the use of the Rust GPIO APIs.
Signed-off-by: Kohei Ito <koheiito.dev@gmail.com>
---
samples/rust/Kconfig | 11 +++
samples/rust/Makefile | 1 +
samples/rust/rust_gpio_consumer.rs | 156 +++++++++++++++++++++++++++++++++++++
3 files changed, 168 insertions(+)
diff --git a/samples/rust/Kconfig b/samples/rust/Kconfig
index c49ab9106345..1085d90c87f3 100644
--- a/samples/rust/Kconfig
+++ b/samples/rust/Kconfig
@@ -161,6 +161,17 @@ config SAMPLE_RUST_DRIVER_AUXILIARY
If unsure, say N.
+config SAMPLE_RUST_GPIO_CONSUMER
+ tristate "GPIO Consumer Driver"
+ depends on GPIOLIB
+ help
+ This option builds the Rust GPIO consumer sample.
+
+ To compile this as a module, choose M here:
+ the module will be called rust_gpio_consumer.
+
+ If unsure, say N.
+
config SAMPLE_RUST_SOC
tristate "SoC Driver"
select SOC_BUS
diff --git a/samples/rust/Makefile b/samples/rust/Makefile
index 6c0aaa58cccc..1cf181dea6d3 100644
--- a/samples/rust/Makefile
+++ b/samples/rust/Makefile
@@ -15,6 +15,7 @@ obj-$(CONFIG_SAMPLE_RUST_DRIVER_USB) += rust_driver_usb.o
obj-$(CONFIG_SAMPLE_RUST_DRIVER_FAUX) += rust_driver_faux.o
obj-$(CONFIG_SAMPLE_RUST_DRIVER_AUXILIARY) += rust_driver_auxiliary.o
obj-$(CONFIG_SAMPLE_RUST_CONFIGFS) += rust_configfs.o
+obj-$(CONFIG_SAMPLE_RUST_GPIO_CONSUMER) += rust_gpio_consumer.o
obj-$(CONFIG_SAMPLE_RUST_SOC) += rust_soc.o
rust_print-y := rust_print_main.o rust_print_events.o
diff --git a/samples/rust/rust_gpio_consumer.rs b/samples/rust/rust_gpio_consumer.rs
new file mode 100644
index 000000000000..b70b305126fc
--- /dev/null
+++ b/samples/rust/rust_gpio_consumer.rs
@@ -0,0 +1,156 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Rust GPIO consumer driver sample.
+
+use kernel::{
+ device::{
+ self,
+ Core, //
+ },
+ gpio::{
+ self,
+ consumer::{
+ GpioDesc,
+ GpiodFlags, //
+ }, //
+ },
+ of,
+ platform,
+ prelude::*,
+ time::{
+ delay::fsleep,
+ Delta, //
+ }, //
+};
+
+struct SampleDriver;
+
+impl platform::Driver for SampleDriver {
+ type IdInfo = ();
+ type Data<'bound> = Self;
+ const OF_ID_TABLE: Option<of::IdTable<Self::IdInfo>> = Some(&OF_TABLE);
+
+ fn probe<'bound>(
+ pdev: &'bound platform::Device<Core<'_>>,
+ _info: Option<&'bound Self::IdInfo>,
+ ) -> impl PinInit<Self, Error> + 'bound {
+ let dev = pdev.as_ref();
+
+ dev_dbg!(dev, "Probe Rust GPIO consumer driver sample.\n");
+
+ Self::examine_gpio(dev)?;
+
+ Ok(Self)
+ }
+}
+
+type GetValue = fn(&GpioDesc) -> Result<gpio::LogicalLineLevel>;
+type GetRawValue = fn(&GpioDesc) -> Result<gpio::PhysicalLineLevel>;
+type SetValue = fn(&GpioDesc, gpio::LogicalLineLevel) -> Result<()>;
+type SetRawValue = fn(&GpioDesc, gpio::PhysicalLineLevel) -> Result<()>;
+
+impl SampleDriver {
+ fn examine_gpio(dev: &device::Device) -> Result {
+ let gpiod = GpioDesc::get(dev, None, GpiodFlags::ASIS)?;
+
+ let cansleep = gpiod.cansleep()?;
+ let (get_value, get_raw_value, set_value, set_raw_value): (
+ GetValue,
+ GetRawValue,
+ SetValue,
+ SetRawValue,
+ ) = if cansleep {
+ (
+ |gpiod| gpiod.get_value_cansleep(),
+ |gpiod| gpiod.get_raw_value_cansleep(),
+ |gpiod, value| gpiod.set_value_cansleep(value),
+ |gpiod, value| gpiod.set_raw_value_cansleep(value),
+ )
+ } else {
+ (
+ |gpiod| gpiod.get_value(),
+ |gpiod| gpiod.get_raw_value(),
+ |gpiod, value| gpiod.set_value(value),
+ |gpiod, value| gpiod.set_raw_value(value),
+ )
+ };
+
+ let pr_status = || -> Result<()> {
+ dev_info!(
+ dev,
+ "direction: {}, value: {}, raw value: {}\n",
+ gpiod.get_direction()?,
+ get_value(&gpiod)?,
+ get_raw_value(&gpiod)?
+ );
+ Ok(())
+ };
+
+ let active_low = gpiod.is_active_low()?;
+
+ dev_info!(
+ dev,
+ "got the GPIO ({}{})\n",
+ if active_low {
+ "active low"
+ } else {
+ "active high"
+ },
+ if cansleep { ", sleepy" } else { "" }
+ );
+ pr_status()?;
+
+ gpiod.direction_output(gpio::LogicalLineLevel::Inactive)?;
+ dev_info!(dev, "line is inactivated\n");
+ pr_status()?;
+
+ fsleep(Delta::from_millis(1));
+
+ set_value(&gpiod, gpio::LogicalLineLevel::Active)?;
+ dev_info!(dev, "line is activated\n");
+ pr_status()?;
+
+ fsleep(Delta::from_millis(1));
+
+ set_value(&gpiod, gpio::LogicalLineLevel::Inactive)?;
+ dev_info!(dev, "GPIO: line is inactivated\n");
+ pr_status()?;
+
+ fsleep(Delta::from_millis(1));
+
+ let level = get_raw_value(&gpiod)?;
+
+ gpiod.direction_input()?;
+ dev_info!(dev, "line is input mode\n");
+ pr_status()?;
+
+ fsleep(Delta::from_millis(1));
+
+ gpiod.direction_output_raw(!level)?;
+ dev_info!(dev, "line is toggled\n");
+ pr_status()?;
+
+ fsleep(Delta::from_millis(1));
+
+ set_raw_value(&gpiod, !!level)?;
+ dev_info!(dev, "line is toggled\n");
+ pr_status()?;
+
+ Ok(())
+ }
+}
+
+kernel::of_device_table!(
+ OF_TABLE,
+ MODULE_OF_TABLE,
+ <SampleDriver as platform::Driver>::IdInfo,
+ [(of::DeviceId::new(c"test,rust-gpio-consumer"), ())]
+);
+
+kernel::module_platform_driver! {
+ type: SampleDriver,
+ name: "rust_gpio_consumer",
+ authors: ["Kohei Ito"],
+ description: "Rust GPIO consumer driver",
+ license: "GPL v2",
+}
--
2.50.1
^ permalink raw reply related [flat|nested] 11+ messages in thread* Re: [PATCH 3/3] sample: rust: Add GPIO consumer sample driver
2026-09-06 8:45 ` [PATCH 3/3] sample: rust: Add GPIO consumer sample driver Kohei Ito
@ 2026-09-10 7:37 ` Bartosz Golaszewski
2026-09-13 8:46 ` Kohei Ito
0 siblings, 1 reply; 11+ messages in thread
From: Bartosz Golaszewski @ 2026-09-10 7:37 UTC (permalink / raw)
To: Kohei Ito
Cc: linux-kernel, rust-for-linux, linux-gpio, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Daniel Almeida, Tamir Duberstein, Alexandre Courbot,
Onur Özkan
On Sun, 6 Sep 2026 10:45:51 +0200, Kohei Ito <koheiito.dev@gmail.com> said:
> Add a sample driver to demonstrate the use of the Rust GPIO APIs.
>
> Signed-off-by: Kohei Ito <koheiito.dev@gmail.com>
> ---
I don't like samples as they rarely get built or tested. We seem to already
have kunit support for rust, wouldn't it make more sense to implement a kunit
module for rust GPIO abstractions? If we don't have provider abstractions, you
should be able to reuse gpio-sim as the GPIO controller for testing just by
instantiating simulated GPIO devices.
Bartosz
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH 3/3] sample: rust: Add GPIO consumer sample driver
2026-09-10 7:37 ` Bartosz Golaszewski
@ 2026-09-13 8:46 ` Kohei Ito
0 siblings, 0 replies; 11+ messages in thread
From: Kohei Ito @ 2026-09-13 8:46 UTC (permalink / raw)
To: Bartosz Golaszewski
Cc: linux-kernel, rust-for-linux, linux-gpio, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Daniel Almeida, Tamir Duberstein, Alexandre Courbot,
Onur Özkan
Hi, Bartosz,
On Thu, Sep 10, 2026 at 12:37:17AM -0700, Bartosz Golaszewski wrote:
> On Sun, 6 Sep 2026 10:45:51 +0200, Kohei Ito <koheiito.dev@gmail.com> said:
> > Add a sample driver to demonstrate the use of the Rust GPIO APIs.
> >
> > Signed-off-by: Kohei Ito <koheiito.dev@gmail.com>
> > ---
>
> I don't like samples as they rarely get built or tested. We seem to already
> have kunit support for rust, wouldn't it make more sense to implement a kunit
> module for rust GPIO abstractions? If we don't have provider abstractions, you
> should be able to reuse gpio-sim as the GPIO controller for testing just by
> instantiating simulated GPIO devices.
Thank you for your suggestion.
I assume the kunit module you have in mind would be implemented like
`gpiolib-kunit.c`. Based on your comment, I agree that a kunit-based
approach is more appropriate than a sample driver.
However, as far as I know, we don't yet have Rust abstractions for
platform_device registration and software_node, which are required to
implement a kunit-based module for testing GPIO consumer APIs. Given the
current state of Rust for Linux, I think creating a sample driver is a
more practical approach for now. I would like to consider migrating to a
kunit-based module as future work.
Best regards,
Kohei Ito
^ permalink raw reply [flat|nested] 11+ messages in thread