libgpiod: Add Rust bindings
v6 cover libgpiod: Add Rust bindings
Hello,
Here is another version of rust bindings for libgpiod v2.0, based of the next/libgpiod-2.0 + the HTE patch from Bartosz [1].
Pushed here:
https://github.com/vireshk/libgpiod master
[I have pushed v5 and v4 there too, in case someone wants to look at the changes].
V5->V6:
- Updates according to the new line-settings interface.
- New file, line_settings.rs.
- Renamed 'enum Setting' as 'SettingVal' to avoid conflicting names, as we also have 'struct Settings' now.
- Support for HTE clock type.
- Implement 'Eq' for public structure/enums (reported by build).
- Remove 'SettingKindMap' and 'SettingMap' as they aren't required anymore.
- Updated tests based on new interface.
V4->V5:
- Arrange as workspace with crates for libgpiod-sys, libgpiod, gpiosim.
- Use static libgpiod and libgpiosim libraries instead of rebuilding again.
- Arrange in modules instead of flattened approach.
- New enums like Setting and SettingKind and new types based on them SettingMap and SettingKindMap.
- New property independent helpers for line_config, like set_prop_default().
- Improved tests/examples, new example for gpiowatch.
- Add pre-built bindings for gpiosim too.
- Many other changes.
V3->V4:
- Rebased on top of new changes, and made changes accordingly.
- Added rust integration tests with gpiosim.
- Found a kernel bug with tests, sent a patch for that to LKML.
V2->V3:
- Remove naming redundancy, users just need to do this now
use libgpiod:{Chip, Direction, LineConfig} now (Bartosz);
- Fix lifetime issues between event-buffer and edge-event modules, the event buffer is released after the last edge-event reference is dropped (Bartosz).
- Allow edge-event to be copied, and freed later (Bartosz).
- Add two separate rust crates, sys and wrapper (Gerard).
- Null-terminate the strings passed to libgpiod (Wedson).
- Drop unnecessary checks to validate string returned from chip:name/label/path.
- Fix SAFETY comments (Wedson).
- Drop unnecessary clone() instances (Bartosz).
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
[1] https://lore.kernel.org/all/[email protected]/
Viresh Kumar (8):
libgpiod: Add libgpiod-sys rust crate
libgpiod-sys: Add pre generated rust bindings
libgpiod: Add rust wrapper crate
libgpiod: Add rust examples
libgpiod: Add gpiosim rust crate
gpiosim: Add pre generated rust bindings
libgpiod: Add rust tests
libgpiod: Integrate building of rust bindings with make
.gitignore | 5 + README | 8 +- TODO | 8 - bindings/Makefile.am | 6 + bindings/rust/Cargo.toml | 7 + bindings/rust/Makefile.am | 18 + bindings/rust/gpiosim/Cargo.toml | 18 + bindings/rust/gpiosim/README.md | 11 + bindings/rust/gpiosim/build.rs | 43 + bindings/rust/gpiosim/src/bindings.rs | 180 +++ bindings/rust/gpiosim/src/lib.rs | 25 + bindings/rust/gpiosim/src/sim.rs | 323 +++++ bindings/rust/libgpiod-sys/Cargo.toml | 15 + bindings/rust/libgpiod-sys/README.md | 11 + bindings/rust/libgpiod-sys/build.rs | 41 + bindings/rust/libgpiod-sys/src/bindings.rs | 1173 +++++++++++++++++ bindings/rust/libgpiod-sys/src/lib.rs | 13 + bindings/rust/libgpiod/Cargo.toml | 16 + bindings/rust/libgpiod/examples/gpiodetect.rs | 30 + bindings/rust/libgpiod/examples/gpiofind.rs | 35 + bindings/rust/libgpiod/examples/gpioget.rs | 42 + bindings/rust/libgpiod/examples/gpioinfo.rs | 95 ++ bindings/rust/libgpiod/examples/gpiomon.rs | 73 + bindings/rust/libgpiod/examples/gpioset.rs | 60 + bindings/rust/libgpiod/examples/gpiowatch.rs | 54 + bindings/rust/libgpiod/src/chip.rs | 253 ++++ bindings/rust/libgpiod/src/edge_event.rs | 102 ++ bindings/rust/libgpiod/src/event_buffer.rs | 90 ++ bindings/rust/libgpiod/src/info_event.rs | 68 + bindings/rust/libgpiod/src/lib.rs | 471 +++++++ bindings/rust/libgpiod/src/line_config.rs | 118 ++ bindings/rust/libgpiod/src/line_info.rs | 180 +++ bindings/rust/libgpiod/src/line_request.rs | 246 ++++ bindings/rust/libgpiod/src/line_settings.rs | 277 ++++ bindings/rust/libgpiod/src/request_config.rs | 96 ++ bindings/rust/libgpiod/tests/chip.rs | 97 ++ bindings/rust/libgpiod/tests/common/config.rs | 134 ++ bindings/rust/libgpiod/tests/common/mod.rs | 10 + bindings/rust/libgpiod/tests/edge_event.rs | 294 +++++ bindings/rust/libgpiod/tests/info_event.rs | 125 ++ bindings/rust/libgpiod/tests/line_config.rs | 105 ++ bindings/rust/libgpiod/tests/line_info.rs | 279 ++++ bindings/rust/libgpiod/tests/line_request.rs | 519 ++++++++ bindings/rust/libgpiod/tests/line_settings.rs | 226 ++++ .../rust/libgpiod/tests/request_config.rs | 38 + configure.ac | 16 + 46 files changed, 6043 insertions(+), 11 deletions(-) create mode 100644 bindings/rust/Cargo.toml create mode 100644 bindings/rust/Makefile.am create mode 100644 bindings/rust/gpiosim/Cargo.toml create mode 100644 bindings/rust/gpiosim/README.md create mode 100644 bindings/rust/gpiosim/build.rs create mode 100644 bindings/rust/gpiosim/src/bindings.rs create mode 100644 bindings/rust/gpiosim/src/lib.rs create mode 100644 bindings/rust/gpiosim/src/sim.rs create mode 100644 bindings/rust/libgpiod-sys/Cargo.toml create mode 100644 bindings/rust/libgpiod-sys/README.md create mode 100644 bindings/rust/libgpiod-sys/build.rs create mode 100644 bindings/rust/libgpiod-sys/src/bindings.rs create mode 100644 bindings/rust/libgpiod-sys/src/lib.rs create mode 100644 bindings/rust/libgpiod/Cargo.toml create mode 100644 bindings/rust/libgpiod/examples/gpiodetect.rs create mode 100644 bindings/rust/libgpiod/examples/gpiofind.rs create mode 100644 bindings/rust/libgpiod/examples/gpioget.rs create mode 100644 bindings/rust/libgpiod/examples/gpioinfo.rs create mode 100644 bindings/rust/libgpiod/examples/gpiomon.rs create mode 100644 bindings/rust/libgpiod/examples/gpioset.rs create mode 100644 bindings/rust/libgpiod/examples/gpiowatch.rs create mode 100644 bindings/rust/libgpiod/src/chip.rs create mode 100644 bindings/rust/libgpiod/src/edge_event.rs create mode 100644 bindings/rust/libgpiod/src/event_buffer.rs create mode 100644 bindings/rust/libgpiod/src/info_event.rs create mode 100644 bindings/rust/libgpiod/src/lib.rs create mode 100644 bindings/rust/libgpiod/src/line_config.rs create mode 100644 bindings/rust/libgpiod/src/line_info.rs create mode 100644 bindings/rust/libgpiod/src/line_request.rs create mode 100644 bindings/rust/libgpiod/src/line_settings.rs create mode 100644 bindings/rust/libgpiod/src/request_config.rs create mode 100644 bindings/rust/libgpiod/tests/chip.rs create mode 100644 bindings/rust/libgpiod/tests/common/config.rs create mode 100644 bindings/rust/libgpiod/tests/common/mod.rs create mode 100644 bindings/rust/libgpiod/tests/edge_event.rs create mode 100644 bindings/rust/libgpiod/tests/info_event.rs create mode 100644 bindings/rust/libgpiod/tests/line_config.rs create mode 100644 bindings/rust/libgpiod/tests/line_info.rs create mode 100644 bindings/rust/libgpiod/tests/line_request.rs create mode 100644 bindings/rust/libgpiod/tests/line_settings.rs create mode 100644 bindings/rust/libgpiod/tests/request_config.rs
Signature · 1 line
2.31.1.272.g89b43f80a514
PATCH v6 1/8 libgpiod: Add libgpiod-sys rust crate
This adds libgpiod-sys rust crate, which provides FFI (foreign function interface) bindings for libgpiod APIs.
--- .gitignore | 5 ++++ bindings/rust/Cargo.toml | 5 ++++ bindings/rust/libgpiod-sys/Cargo.toml | 15 ++++++++++ bindings/rust/libgpiod-sys/build.rs | 41 +++++++++++++++++++++++++++ bindings/rust/libgpiod-sys/src/lib.rs | 13 +++++++++ 5 files changed, 79 insertions(+) create mode 100644 bindings/rust/Cargo.toml create mode 100644 bindings/rust/libgpiod-sys/Cargo.toml create mode 100644 bindings/rust/libgpiod-sys/build.rs create mode 100644 bindings/rust/libgpiod-sys/src/lib.rs
.gitignore +5 -0
@@ -35,3 +35,8 @@ stamp-h1 # profiling *.gcda *.gcno + +# Added by cargo + +target +Cargo.lock
bindings/rust/Cargo.toml new file +5 -0
@@ -0,0 +1,5 @@ +[workspace] + +members = [ + "libgpiod-sys", +]
bindings/rust/libgpiod-sys/Cargo.toml new file +15 -0
@@ -0,0 +1,15 @@ +[package] +name = "libgpiod-sys" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +[features] +generate = [ "bindgen" ] + +[build-dependencies] +bindgen = { version = "0.59.1", optional = true } +cc = "1.0.46"
bindings/rust/libgpiod-sys/build.rs new file +41 -0
41 lines not on this page · raw message
bindings/rust/libgpiod-sys/src/lib.rs new file +13 -0
@@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0 + +#[allow(non_camel_case_types, non_upper_case_globals)] +#[cfg_attr(test, allow(deref_nullptr, non_snake_case))] + +mod bindings_raw { +#[cfg(feature = "generate" )] +include! (concat! (env! ("OUT_DIR" ),"/bindings.rs" )); + +#[cfg(not(feature = "generate" ))] +include! ("bindings.rs" ); +} +pub use bindings_raw::*;
Signature · 1 line
2.31.1.272.g89b43f80a514
PATCH v6 2/8 libgpiod-sys: Add pre generated rust bindings
In reply to Viresh KumarThis adds a copy of pre generated bindings and adds the suggested way of updating those in README.
--- bindings/rust/libgpiod-sys/README.md | 11 + bindings/rust/libgpiod-sys/src/bindings.rs | 1173 ++++++++++++++++++++ 2 files changed, 1184 insertions(+) create mode 100644 bindings/rust/libgpiod-sys/README.md create mode 100644 bindings/rust/libgpiod-sys/src/bindings.rs
bindings/rust/libgpiod-sys/README.md new file +11 -0
@@ -0,0 +1,11 @@ +# Generated libgpiod-sys Rust FFI bindings +Automatically generated Rust FFI bindings via + [bindgen](https://github.com/rust-lang/rust-bindgen). + +## Updating bindings +1. Clone the source from + <https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/> +2. run `cd libgpiod/bindings/rust/libgpiod-sys/` +2. run `cargo build --features generate` +3. Copy the bindings 'cp ../target/debug/build/libgpiod-sys-###/out/bindings.rs src/bindings.rs' +4. Commit changes in `src/bindings.rs`
bindings/rust/libgpiod-sys/src/bindings.rs new file +1173 -0
1,173 lines not on this page · raw message
Signature · 1 line
2.31.1.272.g89b43f80a514
PATCH v6 3/8 libgpiod: Add rust wrapper crate
In reply to Viresh KumarAdd rust wrapper crate, around the libpiod-sys crate added earlier, to provide a convenient interface for the users.
--- bindings/rust/Cargo.toml | 1 + bindings/rust/libgpiod/Cargo.toml | 13 + bindings/rust/libgpiod/src/chip.rs | 253 ++++++++++ bindings/rust/libgpiod/src/edge_event.rs | 102 ++++ bindings/rust/libgpiod/src/event_buffer.rs | 90 ++++ bindings/rust/libgpiod/src/info_event.rs | 68 +++ bindings/rust/libgpiod/src/lib.rs | 471 +++++++++++++++++++ bindings/rust/libgpiod/src/line_config.rs | 118 +++++ bindings/rust/libgpiod/src/line_info.rs | 180 +++++++ bindings/rust/libgpiod/src/line_request.rs | 246 ++++++++++ bindings/rust/libgpiod/src/line_settings.rs | 277 +++++++++++ bindings/rust/libgpiod/src/request_config.rs | 96 ++++ 12 files changed, 1915 insertions(+) create mode 100644 bindings/rust/libgpiod/Cargo.toml create mode 100644 bindings/rust/libgpiod/src/chip.rs create mode 100644 bindings/rust/libgpiod/src/edge_event.rs create mode 100644 bindings/rust/libgpiod/src/event_buffer.rs create mode 100644 bindings/rust/libgpiod/src/info_event.rs create mode 100644 bindings/rust/libgpiod/src/lib.rs create mode 100644 bindings/rust/libgpiod/src/line_config.rs create mode 100644 bindings/rust/libgpiod/src/line_info.rs create mode 100644 bindings/rust/libgpiod/src/line_request.rs create mode 100644 bindings/rust/libgpiod/src/line_settings.rs create mode 100644 bindings/rust/libgpiod/src/request_config.rs
bindings/rust/Cargo.toml +1 -0
@@ -2,4 +2,5 @@ members = [ "libgpiod-sys", + "libgpiod" ]
bindings/rust/libgpiod/Cargo.toml new file +13 -0
@@ -0,0 +1,13 @@ +[package] +name = "libgpiod" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +intmap = "2.0.0" +libc = ">=0.2.39" +libgpiod-sys = { path = "../libgpiod-sys" } +thiserror = "1.0" +vmm-sys-util = "=0.10.0"
bindings/rust/libgpiod/src/chip.rs new file +253 -0
253 lines not on this page · raw message
bindings/rust/libgpiod/src/edge_event.rs new file +102 -0
102 lines not on this page · raw message
bindings/rust/libgpiod/src/event_buffer.rs new file +90 -0
90 lines not on this page · raw message
bindings/rust/libgpiod/src/info_event.rs new file +68 -0
68 lines not on this page · raw message
bindings/rust/libgpiod/src/lib.rs new file +471 -0
471 lines not on this page · raw message
bindings/rust/libgpiod/src/line_config.rs new file +118 -0
118 lines not on this page · raw message
bindings/rust/libgpiod/src/line_info.rs new file +180 -0
180 lines not on this page · raw message
bindings/rust/libgpiod/src/line_request.rs new file +246 -0
246 lines not on this page · raw message
bindings/rust/libgpiod/src/line_settings.rs new file +277 -0
277 lines not on this page · raw message
bindings/rust/libgpiod/src/request_config.rs new file +96 -0
96 lines not on this page · raw message
Signature · 1 line
2.31.1.272.g89b43f80a514
Re: [PATCH V6 3/8] libgpiod: Add rust wrapper crate
Quoted 6 lines · Viresh Kumar
Add rust wrapper crate, around the libpiod-sys crate added earlier, to provide a convenient interface for the users.
Signed-off-by Viresh Kumar <[email protected]>---
Hey Viresh!
Thanks for being patient with me. :)
I obviously cannot give a comprehensive review of Rust code as you know it much better than I do but there's one thing that bothers me at first glance. Please see below.
[snip]
Quoted 75 lines
bindings/rust/libgpiod/src/event_buffer.rs new file +69
-0@@ -0,0 +1,90 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar <[email protected]> + +use std::os::raw::c_ulong; +use std::sync::Arc ; + +use vmm_sys_util::errno::Error as Errno ; + +use super ::{edge, gpiod,Error ,OperationType ,Result }; + +/// Line edge events buffer +#[derive( Clone , Debug , Eq , PartialEq )] +pub (crate )struct BufferInternal { + buffer: *mut gpiod::gpiod_edge_event_buffer , +} + +impl BufferInternal { +/// Create a new edge event buffer. +/// +/// If capacity equals 0, it will be set to a default value of 64. If +/// capacity is larger than 1024, it will be limited to 1024. +pub fn new (capacity:usize ) ->Result <Self > { +let buffer =unsafe { gpiod::gpiod_edge_event_buffer_new (capacityas c_ulong ) }; +if buffer.is_null () { +return Err (Error ::OperationFailed ( +OperationType ::EdgeEventBufferNew , +Errno ::last (), + )); + } + +Ok (Self { buffer }) + } + +/// Private helper, Returns gpiod_edge_event_buffer +pub (crate )fn buffer (&self) -> *mut gpiod::gpiod_edge_event_buffer { + self.buffer + } +} + +impl Drop for BufferInternal { +/// Free the edge event buffer and release all associated resources. +fn drop (&mut self) { +unsafe { gpiod::gpiod_edge_event_buffer_free (self.buffer) }; + } +} + +/// Line edge events buffer +#[derive( Clone , Debug , Eq , PartialEq )] +pub struct Buffer { + ibuffer:Arc <BufferInternal >, +} + +impl Buffer { +/// Create a new edge event buffer. +/// +/// If capacity equals 0, it will be set to a default value of 64. If +/// capacity is larger than 1024, it will be limited to 1024. +pub fn new (capacity:usize ) ->Result <Self > { +Ok (Self { + ibuffer:Arc ::new (BufferInternal ::new (capacity)?), + }) + } + +/// Private helper, Returns gpiod_edge_event_buffer +pub (crate )fn buffer (&self) -> *mut gpiod::gpiod_edge_event_buffer { + self.ibuffer.buffer () + }
bindings/rust/libgpiod/src/event_buffer.rs line 79
Quoted 10 lines
bindings/rust/libgpiod/src/event_buffer.rs new file +10
-0+ +/// Get the capacity of the event buffer. +pub fn capacity (&self) ->usize { +unsafe { gpiod::gpiod_edge_event_buffer_get_capacity (self.buffer ())as usize } + } + +/// Read an event stored in the buffer. +pub fn event (&self, index:u64 ) ->Result <edge::Event > { + edge::Event ::new (&self.ibuffer, index) + }
In Event's new() you call gpiod_edge_event_buffer_get_event() which returns a pointer to an event stored inside the buffer. There's also the event_clone() function that calls gpiod_edge_event_copy() but I don't see it called anywhere. Should users pay attention to the lifetime of the buffer storing the event? Because IMO if the buffer goes out of scope, the program will crash attempting to access the event.
In C++ the events in the buffer can only be accessed as const edge_event& so it's clear we're only holding a reference to an object existing somewhere else. Internally, the object stored in the buffer doesn't copy the edge event, only holds a C pointer to the event structure in struct gpiod_edge_event_buffer. Only upon calling edge_event& edge_event::operator=(const edge_event& other) will we trigger a copy.
This way doing for (const auto& event: buffer) allows us to iterate over events without doing any additional allocations.
Can we reproduce that behavior in Rust? For instance, the above function could return a borrowed reference and then we could have some interface to trigger the copy? Maybe do an implicit copy like in C++? I don't know if that's possible though.
Bartosz
Quoted 11 lines
bindings/rust/libgpiod/src/event_buffer.rs new file +11
-0+ +/// Get the number of events the buffer contains. +pub fn len (&self) ->usize { +unsafe { gpiod::gpiod_edge_event_buffer_get_num_events (self.buffer ())as usize } + } + +/// Check if buffer is empty. +pub fn is_empty (&self) ->bool { + self.len () ==0 + } +}
[snip]
Re: [PATCH V6 3/8] libgpiod: Add rust wrapper crate
Hi Bartosz,
Quoted 1 line · Bartosz Golaszewski
Thanks for being patient with me. :)
I should thank you for being patient with me :)
Quoted 9 lines
bindings/rust/libgpiod/src/event_buffer.rs line 79
Quoted 4 lines
bindings/rust/libgpiod/src/event_buffer.rs new file +4
-0+/// Read an event stored in the buffer. +pub fn event (&self, index:u64 ) ->Result <edge::Event > { + edge::Event ::new (&self.ibuffer, index) + }In Event's
new()you callgpiod_edge_event_buffer_get_event()which returns a pointer to an event stored inside the buffer. There's also theevent_clone()function that callsgpiod_edge_event_copy()but I don't see it called anywhere.
I thought that is required if the user is concerned that the buffer will be overwritten by a new event, hence make a copy. Is that understanding correct? I didn't use it here for that reason, but it can be useful to the user expecting a lot of events.
Quoted 2 lines
Should users pay attention to the
lifetime of the buffer storing the event?
No.
Quoted 3 lines
Because IMO if the buffer
goes out of scope, the program will crash attempting to access the event.
This is where Rust's memory safety comes in. If you see the design of the 'structure Event', it saves a reference to the 'struct BufferInternal'. The drop() implementation of BufferInternal calls:gpiod_edge_event_buffer_free(), but it won't get called unless all the references to it have gone out of scope. So untill the time user is still using any of the events, the buffer won't get freed. So we will never have invalid reference issue here.
Quoted 8 lines
In C++ the events in the buffer can only be accessed as const edge_event& so it's clear we're only holding a reference to an object existing somewhere else. Internally, the object stored in the buffer doesn't copy the edge event, only holds a C pointer to the event structure in struct
gpiod_edge_event_buffer. Only upon calling edge_event& edge_event::operator=(const edge_event& other) will we trigger a copy.
Quoted 7 lines
This way doing
for (const auto& event: buffer)allows us to iterate over events without doing any additional allocations.Can we reproduce that behavior in Rust? For instance, the above function could return a borrowed reference and then we could have some interface to trigger the copy? Maybe do an implicit copy like in C++? I don't know if that's possible though.
So here in Rust, you clone() normally to make a copy, but the standard clone() declaration can't have an error returned and so I had to name it event_clone().
--
Viresh
Re: [PATCH V6 3/8] libgpiod: Add rust wrapper crate
Quoted 19 lines · Viresh Kumar
Hi Bartosz,
Quoted 1 line · Bartosz Golaszewski
Thanks for being patient with me. :)
I should thank you for being patient with me :)
Quoted 9 lines
bindings/rust/libgpiod/src/event_buffer.rs line 79
Quoted 4 lines
bindings/rust/libgpiod/src/event_buffer.rs new file +4
-0+/// Read an event stored in the buffer. +pub fn event (&self, index:u64 ) ->Result <edge::Event > { + edge::Event ::new (&self.ibuffer, index) + }In Event's
new()you callgpiod_edge_event_buffer_get_event()which returns a pointer to an event stored inside the buffer. There's also theevent_clone()function that callsgpiod_edge_event_copy()but I don't see it called anywhere.
Quoted 5 lines
I thought that is required if the user is concerned that the buffer will be overwritten by a new event, hence make a copy. Is that understanding correct? I didn't use it here for that reason, but it can be useful to the user expecting a lot of events.
Yes, that's one of the use-cases. Copy an event from a buffer before reading more into it.
Quoted 9 lines
Quoted 2 lines
Should users pay attention to the
lifetime of the buffer storing the event?No.
Quoted 3 lines
Because IMO if the buffer
goes out of scope, the program will crash attempting to access the event.
Quoted 7 lines
This is where Rust's memory safety comes in. If you see the design of the 'structure Event', it saves a reference to the 'struct BufferInternal'. The
drop()implementation of BufferInternal calls:gpiod_edge_event_buffer_free(), but it won't get called unless all the references to it have gone out of scope. So untill the time user is still using any of the events, the buffer won't get freed. So we will never have invalid reference issue here.
You've got to help me out here, just looking at the code doesn't make things clear for me.
Am I right to understand that each Event holds a reference to the BufferInternal object and it will only be destroyed when the last event is dropped? If we read new events into the buffer, does that create a new BufferInternal? Is that efficient? Aren't we allocating memory for each new BufferInternal at every read?
Quoted 12 lines
Quoted 12 lines
In C++ the events in the buffer can only be accessed as const edge_event& so it's clear we're only holding a reference to an object existing somewhere else. Internally, the object stored in the buffer doesn't copy the edge event, only holds a C pointer to the event structure in struct
gpiod_edge_event_buffer. Only upon calling edge_event& edge_event::operator=(const edge_event& other) will we trigger a copy.This way doing
for (const auto& event: buffer)allows us to iterate over events without doing any additional allocations.
Quoted 9 lines
Quoted 4 lines
Can we reproduce that behavior in Rust? For instance, the above function could return a borrowed reference and then we could have some interface to trigger the copy? Maybe do an implicit copy like in C++? I don't know if that's possible though.
So here in Rust, you
clone()normally to make a copy, but the standardclone()declaration can't have an error returned and so I had to name itevent_clone().
Bart
Re: [PATCH V6 3/8] libgpiod: Add rust wrapper crate
Quoted 10 lines · Bartosz Golaszewski
Quoted 7 lines · Viresh Kumar
This is where Rust's memory safety comes in. If you see the design of the 'structure Event', it saves a reference to the 'struct BufferInternal'. The
drop()implementation of BufferInternal calls:gpiod_edge_event_buffer_free(), but it won't get called unless all the references to it have gone out of scope. So untill the time user is still using any of the events, the buffer won't get freed. So we will never have invalid reference issue here.
Quoted 2 lines
You've got to help me out here, just looking at the code doesn't make things clear for me.
Just to clarify, Buffer is just a wrapper over BufferInternal, you can think of them as the same entity for simplicity.
Quoted 3 lines
Am I right to understand that each Event holds a reference to the BufferInternal object and it will only be destroyed when the last event is dropped?
Yes. But the buffer variable itself too holds a reference to the Buffer. Even if all the events are dropped, the buffer won't get free unless the buffer variable gets dropped too.
Quoted 2 lines
If we read new events into the buffer, does that
create a new BufferInternal?
No. It uses the existing buffer only, that we created using Buffer::new().
Quoted 2 lines
Is that efficient? Aren't we allocating
memory for each new BufferInternal at every read?
No, we reuse the same buffer for all the reads.
If you see the gpiomon.rs example, it shows how this is being done. The buffer (and internal buffer too) is created only once in line 42:
let buffer = edge::event::Buffer::new(1)?;
This allocates the memory for buffer only once, after that we continue reading from the same buffer.
The BufferInternal structure is enclosed within an Arc<> instance, which is "Atomically Reference Counted", very much like kref_get/put in kernel.
Unless the last reference is dropped, it stays.
Hope I was able to clarify this here.
Re: [PATCH V6 3/8] libgpiod: Add rust wrapper crate
Quoted 22 lines · Viresh Kumar
Quoted 10 lines · Bartosz Golaszewski
Quoted 7 lines · Viresh Kumar
This is where Rust's memory safety comes in. If you see the design of the 'structure Event', it saves a reference to the 'struct BufferInternal'. The
drop()implementation of BufferInternal calls:gpiod_edge_event_buffer_free(), but it won't get called unless all the references to it have gone out of scope. So untill the time user is still using any of the events, the buffer won't get freed. So we will never have invalid reference issue here.Quoted 2 lines
You've got to help me out here, just looking at the code doesn't make things clear for me.
Just to clarify, Buffer is just a wrapper over BufferInternal, you can think of them as the same entity for simplicity.
Quoted 3 lines
Am I right to understand that each Event holds a reference to the BufferInternal object and it will only be destroyed when the last event is dropped?
Quoted 9 lines
Yes. But the buffer variable itself too holds a reference to the Buffer. Even if all the events are dropped, the buffer won't get free unless the buffer variable gets dropped too.
Quoted 2 lines
If we read new events into the buffer, does that
create a new BufferInternal?No. It uses the existing buffer only, that we created using
Buffer::new().
So what happens if I do this:
let buf = edge::event::Buffer::new(10)?;
let request = chip.request_lines(&rconfig, &lconfig)?; let count = request.read_edge_events(&buffer)?; let event = buffer.event(0);
let count = request.read_edge_events(&buffer)?; println!("line: {}", event.line_offset());
?
Quoted 19 lines
Quoted 2 lines
Is that efficient? Aren't we allocating
memory for each new BufferInternal at every read?No, we reuse the same buffer for all the reads.
If you see the
gpiomon.rsexample, it shows how this is being done. The buffer (and internal buffer too) is created only once in line 42:let buffer =
edge::event::Buffer::new(1)?;This allocates the memory for buffer only once, after that we continue reading from the same buffer.
The BufferInternal structure is enclosed within an Arc<> instance, which is "Atomically Reference Counted", very much like kref_get/put in kernel.
Unless the last reference is dropped, it stays.
Hope I was able to clarify this here.
Re: [PATCH V6 3/8] libgpiod: Add rust wrapper crate
Here is what happens at each call for easier understanding:
Quoted 3 lines · Bartosz Golaszewski
So what happens if I do this:
let buf =
edge::event::Buffer::new(10)?;
buf1 = gpiod_edge_event_buffer_new()
Quoted 1 line
let request =
chip.request_lines(&rconfig, &lconfig)?;
gpiod_chip_request_lines()
Quoted 1 line
let count =
request.read_edge_events(&buffer)?;
gpiod_line_request_read_edge_event(buf1)
Quoted 1 line
let event =
buffer.event(0);
event1 = gpiod_edge_event_buffer_get_event(buf1, 0)
Quoted 1 line
let count =
request.read_edge_events(&buffer)?;
gpiod_line_request_read_edge_event(buf1)
Quoted 1 line
println!("line: {}",
event.line_offset());
gpiod_edge_event_get_line_offset(event1)
We will allocate a single buffer (buf1) and the event (event1) will be a reference onto its first event entry in the buffer. It will print offset value from the second read_edge_events() here instead of first, as it was just a reference to the first event. And for such a use case, the user should do
event = buffer.event(0).event_clone();
Signature · 1 line
viresh
Re: [PATCH V6 3/8] libgpiod: Add rust wrapper crate
Quoted 36 lines · Viresh Kumar
Here is what happens at each call for easier understanding:
Quoted 3 lines · Bartosz Golaszewski
So what happens if I do this:
let buf =
edge::event::Buffer::new(10)?;buf1 =
gpiod_edge_event_buffer_new()Quoted 1 line
let request =
chip.request_lines(&rconfig, &lconfig)?;
gpiod_chip_request_lines()Quoted 1 line
let count =
request.read_edge_events(&buffer)?;
gpiod_line_request_read_edge_event(buf1)Quoted 1 line
let event =
buffer.event(0);event1 =
gpiod_edge_event_buffer_get_event(buf1, 0)Quoted 1 line
let count =
request.read_edge_events(&buffer)?;
gpiod_line_request_read_edge_event(buf1)Quoted 1 line
println!("line: {}",
event.line_offset());
gpiod_edge_event_get_line_offset(event1)We will allocate a single buffer (buf1) and the event (event1) will be a reference onto its first event entry in the buffer. It will print offset value from the second
read_edge_events()here instead of first, as it was just a reference to the first event. And for such a use case, the user should do
Quoted 4 lines
event = buffer.event(0).event_clone();
--
viresh
Ok, so this is not correct. The doc forgpiod_edge_event_buffer_get_event() says:
* @return Pointer to an event stored in the buffer. The lifetime of the
* event is tied to the buffer object. Users must not free the event
* returned by this function.
We make no guarantees that the address returned by gpiod_edge_event_buffer_get_event() will still be valid after a call to gpiod_line_request_read_edge_event(). In fact the doc for the latter says:
* @note Any exising events in the buffer are overwritten. This is not an
* append operation.
So we're being clear that after a call to gpiod_line_request_read_edge_event(), all previously returned edge event pointers are invalid unless the objects to which they point were explicitly copied.
Current implementation does indeed work like what you describe but that behavior is not "contractually" guaranteed and can change even between minor releases.
In C++ if you do:
const edge_event& ev = buffer.get_event(0);request.read_edge_event(buffer);std::cout << ev << std::endl;
You risk a segfault.
My understanding is that Rust should not allow a crash in this situation by design.
Bart
Re: [PATCH V6 3/8] libgpiod: Add rust wrapper crate
Quoted 26 lines · Bartosz Golaszewski
Ok, so this is not correct. The doc for
gpiod_edge_event_buffer_get_event()says:* @return Pointer to an event stored in the buffer. The lifetime of the
* event is tied to the buffer object. Users must not free the event
* returned by this function.We make no guarantees that the address returned by
gpiod_edge_event_buffer_get_event()will still be valid after a call togpiod_line_request_read_edge_event(). In fact the doc for the latter says:* @note Any exising events in the buffer are overwritten. This is not an
* append operation.So we're being clear that after a call to
gpiod_line_request_read_edge_event(), all previously returned edge event pointers are invalid unless the objects to which they point were explicitly copied.Current implementation does indeed work like what you describe but that behavior is not "contractually" guaranteed and can change even between minor releases.
In C++ if you do:
Quoted 8 lines
const edge_event& ev =
buffer.get_event(0);request.read_edge_event(buffer);std::cout<< ev <<std::endl;You risk a segfault.
My understanding is that Rust should not allow a crash in this situation by design.
Hmm, so what exactly do we want to do here then ?
- Don't allow events to be referenced ? i.e. make event_clone() the default behavior ?
- Don't allow read_edge_event() to be called twice for a buffer ? that will be inefficient though.
- Somehow guarantee that reference to all the events are dropped before issuing read_edge_event() again, else make it fail ? I am not sure how straight forward that can be though.
Signature · 1 line
viresh
Re: [PATCH V6 3/8] libgpiod: Add rust wrapper crate
Quoted 34 lines · Viresh Kumar
Quoted 32 lines · Bartosz Golaszewski
Ok, so this is not correct. The doc for
gpiod_edge_event_buffer_get_event()says:* @return Pointer to an event stored in the buffer. The lifetime of the
* event is tied to the buffer object. Users must not free the event
* returned by this function.We make no guarantees that the address returned by
gpiod_edge_event_buffer_get_event()will still be valid after a call togpiod_line_request_read_edge_event(). In fact the doc for the latter says:* @note Any exising events in the buffer are overwritten. This is not an
* append operation.So we're being clear that after a call to
gpiod_line_request_read_edge_event(), all previously returned edge event pointers are invalid unless the objects to which they point were explicitly copied.Current implementation does indeed work like what you describe but that behavior is not "contractually" guaranteed and can change even between minor releases.
In C++ if you do:
const edge_event& ev =
buffer.get_event(0);request.read_edge_event(buffer);std::cout<< ev <<std::endl;You risk a segfault.
Quoted 8 lines
Quoted 2 lines
My understanding is that Rust should not allow a crash in this situation by design.
Hmm, so what exactly do we want to do here then ?
- Don't allow events to be referenced ? i.e. make
event_clone()the default behavior ?
God no, that would be wasteful.
Quoted 3 lines
- Don't allow
read_edge_event()to be called twice for a buffer ? that will be inefficient though.
Not good either.
Quoted 3 lines
- Somehow guarantee that reference to all the events are dropped before issuing
read_edge_event()again, else make it fail ? I am not sure how straight forward that can be though.
In C++ the preferred way is to do buffer.get_event(0) which will return a constant reference. If you store that reference as const edge_event& ev = buffer.get_event(0) and reuse it after rereading into that buffer and the program crashes - that's on you. In most cases you should just do buffer.get_event(0).line_offset() etc. If you do:
edge_event event = buffer.get_event(0);
You'll copy the event and it will survive the overwriting of the buffer.
I'm a Rust beginner but my understanding is that the whole idea of the language design is to *not* allow a situation where the program can crash. It should be detected at build-time. We must not rely on "contracts" defined by documentation.
Is there a way to invalidate a reference in Rust? Have a small (cheap) object in the buffer which the event references and which would get dropped when reading into the buffer?
Bart
Re: [PATCH V6 3/8] libgpiod: Add rust wrapper crate
Quoted 7 lines · Bartosz Golaszewski
Quoted 5 lines · Viresh Kumar
Hmm, so what exactly do we want to do here then ?
- Don't allow events to be referenced ? i.e. make
event_clone()the default behavior ?
Quoted 7 lines
God no, that would be wasteful.
Quoted 3 lines
- Don't allow
read_edge_event()to be called twice for a buffer ? that will be inefficient though.Not good either.
As I expected for both of them :)
Quoted 10 lines
Quoted 3 lines
- Somehow guarantee that reference to all the events are dropped before issuing
read_edge_event()again, else make it fail ? I am not sure how straight forward that can be though.In C++ the preferred way is to do
buffer.get_event(0)which will return a constant reference. If you store that reference as const edge_event& ev =buffer.get_event(0)and reuse it after rereading into that buffer and the program crashes - that's on you. In most cases you should just do buffer.get_event(0).line_offset() etc. If you do:
Quoted 3 lines
edge_eventevent =buffer.get_event(0);You'll copy the event and it will survive the overwriting of the buffer.
Right, same happens here.
Quoted 4 lines
I'm a Rust beginner but my understanding is that the whole idea of the language design is to *not* allow a situation where the program can crash. It should be detected at build-time. We must not rely on "contracts" defined by documentation.
If everything was written in Rust, then this problem won't occur for sure. But in this case part of the code is available via FFI (foreign function interface) and they guarantees are a bit limited there and depend on what the FFI guarantees.
Quoted 3 lines
Is there a way to invalidate a reference in Rust? Have a small (cheap) object in the buffer which the event references and which would get dropped when reading into the buffer?
I am not sure. There are locks, but then they have a cost.
Miguel, any suggestions ?
Bartosz, just as an FYI I am out on vacation until end of next week and won't have access to a workstation. I can still reply via Gmail (html) from my phone though.
Signature · 1 line
viresh
Re: [PATCH V6 3/8] libgpiod: Add rust wrapper crate
Quoted 45 lines · Viresh Kumar
Quoted 7 lines · Bartosz Golaszewski
Quoted 5 lines · Viresh Kumar
Hmm, so what exactly do we want to do here then ?
- Don't allow events to be referenced ? i.e. make
event_clone()the default behavior ?Quoted 7 lines
God no, that would be wasteful.
Quoted 3 lines
- Don't allow
read_edge_event()to be called twice for a buffer ? that will be inefficient though.Not good either.
As I expected for both of them :)
Quoted 10 lines
Quoted 3 lines
- Somehow guarantee that reference to all the events are dropped before issuing
read_edge_event()again, else make it fail ? I am not sure how straight forward that can be though.In C++ the preferred way is to do
buffer.get_event(0)which will return a constant reference. If you store that reference as const edge_event& ev =buffer.get_event(0)and reuse it after rereading into that buffer and the program crashes - that's on you. In most cases you should just do buffer.get_event(0).line_offset() etc. If you do:Quoted 3 lines
edge_eventevent =buffer.get_event(0);You'll copy the event and it will survive the overwriting of the buffer.
Right, same happens here.
Quoted 4 lines
I'm a Rust beginner but my understanding is that the whole idea of the language design is to *not* allow a situation where the program can crash. It should be detected at build-time. We must not rely on "contracts" defined by documentation.
If everything was written in Rust, then this problem won't occur for sure. But in this case part of the code is available via FFI (foreign function interface) and they guarantees are a bit limited there and depend on what the FFI guarantees.
Quoted 6 lines
Quoted 3 lines
Is there a way to invalidate a reference in Rust? Have a small (cheap) object in the buffer which the event references and which would get dropped when reading into the buffer?
I am not sure. There are locks, but then they have a cost.
I'm not talking about locking, this should be left to the user of the module.
Can we force-drop an object still referenced by other objects in Rust? This is what I had in mind - a small, dummy, cheap object inside the buffer that's created when reading into the buffer. Each even would reference it and then Rust would not allow us to drop it as long as there are references to it. Does it make sense? Is that possible?
Quoted 6 lines
Miguel, any suggestions ?
Bartosz, just as an FYI I am out on vacation until end of next week and won't have access to a workstation. I can still reply via Gmail (html) from my phone though.
Nah, just take the time off and rest. BTW, I'm starting at linaro next week. :)
Bart
Re: [PATCH V6 3/8] libgpiod: Add rust wrapper crate
Quoted 1 line · Bartosz Golaszewski
I'm not talking about locking, this should be left to the user of the module.
Yeah, I get that.
Quoted 1 line
Can we force-drop an object still referenced by other objects in Rust?
No, AFAICT.
We can call drop(x) on an object, but as long as an reference to it is out there, we won't end up calling the actualy drop() routine for the object.
Quoted 4 lines
This is what I had in mind - a small, dummy, cheap object inside the buffer that's created when reading into the buffer. Each even would reference it and then Rust would not allow us to drop it as long as there are references to it. Does it make sense? Is that possible?
I don't think so.
Quoted 1 line
Nah, just take the time off and rest. BTW, I'm starting at linaro next week. :)
Excellent, welcome buddy :)
--
Viresh
Re: [PATCH V6 3/8] libgpiod: Add rust wrapper crate
Quoted 14 lines · Viresh Kumar
Quoted 1 line · Bartosz Golaszewski
I'm not talking about locking, this should be left to the user of the module.
Yeah, I get that.
Quoted 1 line
Can we force-drop an object still referenced by other objects in Rust?
No, AFAICT.
We can call drop(x) on an object, but as long as an reference to it is out there, we won't end up calling the actualy
drop()routine for the object.
Quoted 6 lines
Quoted 4 lines
This is what I had in mind - a small, dummy, cheap object inside the buffer that's created when reading into the buffer. Each even would reference it and then Rust would not allow us to drop it as long as there are references to it. Does it make sense? Is that possible?
I don't think so.
Miguel: Could you give us advice on how to proceed with this?
Bart
Quoted 7 lines
Quoted 2 lines
Nah, just take the time off and rest. BTW, I'm starting at linaro next week. :)
Excellent, welcome buddy :)
--
Viresh
Re: [PATCH V6 3/8] libgpiod: Add rust wrapper crate
In reply to Bartosz GolaszewskiQuoted 53 lines · Bartosz Golaszewski
Quoted 45 lines · Viresh Kumar
Quoted 7 lines · Bartosz Golaszewski
Quoted 5 lines · Viresh Kumar
Hmm, so what exactly do we want to do here then ?
- Don't allow events to be referenced ? i.e. make
event_clone()the default behavior ?Quoted 7 lines
God no, that would be wasteful.
Quoted 3 lines
- Don't allow
read_edge_event()to be called twice for a buffer ? that will be inefficient though.Not good either.
As I expected for both of them :)
Quoted 10 lines
Quoted 3 lines
- Somehow guarantee that reference to all the events are dropped before issuing
read_edge_event()again, else make it fail ? I am not sure how straight forward that can be though.In C++ the preferred way is to do
buffer.get_event(0)which will return a constant reference. If you store that reference as const edge_event& ev =buffer.get_event(0)and reuse it after rereading into that buffer and the program crashes - that's on you. In most cases you should just do buffer.get_event(0).line_offset() etc. If you do:Quoted 3 lines
edge_eventevent =buffer.get_event(0);You'll copy the event and it will survive the overwriting of the buffer.
Right, same happens here.
Quoted 4 lines
I'm a Rust beginner but my understanding is that the whole idea of the language design is to *not* allow a situation where the program can crash. It should be detected at build-time. We must not rely on "contracts" defined by documentation.
If everything was written in Rust, then this problem won't occur for sure. But in this case part of the code is available via FFI (foreign function interface) and they guarantees are a bit limited there and depend on what the FFI guarantees.
Quoted 6 lines
Quoted 3 lines
Is there a way to invalidate a reference in Rust? Have a small (cheap) object in the buffer which the event references and which would get dropped when reading into the buffer?
I am not sure. There are locks, but then they have a cost.
Quoted 8 lines
I'm not talking about locking, this should be left to the user of the module.
Can we force-drop an object still referenced by other objects in Rust? This is what I had in mind - a small, dummy, cheap object inside the buffer that's created when reading into the buffer. Each even would reference it and then Rust would not allow us to drop it as long as there are references to it. Does it make sense? Is that possible?
No, Rust will explicitly prevent you from dropping referenced objects.
But is this the sort of thing you mean:
use std::process::ExitCode;
#[derive(Clone)]
struct Event {
pub id: u8
}
struct Events {
b: Vec<Event>
}
impl Events {
pub fn get(&self, idx: usize) -> Option<&Event> {
self.b.get(idx)
}
}struct Buffer {
count: u8,
events: Option<Events>,
}impl Buffer {
pub fn read(&mut self) -> Result<&Events, ()> {
self.count += 1;
self.events = Some(Events{b: vec![Event{id: self.count}]});
self.events.as_ref().ok_or(())
}
}fn main() -> Result<ExitCode, ()>{
let mut b = Buffer{count: 0, events:None};
let mut ee = b.read()?;
let e = ee.get(0);
println!("{:?}", e.unwrap().id);
let cloned_e = e.unwrap().clone();
drop(e); // <-- comment out to try to create a dangling event reference
ee = b.read()?;
let e = ee.get(0); // <-- comment out to try to create a dangling event reference
println!("{:?}", cloned_e.id);
println!("{:?}", e.unwrap().id);
Ok(ExitCode::from(42))
}That is a skeletal proof of concept - the small, dummy, cheap object is the Vec in Events. Is that cheap enough? You might be able to replace that with something cheaper, but Events needs to be able to pull an Event reference from somewhere (from the borrow checker's PoV) so it made this demo simpler.
Comment out the two lines to try to carry e across the buffer read(). The compiler will not allow it, as e already borrows from b.
In an actual implementation Event would wrap the C event, and Events.get() would get the event pointer for the Event and return that as a reference. The Event clone would call into C, rather than being derived as it is here.
Key points: Buffer has to own the Events snapshot that is returned by reference by read(). The return by reference creates a borrow on the Buffer. The read() requires a &mut to prevent the Buffer being read while there are any borrows outstanding.
The Events returns individual events by reference to create a borrow on the Events to prevent it (and the Buffer) being dropped or modified. The Event clone returns a concrete event that does not have a borrow of the Events or Buffer.
There may well be better ways to do this, and you would really want to do some benchmarking to compare it with the immediate clone option - it may well be worse, but hopefully it at least demonstrates the semantics you are after.
Cheers,
Kent.
Re: [PATCH V6 3/8] libgpiod: Add rust wrapper crate
In reply to Bartosz GolaszewskiQuoted 4 lines · Bartosz Golaszewski
I'm a Rust beginner but my understanding is that the whole idea of the language design is to *not* allow a situation where the program can crash. It should be detected at build-time. We must not rely on
More precisely, it needs to avoid UB (which is defined similarly as in C++, and of course UB may lead to a crash if one is lucky).
Quoted 3 lines
Is there a way to invalidate a reference in Rust? Have a small (cheap) object in the buffer which the event references and which would get dropped when reading into the buffer?
From your C++ example above:
const edge_event& ev = buffer.get_event(0);
request.read_edge_event(buffer);
std::cout << ev << std::endl;It looks like a container whose elements get invalidated, so read_edge_event could require an exclusive reference to buffer in Rust, that way you cannot keep borrows to its elements like ev if you want to call it. But of course this requires tying the lifetime of the events to that of the buffer.
Cheers,
Miguel
Re: [PATCH V6 3/8] libgpiod: Add rust wrapper crate
Quoted 5 lines · Miguel Ojeda
It looks like a container whose elements get invalidated, so
read_edge_eventcould require an exclusive reference tobufferin Rust, that way you cannot keep borrows to its elements likeevif you want to call it. But of course this requires tying the lifetime of the events to that of the buffer.
What about the below code changes on top of V6 ?
Changes:
- Removed BufferInternal.
- Event contains a reference to the Buffer now, with lifetime.
- read_edge_event() expects a mutable reference to buffer, to make it exclusive, i.e. disallow any previous Event references to exist at compilation itself.
bindings/rust/libgpiod/src/edge_event.rs +10 -11
@@ -3,12 +3,11 @@// Copyright 2022 Linaro Ltd. All Rights Reserved. // Viresh Kumar <[email protected]> -use std::sync::Arc ;use std::time::Duration ;use vmm_sys_util::errno::Error as Errno ; -use super ::{edge::event::BufferInternal , gpiod,EdgeKind ,Error ,Offset ,OperationType ,Result }; +use super ::{edge::event::Buffer , gpiod,EdgeKind ,Error ,Offset ,OperationType ,Result };/// Line edge events handling ///
@@ -22,15 +21,15 @@ use super::{edge::event::BufferInternal, gpiod, EdgeKind, Error, Offset, Operati /// number of events are being read. #[derive(Debug, Eq, PartialEq)] -pub struct Event { - ibuffer: Option<Arc<BufferInternal>>, +pub struct Event<'b> { + buffer: Option<&'b Buffer>, event: *mut gpiod::gpiod_edge_event, } -impl Event { +impl<'b> Event<'b> { /// Get an event stored in the buffer. - pub(crate) fn new(ibuffer: &Arc<BufferInternal>, index: u64) -> Result<Self> { - let event = unsafe { gpiod::gpiod_edge_event_buffer_get_event(ibuffer.buffer(), index) }; + pub(crate) fn new(buffer: &'b Buffer, index: u64) -> Result<Self> { + let event = unsafe { gpiod::gpiod_edge_event_buffer_get_event(buffer.buffer(), index) }; if event.is_null() { return Err(Error::OperationFailed( OperationType::EdgeEventBufferGetEvent,
@@ -39,7 +38,7 @@ impl Event { }Ok (Self { - ibuffer:Some (ibuffer.clone ()), + buffer:Some (buffer), event, }) }
@@ -54,7 +53,7 @@ impl Event { }Ok (Self { - ibuffer:None , + buffer:None , event, }) }
@@ -91,11 +90,11 @@ impl Event { } } -impl Drop for Event { +impl <'b>Drop for Event <'b> {/// Free the edge event. fn drop (&mut self) {// Free the event only if a copy is made -if self.ibuffer.is_none () { +if self.buffer.is_none () {unsafe { gpiod::gpiod_edge_event_free (self.event) }; } }
bindings/rust/libgpiod/src/event_buffer.rs +10 -34
74 lines not on this page · raw message
bindings/rust/libgpiod/src/line_request.rs +1 -1
@@ -218,7 +218,7 @@ impl Request {/// Get a number of edge events from a line request. /// /// This function will block if no event was queued for the line. -pub fn read_edge_events (&self, buffer: &edge::event::Buffer ) ->Result <u32 > { +pub fn read_edge_events (&self, buffer: &mut edge::event::Buffer ) ->Result <u32 > {let ret =unsafe { gpiod::gpiod_line_request_read_edge_event ( self.request,
And here is an example where we get compilation error on the commented line:
fn main() -> Result<()> {
let mut lsettings = line::Settings::new()?;
let lconfig = line::Config::new()?;
let offsets = vec![1, 2];
lsettings.set_prop(&[SettingVal::EdgeDetection(Some(Edge::Both))])?;
lconfig.add_line_settings(&offsets, lsettings)?;
let path = "/dev/gpiochip0".to_string();
let chip = Chip::open(&path)?;
let rconfig = request::Config::new()?;
let mut buffer = edge::event::Buffer::new(2)?;
let request = chip.request_lines(&rconfig, &lconfig)?;
loop {
match request.wait_edge_event(None) {
Err(x) => {
println!("{:?}", x);
return Err(Error::InvalidArguments);
}
Ok(false) => {
// This shouldn't happen as the call is blocking.
panic!();
}
Ok(true) => (),
}
request.read_edge_events(&mut buffer)?;
let event0 = buffer.event(0)?;
let event1 = buffer.event(1)?;
println!("{:?}", (event0.line_offset(), event1.line_offset()));
drop(event0);
// This fails to compile
// request.read_edge_events(&mut buffer)?;
drop(event1);
// This compiles fine
request.read_edge_events(&mut buffer)?;
}
}Signature · 1 line
viresh
Re: [PATCH V6 3/8] libgpiod: Add rust wrapper crate
Quoted 9 lines · Viresh Kumar
Quoted 5 lines · Miguel Ojeda
It looks like a container whose elements get invalidated, so
read_edge_eventcould require an exclusive reference tobufferin Rust, that way you cannot keep borrows to its elements likeevif you want to call it. But of course this requires tying the lifetime of the events to that of the buffer.What about the below code changes on top of V6 ?
Can you clone the event to detach it from the buffer? Or are you now forced to drop the event before reading more?
Cheers,
Kent.
Re: [PATCH V6 3/8] libgpiod: Add rust wrapper crate
Quoted 8 lines · Kent Gibson
Quoted 7 lines · Viresh Kumar
Quoted 5 lines · Miguel Ojeda
It looks like a container whose elements get invalidated, so
read_edge_eventcould require an exclusive reference tobufferin Rust, that way you cannot keep borrows to its elements likeevif you want to call it. But of course this requires tying the lifetime of the events to that of the buffer.
Quoted 4 lines
Quoted 2 lines
What about the below code changes on top of V6 ?
Can you clone the event to detach it from the buffer?
I thought we can always do:
event.event_clone() to get a copy, which won't have a reference to the Buffer. This is how it is defined:
pub fn event_clone(&self) -> Result<Self> {
let event = unsafe { gpiod::gpiod_edge_event_copy(self.event) };
if event.is_null() {
return Err(Error::OperationFailed(
OperationType::EdgeEventCopy,
Errno::last(),
));
}
Ok(Self {
buffer: None,
event,
})
}But when I try to do this in the earlier example:
@@ -40,10 +40,12 @@ fn main() -> Result<()> {
let event1 = buffer.event(1)?;
println!("{:?}", (event0.line_offset(), event1.line_offset()));
+ let event0_copy = event0.event_clone()?;
drop(event0);
// This fails to compile
// request.read_edge_events(&mut buffer)?;
drop(event1);
request.read_edge_events(&mut buffer)?;
+ drop(event0_copy);
}}
compilation fails :(
error[E0502]: cannot borrow buffer as mutable because it is also borrowed as immutable
--> libgpiod/examples/gpiobufferevent.rs:48:34
| 39 | let event0 = buffer.event(0)?; | --------------- immutable borrow occurs here ... 48 | request.read_edge_events(&mut buffer)?; | ^^^^^^^^^^^ mutable borrow occurs here 49 | drop(event0_copy); | ----------- immutable borrow later used here
And I am not sure why, as the reference isn't used anymore in this case to the event0.
Signature · 1 line
viresh
Re: [PATCH V6 3/8] libgpiod: Add rust wrapper crate
Quoted 2 lines · Viresh Kumar
And I am not sure why, as the reference isn't used anymore in this case to the event0.
This works though:
bindings/rust/libgpiod/examples/gpiobufferevent.rs +2 -0
@@ -40,10 +40,12 @@ fn main() -> Result<()> {let event1 = buffer.event (1 )?;println! ("{:?}" , (event0.line_offset (), event1.line_offset ())); +let event0_copy = edge::Event ::event_clone (&event0)?;drop (event0);// This fails to compile // request.read_edge_events(&mut buffer)?; drop (event1); request.read_edge_events (&mut buffer)?; +drop (event0_copy); } }
bindings/rust/libgpiod/src/edge_event.rs +2 -2
@@ -43,8 +43,8 @@ impl<'b> Event<'b> { }) } - pub fn event_clone(&self) -> Result<Self> { - let event = unsafe { gpiod::gpiod_edge_event_copy(self.event) }; + pub fn event_clone(event: &Event) -> Result<Self> { + let event = unsafe { gpiod::gpiod_edge_event_copy(event.event) }; if event.is_null() { return Err(Error::OperationFailed( OperationType::EdgeEventCopy,
Signature · 1 line
viresh
Re: [PATCH V6 3/8] libgpiod: Add rust wrapper crate
In reply to Viresh KumarQuoted 8 lines · Viresh Kumar
What about the below code changes on top of V6 ?
Changes:
- Removed BufferInternal.
- Event contains a reference to the Buffer now, with lifetime.
-read_edge_event()expects a mutable reference to buffer, to make it exclusive, i.e. disallow any previous Event references to exist at compilation itself.
Bartosz, should I send a V7 now with these changes ? I hope everything is settled ?
Signature · 1 line
viresh
Re: [PATCH V6 3/8] libgpiod: Add rust wrapper crate
Quoted 11 lines · Viresh Kumar
Quoted 8 lines · Viresh Kumar
What about the below code changes on top of V6 ?
Changes:
- Removed BufferInternal.
- Event contains a reference to the Buffer now, with lifetime.
-read_edge_event()expects a mutable reference to buffer, to make it exclusive, i.e. disallow any previous Event references to exist at compilation itself.
Quoted 3 lines
Bartosz, should I send a V7 now with these changes ? I hope everything is settled ?
Maybe also add chained mutators everywhere? To be able to do settings.set_direction().set_edge() etc.?
And I would still love a thorough API review from someone who actually knows rust too. :(
But I will play some more with v7 so do send it.
Bartosz
Re: [PATCH V6 3/8] libgpiod: Add rust wrapper crate
Quoted 2 lines · Bartosz Golaszewski
Maybe also add chained mutators everywhere? To be able to do settings.set_direction().set_edge() etc.?
Based on Kent's suggestion earlier, what I have implemented is set_prop(), to which one can pass all settings and it will apply them in a loop.
pub fn set_prop(&mut self, values: &[SettingVal]) -> Result<()> {
for value in values {
match value {
SettingVal::Direction(val) => self.set_direction(*val)?,
SettingVal::EdgeDetection(val) => self.set_edge_detection(*val)?,
SettingVal::Bias(val) => self.set_bias(*val)?,
SettingVal::Drive(val) => self.set_drive(*val)?,
SettingVal::ActiveLow(val) => self.set_active_low(*val),
SettingVal::DebouncePeriod(val) => self.set_debounce_period(*val),
SettingVal::EventClock(val) => self.set_event_clock(*val)?,
SettingVal::OutputValue(val) => self.set_output_value(*val)?,
}
}
Ok(())
}I think that replaces the need of nested ones ? And if we want to add those later, we can always come back and add them. But I am not sure it would be required.
Quoted 2 lines
And I would still love a thorough API review from someone who actually knows rust too. :(
Well, Kent did a very good job earlier. I am not sure if he has extra cycles to review this once again, though not a lot has changed since last time.
Quoted 1 line
But I will play some more with v7 so do send it.
Great.
Signature · 1 line
viresh
Re: [PATCH V6 3/8] libgpiod: Add rust wrapper crate
Quoted 26 lines · Viresh Kumar
Quoted 2 lines · Bartosz Golaszewski
Maybe also add chained mutators everywhere? To be able to do settings.set_direction().set_edge() etc.?
Based on Kent's suggestion earlier, what I have implemented is
set_prop(), to which one can pass all settings and it will apply them in a loop.pub fn set_prop(&mut self, values: &[SettingVal]) -> Result<()> { for value in values { match value { SettingVal::Direction(val) => self.set_direction(*val)?, SettingVal::EdgeDetection(val) => self.set_edge_detection(*val)?, SettingVal::Bias(val) => self.set_bias(*val)?, SettingVal::Drive(val) => self.set_drive(*val)?, SettingVal::ActiveLow(val) => self.set_active_low(*val), SettingVal::DebouncePeriod(val) => self.set_debounce_period(*val), SettingVal::EventClock(val) => self.set_event_clock(*val)?, SettingVal::OutputValue(val) => self.set_output_value(*val)?, } } Ok(()) }
Quoted 4 lines
I think that replaces the need of nested ones ? And if we want to add those later, we can always come back and add them. But I am not sure it would be required.
I cannot find Kent's comment on that - what was the reasoning behind this?
Quoted 7 lines
Quoted 2 lines
And I would still love a thorough API review from someone who actually knows rust too. :(
Well, Kent did a very good job earlier. I am not sure if he has extra cycles to review this once again, though not a lot has changed since last time.
Yeah sorry Kent, I forgot we're at v6 already and you did review the previous iterations. :)
Bart
Quoted 6 lines
Quoted 1 line
But I will play some more with v7 so do send it.
Great.
--
viresh
Re: [PATCH V6 3/8] libgpiod: Add rust wrapper crate
Quoted 27 lines · Bartosz Golaszewski
Quoted 26 lines · Viresh Kumar
Quoted 2 lines · Bartosz Golaszewski
Maybe also add chained mutators everywhere? To be able to do settings.set_direction().set_edge() etc.?
Based on Kent's suggestion earlier, what I have implemented is
set_prop(), to which one can pass all settings and it will apply them in a loop.pub fn set_prop(&mut self, values: &[SettingVal]) -> Result<()> { for value in values { match value { SettingVal::Direction(val) => self.set_direction(*val)?, SettingVal::EdgeDetection(val) => self.set_edge_detection(*val)?, SettingVal::Bias(val) => self.set_bias(*val)?, SettingVal::Drive(val) => self.set_drive(*val)?, SettingVal::ActiveLow(val) => self.set_active_low(*val), SettingVal::DebouncePeriod(val) => self.set_debounce_period(*val), SettingVal::EventClock(val) => self.set_event_clock(*val)?, SettingVal::OutputValue(val) => self.set_output_value(*val)?, } } Ok(()) }
Quoted 7 lines
Quoted 4 lines
I think that replaces the need of nested ones ? And if we want to add those later, we can always come back and add them. But I am not sure it would be required.
I cannot find Kent's comment on that - what was the reasoning behind this?
The comment was:
Add a Setting enum that has a variant for each setting. Then you only need 3 mutators total.
And the user can define configs as a list of Settings. So perhaps the mutators should accept &[Setting]. And &[offsets] rather than just offset.
Similarly, gets can be consolidated into:
get_prop_offset(self, offset, SettingKind) -> Result<Setting>
and
get_prop_default(self, SettingKind) -> Result<Setting>
(simplified signatures)
Prior to the line_settings change there was a veritable forest of mutators, so the idea was to consolidate them where possible, cos in Rust you can. But behind the scenes the implementation is just fanning them out again, so I'm not sure I see the benefit if that is the case.
If the mutators for each field still exist they may as well be pub.
And they should return Result<&mut Self> so they can be chained, as you suggest.
Wrt the values param (which I would prefer was called props), the idea was that the config could be built and passed around as pure Rust objects. The set_prop() applied that list to the C line config, at the time using one of the default/offset/subset mutators. So it decoupled the settings from the scope they were to be applied to. Not such an issue now - the scope is always line.
Might still be useful, might not.
Quoted 11 lines
Quoted 7 lines
Quoted 2 lines
And I would still love a thorough API review from someone who actually knows rust too. :(
Well, Kent did a very good job earlier. I am not sure if he has extra cycles to review this once again, though not a lot has changed since last time.
Yeah sorry Kent, I forgot we're at v6 already and you did review the previous iterations. :)
My review was of v4. Things have changed a bit since then. And I agree with your original point - it would be good to get a review from someone that actually uses Rust in anger - I only toy with it.
Cheers,
Kent.
Re: [PATCH V6 3/8] libgpiod: Add rust wrapper crate
Quoted 6 lines · Kent Gibson
If the mutators for each field still exist they may as well be pub.
And they should return Result<&mut Self> so they can be chained, as you suggest.
Wrt the values param (which I would prefer was called props)
Is this fine now ? Rebased over v7.
bindings/rust/libgpiod/src/line_settings.rs +41 -36
@@ -70,18 +70,18 @@ impl Settings { }/// Set line prop setting. -pub fn set_prop (&mut self, values: &[SettingVal ]) ->Result <()> { -for valuein values { -match value { -SettingVal ::Direction (val) => self.set_direction (*val)?, -SettingVal ::EdgeDetection (val) => self.set_edge_detection (*val)?, -SettingVal ::Bias (val) => self.set_bias (*val)?, -SettingVal ::Drive (val) => self.set_drive (*val)?, -SettingVal ::ActiveLow (val) => self.set_active_low (*val), -SettingVal ::DebouncePeriod (val) => self.set_debounce_period (*val), -SettingVal ::EventClock (val) => self.set_event_clock (*val)?, -SettingVal ::OutputValue (val) => self.set_output_value (*val)?, - } +pub fn set_prop (&mut self, props: &[SettingVal ]) ->Result <()> { +for propertyin props { +match property { +SettingVal ::Direction (prop) => self.set_direction (*prop)?, +SettingVal ::EdgeDetection (prop) => self.set_edge_detection (*prop)?, +SettingVal ::Bias (prop) => self.set_bias (*prop)?, +SettingVal ::Drive (prop) => self.set_drive (*prop)?, +SettingVal ::ActiveLow (prop) => self.set_active_low (*prop), +SettingVal ::DebouncePeriod (prop) => self.set_debounce_period (*prop), +SettingVal ::EventClock (prop) => self.set_event_clock (*prop)?, +SettingVal ::OutputValue (prop) => self.set_output_value (*prop)?, + }; }Ok (())
@@ -102,7 +102,7 @@ impl Settings { }/// Set the line direction. -fn set_direction (&mut self, direction:Direction ) ->Result <()> { +pub fn set_direction (&mut self, direction:Direction ) ->Result <&mut Self > {let ret =unsafe { gpiod::gpiod_line_settings_set_direction ( self.settings,
@@ -116,17 +116,17 @@ impl Settings { Errno::last(), )) }else { -Ok (()) +Ok (self) } }/// Get the direction setting. -fn direction (&self) ->Result <Direction > { +pub fn direction (&self) ->Result <Direction > {Direction ::new (unsafe { gpiod::gpiod_line_settings_get_direction (self.settings) }as u32 ) }/// Set the edge event detection setting. -fn set_edge_detection (&mut self, edge:Option <Edge >) ->Result <()> { +pub fn set_edge_detection (&mut self, edge:Option <Edge >) ->Result <&mut Self > {let ret =unsafe { gpiod::gpiod_line_settings_set_edge_detection ( self.settings,
@@ -140,17 +140,17 @@ impl Settings { Errno::last(), )) }else { -Ok (()) +Ok (self) } }/// Get the edge event detection setting. -fn edge_detection (&self) ->Result <Option <Edge >> { +pub fn edge_detection (&self) ->Result <Option <Edge >> {Edge ::new (unsafe { gpiod::gpiod_line_settings_get_edge_detection (self.settings) }as u32 ) }/// Set the bias setting. -fn set_bias (&mut self, bias:Option <Bias >) ->Result <()> { +pub fn set_bias (&mut self, bias:Option <Bias >) ->Result <&mut Self > {let ret =unsafe { gpiod::gpiod_line_settings_set_bias (self.settings,Bias ::gpiod_bias (bias)as i32 ) };
@@ -161,17 +161,17 @@ impl Settings { Errno::last(), )) }else { -Ok (()) +Ok (self) } }/// Get the bias setting. -fn bias (&self) ->Result <Option <Bias >> { +pub fn bias (&self) ->Result <Option <Bias >> {Bias ::new (unsafe { gpiod::gpiod_line_settings_get_bias (self.settings) }as u32 ) }/// Set the drive setting. -fn set_drive (&mut self, drive:Drive ) ->Result <()> { +pub fn set_drive (&mut self, drive:Drive ) ->Result <&mut Self > {let ret =unsafe { gpiod::gpiod_line_settings_set_drive (self.settings, drive.gpiod_drive ()as i32 ) };
@@ -182,44 +182,49 @@ impl Settings { Errno::last(), )) }else { -Ok (()) +Ok (self) } }/// Get the drive setting. -fn drive (&self) ->Result <Drive > { +pub fn drive (&self) ->Result <Drive > {Drive ::new (unsafe { gpiod::gpiod_line_settings_get_drive (self.settings) }as u32 ) }/// Set active-low setting. -fn set_active_low (&mut self, active_low:bool ) { -unsafe { gpiod::gpiod_line_settings_set_active_low (self.settings, active_low) } +pub fn set_active_low (&mut self, active_low:bool ) -> &mut Self { +unsafe { + gpiod::gpiod_line_settings_set_active_low (self.settings, active_low); + } + self }/// Check the active-low setting. -fn active_low (&self) ->bool { +pub fn active_low (&self) ->bool {unsafe { gpiod::gpiod_line_settings_get_active_low (self.settings) } }/// Set the debounce period setting. -fn set_debounce_period (&mut self, period:Duration ) { +pub fn set_debounce_period (&mut self, period:Duration ) -> &mut Self {unsafe { gpiod::gpiod_line_settings_set_debounce_period_us ( self.settings, period.as_micros ()as u64 , - ) + ); } + + self }/// Get the debounce period. -fn debounce_period (&self) ->Result <Duration > { +pub fn debounce_period (&self) ->Result <Duration > {Ok (Duration ::from_micros (unsafe { gpiod::gpiod_line_settings_get_debounce_period_us (self.settings) })) }/// Set the event clock setting. -fn set_event_clock (&mut self, clock:EventClock ) ->Result <()> { +pub fn set_event_clock (&mut self, clock:EventClock ) ->Result <&mut Self > {let ret =unsafe { gpiod::gpiod_line_settings_set_event_clock (self.settings, clock.gpiod_clock ()as i32 ) };
@@ -230,17 +235,17 @@ impl Settings { Errno::last(), )) }else { -Ok (()) +Ok (self) } }/// Get the event clock setting. -fn event_clock (&self) ->Result <EventClock > { +pub fn event_clock (&self) ->Result <EventClock > {EventClock ::new (unsafe { gpiod::gpiod_line_settings_get_event_clock (self.settings) }as u32 ) }/// Set the output value setting. -fn set_output_value (&mut self, value:Value ) ->Result <()> { +pub fn set_output_value (&mut self, value:Value ) ->Result <&mut Self > {let ret =unsafe { gpiod::gpiod_line_settings_set_output_value (self.settings, value.value ()) };
@@ -250,12 +255,12 @@ impl Settings { Errno::last(), )) }else { -Ok (()) +Ok (self) } }/// Get the output value, 0 or 1. -fn output_value (&self) ->Result <Value > { +pub fn output_value (&self) ->Result <Value > {let value =unsafe { gpiod::gpiod_line_settings_get_output_value (self.settings) };if value !=0 && value !=1 {
Signature · 1 line
viresh
Re: [PATCH V6 3/8] libgpiod: Add rust wrapper crate
Quoted 21 lines · Viresh Kumar
Quoted 6 lines · Kent Gibson
If the mutators for each field still exist they may as well be pub.
And they should return Result<&mut Self> so they can be chained, as you suggest.
Wrt the values param (which I would prefer was called props)
Is this fine now ? Rebased over v7.
bindings/rust/libgpiod/src/line_settings.rs +0
-3@@ -70,18 +70,18 @@ impl Settings { }/// Set line prop setting. -pub fn set_prop (&mut self, values: &[SettingVal ]) ->Result <()> { -for valuein values { -match value {
bindings/rust/libgpiod/src/line_settings.rs impl Settings { · line 73
Quoted 10 lines
bindings/rust/libgpiod/src/line_settings.rs +1
-9- SettingVal::Direction(val) => self.set_direction(*val)?, - SettingVal::EdgeDetection(val) => self.set_edge_detection(*val)?, - SettingVal::Bias(val) => self.set_bias(*val)?, - SettingVal::Drive(val) => self.set_drive(*val)?, - SettingVal::ActiveLow(val) => self.set_active_low(*val), - SettingVal::DebouncePeriod(val) => self.set_debounce_period(*val), - SettingVal::EventClock(val) => self.set_event_clock(*val)?, - SettingVal::OutputValue(val) => self.set_output_value(*val)?, - } +pub fn set_prop (&mut self, props: &[SettingVal ]) ->Result <()> {
^
also &mut Self
Apart from that, yeah that is what I had in mind, as also noted in my forthcoming v7 review.
Cheers,
Kent.
Re: [PATCH V6 3/8] libgpiod: Add rust wrapper crate
Quoted 6 lines · Kent Gibson
Quoted 1 line
+ pub fn
set_prop(&mut self, props: &[SettingVal])-> Result<()> {^
also &mut SelfApart from that, yeah that is what I had in mind, as also noted in my forthcoming v7 review.
Ahh yes.
bindings/rust/libgpiod/src/line_settings.rs +2 -2
@@ -70,7 +70,7 @@ impl Settings { }/// Set line prop setting. -pub fn set_prop (&mut self, props: &[SettingVal ]) ->Result <()> { +pub fn set_prop (&mut self, props: &[SettingVal ]) ->Result <&mut Self > {for propertyin props {match property {SettingVal ::Direction (prop) => self.set_direction (*prop)?,
@@ -84,7 +84,7 @@ impl Settings { }; } -Ok (()) +Ok (self) }/// Get the line prop setting.
Signature · 1 line
viresh
PATCH v6 4/8 libgpiod: Add rust examples
In reply to Viresh KumarAdd examples for the usage of the rust bindings, quite similar to the ones in cxx bindings.
--- bindings/rust/libgpiod/examples/gpiodetect.rs | 30 ++++++ bindings/rust/libgpiod/examples/gpiofind.rs | 35 +++++++ bindings/rust/libgpiod/examples/gpioget.rs | 42 ++++++++ bindings/rust/libgpiod/examples/gpioinfo.rs | 95 +++++++++++++++++++ bindings/rust/libgpiod/examples/gpiomon.rs | 73 ++++++++++++++ bindings/rust/libgpiod/examples/gpioset.rs | 60 ++++++++++++ bindings/rust/libgpiod/examples/gpiowatch.rs | 54 +++++++++++ 7 files changed, 389 insertions(+) create mode 100644 bindings/rust/libgpiod/examples/gpiodetect.rs create mode 100644 bindings/rust/libgpiod/examples/gpiofind.rs create mode 100644 bindings/rust/libgpiod/examples/gpioget.rs create mode 100644 bindings/rust/libgpiod/examples/gpioinfo.rs create mode 100644 bindings/rust/libgpiod/examples/gpiomon.rs create mode 100644 bindings/rust/libgpiod/examples/gpioset.rs create mode 100644 bindings/rust/libgpiod/examples/gpiowatch.rs
bindings/rust/libgpiod/examples/gpiodetect.rs new file +30 -0
@@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar <[email protected]> +// +// Simplified Rust implementation of gpiodetect tool. + +use std::env; +use std::path::Path ; + +use libgpiod::{gpiochip_devices,Error ,Result }; + +fn main () ->Result <()> { +let args:Vec <String > = env::args ().collect (); +if args.len () >1 { +println! ("Usage: {}" , args[0 ]); +return Err (Error ::InvalidArguments ); + } + +for chipin gpiochip_devices (&Path ::new ("/dev" ))? { +println! ( +"{} [{}] ({})" , + chip.name ()?, + chip.label ()?, + chip.num_lines (), + ); + } + +Ok (()) +}
bindings/rust/libgpiod/examples/gpiofind.rs new file +35 -0
@@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar <[email protected]> +// +// Simplified Rust implementation of gpiofind tool. + +use std::env; +use std::path::Path ; + +use libgpiod::{gpiochip_devices,Error ,Result }; + +fn main () ->Result <()> { +let args:Vec <String > = env::args ().collect (); +if args.len () !=2 { +println! ("Usage: {} <line-name>" , args[0 ]); +return Err (Error ::InvalidArguments ); + } + +for chipin gpiochip_devices (&Path ::new ("/dev" ))? { +let offset = chip.line_offset_from_name (&args[1 ]); +if offset.is_ok () { +println! ( +"Line {} found: Chip: {}, offset: {}" , + args[1 ], + chip.name ()?, + offset? + ); +return Ok (()); + } + } + +println! ("Failed to find line: {}" , args[1 ]); +Ok (()) +}
bindings/rust/libgpiod/examples/gpioget.rs new file +42 -0
42 lines not on this page · raw message
bindings/rust/libgpiod/examples/gpioinfo.rs new file +95 -0
95 lines not on this page · raw message
bindings/rust/libgpiod/examples/gpiomon.rs new file +73 -0
73 lines not on this page · raw message
bindings/rust/libgpiod/examples/gpioset.rs new file +60 -0
60 lines not on this page · raw message
bindings/rust/libgpiod/examples/gpiowatch.rs new file +54 -0
54 lines not on this page · raw message
Signature · 1 line
2.31.1.272.g89b43f80a514
PATCH v6 5/8 libgpiod: Add gpiosim rust crate
In reply to Viresh KumarThis adds gpiosim rust crate, which provides helpers to emulate GPIO chips.
--- bindings/rust/Cargo.toml | 3 +- bindings/rust/gpiosim/Cargo.toml | 18 ++ bindings/rust/gpiosim/build.rs | 43 ++++ bindings/rust/gpiosim/src/lib.rs | 25 +++ bindings/rust/gpiosim/src/sim.rs | 323 +++++++++++++++++++++++++++++++ 5 files changed, 411 insertions(+), 1 deletion(-) create mode 100644 bindings/rust/gpiosim/Cargo.toml create mode 100644 bindings/rust/gpiosim/build.rs create mode 100644 bindings/rust/gpiosim/src/lib.rs create mode 100644 bindings/rust/gpiosim/src/sim.rs
bindings/rust/Cargo.toml +2 -1
@@ -2,5 +2,6 @@ members = [ "libgpiod-sys", - "libgpiod" + "libgpiod", + "gpiosim" ]
bindings/rust/gpiosim/Cargo.toml new file +18 -0
@@ -0,0 +1,18 @@ +[package] +name = "gpiosim" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +libc = ">=0.2.39" +libgpiod = { path = "../libgpiod" } +vmm-sys-util = "=0.10.0" + +[features] +generate = [ "bindgen" ] + +[build-dependencies] +bindgen = { version = "0.59.1", optional = true } +cc = "1.0.46"
bindings/rust/gpiosim/build.rs new file +43 -0
43 lines not on this page · raw message
bindings/rust/gpiosim/src/lib.rs new file +25 -0
@@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0 + +#[allow(non_camel_case_types, non_upper_case_globals)] +#[cfg_attr(test, allow(deref_nullptr, non_snake_case))] +#[allow(dead_code)] +mod bindings_raw { +#[cfg(feature = "generate" )] +include! (concat! (env! ("OUT_DIR" ),"/bindings.rs" )); + +#[cfg(not(feature = "generate" ))] +include! ("bindings.rs" ); +} + +use bindings_raw::*; + +pub use bindings_raw::{ +GPIOSIM_HOG_DIR_INPUT ,GPIOSIM_HOG_DIR_OUTPUT_HIGH ,GPIOSIM_HOG_DIR_OUTPUT_LOW , +GPIOSIM_PULL_DOWN ,GPIOSIM_PULL_UP ,GPIOSIM_VALUE_ACTIVE ,GPIOSIM_VALUE_INACTIVE , +}; + +#[allow(dead_code)] +mod sim; + +#[allow(unused_imports)] +pub use sim::*;
bindings/rust/gpiosim/src/sim.rs new file +323 -0
323 lines not on this page · raw message
Signature · 1 line
2.31.1.272.g89b43f80a514
PATCH v6 6/8 gpiosim: Add pre generated rust bindings
In reply to Viresh KumarThis adds a copy of pre generated bindings and adds the suggested way of updating those in README.
--- bindings/rust/gpiosim/README.md | 11 ++ bindings/rust/gpiosim/src/bindings.rs | 180 ++++++++++++++++++++++++++ 2 files changed, 191 insertions(+) create mode 100644 bindings/rust/gpiosim/README.md create mode 100644 bindings/rust/gpiosim/src/bindings.rs
bindings/rust/gpiosim/README.md new file +11 -0
@@ -0,0 +1,11 @@ +# Generated gpiosim Rust FFI bindings +Automatically generated Rust FFI bindings via + [bindgen](https://github.com/rust-lang/rust-bindgen). + +## Updating bindings +1. Clone the source from + <https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/> +2. run `cd libgpiod/bindings/rust/gpiosim/` +2. run `cargo build --features generate` +3. Copy the bindings 'cp ../target/debug/build/gpiosim-###/out/bindings.rs src/bindings.rs' +4. Commit changes in `src/bindings.rs`
bindings/rust/gpiosim/src/bindings.rs new file +180 -0
180 lines not on this page · raw message
Signature · 1 line
2.31.1.272.g89b43f80a514
PATCH v6 7/8 libgpiod: Add rust tests
In reply to Viresh KumarAdd tests for the rust bindings, quite similar to the ones in cxx bindings.
--- bindings/rust/libgpiod/Cargo.toml | 3 + bindings/rust/libgpiod/tests/chip.rs | 97 ++++ bindings/rust/libgpiod/tests/common/config.rs | 134 +++++ bindings/rust/libgpiod/tests/common/mod.rs | 10 + bindings/rust/libgpiod/tests/edge_event.rs | 294 ++++++++++ bindings/rust/libgpiod/tests/info_event.rs | 125 +++++ bindings/rust/libgpiod/tests/line_config.rs | 105 ++++ bindings/rust/libgpiod/tests/line_info.rs | 279 ++++++++++ bindings/rust/libgpiod/tests/line_request.rs | 519 ++++++++++++++++++ bindings/rust/libgpiod/tests/line_settings.rs | 226 ++++++++ .../rust/libgpiod/tests/request_config.rs | 38 ++ 11 files changed, 1830 insertions(+) create mode 100644 bindings/rust/libgpiod/tests/chip.rs create mode 100644 bindings/rust/libgpiod/tests/common/config.rs create mode 100644 bindings/rust/libgpiod/tests/common/mod.rs create mode 100644 bindings/rust/libgpiod/tests/edge_event.rs create mode 100644 bindings/rust/libgpiod/tests/info_event.rs create mode 100644 bindings/rust/libgpiod/tests/line_config.rs create mode 100644 bindings/rust/libgpiod/tests/line_info.rs create mode 100644 bindings/rust/libgpiod/tests/line_request.rs create mode 100644 bindings/rust/libgpiod/tests/line_settings.rs create mode 100644 bindings/rust/libgpiod/tests/request_config.rs
bindings/rust/libgpiod/Cargo.toml +3 -0
@@ -11,3 +11,6 @@ libc = ">=0.2.39" libgpiod-sys = { path = "../libgpiod-sys" } thiserror = "1.0" vmm-sys-util = "=0.10.0" + +[dev-dependencies] +gpiosim = { path = "../gpiosim" }
bindings/rust/libgpiod/tests/chip.rs new file +97 -0
97 lines not on this page · raw message
bindings/rust/libgpiod/tests/common/config.rs new file +134 -0
134 lines not on this page · raw message
bindings/rust/libgpiod/tests/common/mod.rs new file +10 -0
@@ -0,0 +1,10 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar <[email protected]> + +#[allow(dead_code)] +mod config; + +#[allow(unused_imports)] +pub (crate )use config::*;
bindings/rust/libgpiod/tests/edge_event.rs new file +294 -0
294 lines not on this page · raw message
bindings/rust/libgpiod/tests/info_event.rs new file +125 -0
125 lines not on this page · raw message
bindings/rust/libgpiod/tests/line_config.rs new file +105 -0
105 lines not on this page · raw message
bindings/rust/libgpiod/tests/line_info.rs new file +279 -0
279 lines not on this page · raw message
bindings/rust/libgpiod/tests/line_request.rs new file +519 -0
519 lines not on this page · raw message
bindings/rust/libgpiod/tests/line_settings.rs new file +226 -0
226 lines not on this page · raw message
bindings/rust/libgpiod/tests/request_config.rs new file +38 -0
@@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar <[email protected]> + +mod common; + +mod request_config { +use vmm_sys_util::errno::Error as Errno ; + +use libgpiod::{request,Error as ChipError ,OperationType }; + +mod verify { +use super ::*; + +#[test] +fn default () { +let rconfig = request::Config ::new ().unwrap (); + +assert_eq! (rconfig.event_buffer_size (),0 ); +assert_eq! ( + rconfig.consumer ().unwrap_err (), +ChipError ::OperationFailed (OperationType ::RequestConfigGetConsumer ,Errno ::new (0 )) + ); + } + +#[test] +fn initialized () { +const CONSUMER : &str ="foobar" ; +let rconfig = request::Config ::new ().unwrap (); + rconfig.set_consumer (CONSUMER ); + rconfig.set_event_buffer_size (64 ); + +assert_eq! (rconfig.event_buffer_size (),64 ); +assert_eq! (rconfig.consumer ().unwrap (),CONSUMER ); + } + } +}
Signature · 1 line
2.31.1.272.g89b43f80a514
PATCH v6 8/8 libgpiod: Integrate building of rust bindings with make
In reply to Viresh KumarLets make build rust bindings as well.
--- README | 8 +++++--- TODO | 8 -------- bindings/Makefile.am | 6 ++++++ bindings/rust/Makefile.am | 18 ++++++++++++++++++ configure.ac | 16 ++++++++++++++++ 5 files changed, 45 insertions(+), 11 deletions(-) create mode 100644 bindings/rust/Makefile.am
README +5 -3
@@ -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
@@ -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
@@ -14,3 +14,9 @@ if WITH_BINDINGS_PYTHON SUBDIRS += python endif + +if WITH_BINDINGS_RUST + +SUBDIRS += rust + +endif
bindings/rust/Makefile.am new file +18 -0
@@ -0,0 +1,18 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# SPDX-FileCopyrightText: 2022 Viresh Kumar <[email protected]> + +command = cargo build --release --lib + +if WITH_TESTS +command += --tests +endif + +if WITH_EXAMPLES +command += --examples +endif + +all: + $(command) + +clean: + cargo clean
configure.ac +16 -0
@@ -212,6 +212,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
@@ -246,6 +261,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
Quoted 8 lines · Viresh Kumar
Hello,
Here is another version of rust bindings for libgpiod v2.0, based of the next/libgpiod-2.0 + the HTE patch from Bartosz [1].
Pushed here:
Pushed current version as v6, master is used for something else currently.
Signature · 1 line
viresh