Skip to contentSkip to conversation

libgpiod: Add Rust bindings

PATCH Superseded Viresh Kumar 5 participants 26 messages Dec 2, 2021 – Apr 11, 2022 GPIO

v2 of 10 Superseded Latest is v10 →

lore
Share & tools
lore
#0 lore raw

v2 cover libgpiod: Add Rust bindings

Hi Bartosz,

This patch adds rust bindings for libgpiod v2.0, this is already partially tested with the virtio rust backend I am developing, which uses these to talk to the host kernel.

This is based of the next/post-libgpiod-2.0 branch.

I will be adding testing infrastructure later on, once other bindings are converted to use gpiosim.

V1->V2:
- Added examples (I tested everything except gpiomon.rs, didn't have right hardware/mock device to test).
- Build rust bindings as part of Make, update documentation.

Thanks.

--
Viresh

Viresh Kumar (4):
libgpiod: Generate rust FFI bindings
libgpiod: Add rust wrappers
rust: Add examples
rust: Integrate building of rust bindings with make

 .gitignore                           |   5 +
 README                               |   8 +-
 TODO                                 |   8 -
 bindings/Makefile.am                 |   6 +
 bindings/rust/Cargo.toml             |  14 +
 bindings/rust/Makefile.am            |  29 ++
 bindings/rust/build.rs               |  60 ++++
 bindings/rust/examples/gpiodetect.rs |  38 +++
 bindings/rust/examples/gpiofind.rs   |  43 +++
 bindings/rust/examples/gpioget.rs    |  45 +++
 bindings/rust/examples/gpioinfo.rs   |  90 ++++++
 bindings/rust/examples/gpiomon.rs    |  73 +++++
 bindings/rust/examples/gpioset.rs    |  55 ++++
 bindings/rust/src/bindings.rs        |  16 +
 bindings/rust/src/chip.rs            | 197 ++++++++++++
 bindings/rust/src/edge_event.rs      |  78 +++++
 bindings/rust/src/event_buffer.rs    |  59 ++++
 bindings/rust/src/info_event.rs      |  70 +++++
 bindings/rust/src/lib.rs             | 268 +++++++++++++++++
 bindings/rust/src/line_config.rs     | 431 +++++++++++++++++++++++++++
 bindings/rust/src/line_info.rs       | 186 ++++++++++++
 bindings/rust/src/line_request.rs    | 217 ++++++++++++++
 bindings/rust/src/request_config.rs  | 118 ++++++++
 bindings/rust/wrapper.h              |   2 +
 configure.ac                         |  16 +
 25 files changed, 2121 insertions(+), 11 deletions(-)
 create mode 100644 bindings/rust/Cargo.toml
 create mode 100644 bindings/rust/Makefile.am
 create mode 100644 bindings/rust/build.rs
 create mode 100644 bindings/rust/examples/gpiodetect.rs
 create mode 100644 bindings/rust/examples/gpiofind.rs
 create mode 100644 bindings/rust/examples/gpioget.rs
 create mode 100644 bindings/rust/examples/gpioinfo.rs
 create mode 100644 bindings/rust/examples/gpiomon.rs
 create mode 100644 bindings/rust/examples/gpioset.rs
 create mode 100644 bindings/rust/src/bindings.rs
 create mode 100644 bindings/rust/src/chip.rs
 create mode 100644 bindings/rust/src/edge_event.rs
 create mode 100644 bindings/rust/src/event_buffer.rs
 create mode 100644 bindings/rust/src/info_event.rs
 create mode 100644 bindings/rust/src/lib.rs
 create mode 100644 bindings/rust/src/line_config.rs
 create mode 100644 bindings/rust/src/line_info.rs
 create mode 100644 bindings/rust/src/line_request.rs
 create mode 100644 bindings/rust/src/request_config.rs
 create mode 100644 bindings/rust/wrapper.h
Signature · 1 line
2.31.1.272.g89b43f80a514
#1 lore raw

PATCH v2 1/4 libgpiod: Generate rust FFI bindings

Build FFI bindings for the libgpiod helpers.

---
 .gitignore                    |  5 +++
 bindings/rust/Cargo.toml      | 12 +++++++
 bindings/rust/build.rs        | 60 +++++++++++++++++++++++++++++++++++
 bindings/rust/src/bindings.rs | 16 ++++++++++
 bindings/rust/src/lib.rs      |  0
 bindings/rust/wrapper.h       |  2 ++
 6 files changed, 95 insertions(+)
 create mode 100644 bindings/rust/Cargo.toml
 create mode 100644 bindings/rust/build.rs
 create mode 100644 bindings/rust/src/bindings.rs
 create mode 100644 bindings/rust/src/lib.rs
 create mode 100644 bindings/rust/wrapper.h
.gitignore +5 -0
index 2d7cc7fc0758..0d3a842734bf 100644
@@ -29,3 +29,8 @@ libtool
 *-libtool
 m4/
 stamp-h1
+
+# Added by cargo
+
+bindings/rust/target
+bindings/rust/Cargo.lock
bindings/rust/Cargo.toml new file +12 -0
new file mode 100644
index 000000000000..62f3d52ddb0f
@@ -0,0 +1,12 @@
+[package]
+name = "libgpiod"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+
+[build-dependencies]
+bindgen = "0.59.1"
+cc = "1.0.46"
bindings/rust/build.rs new file +60 -0

60 lines not on this page · raw message

bindings/rust/src/bindings.rs new file +16 -0
new file mode 100644
index 000000000000..7d6caa7d9c11
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#[allow(
+    clippy::all,
+    deref_nullptr,
+    dead_code,
+    non_camel_case_types,
+    non_upper_case_globals,
+    non_snake_case,
+    improper_ctypes
+)]
+
+mod bindings_raw {
+    include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
+}
+pub use bindings_raw::*;
bindings/rust/src/lib.rs new file +0 -0
new file mode 100644
index 000000000000..e69de29bb2d1
bindings/rust/wrapper.h new file +2 -0
new file mode 100644
index 000000000000..50dc5f4db406
@@ -0,0 +1,2 @@
+#include <string.h>
+#include "../../include/gpiod.h"
Signature · 1 line
2.31.1.272.g89b43f80a514
#2 lore raw

PATCH v2 2/4 libgpiod: Add rust wrappers

In reply to Viresh Kumar

Add rust wrappers around FFI bindings to provide a convenient interface for the users.

---
 bindings/rust/Cargo.toml            |   2 +
 bindings/rust/src/chip.rs           | 197 +++++++++++++
 bindings/rust/src/edge_event.rs     |  78 +++++
 bindings/rust/src/event_buffer.rs   |  59 ++++
 bindings/rust/src/info_event.rs     |  70 +++++
 bindings/rust/src/lib.rs            | 268 +++++++++++++++++
 bindings/rust/src/line_config.rs    | 431 ++++++++++++++++++++++++++++
 bindings/rust/src/line_info.rs      | 186 ++++++++++++
 bindings/rust/src/line_request.rs   | 217 ++++++++++++++
 bindings/rust/src/request_config.rs | 118 ++++++++
 10 files changed, 1626 insertions(+)
 create mode 100644 bindings/rust/src/chip.rs
 create mode 100644 bindings/rust/src/edge_event.rs
 create mode 100644 bindings/rust/src/event_buffer.rs
 create mode 100644 bindings/rust/src/info_event.rs
 create mode 100644 bindings/rust/src/line_config.rs
 create mode 100644 bindings/rust/src/line_info.rs
 create mode 100644 bindings/rust/src/line_request.rs
 create mode 100644 bindings/rust/src/request_config.rs
bindings/rust/Cargo.toml +2 -0
index 62f3d52ddb0f..d57dfd65ba7d 100644
@@ -6,6 +6,8 @@ edition = "2021"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
+thiserror = "1.0"
+vmm-sys-util = "=0.9.0"
 
 [build-dependencies]
 bindgen = "0.59.1"
bindings/rust/src/chip.rs new file +197 -0

197 lines not on this page · raw message

bindings/rust/src/edge_event.rs new file +78 -0

78 lines not on this page · raw message

bindings/rust/src/event_buffer.rs new file +59 -0

59 lines not on this page · raw message

bindings/rust/src/info_event.rs new file +70 -0

70 lines not on this page · raw message

bindings/rust/src/lib.rs +268 -0

268 lines not on this page · raw message

bindings/rust/src/line_config.rs new file +431 -0

431 lines not on this page · raw message

bindings/rust/src/line_info.rs new file +186 -0

186 lines not on this page · raw message

bindings/rust/src/line_request.rs new file +217 -0

217 lines not on this page · raw message

bindings/rust/src/request_config.rs new file +118 -0

118 lines not on this page · raw message

Signature · 1 line
2.31.1.272.g89b43f80a514
#3 lore raw

Re: [PATCH V2 2/4] libgpiod: Add rust wrappers

Quoted 6 lines · Viresh Kumar

Add rust wrappers around FFI bindings to provide a convenient interface for the users.

---

Hi Viresh, thanks for the hard work.

Obviously this is not something we can merge yet but it's a good base to continue the work.

General comment on the naming convention:

The line from one of the examples: 'use libgpiod::chip::GpiodChip;' looks like it has a lot of redundancy in it. How about calling the crate gpiod and dropping the chip package entirely? Basically follow what C++ and python bindings do by having use gpiod::Chip etc.?

[snip]

bindings/rust/src/chip.rs line 35

Quoted 7 lines
bindings/rust/src/chip.rs new file +7 -0
+
+impl GpiodChipInternal {
+    /// Find a GPIO chip by path.
+    pub(crate) fn open(path: &str) -> Result<Self> {
+        let chip = unsafe { bindings::gpiod_chip_open(path.as_ptr() as *const c_char) };
+        if chip.is_null() {
+            return Err(Error::OperationFailed("GpiodChip open", IoError::last()));

One of my concerns last time was error handling. Does this now translate the error codes from the underlying C code to some kind of rust errors? Can you elaborate on how that works? I see it always returns IoError. In my WIP C++ and Python code I try to translate the errnos into some meaningful exceptions - for instance EINVAL -> ::std::invalid_argument etc. Can we have a similar thing here?

[snip]

bindings/rust/src/chip.rs line 71

Quoted 7 lines
bindings/rust/src/chip.rs new file +7 -0
+
+    /// Get the GPIO chip name as represented in the kernel.
+    pub fn get_name(&self) -> Result<&str> {
+        // SAFETY: The string returned by libgpiod is guaranteed to live as long
+        // as the `struct GpiodChip`.
+        let name = unsafe { bindings::gpiod_chip_get_name(self.ichip.chip()) };
+        if name.is_null() {

This is not possible, the string can be empty at the very least but never null. Same for label and path.

bindings/rust/src/chip.rs line 162

Quoted 7 lines
bindings/rust/src/chip.rs new file +7 -0
+
+        match ret {
+            -1 => Err(Error::OperationFailed(
+                "GpiodChip info-event-wait",
+                IoError::last(),
+            )),
+            0 => Err(Error::OperationTimedOut),

Does it have to be an Error? It's pretty normal for an operation to time out, no?

bindings/rust/src/chip.rs line 170

Quoted 8 lines
bindings/rust/src/chip.rs new file +8 -0
+            _ => Ok(()),
+        }
+    }
+
+    /// Read a single line status change event from this chip. If no events are
+    /// pending, this function will block.
+    pub fn info_event_read(&self) -> Result<GpiodInfoEvent> {
+        GpiodInfoEvent::new(&self.ichip.clone())

Would you mind explaining what the clone() function does here to ichip and why it's needed.

Quoted 36 lines
bindings/rust/src/edge_event.rs new file +36 -0
+
+impl GpiodEdgeEvent {
+    /// Get an event stored in the buffer.
+    pub fn new(buffer: &GpiodEdgeEventBuffer, index: u64) -> Result<Self> {
+        let event = unsafe { bindings::gpiod_edge_event_buffer_get_event(buffer.buffer(), index) };
+        if event.is_null() {
+            return Err(Error::OperationFailed(
+                "GpiodEdgeEvent buffer-get-event",
+                IoError::last(),
+            ));
+        }
+
+        Ok(Self { event })
+    }
+
+    /// Get the event type.
+    pub fn get_event_type(&self) -> Result<EdgeEvent> {
+        EdgeEvent::new(unsafe { bindings::gpiod_edge_event_get_event_type(self.event) } as u32)
+    }
+
+    /// Get the timestamp of the event.
+    pub fn get_timestamp(&self) -> Duration {
+        Duration::from_nanos(unsafe { bindings::gpiod_edge_event_get_timestamp(self.event) })
+    }
+
+    /// Get the offset of the line on which the event was triggered.
+    pub fn get_line_offset(&self) -> u32 {
+        unsafe { bindings::gpiod_edge_event_get_line_offset(self.event) }
+    }
+
+    /// Get the global sequence number of this event.
+    ///
+    /// Returns sequence number of the event relative to all lines in the
+    /// associated line request.
+    pub fn get_global_seqno(&self) -> u64 {
+        unsafe { bindings::gpiod_edge_event_get_global_seqno(self.event) }

bindings/rust/src/edge_event.rs line 71

Quoted 10 lines
bindings/rust/src/edge_event.rs new file +10 -0
+    }
+
+    /// Get the event sequence number specific to concerned line.
+    ///
+    /// Returns sequence number of the event relative to this line within the
+    /// lifetime of the associated line request.
+    pub fn get_line_seqno(&self) -> u64 {
+        unsafe { bindings::gpiod_edge_event_get_line_seqno(self.event) }
+    }
+}

AFAICT this object will not survive the parent buffer. Take a look at the current development version of the C++ bindings where I play with copy constructors to ensure that.

[snip]

I'll address the config objects once we actually have the final version in the C API.

Bart

#4 lore raw

Re: [PATCH V2 2/4] libgpiod: Add rust wrappers

Quoted 13 lines · Bartosz Golaszewski
Quoted 6 lines · Viresh Kumar

Add rust wrappers around FFI bindings to provide a convenient interface for the users.

---

Hi Viresh, thanks for the hard work.

Obviously this is not something we can merge yet but it's a good base to continue the work.

Quoted 7 lines

General comment on the naming convention:

The line from one of the examples: 'use libgpiod::chip::GpiodChip;' looks like it has a lot of redundancy in it. How about calling the crate gpiod and dropping the chip package entirely? Basically follow what C++ and python bindings do by having use gpiod::Chip etc.?

I'm of the opinion that a rust implementation would be better targetting the ioctls directly rather than libgpiod, as my Go library does and for essentially the same reasons.
To test that theory, and as an exercise to learn rust, I've been writing one, and so far I've been calling it gpiod :-|.

Since this crate targets libgpiod it would make sense for it to remain named libgpiod.

I agree with the chip::Chip repitition point - found the same thing myself. OTOH chip::Info and line::Info etc work well, and in rust you can always rename them as you import them into your namespace if it bothers you. So overall I'm still on the fence on this one.

Quoted 27 lines

[snip]

bindings/rust/src/chip.rs line 35

Quoted 7 lines
bindings/rust/src/chip.rs new file +7 -0
+
+impl GpiodChipInternal {
+    /// Find a GPIO chip by path.
+    pub(crate) fn open(path: &str) -> Result<Self> {
+        let chip = unsafe { bindings::gpiod_chip_open(path.as_ptr() as *const c_char) };
+        if chip.is_null() {
+            return Err(Error::OperationFailed("GpiodChip open", IoError::last()));

One of my concerns last time was error handling. Does this now translate the error codes from the underlying C code to some kind of rust errors? Can you elaborate on how that works? I see it always returns IoError. In my WIP C++ and Python code I try to translate the errnos into some meaningful exceptions - for instance EINVAL -> ::std::invalid_argument etc. Can we have a similar thing here?

[snip]

bindings/rust/src/chip.rs line 71

Quoted 7 lines
bindings/rust/src/chip.rs new file +7 -0
+
+    /// Get the GPIO chip name as represented in the kernel.
+    pub fn get_name(&self) -> Result<&str> {
+        // SAFETY: The string returned by libgpiod is guaranteed to live as long
+        // as the `struct GpiodChip`.
+        let name = unsafe { bindings::gpiod_chip_get_name(self.ichip.chip()) };
+        if name.is_null() {
Quoted 3 lines

This is not possible, the string can be empty at the very least but never null. Same for label and path.

On the subject of strings, are paths, names, labels, and consumers guaranteed to be valid UTF-8?
If not then failing to convert them using UTF-8 is not an error (not here but elsewhere in the patch).

Cheers,
Kent.

#5 lore raw

Re: [PATCH V2 2/4] libgpiod: Add rust wrappers

Quoted 3 lines · Kent Gibson

I'm of the opinion that a rust implementation would be better targetting the ioctls directly rather than libgpiod, as my Go library does and for essentially the same reasons.

I remember asking Linus initially about this and I was suggested to use libgpiod instead :)

Quoted 5 lines

To test that theory, and as an exercise to learn rust, I've been writing one, and so far I've been calling it gpiod :-|.

Since this crate targets libgpiod it would make sense for it to remain named libgpiod.

I agree.

Signature · 1 line
viresh
#6 lore raw

Re: [PATCH V2 2/4] libgpiod: Add rust wrappers

Quoted 11 lines · Viresh Kumar
Quoted 3 lines · Kent Gibson

I'm of the opinion that a rust implementation would be better targetting the ioctls directly rather than libgpiod, as my Go library does and for essentially the same reasons.

I remember asking Linus initially about this and I was suggested to use libgpiod instead :)

Quoted 3 lines

To test that theory, and as an exercise to learn rust, I've been writing one, and so far I've been calling it gpiod :-|.

In case you are interested, the first pass of my take on a Rust GPIO library[1] has finally gotten to the point of being core feature complete, and may be usable, although it is sorely lacking any integration tests. And the documentation is still pretty light, or even non-existant for github.
And the event interface is either polled or blocking - haven't gotten to an async interface yet.

Anyway, there are three crates there:
- gpiod-uapi provides a thin and safe wrapper around the ioctls and file reads, for both uAPI versions.
- gpiod provides a more idiomatic abstraction, and hides the uAPI version being used - unless you need v2 specific features.
- gpiodctl provides a binary that bundles all the gpio tools into one.

There are a couple of minimal example apps in the gpiod crate, in addition to the example that gpiodctl provides.

Cheers,
Kent.

[1] https://github.com/warthog618/gpiod-rs

#7 lore raw

Re: [PATCH V2 2/4] libgpiod: Add rust wrappers

Quoted 15 lines · Kent Gibson

In case you are interested, the first pass of my take on a Rust GPIO library[1] has finally gotten to the point of being core feature complete, and may be usable, although it is sorely lacking any integration tests. And the documentation is still pretty light, or even non-existant for github.
And the event interface is either polled or blocking - haven't gotten to an async interface yet.

Anyway, there are three crates there:
- gpiod-uapi provides a thin and safe wrapper around the ioctls and file reads, for both uAPI versions.
- gpiod provides a more idiomatic abstraction, and hides the uAPI version being used - unless you need v2 specific features.
- gpiodctl provides a binary that bundles all the gpio tools into one.

Quoted 7 lines

There are a couple of minimal example apps in the gpiod crate, in addition to the example that gpiodctl provides.

Cheers,
Kent.

[1] https://github.com/warthog618/gpiod-rs

Nice stuff, thanks Kent for sharing that.

I may end up using the libgpiod wrappers though, since I have already done the development using it and it is going to be low maintenance work for me :)

But it is really nice to have raw ioctl interface too for the GPIO devices.

Signature · 1 line
viresh
#8 lore raw

Re: [PATCH V2 2/4] libgpiod: Add rust wrappers

In reply to Bartosz Golaszewski
Quoted 7 lines · Bartosz Golaszewski

On Thu, Dec 2, 2021 at 12:23 PM Viresh Kumar <[email protected]> wrote:
General comment on the naming convention:

The line from one of the examples: 'use libgpiod::chip::GpiodChip;' looks like it has a lot of redundancy in it. How about calling the crate gpiod and dropping the chip package entirely? Basically follow what C++ and python bindings do by having use gpiod::Chip etc.?

That would mean I move back all the code to lib.rs, which makes it contain too much code. I am okay though if you want it that way.

Quoted 11 lines

bindings/rust/src/chip.rs line 35

Quoted 7 lines
bindings/rust/src/chip.rs new file +7 -0
+
+impl GpiodChipInternal {
+    /// Find a GPIO chip by path.
+    pub(crate) fn open(path: &str) -> Result<Self> {
+        let chip = unsafe { bindings::gpiod_chip_open(path.as_ptr() as *const c_char) };
+        if chip.is_null() {
+            return Err(Error::OperationFailed("GpiodChip open", IoError::last()));

One of my concerns last time was error handling. Does this now translate the error codes from the underlying C code to some kind of rust errors? Can you elaborate on how that works?

You can think of it like wrapping around or concatenation of errors in a way. The above line will return this now:

Err(libgpiod::Error::OperationFailed("GpiodChip open", IoError::last())) and will print like for EINVAL:

Operation GpiodChip open Failed: 22

The user crate can further wrap it around with their own error enums, but this error information wouldn't be lost and won't be subject to interpretation as well, as we return the real underlying value now. This is the right/best way to do it I suppose in Rust, this is what I was suggested to do for the vhost-device Rust crate I merged in rust-vmm project.

Quoted 1 line

I see it always returns IoError.

This returns the underling errno value.

Quoted 3 lines

In my WIP C++ and Python code I try to translate the errnos into some meaningful exceptions - for instance EINVAL -> ::std::invalid_argument etc. Can we have a similar thing here?

I would avoid translating errors into some interpretation here and rather pass on the real error number returned by kernel. That can be more useful in debugging the failures later.

Quoted 9 lines

bindings/rust/src/chip.rs line 71

Quoted 6 lines
bindings/rust/src/chip.rs new file +6 -0
+    /// Get the GPIO chip name as represented in the kernel.
+    pub fn get_name(&self) -> Result<&str> {
+        // SAFETY: The string returned by libgpiod is guaranteed to live as long
+        // as the `struct GpiodChip`.
+        let name = unsafe { bindings::gpiod_chip_get_name(self.ichip.chip()) };
+        if name.is_null() {

This is not possible, the string can be empty at the very least but never null. Same for label and path.

Miguel, what's your take on stuff like this ? I am not sure if we should just drop this check altogether.

Quoted 10 lines

bindings/rust/src/chip.rs line 162

Quoted 7 lines
bindings/rust/src/chip.rs new file +7 -0
+
+        match ret {
+            -1 => Err(Error::OperationFailed(
+                "GpiodChip info-event-wait",
+                IoError::last(),
+            )),
+            0 => Err(Error::OperationTimedOut),

Does it have to be an Error? It's pretty normal for an operation to time out, no?

Right, but it is still a timeout and should be represented like that. And it is up for the caller to act based on it. For example in the gpiomon example, we check if the returned value is OperationTimedOut or not, and we retry if it is.

Quoted 11 lines

bindings/rust/src/chip.rs line 170

Quoted 8 lines
bindings/rust/src/chip.rs new file +8 -0
+            _ => Ok(()),
+        }
+    }
+
+    /// Read a single line status change event from this chip. If no events are
+    /// pending, this function will block.
+    pub fn info_event_read(&self) -> Result<GpiodInfoEvent> {
+        GpiodInfoEvent::new(&self.ichip.clone())

Would you mind explaining what the clone() function does here to ichip and why it's needed.

Normally clone() is like copying of the objects, but in this case ichip is declared as Arc (Atomic Reference Counting).

Invoking clone on Arc produces a new Arc instance, which points to the same allocation on the heap as the source Arc, while increasing a reference count.

In rust, when the last reference of an object goes away, it is freed automatically.

Quoted 41 lines
Quoted 41 lines
bindings/rust/src/edge_event.rs new file +41 -0
+impl GpiodEdgeEvent {
+    /// Get an event stored in the buffer.
+    pub fn new(buffer: &GpiodEdgeEventBuffer, index: u64) -> Result<Self> {
+        let event = unsafe { bindings::gpiod_edge_event_buffer_get_event(buffer.buffer(), index) };
+        if event.is_null() {
+            return Err(Error::OperationFailed(
+                "GpiodEdgeEvent buffer-get-event",
+                IoError::last(),
+            ));
+        }
+
+        Ok(Self { event })
+    }
+
+    /// Get the event type.
+    pub fn get_event_type(&self) -> Result<EdgeEvent> {
+        EdgeEvent::new(unsafe { bindings::gpiod_edge_event_get_event_type(self.event) } as u32)
+    }
+
+    /// Get the timestamp of the event.
+    pub fn get_timestamp(&self) -> Duration {
+        Duration::from_nanos(unsafe { bindings::gpiod_edge_event_get_timestamp(self.event) })
+    }
+
+    /// Get the offset of the line on which the event was triggered.
+    pub fn get_line_offset(&self) -> u32 {
+        unsafe { bindings::gpiod_edge_event_get_line_offset(self.event) }
+    }
+
+    /// Get the global sequence number of this event.
+    ///
+    /// Returns sequence number of the event relative to all lines in the
+    /// associated line request.
+    pub fn get_global_seqno(&self) -> u64 {
+        unsafe { bindings::gpiod_edge_event_get_global_seqno(self.event) }
+    }
+
+    /// Get the event sequence number specific to concerned line.
+    ///
+    /// Returns sequence number of the event relative to this line within the
+    /// lifetime of the associated line request.
Quoted 8 lines

bindings/rust/src/edge_event.rs line 71

Quoted 4 lines
bindings/rust/src/edge_event.rs new file +4 -0
+    pub fn get_line_seqno(&self) -> u64 {
+        unsafe { bindings::gpiod_edge_event_get_line_seqno(self.event) }
+    }
+}

AFAICT this object will not survive the parent buffer. Take a look at the current development version of the C++ bindings where I play with copy constructors to ensure that.

Adding back some of the removed code as well.

+impl Drop for GpiodEdgeEvent {
+ /// Free the edge event object and release all associated resources.
+ fn drop(&mut self) {
+ unsafe { bindings::gpiod_edge_event_free(self.event) }
+ }
+}

While testing this I found a bug few days back and I wonder why gpiod_edge_event_free() even exists. The memory for "events" is allocated with the buffer and gpiod_edge_event_free() shouldn't try to free a part of that. This looks buggy.

Yes I realize that the edge event shouldn't exist past the buffer itself, I will try to fix it in a Rusty way (maybe with Arc or something else).

Signature · 1 line
viresh
#9 lore raw

Re: [PATCH V2 2/4] libgpiod: Add rust wrappers

Some drive-by comments. I haven't reviewed the code in detail.

Quoted 5 lines · Viresh Kumar
Quoted 3 lines · Bartosz Golaszewski

On Thu, Dec 2, 2021 at 12:23 PM Viresh Kumar <[email protected]> wrote:
General comment on the naming convention:

Quoted 7 lines
Quoted 4 lines

The line from one of the examples: 'use libgpiod::chip::GpiodChip;' looks like it has a lot of redundancy in it. How about calling the crate gpiod and dropping the chip package entirely? Basically follow what C++ and python bindings do by having use gpiod::Chip etc.?

That would mean I move back all the code to lib.rs, which makes it contain too much code. I am okay though if you want it that way.

You can keep things in different source files/modules and then expose them from lib.rs. For example, we have something like this in the kernel crate:

mod types;
pub use crate::types::{bit, bits_iter, Mode, Opaque, ScopeGuard};

So these are implemented in types.rs but exposed directly from the kernel crate as kernel::bit, kernel::bits_iter, etc.

Quoted 5 lines
Quoted 5 lines

bindings/rust/src/chip.rs line 33

Quoted 5 lines
bindings/rust/src/chip.rs new file +5 -0
+
+impl GpiodChipInternal {
+    /// Find a GPIO chip by path.
+    pub(crate) fn open(path: &str) -> Result<Self> {
+        let chip = unsafe { bindings::gpiod_chip_open(path.as_ptr() as *const c_char) };

Note that Rust strings are not necessarily null-terminated, so if gpiod_chip_open is expecting a null-terminated string, this isn't always going to work. In the kernel we have null-terminated strings as a CStr type, and they can be initialised with constants using the c_str! macro. You'll probably need something similar here if you want to avoid allocations (otherwise you can just allocate a null-terminated copy of path, use it, then free it).

Quoted 7 lines
Quoted 6 lines

bindings/rust/src/chip.rs line 35

Quoted 2 lines
bindings/rust/src/chip.rs new file +2 -0
+        if chip.is_null() {
+            return Err(Error::OperationFailed("GpiodChip open", IoError::last()));

One of my concerns last time was error handling. Does this now translate the error codes from the underlying C code to some kind of rust errors? Can you elaborate on how that works?

Quoted 7 lines

You can think of it like wrapping around or concatenation of errors in a way. The above line will return this now:

Err(libgpiod::Error::OperationFailed("GpiodChip open", IoError::last())) and will print like for EINVAL:

Operation GpiodChip open Failed: 22

Seems ok for printing. Is there a way to get the actual error code (22 in the example above) from the error returned? (As opposed to just a string.)

Quoted 25 lines

The user crate can further wrap it around with their own error enums, but this error information wouldn't be lost and won't be subject to interpretation as well, as we return the real underlying value now. This is the right/best way to do it I suppose in Rust, this is what I was suggested to do for the vhost-device Rust crate I merged in rust-vmm project.

Quoted 1 line

I see it always returns IoError.

This returns the underling errno value.

Quoted 3 lines

In my WIP C++ and Python code I try to translate the errnos into some meaningful exceptions - for instance EINVAL -> ::std::invalid_argument etc. Can we have a similar thing here?

I would avoid translating errors into some interpretation here and rather pass on the real error number returned by kernel. That can be more useful in debugging the failures later.

Quoted 7 lines
Quoted 6 lines
bindings/rust/src/chip.rs new file +6 -0
+    /// Get the GPIO chip name as represented in the kernel.
+    pub fn get_name(&self) -> Result<&str> {
+        // SAFETY: The string returned by libgpiod is guaranteed to live as long
+        // as the `struct GpiodChip`.
+        let name = unsafe { bindings::gpiod_chip_get_name(self.ichip.chip()) };
+        if name.is_null() {
Quoted 5 lines
Quoted 2 lines

This is not possible, the string can be empty at the very least but never null. Same for label and path.

Miguel, what's your take on stuff like this ? I am not sure if we should just drop this check altogether.

I'm obviously not Miguel :) But here's my take on this:

I think it's better to keep the check even if it's not possible today.

But if you choose to remove it, then you probably want to note in the next SAFETY comment for from_raw_parts (which I think you forgot to add) that name is always non-null.

Quoted 27 lines
Quoted 10 lines

bindings/rust/src/chip.rs line 162

Quoted 7 lines
bindings/rust/src/chip.rs new file +7 -0
+
+        match ret {
+            -1 => Err(Error::OperationFailed(
+                "GpiodChip info-event-wait",
+                IoError::last(),
+            )),
+            0 => Err(Error::OperationTimedOut),

Does it have to be an Error? It's pretty normal for an operation to time out, no?

Right, but it is still a timeout and should be represented like that. And it is up for the caller to act based on it. For example in the gpiomon example, we check if the returned value is OperationTimedOut or not, and we retry if it is.

Quoted 11 lines

bindings/rust/src/chip.rs line 170

Quoted 8 lines
bindings/rust/src/chip.rs new file +8 -0
+            _ => Ok(()),
+        }
+    }
+
+    /// Read a single line status change event from this chip. If no events are
+    /// pending, this function will block.
+    pub fn info_event_read(&self) -> Result<GpiodInfoEvent> {
+        GpiodInfoEvent::new(&self.ichip.clone())

Would you mind explaining what the clone() function does here to ichip and why it's needed.

Quoted 8 lines

Normally clone() is like copying of the objects, but in this case ichip is declared as Arc (Atomic Reference Counting).

Invoking clone on Arc produces a new Arc instance, which points to the same allocation on the heap as the source Arc, while increasing a reference count.

In rust, when the last reference of an object goes away, it is freed automatically.

Your description of Arc is accurate, but it doesn't look like the code does what you intended. Note that you're passing the _address_ of the cloned arc to GpiodInfoEvent::new, so what you're doing here is:
incrementing the refcount, calling GpiodInfoEvent::new, then immediately decrementing the count. If you want to keep the extra reference alive, you must transfer ownership of it (and hold on to it for as long as you want it alive).

Quoted 69 lines
Quoted 41 lines
Quoted 41 lines
bindings/rust/src/edge_event.rs new file +41 -0
+impl GpiodEdgeEvent {
+    /// Get an event stored in the buffer.
+    pub fn new(buffer: &GpiodEdgeEventBuffer, index: u64) -> Result<Self> {
+        let event = unsafe { bindings::gpiod_edge_event_buffer_get_event(buffer.buffer(), index) };
+        if event.is_null() {
+            return Err(Error::OperationFailed(
+                "GpiodEdgeEvent buffer-get-event",
+                IoError::last(),
+            ));
+        }
+
+        Ok(Self { event })
+    }
+
+    /// Get the event type.
+    pub fn get_event_type(&self) -> Result<EdgeEvent> {
+        EdgeEvent::new(unsafe { bindings::gpiod_edge_event_get_event_type(self.event) } as u32)
+    }
+
+    /// Get the timestamp of the event.
+    pub fn get_timestamp(&self) -> Duration {
+        Duration::from_nanos(unsafe { bindings::gpiod_edge_event_get_timestamp(self.event) })
+    }
+
+    /// Get the offset of the line on which the event was triggered.
+    pub fn get_line_offset(&self) -> u32 {
+        unsafe { bindings::gpiod_edge_event_get_line_offset(self.event) }
+    }
+
+    /// Get the global sequence number of this event.
+    ///
+    /// Returns sequence number of the event relative to all lines in the
+    /// associated line request.
+    pub fn get_global_seqno(&self) -> u64 {
+        unsafe { bindings::gpiod_edge_event_get_global_seqno(self.event) }
+    }
+
+    /// Get the event sequence number specific to concerned line.
+    ///
+    /// Returns sequence number of the event relative to this line within the
+    /// lifetime of the associated line request.
Quoted 8 lines

bindings/rust/src/edge_event.rs line 71

Quoted 4 lines
bindings/rust/src/edge_event.rs new file +4 -0
+    pub fn get_line_seqno(&self) -> u64 {
+        unsafe { bindings::gpiod_edge_event_get_line_seqno(self.event) }
+    }
+}

AFAICT this object will not survive the parent buffer. Take a look at the current development version of the C++ bindings where I play with copy constructors to ensure that.

Adding back some of the removed code as well.

bindings/rust/src/edge_event.rs new file +6 -0
+impl Drop for GpiodEdgeEvent {
+    /// Free the edge event object and release all associated resources.
+    fn drop(&mut self) {
+        unsafe { bindings::gpiod_edge_event_free(self.event) }
+    }
+}

While testing this I found a bug few days back and I wonder why gpiod_edge_event_free() even exists. The memory for "events" is allocated with the buffer and gpiod_edge_event_free() shouldn't try to free a part of that. This looks buggy.

Yes I realize that the edge event shouldn't exist past the buffer itself, I will try to fix it in a Rusty way (maybe with Arc or something else).

--
viresh

#10 lore raw

Re: [PATCH V2 2/4] libgpiod: Add rust wrappers

Quoted 13 lines · Wedson Almeida Filho

Some drive-by comments. I haven't reviewed the code in detail.

Quoted 10 lines · Viresh Kumar
Quoted 7 lines · Bartosz Golaszewski

On Thu, Dec 2, 2021 at 12:23 PM Viresh Kumar <[email protected]> wrote:
General comment on the naming convention:

The line from one of the examples: 'use libgpiod::chip::GpiodChip;' looks like it has a lot of redundancy in it. How about calling the crate gpiod and dropping the chip package entirely? Basically follow what C++ and python bindings do by having use gpiod::Chip etc.?

Quoted 6 lines
Quoted 2 lines

That would mean I move back all the code to lib.rs, which makes it contain too much code. I am okay though if you want it that way.

You can keep things in different source files/modules and then expose them from lib.rs. For example, we have something like this in the kernel crate:

Right I did that kind of thing somewhere else too. This should work better.

Quoted 6 lines

mod types;
pub use crate::types::{bit, bits_iter, Mode, Opaque, ScopeGuard};

So these are implemented in types.rs but exposed directly from the kernel crate as kernel::bit, kernel::bits_iter, etc.

Quoted 8 lines
Quoted 5 lines
Quoted 5 lines

bindings/rust/src/chip.rs line 33

Quoted 5 lines
bindings/rust/src/chip.rs new file +5 -0
+
+impl GpiodChipInternal {
+    /// Find a GPIO chip by path.
+    pub(crate) fn open(path: &str) -> Result<Self> {
+        let chip = unsafe { bindings::gpiod_chip_open(path.as_ptr() as *const c_char) };

Note that Rust strings are not necessarily null-terminated, so if gpiod_chip_open is expecting a null-terminated string,

I guess it would, though my code works just fine right now and I am not sure why, maybe by chance the next byte is zero or something :)

Quoted 6 lines

this isn't
always going to work. In the kernel we have null-terminated strings as a CStr type, and they can be initialised with constants using the c_str! macro. You'll probably need something similar here if you want to avoid allocations (otherwise you can just allocate a null-terminated copy of path, use it, then free it).

So I must do something like this then ?

    pub(crate) fn open(path: &str) -> Result<Self> {
        let chip = unsafe { bindings::gpiod_chip_open(CStr::from_ptr(path.as_ptr() as *const c_char)) };
Quoted 10 lines
Quoted 10 lines
Quoted 6 lines

bindings/rust/src/chip.rs line 35

Quoted 2 lines
bindings/rust/src/chip.rs new file +2 -0
+        if chip.is_null() {
+            return Err(Error::OperationFailed("GpiodChip open", IoError::last()));

One of my concerns last time was error handling. Does this now translate the error codes from the underlying C code to some kind of rust errors? Can you elaborate on how that works?

You can think of it like wrapping around or concatenation of errors in a way. The above line will return this now:

Quoted 8 lines
Quoted 4 lines

Err(libgpiod::Error::OperationFailed("GpiodChip open", IoError::last())) and will print like for EINVAL:

Operation GpiodChip open Failed: 22

Seems ok for printing. Is there a way to get the actual error code (22 in the example above) from the error returned? (As opposed to just a string.)

The entire thing is returned to the caller,
i.e. libgpiod::Error::OperationFailed("GpiodChip open", 22).

I think caller should be able to get the code out of it somehow, won't something like this work ?

let errno = match error {
libgpiod::Error::OperationFailed(msg, errno) => errno, _ => 0,
};

Quoted 7 lines
Quoted 7 lines
Quoted 7 lines
Quoted 6 lines
bindings/rust/src/chip.rs new file +6 -0
+    /// Get the GPIO chip name as represented in the kernel.
+    pub fn get_name(&self) -> Result<&str> {
+        // SAFETY: The string returned by libgpiod is guaranteed to live as long
+        // as the `struct GpiodChip`.
+        let name = unsafe { bindings::gpiod_chip_get_name(self.ichip.chip()) };
+        if name.is_null() {
Quoted 7 lines
Quoted 5 lines
Quoted 2 lines

This is not possible, the string can be empty at the very least but never null. Same for label and path.

Miguel, what's your take on stuff like this ? I am not sure if we should just drop this check altogether.

I'm obviously not Miguel :) But here's my take on this:

You are always welcome to provide feedback, I am the newbie here :)

Quoted 5 lines

I think it's better to keep the check even if it's not possible today.

But if you choose to remove it, then you probably want to note in the next SAFETY comment for from_raw_parts (which I think you forgot to add) that name is always non-null.

Right.

Quoted 17 lines
Quoted 8 lines
Quoted 7 lines

bindings/rust/src/chip.rs line 170

Quoted 4 lines
bindings/rust/src/chip.rs new file +4 -0
+    /// Read a single line status change event from this chip. If no events are
+    /// pending, this function will block.
+    pub fn info_event_read(&self) -> Result<GpiodInfoEvent> {
+        GpiodInfoEvent::new(&self.ichip.clone())

Would you mind explaining what the clone() function does here to ichip and why it's needed.

Quoted 8 lines

Normally clone() is like copying of the objects, but in this case ichip is declared as Arc (Atomic Reference Counting).

Invoking clone on Arc produces a new Arc instance, which points to the same allocation on the heap as the source Arc, while increasing a reference count.

In rust, when the last reference of an object goes away, it is freed automatically.

Quoted 7 lines

Your description of Arc is accurate, but it doesn't look like the code does what you intended. Note that you're passing the _address_ of the cloned arc to GpiodInfoEvent::new, so what you're doing here is:
incrementing the refcount, calling GpiodInfoEvent::new, then immediately decrementing the count. If you want to keep the extra reference alive, you must transfer ownership of it (and hold on to it for as long as you want it alive).

Ahh, this instance contains a clone by mistake here. It was correctly done at other places where I wanted the newly allocated structure to contain a reference to underlying ichip. I have removed clone() from two places where I was passing the reference instead.

Signature · 1 line
viresh
#11 lore raw

Re: [PATCH V2 2/4] libgpiod: Add rust wrappers

In reply to Viresh Kumar
Quoted 4 lines · Viresh Kumar
Quoted 3 lines · Bartosz Golaszewski

AFAICT this object will not survive the parent buffer. Take a look at the current development version of the C++ bindings where I play with copy constructors to ensure that.

I looked at bindings/cxx/line.cpp::make_line_event() now, is this what you were referring to here ?

Based on what I have understood until now, I think the edge event structure can be modified like this since it is only required to give us certain fixed values:

pub struct EdgeEvent {

-    event: *mut bindings::gpiod_edge_event,
+    event_type: GpiodEdgeEvent,
+    timestamp: Duration,
+    line_offset: u32,
+    global_seqno: u64,
+    line_seqno: u64

}

That way it won't depend on the struct EdgeEventBuffer anymore and can be freed after the buffer is freed.

Quoted 16 lines

Adding back some of the removed code as well.

bindings/rust/src/edge_event.rs new file +6 -0
+impl Drop for GpiodEdgeEvent {
+    /// Free the edge event object and release all associated resources.
+    fn drop(&mut self) {
+        unsafe { bindings::gpiod_edge_event_free(self.event) }
+    }
+}

While testing this I found a bug few days back and I wonder why gpiod_edge_event_free() even exists. The memory for "events" is allocated with the buffer and gpiod_edge_event_free() shouldn't try to free a part of that. This looks buggy.

Yes I realize that the edge event shouldn't exist past the buffer itself, I will try to fix it in a Rusty way (maybe with Arc or something else).

Signature · 1 line
viresh
#12 lore raw

Re: [PATCH V2 2/4] libgpiod: Add rust wrappers

In reply to Viresh Kumar
Quoted 1 line · Viresh Kumar

[snip]

Quoted 8 lines
bindings/rust/src/edge_event.rs new file +6 -0
+impl Drop for GpiodEdgeEvent {
+    /// Free the edge event object and release all associated resources.
+    fn drop(&mut self) {
+        unsafe { bindings::gpiod_edge_event_free(self.event) }
+    }
+}
Quoted 8 lines

While testing this I found a bug few days back and I wonder why gpiod_edge_event_free() even exists. The memory for "events" is allocated with the buffer and gpiod_edge_event_free() shouldn't try to free a part of that. This looks buggy.

Yes I realize that the edge event shouldn't exist past the buffer itself, I will try to fix it in a Rusty way (maybe with Arc or something else).

No, it's a different story altogether. In C the buffer allocates memory for events and when you "get" an event, you only have a pointer to the memory space in the buffer that you must not free. But you can "copy" an event with gpiod_edge_event_copy() which returns you a deep copy of the event that will survive the parent and that must be freed with gpiod_edge_event_free(). This is done so that by default we try to limit the number of allocations (as there can be a lot of events) unless the user decides to manually copy the event.

In C++ I used that mechanism together with the buffer's const event_get() and event's copy assignment operator. "Getting" an event returns a const reference to the event (still in buffer's memory) but copying it triggers a deep copy. The memory management is of course handled by the destructor.

This is not used in Python as speed is no longer a concern and we'd be creating new python objects anyway. But in Rust, I think it makes sense to reuse this mechanism.

Bart

#13 lore raw

Re: [PATCH V2 2/4] libgpiod: Add rust wrappers

Quoted 15 lines · Bartosz Golaszewski

No, it's a different story altogether. In C the buffer allocates memory for events and when you "get" an event, you only have a pointer to the memory space in the buffer that you must not free. But you can "copy" an event with gpiod_edge_event_copy() which returns you a deep copy of the event that will survive the parent and that must be freed with gpiod_edge_event_free(). This is done so that by default we try to limit the number of allocations (as there can be a lot of events) unless the user decides to manually copy the event.

In C++ I used that mechanism together with the buffer's const event_get() and event's copy assignment operator. "Getting" an event returns a const reference to the event (still in buffer's memory) but copying it triggers a deep copy. The memory management is of course handled by the destructor.

Quoted 3 lines

This is not used in Python as speed is no longer a concern and we'd be creating new python objects anyway. But in Rust, I think it makes sense to reuse this mechanism.

Ahh, what about this then, it just caches all the values when the event is requested ?

pub struct EdgeEvent {
    event_type: LineEdgeEvent,
    timestamp: Duration,
    line_offset: u32,
    global_seqno: u64,
    line_seqno: u64,
}
impl EdgeEvent {
    /// Get an event stored in the buffer.
    pub fn new(buffer: &EdgeEventBuffer, index: u64) -> Result<Self> {
        let event = unsafe { bindings::gpiod_edge_event_buffer_get_event(buffer.buffer(), index) };
        if event.is_null() {
            return Err(Error::OperationFailed(
                "Gpio EdgeEvent buffer-get-event",
                IoError::last(),
            ));
        }

        Ok(Self {
            event_type: LineEdgeEvent::new(unsafe {
                bindings::gpiod_edge_event_get_event_type(event)
            } as u32)?,

            timestamp: Duration::from_nanos(unsafe {
                bindings::gpiod_edge_event_get_timestamp(event)
            }),

            line_offset: unsafe { bindings::gpiod_edge_event_get_line_offset(event) },
            global_seqno: unsafe { bindings::gpiod_edge_event_get_global_seqno(event) },
            line_seqno: unsafe { bindings::gpiod_edge_event_get_line_seqno(event) },
        })
    }

    /// Get the event type.
    pub fn get_event_type(&self) -> LineEdgeEvent {
        self.event_type
    }

    /// Get the timestamp of the event.
    pub fn get_timestamp(&self) -> Duration {
        self.timestamp
    }

    /// Get the offset of the line on which the event was triggered.
    pub fn get_line_offset(&self) -> u32 {
        self.line_offset
    }

    /// Get the global sequence number of this event.
    ///
    /// Returns sequence number of the event relative to all lines in the
    /// associated line request.
    pub fn get_global_seqno(&self) -> u64 {
        self.global_seqno
    }

    /// Get the event sequence number specific to concerned line.
    ///
    /// Returns sequence number of the event relative to this line within the
    /// lifetime of the associated line request.
    pub fn get_line_seqno(&self) -> u64 {
        self.line_seqno
    }
}
Signature · 1 line
viresh
#14 lore raw

Re: [PATCH V2 2/4] libgpiod: Add rust wrappers

Quoted 24 lines · Viresh Kumar
Quoted 15 lines · Bartosz Golaszewski

No, it's a different story altogether. In C the buffer allocates memory for events and when you "get" an event, you only have a pointer to the memory space in the buffer that you must not free. But you can "copy" an event with gpiod_edge_event_copy() which returns you a deep copy of the event that will survive the parent and that must be freed with gpiod_edge_event_free(). This is done so that by default we try to limit the number of allocations (as there can be a lot of events) unless the user decides to manually copy the event.

In C++ I used that mechanism together with the buffer's const event_get() and event's copy assignment operator. "Getting" an event returns a const reference to the event (still in buffer's memory) but copying it triggers a deep copy. The memory management is of course handled by the destructor.

Quoted 3 lines

This is not used in Python as speed is no longer a concern and we'd be creating new python objects anyway. But in Rust, I think it makes sense to reuse this mechanism.

Ahh, what about this then, it just caches all the values when the event is requested ?

Quoted 8 lines
pub struct EdgeEvent {
    event_type: LineEdgeEvent,
    timestamp: Duration,
    line_offset: u32,
    global_seqno: u64,
    line_seqno: u64,
}

Then does it make sense to make it a binding to libgpiod? :)

I don't know Rust that well to be able to come up with an idea off the top of my head but I assume inheritance in one way or another exists in rust? Can you have an EdgeEvent interface implemented by two specialized structs, one that's stored in the buffer (stores a pointer to the internal event) and one that stores the copy (and frees it when it goes out of scope)?

Bart

#15 lore raw

Re: [PATCH V2 2/4] libgpiod: Add rust wrappers

Quoted 34 lines · Bartosz Golaszewski
Quoted 24 lines · Viresh Kumar
Quoted 15 lines · Bartosz Golaszewski

No, it's a different story altogether. In C the buffer allocates memory for events and when you "get" an event, you only have a pointer to the memory space in the buffer that you must not free. But you can "copy" an event with gpiod_edge_event_copy() which returns you a deep copy of the event that will survive the parent and that must be freed with gpiod_edge_event_free(). This is done so that by default we try to limit the number of allocations (as there can be a lot of events) unless the user decides to manually copy the event.

In C++ I used that mechanism together with the buffer's const event_get() and event's copy assignment operator. "Getting" an event returns a const reference to the event (still in buffer's memory) but copying it triggers a deep copy. The memory management is of course handled by the destructor.

Quoted 3 lines

This is not used in Python as speed is no longer a concern and we'd be creating new python objects anyway. But in Rust, I think it makes sense to reuse this mechanism.

Ahh, what about this then, it just caches all the values when the event is requested ?

Quoted 8 lines
pub struct EdgeEvent {
    event_type: LineEdgeEvent,
    timestamp: Duration,
    line_offset: u32,
    global_seqno: u64,
    line_seqno: u64,
}
Quoted 1 line

Then does it make sense to make it a binding to libgpiod? :)

Yes, because EdgeEvent::new() gets all these values from the internal C libgpiod library itself. We are going to make the same copy of data, either at the C library level, or here.

Quoted 5 lines

I don't know Rust that well to be able to come up with an idea off the top of my head but I assume inheritance in one way or another exists in rust? Can you have an EdgeEvent interface implemented by two specialized structs, one that's stored in the buffer (stores a pointer to the internal event)

This is what my initial patch had, right? It isn't safe. There is no way for libgpiod-rust to guarantee that the users don't access the EdgeEvent after the buffer is freed and this needs to be made safer at libgpiod itself.

Quoted 2 lines

and one that stores the copy (and frees it when
it goes out of scope)?

I don't understand what we will get out of such a scheme? Is there any benefit of getting these values via the pointer to the internal C event ?

Any user that wants to use the edge-event data, needs to call the edge-event APIs at least once and this is what the new version does, just calls them from new() itself.

Signature · 1 line
viresh
#16 lore raw

Re: [PATCH V2 2/4] libgpiod: Add rust wrappers

Quoted 48 lines · Viresh Kumar
Quoted 34 lines · Bartosz Golaszewski
Quoted 24 lines · Viresh Kumar
Quoted 15 lines · Bartosz Golaszewski

No, it's a different story altogether. In C the buffer allocates memory for events and when you "get" an event, you only have a pointer to the memory space in the buffer that you must not free. But you can "copy" an event with gpiod_edge_event_copy() which returns you a deep copy of the event that will survive the parent and that must be freed with gpiod_edge_event_free(). This is done so that by default we try to limit the number of allocations (as there can be a lot of events) unless the user decides to manually copy the event.

In C++ I used that mechanism together with the buffer's const event_get() and event's copy assignment operator. "Getting" an event returns a const reference to the event (still in buffer's memory) but copying it triggers a deep copy. The memory management is of course handled by the destructor.

Quoted 3 lines

This is not used in Python as speed is no longer a concern and we'd be creating new python objects anyway. But in Rust, I think it makes sense to reuse this mechanism.

Ahh, what about this then, it just caches all the values when the event is requested ?

Quoted 8 lines
pub struct EdgeEvent {
    event_type: LineEdgeEvent,
    timestamp: Duration,
    line_offset: u32,
    global_seqno: u64,
    line_seqno: u64,
}
Quoted 1 line

Then does it make sense to make it a binding to libgpiod? :)

Yes, because EdgeEvent::new() gets all these values from the internal C libgpiod library itself. We are going to make the same copy of data, either at the C library level, or here.

Quoted 5 lines

I don't know Rust that well to be able to come up with an idea off the top of my head but I assume inheritance in one way or another exists in rust? Can you have an EdgeEvent interface implemented by two specialized structs, one that's stored in the buffer (stores a pointer to the internal event)

Quoted 4 lines

This is what my initial patch had, right? It isn't safe. There is no way for libgpiod-rust to guarantee that the users don't access the EdgeEvent after the buffer is freed and this needs to be made safer at libgpiod itself.

I'm not sure it had the same thing. In libgpiod it's simple a matter of definition. The docs state that a pointer retrieved using gpiod_edge_event_buffer_get_event() must not be freed while one obtained with gpiod_edge_event_copy() does have to be disposed of. In C++ it's safe because either you hold a const reference to the object stored in the event buffer or you have copied it and now it will survive the parent meaning the user can access the event even after the buffer was deleted.

Quoted 6 lines
Quoted 2 lines

and one that stores the copy (and frees it when
it goes out of scope)?

I don't understand what we will get out of such a scheme? Is there any benefit of getting these values via the pointer to the internal C event ?

You avoid allocating memory for those objects. The memory exists in the C buffer. In order to keep a similar level of performance you'd need to replicate the behavior of the buffer in rust as well.

Bart

Quoted 3 lines

Any user that wants to use the edge-event data, needs to call the edge-event APIs at least once and this is what the new version does, just calls them from new() itself.

#17 lore raw

Re: [PATCH V2 2/4] libgpiod: Add rust wrappers

Quoted 24 lines · Bartosz Golaszewski
Quoted 10 lines · Viresh Kumar
Quoted 5 lines

I don't know Rust that well to be able to come up with an idea off the top of my head but I assume inheritance in one way or another exists in rust? Can you have an EdgeEvent interface implemented by two specialized structs, one that's stored in the buffer (stores a pointer to the internal event)

This is what my initial patch had, right? It isn't safe. There is no way for libgpiod-rust to guarantee that the users don't access the EdgeEvent after the buffer is freed and this needs to be made safer at libgpiod itself.

I'm not sure it had the same thing. In libgpiod it's simple a matter of definition. The docs state that a pointer retrieved using gpiod_edge_event_buffer_get_event() must not be freed while one obtained with gpiod_edge_event_copy() does have to be disposed of. In C++ it's safe because either you hold a const reference to the object stored in the event buffer or you have copied it and now it will survive the parent meaning the user can access the event even after the buffer was deleted.

Quoted 3 lines
Quoted 2 lines

and one that stores the copy (and frees it when
it goes out of scope)?

Quoted 7 lines
Quoted 3 lines

I don't understand what we will get out of such a scheme? Is there any benefit of getting these values via the pointer to the internal C event ?

You avoid allocating memory for those objects. The memory exists in the C buffer. In order to keep a similar level of performance you'd need to replicate the behavior of the buffer in rust as well.

I was aligned with all this, I just wanted to skip providing the interface where we use the event without copying as that has Safety issues. I will try to provide both the options now, lets see how it looks.

Signature · 1 line
viresh
#18 lore raw

Re: [PATCH V2 2/4] libgpiod: Add rust wrappers

In reply to Viresh Kumar
Quoted 3 lines · Viresh Kumar

Miguel, what's your take on stuff like this ? I am not sure if we should just drop this check altogether.

Given the C side documents that the pointer is valid (I think; since others are documented as "or NULL" etc., but it is a bit implicit), then avoiding the check is a possibility.

However, to answer this, one needs to understand the risk profile of the project, the stability of the C API, the performance expectations (e.g. is the call supposed to be in the hot path of clients?), etc.

When in doubt (or when there is no reason to not do the check), it is usually better to err on the safe side, specially if you aren't close to the maintainers of the API etc. But since you have access to Linus/Bartosz here, it is best to discuss this with them :) And, after all, you are already trusting the C side to some degree anyway, and it would be good to make sure both sides understand and clearly document the contracts of the functions so that no bindings break in the future (Rust or otherwise).

Cheers,
Miguel

#19 lore raw

Re: [PATCH V2 2/4] libgpiod: Add rust wrappers

Quoted 13 lines · Miguel Ojeda
Quoted 3 lines · Viresh Kumar

Miguel, what's your take on stuff like this ? I am not sure if we should just drop this check altogether.

Given the C side documents that the pointer is valid (I think; since others are documented as "or NULL" etc., but it is a bit implicit), then avoiding the check is a possibility.

However, to answer this, one needs to understand the risk profile of the project, the stability of the C API, the performance expectations (e.g. is the call supposed to be in the hot path of clients?), etc.

Quoted 8 lines

When in doubt (or when there is no reason to not do the check), it is usually better to err on the safe side, specially if you aren't close to the maintainers of the API etc. But since you have access to Linus/Bartosz here, it is best to discuss this with them :) And, after all, you are already trusting the C side to some degree anyway, and it would be good to make sure both sides understand and clearly document the contracts of the functions so that no bindings break in the future (Rust or otherwise).

And the null-checks are dropped, with an update to SAFETY comment :)

Signature · 1 line
viresh
#20 lore raw

Re: [PATCH V2 2/4] libgpiod: Add rust wrappers

In reply to Bartosz Golaszewski
Quoted 5 lines · Bartosz Golaszewski

The line from one of the examples: 'use libgpiod::chip::GpiodChip;' looks like it has a lot of redundancy in it. How about calling the crate gpiod and dropping the chip package entirely? Basically follow what C++ and python bindings do by having use gpiod::Chip etc.?

I agree on avoiding redundancy in names where possible (in particular, the Gpiod in GpiodChip does not buy much); however, note that modules in Rust are used a bit differently from C++ namespaces, so I think it would be a bad idea to drop the modules.

For instance, in Rust, one can be fairly liberal with use, while in C++ one has to worry about ADL and avoid them in global scope in headers. More importantly, Rust modules are used for encapsulation, while in C++ namespaces not really (except for the anonymous case or the """internal""" namespace hack in headers) and in Python privacy is even weaker. In particular, in Rust one can give each item (including free functions, variables, etc. not just types) inside a module different privacy, and importantly, they get special access privileges within their module.

So while in C++ is indeed fairly typical (though not universal, e.g. boost::asio::ip::tcp::socket comes to mind) to have a flatter layout with something like library::Type with privacy within the Type and hiding things in different TUs; in Rust it is fairly common to have a library::module::Item layout (e.g. std::vec::Vec from the standard library), where one puts everything related to the "module" concept inside that module.

In any case, one can also re-export things more conveniently for users, so that is an option too.

Quoted 2 lines

Does it have to be an Error? It's pretty normal for an operation to time out, no?

Hmm... what do you mean? Timing out is considered an error in many APIs, even if it is a frequent occurrence. After all, the request of "doing something in X time" failed (even if the underlying operation may even succeed).

Note that errors in Rust are normal control flow, i.e. they do not involve exceptions (if that is the concern).

Cheers,
Miguel

#21 lore raw

Re: [PATCH V2 2/4] libgpiod: Add rust wrappers

Quoted 2 lines · Miguel Ojeda

In any case, one can also re-export things more conveniently for users, so that is an option too.

This is what I have done now, and this is how users access them:

use libgpiod::{Chip, Edge, EdgeEventBuffer, Error, LineConfig, LineEdgeEvent, RequestConfig};

Signature · 1 line
viresh
#22 lore raw

PATCH v2 3/4 rust: Add examples

In reply to Viresh Kumar

Add examples for the usage of the rust bindings, quite similar to the ones in cxx bindings.

---
 bindings/rust/examples/gpiodetect.rs | 38 ++++++++++++
 bindings/rust/examples/gpiofind.rs   | 43 +++++++++++++
 bindings/rust/examples/gpioget.rs    | 45 ++++++++++++++
 bindings/rust/examples/gpioinfo.rs   | 90 ++++++++++++++++++++++++++++
 bindings/rust/examples/gpiomon.rs    | 73 ++++++++++++++++++++++
 bindings/rust/examples/gpioset.rs    | 55 +++++++++++++++++
 6 files changed, 344 insertions(+)
 create mode 100644 bindings/rust/examples/gpiodetect.rs
 create mode 100644 bindings/rust/examples/gpiofind.rs
 create mode 100644 bindings/rust/examples/gpioget.rs
 create mode 100644 bindings/rust/examples/gpioinfo.rs
 create mode 100644 bindings/rust/examples/gpiomon.rs
 create mode 100644 bindings/rust/examples/gpioset.rs
bindings/rust/examples/gpiodetect.rs new file +38 -0
new file mode 100644
index 000000000000..c4b2ef31469d
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
+//
+// Copyright 2021 Linaro Ltd. All Rights Reserved.
+//     Viresh Kumar <[email protected]>
+//
+// Simplified Rust implementation of gpiodetect tool.
+
+use std::env;
+use std::fs;
+use std::path::Path;
+
+use libgpiod::chip::GpiodChip;
+use libgpiod::gpiod_is_gpiochip_device;
+
+fn main() {
+    let args: Vec<String> = env::args().collect();
+    if args.len() > 1 {
+        println!("Usage: {}", args[0]);
+        return;
+    }
+
+    for entry in fs::read_dir(Path::new("/dev")).unwrap() {
+        let pathbuf = entry.unwrap().path();
+        let path = pathbuf.to_str().unwrap();
+
+        if gpiod_is_gpiochip_device(path) {
+            let chip = GpiodChip::open(path).unwrap();
+            let ngpio = chip.get_num_lines();
+
+            println!(
+                "{} [{}] ({})",
+                chip.get_name().unwrap(),
+                chip.get_label().unwrap(),
+                ngpio
+            );
+        }
+    }
+}
bindings/rust/examples/gpiofind.rs new file +43 -0

43 lines not on this page · raw message

bindings/rust/examples/gpioget.rs new file +45 -0

45 lines not on this page · raw message

bindings/rust/examples/gpioinfo.rs new file +90 -0

90 lines not on this page · raw message

bindings/rust/examples/gpiomon.rs new file +73 -0

73 lines not on this page · raw message

bindings/rust/examples/gpioset.rs new file +55 -0

55 lines not on this page · raw message

Signature · 1 line
2.31.1.272.g89b43f80a514
#23 lore raw

PATCH v2 4/4 rust: Integrate building of rust bindings with make

In reply to Viresh Kumar

Let make build rust bindings as well.

---
 README                    |  8 +++++---
 TODO                      |  8 --------
 bindings/Makefile.am      |  6 ++++++
 bindings/rust/Makefile.am | 29 +++++++++++++++++++++++++++++
 configure.ac              | 16 ++++++++++++++++
 5 files changed, 56 insertions(+), 11 deletions(-)
 create mode 100644 bindings/rust/Makefile.am
README +5 -3
index 814a0f161fd2..68b5d69f9b66 100644
@@ -119,9 +119,9 @@ TOOLS
 BINDINGS
 --------
 
-High-level, object-oriented bindings for C++ and python3 are provided. They
-can be enabled by passing --enable-bindings-cxx and --enable-bindings-python
-arguments respectively to configure.
+High-level, object-oriented bindings for C++, python3 and Rust are provided.
+They can be enabled by passing --enable-bindings-cxx, --enable-bindings-python
+and --enable-bindings-rust arguments respectively to configure.
 
 C++ bindings require C++11 support and autoconf-archive collection if building
 from git.
@@ -132,6 +132,8 @@ the PYTHON_CPPFLAGS and PYTHON_LIBS variables in order to point the build
 system to the correct locations. During native builds, the configure script
 can auto-detect the location of the development files.
 
+Rust bindings require cargo support.
+
 TESTING
 -------
 
TODO +0 -8
index 8bb4d8f3ad56..cf4fd7b4a962 100644
@@ -28,14 +28,6 @@ and is partially functional.
 
 ----------
 
-* implement rust bindings
-
-With Rust gaining popularity as a low-level system's language and the
-possibility of it making its way into the linux kernel, it's probably time to
-provide Rust bindings to libgpiod as part of the project.
-
-----------
-
 * implement a simple daemon for controlling GPIOs in C together with a client
   program
 
bindings/Makefile.am +6 -0
index 8f8c762f254f..004ae23dbc58 100644
@@ -14,3 +14,9 @@ if WITH_BINDINGS_PYTHON
 SUBDIRS += python
 
 endif
+
+if WITH_BINDINGS_RUST
+
+SUBDIRS += rust
+
+endif
bindings/rust/Makefile.am new file +29 -0
new file mode 100644
index 000000000000..ac05692b966d
@@ -0,0 +1,29 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2021 Viresh Kumar <[email protected]>
+
+LIBGPIOD_SRC =					\
+	Cargo.toml				\
+	build.rs				\
+	src/bindings.rs				\
+	src/chip.rs				\
+	src/edge_event.rs			\
+	src/event_buffer.rs			\
+	src/info_event.rs			\
+	src/lib.rs				\
+	src/line_config.rs			\
+	src/line_info.rs			\
+	src/line_request.rs			\
+	src/request_config.rs			\
+	examples/gpiodetect.rs			\
+	examples/gpiofind.rs			\
+	examples/gpioget.rs			\
+	examples/gpioinfo.rs			\
+	examples/gpiomon.rs			\
+	examples/gpioset.rs
+
+
+all: $(LIBGPIOD_SRC)
+	cargo build --lib --examples
+
+clean:
+	cargo clean
configure.ac +16 -0
index ce6de99c1edd..e83eca2a5e14 100644
@@ -199,6 +199,21 @@ then
 		[AC_SUBST(PYTHON_LIBS, [`$PYTHON-config --libs`])])
 fi
 
+AC_ARG_ENABLE([bindings-rust],
+	[AS_HELP_STRING([--enable-bindings-rust],[enable rust bindings [default=no]])],
+	[if test "x$enableval" = xyes; then with_bindings_rust=true; fi],
+	[with_bindings_rust=false])
+AM_CONDITIONAL([WITH_BINDINGS_RUST], [test "x$with_bindings_rust" = xtrue])
+
+if test "x$with_bindings_rust" = xtrue
+then
+	AC_CHECK_PROG([has_cargo], [cargo], [true], [false])
+	if test "x$has_cargo" = xfalse
+	then
+		AC_MSG_ERROR([cargo not found - needed for rust bindings])
+	fi
+fi
+
 AC_CHECK_PROG([has_doxygen], [doxygen], [true], [false])
 AM_CONDITIONAL([HAS_DOXYGEN], [test "x$has_doxygen" = xtrue])
 if test "x$has_doxygen" = xfalse
@@ -232,6 +247,7 @@ AC_CONFIG_FILES([Makefile
 		 bindings/python/Makefile
 		 bindings/python/examples/Makefile
 		 bindings/python/tests/Makefile
+		 bindings/rust/Makefile
 		 man/Makefile])
 
 AC_OUTPUT
Signature · 1 line
2.31.1.272.g89b43f80a514
#24 lore raw
In reply to Viresh Kumar
Quoted 10 lines · Viresh Kumar

Hi Bartosz,

This patch adds rust bindings for libgpiod v2.0, this is already partially tested with the virtio rust backend I am developing, which uses these to talk to the host kernel.

This is based of the next/post-libgpiod-2.0 branch.

I will be adding testing infrastructure later on, once other bindings are converted to use gpiosim.

Hi Bartosz,

I can see a lots of patches related to V2 version making there way into libgpiod, but it is a bit hard for me to follow them currently.

Just wanted to request you, if you can ping me once the API is stable enough and most of the V2 changes you planned are merged, so I can rebase my stuff over those and send for review.

Thanks.

Signature · 1 line
viresh
#25 lore raw
Quoted 18 lines · Viresh Kumar
Quoted 10 lines · Viresh Kumar

Hi Bartosz,

This patch adds rust bindings for libgpiod v2.0, this is already partially tested with the virtio rust backend I am developing, which uses these to talk to the host kernel.

This is based of the next/post-libgpiod-2.0 branch.

I will be adding testing infrastructure later on, once other bindings are converted to use gpiosim.

Hi Bartosz,

I can see a lots of patches related to V2 version making there way into libgpiod, but it is a bit hard for me to follow them currently.

Quoted 8 lines

Just wanted to request you, if you can ping me once the API is stable enough and most of the V2 changes you planned are merged, so I can rebase my stuff over those and send for review.

Thanks.

--
viresh

Hi Viresh!

Yes, I will! Just to give you a summary: the C API is mostly ready but I can't guarantee it won't change more. The C++ API needs another revision that should be posted today or tomorrow. The Python bindings are still WIP but will be ready soon. Once those three are agreed upon, we'll merge them into master as one (for bisectability), drop the next branch and work from there on new features (including Rust bindings). That's my plan at least.

Bart