Rust for Linux List
 help / color / mirror / Atom feed
From: Kohei Ito <koheiito.dev@gmail.com>
To: "Miguel Ojeda" <ojeda@kernel.org>,
	"Boqun Feng" <boqun@kernel.org>, "Gary Guo" <gary@garyguo.net>,
	"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
	"Benno Lossin" <lossin@kernel.org>,
	"Andreas Hindborg" <a.hindborg@kernel.org>,
	"Alice Ryhl" <aliceryhl@google.com>,
	"Trevor Gross" <tmgross@umich.edu>,
	"Danilo Krummrich" <dakr@kernel.org>,
	"Daniel Almeida" <daniel.almeida@collabora.com>,
	"Tamir Duberstein" <tamird@kernel.org>,
	"Alexandre Courbot" <acourbot@nvidia.com>,
	"Onur Özkan" <work@onurozkan.dev>
Cc: linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org,
	 linux-gpio@vger.kernel.org, Kohei Ito <koheiito.dev@gmail.com>
Subject: [PATCH 2/3] rust: gpio: Add basic consumer abstractions
Date: Sun, 06 Sep 2026 17:45:50 +0900	[thread overview]
Message-ID: <20260906-add-rust-gpio-consumer-v1-2-24d192f93760@gmail.com> (raw)
In-Reply-To: <20260906-add-rust-gpio-consumer-v1-0-24d192f93760@gmail.com>

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


  parent reply	other threads:[~2026-09-06  8:46 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
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  9:56   ` Miguel Ojeda
2026-09-06 13:09     ` Gary Guo
2026-09-06 15:53       ` Kohei Ito
2026-09-06  8:45 ` Kohei Ito [this message]
2026-09-10  7:38   ` [PATCH 2/3] rust: gpio: Add basic consumer abstractions 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
2026-09-10  7:37   ` Bartosz Golaszewski
2026-09-13  8:46     ` Kohei Ito

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=20260906-add-rust-gpio-consumer-v1-2-24d192f93760@gmail.com \
    --to=koheiito.dev@gmail.com \
    --cc=a.hindborg@kernel.org \
    --cc=acourbot@nvidia.com \
    --cc=aliceryhl@google.com \
    --cc=bjorn3_gh@protonmail.com \
    --cc=boqun@kernel.org \
    --cc=dakr@kernel.org \
    --cc=daniel.almeida@collabora.com \
    --cc=gary@garyguo.net \
    --cc=linux-gpio@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=lossin@kernel.org \
    --cc=ojeda@kernel.org \
    --cc=rust-for-linux@vger.kernel.org \
    --cc=tamird@kernel.org \
    --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