libgpiod: Add Rust bindings
v7 cover libgpiod: Add Rust bindings
Hello,
Here is another version of rust bindings for libgpiod v2.0, based of the next/libgpiod-2.0.
Pushed here:
https://github.com/vireshk/libgpiod v7
[I have pushed v6 and v5 there too, in case someone wants to look at the changes].
V6->V7:
- Don't let buffer read new events if the earlier events are still referenced.
- BufferIntenal is gone now, to make the above work.
- Update example and tests too for the same.
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
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 | 101 ++ bindings/rust/libgpiod/src/event_buffer.rs | 66 + 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 | 296 +++++ 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, 6020 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 v7 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
Re: [PATCH V7 1/8] libgpiod: Add libgpiod-sys rust crate
Quoted 3 lines · Viresh Kumar
This adds libgpiod-sys rust crate, which provides FFI (foreign function interface) bindings for libgpiod APIs.
For the whole series:
Mail subject should start with [libgpiod v2]
Commits should begin with "bindings: rust: ", rather than "libgpiod:" as the latter is self-evident.
Quoted 43 lines
Signed-off-by Viresh Kumar <[email protected]>--- .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.lockbindings/rust/Cargo.toml new file +5
-0@@ -0,0 +1,5 @@ +[workspace] + +members = [ + "libgpiod-sys", +]bindings/rust/libgpiod-sys/Cargo.toml new file +0
-0
bindings/rust/libgpiod-sys/Cargo.toml line 5
Quoted 5 lines
bindings/rust/libgpiod-sys/Cargo.toml new file +5
-0@@ -0,0 +1,15 @@ +[package] +name = "libgpiod-sys" +version = "0.1.0" +edition = "2018" +
Add license and other relevant keys as per the link you helpfully include...
Quoted 63 lines
bindings/rust/libgpiod-sys/Cargo.toml new file +10
-0+# 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@@ -0,0 +1,41 @@ +#[cfg(feature = "generate" )] +extern crate bindgen; +#[cfg(feature = "generate" )] +use std::env; +#[cfg(feature = "generate" )] +use std::path::PathBuf ; + +#[cfg(feature = "generate" )] +fn generate_bindings () { +// Tell cargo to invalidate the built crate whenever following files change +println! ("cargo:rerun-if-changed=../../../include/gpiod.h" ); + +// The bindgen::Builder is the main entry point +// to bindgen, and lets you build up options for +// the resulting bindings. +let bindings = bindgen::Builder ::default () +// The input header we would like to generate +// bindings for. + .header ("../../../include/gpiod.h" ) +// Tell cargo to invalidate the built crate whenever any of the +// included header files changed. + .parse_callbacks (Box ::new (bindgen::CargoCallbacks )) +// Finish the builder and generate the bindings. + .generate () +// Unwrap the Result and panic on failure. + .expect ("Unable to generate bindings" ); + +// Write the bindings to the $OUT_DIR/bindings.rs file. +let out_path =PathBuf ::from (env::var ("OUT_DIR" ).unwrap ()); + bindings + .write_to_file (out_path.join ("bindings.rs" )) + .expect ("Couldn't write bindings!" ); +} + +fn main () { +#[cfg(feature = "generate" )] +generate_bindings (); + +println! ("cargo:rustc-link-search=./../../lib/.libs/" ); +println! ("cargo:rustc-link-lib=static=gpiod" ); +}bindings/rust/libgpiod-sys/src/lib.rs new file +0
-0
bindings/rust/libgpiod-sys/src/lib.rs line 2
Quoted 2 lines
bindings/rust/libgpiod-sys/src/lib.rs new file +2
-0@@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0 +
GPL-2.0?
Elsewhere you use Apache 2.0 AND BSD-3-Clause for rust code.
Cheers,
Kent.
Quoted 14 lines
bindings/rust/libgpiod-sys/src/lib.rs new file +11
-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
Re: [PATCH V7 1/8] libgpiod: Add libgpiod-sys rust crate
Quoted 5 lines · Kent Gibson
Quoted 5 lines
bindings/rust/libgpiod-sys/Cargo.toml new file +0
-0
Quoted 8 lines
bindings/rust/libgpiod-sys/Cargo.toml line 5
Quoted 6 lines
bindings/rust/libgpiod-sys/Cargo.toml new file +5
-0@@ -0,0 +1,15 @@ +[package] +name = "libgpiod-sys" +version = "0.1.0" +edition = "2018" +Add license and other relevant keys as per the link you helpfully include...
Is this enough ?
bindings/rust/libgpiod-sys/Cargo.toml +5 -0
@@ -1,6 +1,11 @@ [package] name = "libgpiod-sys" version = "0.1.0" +authors = ["Viresh Kumar <[email protected]>"] +description = "A rust library for libgpiod public header bindings" +repository = "https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/" +keywords = ["libgpiod", "gpio"] +license = "LGPL-2.1-or-later" edition = "2018"
I picked license details from C code, is that what we should be using here too ?
Quoted 11 lines
bindings/rust/libgpiod-sys/src/lib.rs line 2
Quoted 8 lines
bindings/rust/libgpiod-sys/src/lib.rs new file +2
-0@@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0 +GPL-2.0?
Quoted 1 line
Elsewhere you use Apache 2.0 AND BSD-3-Clause for rust code.
I would use the same information here too, once decided.
Signature · 1 line
viresh
Re: [PATCH V7 1/8] libgpiod: Add libgpiod-sys rust crate
Quoted 2 lines · Viresh Kumar
+description = "A rust library for libgpiod public header bindings"
I think "A rust library for" is redundant (I would write it in GitHub's description, but not in Cargo's). But double-check with a few other well-known libraries out there to see what they do for their Cargo descriptions.
Quoted 1 line
+repository = "https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/"
No need for the trailing slash, I think.
More importantly, you may want to add a "documentation", "categories" and "rust-version" keys.
Cheers,
Miguel
Re: [PATCH V7 1/8] libgpiod: Add libgpiod-sys rust crate
In reply to Viresh KumarQuoted 15 lines · Viresh Kumar
Quoted 5 lines · Kent Gibson
Quoted 5 lines
bindings/rust/libgpiod-sys/Cargo.toml new file +0
-0Quoted 8 lines
bindings/rust/libgpiod-sys/Cargo.toml line 5
Quoted 6 lines
bindings/rust/libgpiod-sys/Cargo.toml new file +5
-0@@ -0,0 +1,15 @@ +[package] +name = "libgpiod-sys" +version = "0.1.0" +edition = "2018" +Add license and other relevant keys as per the link you helpfully include...
Quoted 2 lines
Is this enough ?
You should add "categories" and "documentation" if you intend to publish to crates.io.
Quoted 5 lines
bindings/rust/libgpiod-sys/Cargo.toml +0
-0
bindings/rust/libgpiod-sys/Cargo.toml line 10
Quoted 10 lines
bindings/rust/libgpiod-sys/Cargo.toml +5
-0@@ -1,6 +1,11 @@ [package] name = "libgpiod-sys" version = "0.1.0" +authors = ["Viresh Kumar <[email protected]>"] +description = "A rust library for libgpiod public header bindings" +repository = "https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/" +keywords = ["libgpiod", "gpio"] +license = "LGPL-2.1-or-later" edition = "2018"
Why the 2018 edition, rather than 2021?
Quoted 3 lines
I picked license details from C code, is that what we should be using here too ?
LGPL is probably not appropriate here, as the binding code ends up being part of the binary - unless you plan to package it as a dynamic library?
The licenses of your dependencies are:
$ cargo license (MIT OR Apache-2.0) AND Unicode-DFS-2016 (1): unicode-ident Apache-2.0 AND BSD-3-Clause (1): vmm-sys-util Apache-2.0 OR MIT (8): bitflags, cc, libc, proc-macro2, quote, syn, thiserror, thiserror-impl MIT (1): intmap N/A (3): gpiosim, libgpiod, libgpiod-sys
IANAL, but one or more of those would be more appropriate than either GPL or LGPL.
You, Bart and linaro(?) will have to come to some agreement on which license or combination to go with - it depends.
Cheers,
Kent.
Re: [PATCH V7 1/8] libgpiod: Add libgpiod-sys rust crate
Quoted 2 lines · Kent Gibson
You should add "categories" and "documentation" if you intend to publish to crates.io.
In order to add documentations, should I commit documentation (created with cargo) as well in libgpiod, so I get a link to it ?
Quoted 8 lines
LGPL is probably not appropriate here, as the binding code ends up being part of the binary - unless you plan to package it as a dynamic library?
The licenses of your dependencies are:
$ cargo license (MIT OR Apache-2.0) AND Unicode-DFS-2016 (1): unicode-ident Apache-2.0 AND BSD-3-Clause (1): vmm-sys-util
This is the license used by the user crate for libgpiod, vhost-device, I am inclined to use this then, unless someone has an objection to it.
Quoted 8 lines
Apache-2.0 OR MIT (8): bitflags, cc, libc, proc-macro2, quote, syn, thiserror, thiserror-impl MIT (1): intmap
N/A (3): gpiosim, libgpiod, libgpiod-sysIANAL, but one or more of those would be more appropriate than either GPL or LGPL.
You, Bart and linaro(?) will have to come to some agreement on which license or combination to go with - it depends.
Signature · 1 line
viresh
Re: [PATCH V7 1/8] libgpiod: Add libgpiod-sys rust crate
Quoted 7 lines · Viresh Kumar
Quoted 2 lines · Kent Gibson
You should add "categories" and "documentation" if you intend to publish to crates.io.
In order to add documentations, should I commit documentation (created with cargo) as well in libgpiod, so I get a link to it ?
That doesn't really work - you get a link to the files in github, not as a web site. You need somewhere that will host those generated files as a web site. You could do that with github-pages if you don't have other options. When I've done that I would commit the docs to a separate branch, just for the docs, and have github-pages host that branch.
If you eventually publish your crate to crates.io you get documentation on docs.rs for free - and you can skip the documentation key in that case too
- it defaults to the appropriate page on docs.rs.
I assume that would be the case long term - you just need to find someway to host them in the meantime.
Quoted 4 lines
Quoted 4 lines
LGPL is probably not appropriate here, as the binding code ends up being part of the binary - unless you plan to package it as a dynamic library?
Quoted 8 lines
Quoted 4 lines
The licenses of your dependencies are:
$ cargo license (MIT OR Apache-2.0) AND Unicode-DFS-2016 (1): unicode-ident Apache-2.0 AND BSD-3-Clause (1): vmm-sys-utilThis is the license used by the user crate for libgpiod, vhost-device, I am inclined to use this then, unless someone has an objection to it.
I'm wondering if now the python bindings include actual python code there may be a similar issue there - not sure how Bart intends to package that, or what licensing implications that may have.
In both cases Bart has the final call.
Cheers,
Kent.
Quoted 11 lines
Quoted 8 lines
Apache-2.0 OR MIT (8): bitflags, cc, libc, proc-macro2, quote, syn, thiserror, thiserror-impl MIT (1): intmap
N/A (3): gpiosim, libgpiod, libgpiod-sysIANAL, but one or more of those would be more appropriate than either GPL or LGPL.
You, Bart and linaro(?) will have to come to some agreement on which license or combination to go with - it depends.Signature · 1 line
viresh
Re: [PATCH V7 1/8] libgpiod: Add libgpiod-sys rust crate
Quoted 4 lines · Kent Gibson
That doesn't really work - you get a link to the files in github, not as a web site. You need somewhere that will host those generated files as a web site. You could do that with github-pages if you don't have other options.
I don't have other options for now, so it is github.
Quoted 2 lines
When I've done that I would commit the docs to a separate branch, just for the docs, and have github-pages host that branch.
Okay.
Quoted 3 lines
If you eventually publish your crate to crates.io you get documentation on
docs.rsfor free - and you can skip the documentation key in that case too
- it defaults to the appropriate page ondocs.rs.
Right, that I knew.
Quoted 2 lines
I assume that would be the case long term - you just need to find someway to host them in the meantime.
I wonder if we should be doing this at all right now, or if required only Bartosz should do it instead of me, once this is merged ?
Maybe we need a libgpiod username for that on github ? To make it independent of personal usernames ? From what I saw, the website name will be username.github.io eventually.
Signature · 1 line
viresh
Re: [PATCH V7 1/8] libgpiod: Add libgpiod-sys rust crate
Quoted 17 lines · Viresh Kumar
Quoted 4 lines · Kent Gibson
That doesn't really work - you get a link to the files in github, not as a web site. You need somewhere that will host those generated files as a web site. You could do that with github-pages if you don't have other options.
I don't have other options for now, so it is github.
Quoted 2 lines
When I've done that I would commit the docs to a separate branch, just for the docs, and have github-pages host that branch.
Okay.
Quoted 3 lines
If you eventually publish your crate to crates.io you get documentation on
docs.rsfor free - and you can skip the documentation key in that case too
- it defaults to the appropriate page ondocs.rs.
Quoted 8 lines
Right, that I knew.
Quoted 2 lines
I assume that would be the case long term - you just need to find someway to host them in the meantime.
I wonder if we should be doing this at all right now, or if required only Bartosz should do it instead of me, once this is merged ?
I was assuming it was an interim solution, so it doesn't matter so much.
I'd be happy with docs.rs once libgpiod v2 is released and the bindings published to crates.io, and I'm personally good with looking at the generated docs locally in the meantime.
It would be easier for others to take a look if the docs were hosted, but I don't have any feel as to whether that is worth the effort or not.
Quoted 4 lines
Maybe we need a libgpiod username for that on github ? To make it independent of personal usernames ? From what I saw, the website name will be username.github.io eventually.
A libgpiod group account might be useful.
And it would be nice to have a libgpiod space somewhere, not just piggybacking on the gpio mailing list. Not sure if github is the best place - but I haven't given it much thought.
Bart, do you have any opinions?
Cheers,
Kent.
Re: [PATCH V7 1/8] libgpiod: Add libgpiod-sys rust crate
Quoted 5 lines · Kent Gibson
I was assuming it was an interim solution, so it doesn't matter so much.
I'd be happy with
docs.rsonce libgpiod v2 is released and the bindings published to crates.io, and I'm personally good with looking at the generated docs locally in the meantime.
I do plan to publish them to crates.io.
Quoted 2 lines
It would be easier for others to take a look if the docs were hosted, but I don't have any feel as to whether that is worth the effort or not.
Lets get the code merge without the docs published, we can always come back and publish the documentation somewhere once we are sure where to publish them, if it is really needed since we are going to migrate to docs.rs anyway..
Signature · 1 line
viresh
Re: [PATCH V7 1/8] libgpiod: Add libgpiod-sys rust crate
In reply to Kent GibsonQuoted 42 lines · Kent Gibson
Quoted 17 lines · Viresh Kumar
Quoted 4 lines · Kent Gibson
That doesn't really work - you get a link to the files in github, not as a web site. You need somewhere that will host those generated files as a web site. You could do that with github-pages if you don't have other options.
I don't have other options for now, so it is github.
Quoted 2 lines
When I've done that I would commit the docs to a separate branch, just for the docs, and have github-pages host that branch.
Okay.
Quoted 3 lines
If you eventually publish your crate to crates.io you get documentation on
docs.rsfor free - and you can skip the documentation key in that case too
- it defaults to the appropriate page ondocs.rs.Quoted 8 lines
Right, that I knew.
Quoted 2 lines
I assume that would be the case long term - you just need to find someway to host them in the meantime.
I wonder if we should be doing this at all right now, or if required only Bartosz should do it instead of me, once this is merged ?
I was assuming it was an interim solution, so it doesn't matter so much.
I'd be happy with
docs.rsonce libgpiod v2 is released and the bindings published to crates.io, and I'm personally good with looking at the generated docs locally in the meantime.It would be easier for others to take a look if the docs were hosted, but I don't have any feel as to whether that is worth the effort or not.
Quoted 4 lines
Maybe we need a libgpiod username for that on github ? To make it independent of personal usernames ? From what I saw, the website name will be username.github.io eventually.
Quoted 8 lines
A libgpiod group account might be useful.
And it would be nice to have a libgpiod space somewhere, not just piggybacking on the gpio mailing list. Not sure if github is the best place - but I haven't given it much thought.
Bart, do you have any opinions?
For a long time I tried avoiding github etc. but I think I'll finally have to give in and at least set up a git mirror and a discussion place. Not sure which one is better, github, gitlab, something else?
BTW about crates.io - the python bindings in libgpiod v2 use setup.py and I also plan to publish a python package on pypi so decentralization is happening this time.
I also plan to split the yocto recipe into libgpiod and python3-libgpiod.
Bart
PATCH v7 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 v7 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 | 101 ++++ bindings/rust/libgpiod/src/event_buffer.rs | 66 +++ 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, 1890 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 +101 -0
101 lines not on this page · raw message
bindings/rust/libgpiod/src/event_buffer.rs new file +66 -0
66 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 V7 3/8] libgpiod: Add rust wrapper crate
Quoted 46 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]>--- 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 | 101 ++++ bindings/rust/libgpiod/src/event_buffer.rs | 66 +++ 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, 1890 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.rsbindings/rust/Cargo.toml +1
-0@@ -2,4 +2,5 @@ members = [ "libgpiod-sys", + "libgpiod" ]bindings/rust/libgpiod/Cargo.toml new file +0
-0
bindings/rust/libgpiod/Cargo.toml line 4
Quoted 4 lines
bindings/rust/libgpiod/Cargo.toml new file +4
-0@@ -0,0 +1,13 @@ +[package] +name = "libgpiod" +version = "0.1.0" +edition = "2018"
Add license and other relevant keys as per the link.
Quoted 15 lines
bindings/rust/libgpiod/Cargo.toml new file +9
-0+ +# 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 +0
-0
bindings/rust/libgpiod/src/chip.rs line 6
Quoted 6 lines
bindings/rust/libgpiod/src/chip.rs new file +6
-0@@ -0,0 +1,253 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar <[email protected]> + +use libc::strlen;
Avoid importing function names. Idiomatically, modules and types are ok, but not functions. So says the Rust book.
bindings/rust/libgpiod/src/chip.rs line 13
Quoted 7 lines
bindings/rust/libgpiod/src/chip.rs new file +7
-0+use std::os::raw::c_char; +use std::path::Path ; +use std::sync::Arc ; +use std::time::Duration ; +use std::{slice, str}; + +use vmm_sys_util::errno::Error as Errno ;
Why vmm_sys_util::errno::Error rather than, say, errno::Errno?
bindings/rust/libgpiod/src/chip.rs line 21
Quoted 8 lines
bindings/rust/libgpiod/src/chip.rs new file +8
-0+ +use super ::{gpiod, info, line, request,Error ,Offset ,OperationType ,Result }; + +#[derive( Clone , Debug , Eq , PartialEq )] +pub (crate )struct Internal { + chip: *mut gpiod::gpiod_chip , +} +
Derived implmentation of Clone on an object containing a raw pointer and a Drop that frees that pointer? That wont end well.
If you have an Arc managing references to it below, why the need for Clone?
Types that wrap C types that should impl Clone have a C _copy function, and require an explicit impl that calls it, not a derived.
bindings/rust/libgpiod/src/chip.rs line 28
Quoted 7 lines
bindings/rust/libgpiod/src/chip.rs new file +7
-0+impl Internal { +/// Find a chip by path. +pub (crate )fn open <P :AsRef <Path >>(path: &P ) ->Result <Self > { +// Null-terminate the string +let path = path.as_ref ().to_string_lossy () +"\0" ; + +let chip =unsafe { gpiod::gpiod_chip_open (path.as_ptr ()as *const c_char ) };
All unsafes should have a preceding SAFETY comment.
Quoted 4 lines
bindings/rust/libgpiod/src/chip.rs new file +4
-0+ if chip.is_null() { + return Err(Error::OperationFailed( + OperationType::ChipOpen, + Errno::last(),
bindings/rust/libgpiod/src/chip.rs line 42
Quoted 10 lines
bindings/rust/libgpiod/src/chip.rs new file +10
-0+ )); + } + +Ok (Self { chip }) + } + +/// Private helper, Returns gpiod_chip +pub (crate )fn chip (&self) -> *mut gpiod::gpiod_chip { + self.chip + }
Just make self.chip itself pub(crate)?
Quoted 11 lines
bindings/rust/libgpiod/src/chip.rs new file +11
-0+} + +impl Drop for Internal { +/// Close the chip and release all associated resources. +fn drop (&mut self) { +unsafe { gpiod::gpiod_chip_close (self.chip) } + } +} + +/// GPIO chip +///
bindings/rust/libgpiod/src/chip.rs line 63
Quoted 10 lines
bindings/rust/libgpiod/src/chip.rs new file +10
-0+/// A GPIO chip object is associated with an open file descriptor to the GPIO +/// character device. It exposes basic information about the chip and allows +/// callers to retrieve information about each line, watch lines for state +/// changes and make line requests. +#[derive( Clone , Debug , Eq , PartialEq )] +pub struct Chip { + ichip:Arc <Internal >, + info:Info , +} +
Doesn't info need an Arc as well? (see struct Info below).
Actually, does it make sense to be able to Clone the Chip? Two threads could then watch different lines and wait_info_event() from the one chip.
That isn't going to work, at least not the way you would want.
bindings/rust/libgpiod/src/chip.rs line 66
Quoted 3 lines
bindings/rust/libgpiod/src/chip.rs new file +3
-0+unsafe impl Send for Chip {} +unsafe impl Sync for Chip {} +
Send makes sense to me, but not Sync, due to the waiting for info events. Similarly for line_request::Request.
At least not without some additional synchronisation/locking, and I'm not sure we want to go there.
bindings/rust/libgpiod/src/chip.rs line 72
Quoted 6 lines
bindings/rust/libgpiod/src/chip.rs new file +6
-0+impl Chip { +/// Find a chip by path. +pub fn open <P :AsRef <Path >>(path: &P ) ->Result <Self > { +let ichip =Arc ::new (Internal ::open (path)?); +let info =Info ::new (ichip.clone ())?; +
info is not always necessary, e.g. if the user just want to request a line. Maybe lazy load it? Assuming you want to keep a copy cached with the Chip at all.
Quoted 23 lines
bindings/rust/libgpiod/src/chip.rs new file +23
-0+Ok (Self { ichip, info }) + } + +/// Get the chip name as represented in the kernel. +pub fn name (&self) ->Result <&str > { + self.info.name () + } + +/// Get the chip label as represented in the kernel. +pub fn label (&self) ->Result <&str > { + self.info.label () + } + +/// Get the number of GPIO lines exposed by the chip. +pub fn num_lines (&self) ->usize { + self.info.num_lines () + } + +/// Get the path used to find the chip. +pub fn path (&self) ->Result <&str > { +// SAFETY: The string returned by libgpiod is guaranteed to live as long +// as the `struct Chip`. +let path =unsafe { gpiod::gpiod_chip_get_path (self.ichip.chip ()) };
bindings/rust/libgpiod/src/chip.rs line 105
Quoted 10 lines
bindings/rust/libgpiod/src/chip.rs new file +10
-0+ +// SAFETY: The string is guaranteed to be valid here by the C API. + str::from_utf8 (unsafe { slice::from_raw_parts (pathas *const u8 ,strlen (path)as usize ) }) + .map_err (Error ::StringNotUtf8 ) + } + +/// Get information about the chip. +pub fn info (&self) ->Result <Info > { +Info ::new (self.ichip.clone ()) + }
You already wrap all the methods of Info, so why the need to return a copy as well? Do you really need both?
And the functions of Info are currently pub(crate) so what could they do with it anyway?
Quoted 69 lines
bindings/rust/libgpiod/src/chip.rs new file +69
-0+ +/// Get a snapshot of information about the line. +pub fn line_info (&self, offset:Offset ) ->Result <line::Info > { + line::Info ::new (self.ichip.clone (), offset) + } + +/// Get the current snapshot of information about the line at given offset and start watching +/// it for future changes. +pub fn watch_line_info (&self, offset:Offset ) ->Result <line::Info > { + line::Info ::new_watch (self.ichip.clone (), offset) + } + +/// Stop watching a line +pub fn unwatch (&self, offset:Offset ) { +unsafe { + gpiod::gpiod_chip_unwatch_line_info (self.ichip.chip (), offset); + } + } + +/// Get the file descriptor associated with the chip. +/// +/// The returned file descriptor must not be closed by the caller, else other methods for the +/// `struct Chip` may fail. +pub fn fd (&self) ->Result <u32 > { +let fd =unsafe { gpiod::gpiod_chip_get_fd (self.ichip.chip ()) }; + +if fd <0 { +Err (Error ::OperationFailed ( +OperationType ::ChipGetFd , +Errno ::last (), + )) + }else { +Ok (fdas u32 ) + } + } + +/// Wait for line status events on any of the watched lines on the chip. +pub fn wait_info_event (&self, timeout:Option <Duration >) ->Result <bool > { +let timeout =match timeout { +Some (x) => x.as_nanos ()as i64 , +// Block indefinitely +None => -1 , + }; + +let ret =unsafe { gpiod::gpiod_chip_wait_info_event (self.ichip.chip (), timeout) }; + +match ret { + -1 =>Err (Error ::OperationFailed ( +OperationType ::ChipWaitInfoEvent , +Errno ::last (), + )), +0 =>Ok (false ), + _ =>Ok (true ), + } + } + +/// Read a single line status change event from the chip. If no events are +/// pending, this function will block. +pub fn read_info_event (&self) ->Result <info::Event > { + info::Event ::new (&self.ichip) + } + +/// Map a GPIO line's name to its offset within the chip. +pub fn line_offset_from_name (&self, name: &str ) ->Result <Offset > { +// Null-terminate the string +let name = name.to_owned () +"\0" ; + +let ret =unsafe { + gpiod::gpiod_chip_get_line_offset_from_name(
bindings/rust/libgpiod/src/chip.rs line 184
Quoted 10 lines
bindings/rust/libgpiod/src/chip.rs new file +10
-0+ self.ichip.chip(), + name.as_ptr() as *const c_char, + ) + }; + +if ret == -1 { +Err (Error ::OperationFailed ( +OperationType ::ChipGetLine , +Errno ::last (), + ))
The OperationType identifies the underlying C call that caused the error? So it should be ChipGetLineOffsetFromName?
Quoted 11 lines
bindings/rust/libgpiod/src/chip.rs new file +11
-0+ }else { +Ok (retas u32 ) + } + } + +/// Request a set of lines for exclusive usage. +pub fn request_lines ( + &self, + rconfig: &request::Config , + lconfig: &line::Config , + ) ->Result <line::Request > {
bindings/rust/libgpiod/src/chip.rs line 205
Quoted 10 lines
bindings/rust/libgpiod/src/chip.rs new file +10
-0+ line::Request ::new (&self.ichip, rconfig, lconfig) + } +} + +/// GPIO chip Information +#[derive( Clone , Debug , Eq , PartialEq )] +pub struct Info { + info: *mut gpiod::gpiod_chip_info , +} +
Does struct Info even need to be pub?
Clone/Drop wrapping a raw pointer again.
bindings/rust/libgpiod/src/chip.rs line 218
Quoted 13 lines
bindings/rust/libgpiod/src/chip.rs new file +13
-0+impl Info { +/// Find a GPIO chip by path. +pub (crate )fn new (chip:Arc <Internal >) ->Result <Self > { +let info =unsafe { gpiod::gpiod_chip_get_info (chip.chip ()) }; +if info.is_null () { +return Err (Error ::OperationFailed ( +OperationType ::ChipInfoGet , +Errno ::last (), + )); + } + +Ok (Self { info }) + }
Do these functions need to be pub(crate)? They are only used within the module (by Chip), and are visible to that anyway.
Quoted 154 lines
bindings/rust/libgpiod/src/chip.rs new file +35
-0+ +/// Get the GPIO chip name as represented in the kernel. +pub (crate )fn name (&self) ->Result <&str > { +// SAFETY: The string returned by libgpiod is guaranteed to live as long +// as the `struct Chip`. +let name =unsafe { gpiod::gpiod_chip_info_get_name (self.info) }; + +// SAFETY: The string is guaranteed to be valid here by the C API. + str::from_utf8 (unsafe { slice::from_raw_parts (nameas *const u8 ,strlen (name)as usize ) }) + .map_err (Error ::StringNotUtf8 ) + } + +/// Get the GPIO chip label as represented in the kernel. +pub (crate )fn label (&self) ->Result <&str > { +// SAFETY: The string returned by libgpiod is guaranteed to live as long +// as the `struct Chip`. +let label =unsafe { gpiod::gpiod_chip_info_get_label (self.info) }; + +// SAFETY: The string is guaranteed to be valid here by the C API. + str::from_utf8 (unsafe { slice::from_raw_parts (labelas *const u8 ,strlen (label)as usize ) }) + .map_err (Error ::StringNotUtf8 ) + } + +/// Get the number of GPIO lines exposed by the chip. +pub (crate )fn num_lines (&self) ->usize { +unsafe { gpiod::gpiod_chip_info_get_num_lines (self.info)as usize } + } +} + +impl Drop for Info { +/// Close the GPIO chip info and release all associated resources. +fn drop (&mut self) { +unsafe { gpiod::gpiod_chip_info_free (self.info) } + } +}bindings/rust/libgpiod/src/edge_event.rs new file +101
-0@@ -0,0 +1,101 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar <[email protected]> + +use std::time::Duration ; + +use vmm_sys_util::errno::Error as Errno ; + +use super ::{edge::event::Buffer , gpiod,EdgeKind ,Error ,Offset ,OperationType ,Result }; + +/// Line edge events handling +/// +/// An edge event object contains information about a single line edge event. +/// It contains the event type, timestamp and the offset of the line on which +/// the event occurred as well as two sequence numbers (global for all lines +/// in the associated request and local for this line only). +/// +/// Edge events are stored into an edge-event buffer object to improve +/// performance and to limit the number of memory allocations when a large +/// number of events are being read. + +#[derive( Debug , Eq , PartialEq )] +pub struct Event <'b> { + buffer:Option <&'bBuffer >, + event: *mut gpiod::gpiod_edge_event , +} + +impl <'b>Event <'b> { +/// Get an event stored in the buffer. +pub (crate )fn new (buffer: &'bBuffer , 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 , +Errno ::last (), + )); + } + +Ok (Self { + buffer:Some (buffer), + 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 , +Errno ::last (), + )); + } + +Ok (Self { + buffer:None , + event, + }) + } + +/// Get the event type. +pub fn event_type (&self) ->Result <EdgeKind > { +EdgeKind ::new (unsafe { gpiod::gpiod_edge_event_get_event_type (self.event) }as u32 ) + } + +/// Get the timestamp of the event. +pub fn timestamp (&self) ->Duration { +Duration ::from_nanos (unsafe { gpiod::gpiod_edge_event_get_timestamp_ns (self.event) }) + } + +/// Get the offset of the line on which the event was triggered. +pub fn line_offset (&self) ->Offset { +unsafe { gpiod::gpiod_edge_event_get_line_offset (self.event) } + } + +/// Get the global sequence number of the event. +/// +/// Returns sequence number of the event relative to all lines in the +/// associated line request. +pub fn global_seqno (&self) ->u64 { +unsafe { gpiod::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 the line within the +/// lifetime of the associated line request. +pub fn line_seqno (&self) ->u64 { +unsafe { gpiod::gpiod_edge_event_get_line_seqno (self.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.buffer.is_none () { +unsafe { gpiod::gpiod_edge_event_free (self.event) }; + } + } +}bindings/rust/libgpiod/src/event_buffer.rs new file +6
-0@@ -0,0 +1,66 @@ +// 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;
bindings/rust/libgpiod/src/event_buffer.rs line 16
Quoted 10 lines
bindings/rust/libgpiod/src/event_buffer.rs new file +10
-0+ +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 struct Buffer { + buffer: *mut gpiod::gpiod_edge_event_buffer , +}
Clone/Drop wrapping a raw pointer again.
Quoted 12 lines
bindings/rust/libgpiod/src/event_buffer.rs new file +12
-0+ +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> { + let buffer = unsafe { gpiod::gpiod_edge_event_buffer_new(capacity as c_ulong) }; + if buffer.is_null() { + return Err(Error::OperationFailed( + OperationType::EdgeEventBufferNew, + Errno::last(),
bindings/rust/libgpiod/src/event_buffer.rs line 38
Quoted 10 lines
bindings/rust/libgpiod/src/event_buffer.rs new file +10
-0+ )); + } + +Ok (Self { buffer }) + } + +/// Private helper, Returns gpiod_edge_event_buffer +pub (crate )fn buffer (&self) -> *mut gpiod::gpiod_edge_event_buffer { + self.buffer + }
Just make self.buffer pub(crate)? Why is the idea of passing a raw pointer around making my skin crawl?
Or even better, move the guts of line_request::Request::read_edge_events() into a helper function here that it can call so it doesn't need access to self.buffer.
bindings/rust/libgpiod/src/event_buffer.rs line 48
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, index) + }
What is to prevent reading an event from a slot that has not yet been populated? I realise doing that is possible in C, but it should be prevented in the Rust bindings.
Quoted 41 lines
bindings/rust/libgpiod/src/event_buffer.rs new file +18
-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 + } +} + +impl Drop for Buffer { +/// Free the edge event buffer and release all associated resources. +fn drop (&mut self) { +unsafe { gpiod::gpiod_edge_event_buffer_free (self.buffer) }; + } +}bindings/rust/libgpiod/src/info_event.rs new file +17
-0@@ -0,0 +1,68 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// 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 ::{chip, gpiod, line,Error ,InfoChangeKind ,OperationType ,Result }; + +/// Line status watch events +/// +/// Accessors for the info event objects allowing to monitor changes in GPIO +/// line state. +///
bindings/rust/libgpiod/src/info_event.rs line 27
Quoted 10 lines
bindings/rust/libgpiod/src/info_event.rs new file +10
-0+/// Callers can be notified about changes in line's state using the interfaces +/// exposed by GPIO chips. Each info event contains information about the event +/// itself (timestamp, type) as well as a snapshot of line's state in the form +/// of a line-info object. + +#[derive( Clone , Debug , Eq , PartialEq )] +pub struct Event { + event: *mut gpiod::gpiod_info_event , +} +
Clone/Drop wrapping a raw pointer again.
bindings/rust/libgpiod/src/info_event.rs line 40
Quoted 13 lines
bindings/rust/libgpiod/src/info_event.rs new file +13
-0+impl Event { +/// Get a single chip's line's status change event. +pub (crate )fn new (ichip: &Arc <chip::Internal >) ->Result <Self > { +let event =unsafe { gpiod::gpiod_chip_read_info_event (ichip.chip ()) }; +if event.is_null () { +return Err (Error ::OperationFailed ( +OperationType ::ChipReadInfoEvent , +Errno ::last (), + )); + } + +Ok (Self { event }) + }
The new() should be:
pub(crate) fn new(event: *mut gpiod::gpiod_info_event) -> Self {
Self {event}
}The guts should be in Chip - where this new() is called.
Quoted 46 lines
bindings/rust/libgpiod/src/info_event.rs new file +28
-0+ +/// Private helper, Returns gpiod_info_event +pub (crate )fn event (&self) -> *mut gpiod::gpiod_info_event { + self.event + } + +/// Get the event type of the status change event. +pub fn event_type (&self) ->Result <InfoChangeKind > { +InfoChangeKind ::new (unsafe { gpiod::gpiod_info_event_get_event_type (self.event) }as u32 ) + } + +/// Get the timestamp of the event, read from the monotonic clock. +pub fn timestamp (&self) ->Duration { +Duration ::from_nanos (unsafe { gpiod::gpiod_info_event_get_timestamp_ns (self.event) }) + } + +/// Get the line-info object associated with the event. +pub fn line_info (&self) ->Result <line::Info > { + line::Info ::new_from_event (self) + } +} + +impl Drop for Event { +/// Free the info event object and release all associated resources. +fn drop (&mut self) { +unsafe { gpiod::gpiod_info_event_free (self.event) } + } +}bindings/rust/libgpiod/src/lib.rs new file +12
-0@@ -0,0 +1,471 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Rust wrappers for GPIOD APIs +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar <[email protected]> + +//! libgpiod public API +//! +//! This is the complete documentation of the public Rust API made available to +//! users of libgpiod. +//!
bindings/rust/libgpiod/src/lib.rs line 22
Quoted 10 lines
bindings/rust/libgpiod/src/lib.rs new file +10
-0+//! The API is logically split into several parts such as: GPIO chip & line +//! operators, GPIO events handling etc. + +/// GPIO chip related definitions. +pub mod chip; + +mod edge_event; +mod event_buffer; + +/// GPIO chip edge event related definitions.
^
linebindings/rust/libgpiod/src/lib.rs line 31
Quoted 9 lines
bindings/rust/libgpiod/src/lib.rs new file +9
-0+pub mod edge { +pub use crate ::edge_event::*; + +/// GPIO chip edge event buffer related definitions. +pub mod event { +pub use crate ::event_buffer::*; + } +} +
Not sure why event_buffer needs to be nested inside an event module. event_buffer should be in the request module.
edge_event is a tougher call, either line or request. Probably the latter.
bindings/rust/libgpiod/src/lib.rs line 38
Quoted 7 lines
bindings/rust/libgpiod/src/lib.rs new file +7
-0+mod info_event; + +/// GPIO chip info event related definitions. +pub mod info { +pub use crate ::info_event::*; +} +
Why is this a top level module, not part of chip?
bindings/rust/libgpiod/src/lib.rs line 50
Quoted 12 lines
bindings/rust/libgpiod/src/lib.rs new file +12
-0+mod line_config; +mod line_info; +mod line_request; +mod line_settings; + +/// GPIO chip line related definitions. +pub mod line { +pub use crate ::line_config::*; +pub use crate ::line_info::*; +pub use crate ::line_request::*; +pub use crate ::line_settings::*; +}
The line_request should be in the request module?
Quoted 90 lines
bindings/rust/libgpiod/src/lib.rs new file +90
-0+ +mod request_config; + +/// GPIO chip request related definitions. +pub mod request { +pub use crate ::request_config::*; +} + +use libgpiod_sysas gpiod; + +use intmap::IntMap ; +use libc::strlen; +use std::fs; +use std::os::raw::c_char; +use std::path::Path ; +use std::time::Duration ; +use std::{fmt, slice, str}; + +use thiserror::Error as ThisError ; +use vmm_sys_util::errno::Error as Errno ; + +/// Operation types, used with OperationFailed() Error. +#[derive( Copy , Clone , Debug , Eq , PartialEq )] +pub enum OperationType { +ChipOpen , +ChipGetFd , +ChipWaitInfoEvent , +ChipGetLine , +ChipGetLineInfo , +ChipInfoGet , +ChipReadInfoEvent , +ChipWatchLineInfo , +EdgeEventBufferGetEvent , +EdgeEventCopy , +EdgeEventBufferNew , +InfoEventGetLineInfo , +LineConfigNew , +LineConfigAddSettings , +LineConfigGetOffsets , +LineConfigGetSetting , +LineRequest , +LineRequestReconfigLines , +LineRequestGetVal , +LineRequestGetValSubset , +LineRequestSetVal , +LineRequestSetValSubset , +LineRequestReadEdgeEvent , +LineRequestWaitEdgeEvent , +LineSettingsNew , +LineSettingsCopy , +LineSettingsGetOutVal , +LineSettingsSetDirection , +LineSettingsSetEdgeDetection , +LineSettingsSetBias , +LineSettingsSetDrive , +LineSettingsSetActiveLow , +LineSettingsSetDebouncePeriod , +LineSettingsSetEventClock , +LineSettingsSetOutputValue , +RequestConfigNew , +RequestConfigGetConsumer , +SimBankGetVal , +SimBankNew , +SimBankSetLabel , +SimBankSetNumLines , +SimBankSetLineName , +SimBankSetPull , +SimBankHogLine , +SimCtxNew , +SimDevNew , +SimDevEnable , +SimDevDisable , +} + +impl fmt::Display for OperationType { +fn fmt (&self, f: &mut fmt::Formatter <'_>) -> fmt::Result { +write! (f,"{:?}" , self) + } +} + +/// Result of libgpiod operations. +pub type Result <T > = std::result::Result <T ,Error >; + +/// Error codes for libgpiod operations. +#[derive( Copy , Clone , Debug , PartialEq , ThisError )] +pub enum Error { +#[error( "Failed to get {0}" )] +NullString (&'staticstr ), +#[error( "String not utf8: {0:?}" )] +StringNotUtf8 (str::Utf8Error ),
bindings/rust/libgpiod/src/lib.rs line 150
Quoted 10 lines
bindings/rust/libgpiod/src/lib.rs new file +10
-0+#[error( "Invalid enum {0} value: {1}" )] + InvalidEnumValue(&'static str, u32), +#[error( "Operation {0} Failed: {1}" )] + OperationFailed(OperationType, Errno), +#[error( "Invalid Arguments" )] + InvalidArguments, +#[error( "Std Io Error" )] + IoError, +} +
The remaining types in this file look specific to lines and should be in the line module?
Quoted 10 lines
bindings/rust/libgpiod/src/lib.rs new file +10
-0+/// Value settings. +#[derive( Copy , Clone , Debug , Eq , PartialEq )] +pub enum Value { +/// Active +Active , +/// Inactive +InActive , +} + +/// Maps offset to Value.
bindings/rust/libgpiod/src/lib.rs line 170
Quoted 10 lines
bindings/rust/libgpiod/src/lib.rs new file +10
-0+pub type ValueMap =IntMap <Value >; + +impl Value { +pub fn new (val:i32 ) ->Result <Self > { +match val { +0 =>Ok (Value ::InActive ), +1 =>Ok (Value ::Active ), + _ =>Err (Error ::InvalidEnumValue ("Value" , valas u32 )), + } + }
Move the Ok outside the match, and return the Err immediately. Not a biggy here, but where there are more variants it makes it easier to read, if nothing else.
Quoted 274 lines
bindings/rust/libgpiod/src/lib.rs new file +274
-0+ +fn value (&self) ->i32 { +match self { +Value ::Active =>1 , +Value ::InActive =>0 , + } + } +} + +/// Offset type. +pub type Offset =u32 ; + +/// Direction settings. +#[derive( Copy , Clone , Debug , Eq , PartialEq )] +pub enum Direction { +/// Request the line(s), but don't change direction. +AsIs , +/// Direction is input - for reading the value of an externally driven GPIO line. +Input , +/// Direction is output - for driving the GPIO line. +Output , +} + +impl Direction { +fn new (dir:u32 ) ->Result <Self > { +match dir { + gpiod::GPIOD_LINE_DIRECTION_AS_IS =>Ok (Direction ::AsIs ), + gpiod::GPIOD_LINE_DIRECTION_INPUT =>Ok (Direction ::Input ), + gpiod::GPIOD_LINE_DIRECTION_OUTPUT =>Ok (Direction ::Output ), + _ =>Err (Error ::InvalidEnumValue ("Direction" , dir)), + } + } + +fn gpiod_direction (&self) ->u32 { +match self { +Direction ::AsIs => gpiod::GPIOD_LINE_DIRECTION_AS_IS , +Direction ::Input => gpiod::GPIOD_LINE_DIRECTION_INPUT , +Direction ::Output => gpiod::GPIOD_LINE_DIRECTION_OUTPUT , + } + } +} + +/// Internal bias settings. +#[derive( Copy , Clone , Debug , Eq , PartialEq )] +pub enum Bias { +/// The internal bias is disabled. +Disabled , +/// The internal pull-up bias is enabled. +PullUp , +/// The internal pull-down bias is enabled. +PullDown , +} + +impl Bias { +fn new (bias:u32 ) ->Result <Option <Self >> { +match bias { + gpiod::GPIOD_LINE_BIAS_UNKNOWN =>Ok (None ), + gpiod::GPIOD_LINE_BIAS_AS_IS =>Ok (None ), + gpiod::GPIOD_LINE_BIAS_DISABLED =>Ok (Some (Bias ::Disabled )), + gpiod::GPIOD_LINE_BIAS_PULL_UP =>Ok (Some (Bias ::PullUp )), + gpiod::GPIOD_LINE_BIAS_PULL_DOWN =>Ok (Some (Bias ::PullDown )), + _ =>Err (Error ::InvalidEnumValue ("Bias" , bias)), + } + } + +fn gpiod_bias (bias:Option <Bias >) ->u32 { +match bias { +None => gpiod::GPIOD_LINE_BIAS_AS_IS , +Some (bias) =>match bias { +Bias ::Disabled => gpiod::GPIOD_LINE_BIAS_DISABLED , +Bias ::PullUp => gpiod::GPIOD_LINE_BIAS_PULL_UP , +Bias ::PullDown => gpiod::GPIOD_LINE_BIAS_PULL_DOWN , + }, + } + } +} + +/// Drive settings. +#[derive( Copy , Clone , Debug , Eq , PartialEq )] +pub enum Drive { +/// Drive setting is push-pull. +PushPull , +/// Line output is open-drain. +OpenDrain , +/// Line output is open-source. +OpenSource , +} + +impl Drive { +fn new (drive:u32 ) ->Result <Self > { +match drive { + gpiod::GPIOD_LINE_DRIVE_PUSH_PULL =>Ok (Drive ::PushPull ), + gpiod::GPIOD_LINE_DRIVE_OPEN_DRAIN =>Ok (Drive ::OpenDrain ), + gpiod::GPIOD_LINE_DRIVE_OPEN_SOURCE =>Ok (Drive ::OpenSource ), + _ =>Err (Error ::InvalidEnumValue ("Drive" , drive)), + } + } + +fn gpiod_drive (&self) ->u32 { +match self { +Drive ::PushPull => gpiod::GPIOD_LINE_DRIVE_PUSH_PULL , +Drive ::OpenDrain => gpiod::GPIOD_LINE_DRIVE_OPEN_DRAIN , +Drive ::OpenSource => gpiod::GPIOD_LINE_DRIVE_OPEN_SOURCE , + } + } +} + +/// Edge detection settings. +#[derive( Copy , Clone , Debug , Eq , PartialEq )] +pub enum Edge { +/// Line detects rising edge events. +Rising , +/// Line detects falling edge events. +Falling , +/// Line detects both rising and falling edge events. +Both , +} + +impl Edge { +fn new (edge:u32 ) ->Result <Option <Self >> { +match edge { + gpiod::GPIOD_LINE_EDGE_NONE =>Ok (None ), + gpiod::GPIOD_LINE_EDGE_RISING =>Ok (Some (Edge ::Rising )), + gpiod::GPIOD_LINE_EDGE_FALLING =>Ok (Some (Edge ::Falling )), + gpiod::GPIOD_LINE_EDGE_BOTH =>Ok (Some (Edge ::Both )), + _ =>Err (Error ::InvalidEnumValue ("Edge" , edge)), + } + } + +fn gpiod_edge (edge:Option <Edge >) ->u32 { +match edge { +None => gpiod::GPIOD_LINE_EDGE_NONE , +Some (edge) =>match edge { +Edge ::Rising => gpiod::GPIOD_LINE_EDGE_RISING , +Edge ::Falling => gpiod::GPIOD_LINE_EDGE_FALLING , +Edge ::Both => gpiod::GPIOD_LINE_EDGE_BOTH , + }, + } + } +} + +/// Line setting kind. +#[derive( Copy , Clone , Debug , Eq , PartialEq )] +pub enum SettingKind { +/// Line direction. +Direction , +/// Bias. +Bias , +/// Drive. +Drive , +/// Edge detection. +EdgeDetection , +/// Active-low setting. +ActiveLow , +/// Debounce period. +DebouncePeriod , +/// Event clock type. +EventClock , +/// Output value. +OutputValue , +} + +/// Line settings. +#[derive( Copy , Clone , Debug , Eq , PartialEq )] +pub enum SettingVal { +/// Line direction. +Direction (Direction ), +/// Bias. +Bias (Option <Bias >), +/// Drive. +Drive (Drive ), +/// Edge detection. +EdgeDetection (Option <Edge >), +/// Active-low setting. +ActiveLow (bool ), +/// Debounce period. +DebouncePeriod (Duration ), +/// Event clock type. +EventClock (EventClock ), +/// Output value. +OutputValue (Value ), +} + +impl fmt::Display for SettingVal { +fn fmt (&self, f: &mut fmt::Formatter <'_>) -> fmt::Result { +write! (f,"{:?}" , self) + } +} + +/// Event clock settings. +#[derive( Copy , Clone , Debug , Eq , PartialEq )] +pub enum EventClock { +/// Line uses the monotonic clock for edge event timestamps. +Monotonic , +/// Line uses the realtime clock for edge event timestamps. +Realtime , +/// Line uses the hardware timestamp engine clock for edge event timestamps. +HTE , +} + +impl EventClock { +fn new (clock:u32 ) ->Result <Self > { +match clock { + gpiod::GPIOD_LINE_EVENT_CLOCK_MONOTONIC =>Ok (EventClock ::Monotonic ), + gpiod::GPIOD_LINE_EVENT_CLOCK_REALTIME =>Ok (EventClock ::Realtime ), + gpiod::GPIOD_LINE_EVENT_CLOCK_HTE =>Ok (EventClock ::HTE ), + _ =>Err (Error ::InvalidEnumValue ("Eventclock" , clock)), + } + } + +fn gpiod_clock (&self) ->u32 { +match self { +EventClock ::Monotonic => gpiod::GPIOD_LINE_EVENT_CLOCK_MONOTONIC , +EventClock ::Realtime => gpiod::GPIOD_LINE_EVENT_CLOCK_REALTIME , +EventClock ::HTE => gpiod::GPIOD_LINE_EVENT_CLOCK_HTE , + } + } +} + +/// Line status change event types. +#[derive( Copy , Clone , Debug , Eq , PartialEq )] +pub enum InfoChangeKind { +/// Line has been requested. +LineRequested , +/// Previously requested line has been released. +LineReleased , +/// Line configuration has changed. +LineConfigChanged , +} + +impl InfoChangeKind { +fn new (kind:u32 ) ->Result <Self > { +match kind { + gpiod::GPIOD_INFO_EVENT_LINE_REQUESTED =>Ok (InfoChangeKind ::LineRequested ), + gpiod::GPIOD_INFO_EVENT_LINE_RELEASED =>Ok (InfoChangeKind ::LineReleased ), + gpiod::GPIOD_INFO_EVENT_LINE_CONFIG_CHANGED =>Ok (InfoChangeKind ::LineConfigChanged ), + _ =>Err (Error ::InvalidEnumValue ("InfoChangeKind" , kind)), + } + } +} + +/// Edge event types. +#[derive( Copy , Clone , Debug , Eq , PartialEq )] +pub enum EdgeKind { +/// Rising edge event. +Rising , +/// Falling edge event. +Falling , +} + +impl EdgeKind { +fn new (kind:u32 ) ->Result <Self > { +match kind { + gpiod::GPIOD_EDGE_EVENT_RISING_EDGE =>Ok (EdgeKind ::Rising ), + gpiod::GPIOD_EDGE_EVENT_FALLING_EDGE =>Ok (EdgeKind ::Falling ), + _ =>Err (Error ::InvalidEnumValue ("EdgeEvent" , kind)), + } + } +} + +/// Various libgpiod-related functions. + +/// Check if the file pointed to by path is a GPIO chip character device. +/// +/// Returns true if the file exists and is a GPIO chip character device or a +/// symbolic link to it. +pub fn is_gpiochip_device <P :AsRef <Path >>(path: &P ) ->bool { +// Null-terminate the string +let path = path.as_ref ().to_string_lossy () +"\0" ; + +unsafe { gpiod::gpiod_is_gpiochip_device (path.as_ptr ()as *const c_char ) } +} + +/// Iterator for GPIO devices.
bindings/rust/libgpiod/src/lib.rs line 454
Quoted 10 lines
bindings/rust/libgpiod/src/lib.rs new file +10
-0+pub fn gpiochip_devices <P :AsRef <Path >>(path: &P ) ->Result <Vec <chip::Chip >> { +let mut chips =Vec ::new (); + +for entryin fs::read_dir (path).map_err (|_|Error ::IoError )?.flatten () { +let path = entry.path (); + +if is_gpiochip_device (&path) { + chips.push (chip::Chip ::open (&path)?); + } + }
If there is a symlink to a device both in the path, or multiple symlinks to the same device, you will get the same chip several times in the returned Vec?
Some mention of the order in which devices are returned? If you are returning a Vec then sorted might be nice, else state they are not sorted.
And strictly speaking a Vec is not an iterator. It can be iterated over, but Vec.iter() is its iterator.
Quoted 7 lines
bindings/rust/libgpiod/src/lib.rs new file +7
-0+ +Ok (chips) +} + +/// Get the API version of the library as a human-readable string. +pub fn version_string () ->Result <&'staticstr > { +// SAFETY: The string returned by libgpiod is guaranteed to live forever.
bindings/rust/libgpiod/src/lib.rs line 471
Quoted 10 lines
bindings/rust/libgpiod/src/lib.rs new file +10
-0+let version =unsafe { gpiod::gpiod_version_string () }; + +if version.is_null () { +return Err (Error ::NullString ("GPIO library version" )); + } + +// SAFETY: The string is guaranteed to be valid here by the C API. + str::from_utf8 (unsafe { slice::from_raw_parts (versionas *const u8 ,strlen (version)as usize ) }) + .map_err (Error ::StringNotUtf8 ) +}
What if the Rust bindings version differ from the libgpiod version?
Quoted 17 lines
bindings/rust/libgpiod/src/line_config.rs new file +11
-0@@ -0,0 +1,118 @@ +// 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::slice::from_raw_parts; + +use vmm_sys_util::errno::Error as Errno ; + +use super ::{gpiod, line::Settings ,Error ,Offset ,OperationType ,Result };
bindings/rust/libgpiod/src/line_config.rs line 21
Quoted 10 lines
bindings/rust/libgpiod/src/line_config.rs new file +10
-0+ +/// Line configuration objects. +/// +/// The line-config object contains the configuration for lines that can be +/// used in two cases: +/// - when making a line request +/// - when reconfiguring a set of already requested lines. +/// +/// A new line-config object is empty. Using it in a request will lead to an +/// error. In order to a line-config to become useful, it needs to be assigned
^
forbindings/rust/libgpiod/src/line_config.rs line 32
Quoted 11 lines
bindings/rust/libgpiod/src/line_config.rs new file +11
-0+/// at least one offset-to-settings mapping by calling +/// ::gpiod_line_config_add_line_settings. +/// +/// When calling ::gpiod_chip_request_lines, the library will request all +/// offsets that were assigned settings in the order that they were assigned. + +#[derive( Clone , Debug , Eq , PartialEq )] +pub struct Config { + config: *mut gpiod::gpiod_line_config , +} +
Clone/Drop wrapping a raw pointer again.
Quoted 44 lines
bindings/rust/libgpiod/src/line_config.rs new file +44
-0+impl Config { +/// Create a new line config object. +pub fn new () ->Result <Self > { +let config =unsafe { gpiod::gpiod_line_config_new () }; + +if config.is_null () { +return Err (Error ::OperationFailed ( +OperationType ::LineConfigNew , +Errno ::last (), + )); + } + +Ok (Self { config }) + } + +/// Private helper, Returns gpiod_line_config +pub (crate )fn config (&self) -> *mut gpiod::gpiod_line_config { + self.config + } + +/// Resets the entire configuration stored in the object. This is useful if +/// the user wants to reuse the object without reallocating it. +pub fn reset (&mut self) { +unsafe { gpiod::gpiod_line_config_reset (self.config) } + } + +/// Add line settings for a set of offsets. +pub fn add_line_settings (&self, offsets: &[Offset ], settings:Settings ) ->Result <()> { +let ret =unsafe { + gpiod::gpiod_line_config_add_line_settings ( + self.config, + offsets.as_ptr (), + offsets.len ()as c_ulong , + settings.settings (), + ) + }; + +if ret == -1 { +Err (Error ::OperationFailed ( +OperationType ::LineConfigAddSettings , +Errno ::last (), + )) + }else { +Ok (())
bindings/rust/libgpiod/src/line_config.rs line 86
Quoted 10 lines
bindings/rust/libgpiod/src/line_config.rs new file +10
-0+ } + } + + /// Get line settings for offset. + pub fn line_settings(&self, offset: Offset) -> Result<Settings> { + let settings = unsafe { gpiod::gpiod_line_config_get_line_settings(self.config, offset) }; + + if settings.is_null() { + return Err(Error::OperationFailed( + OperationType::LineConfigGetSetting,
^
SettingsQuoted 60 lines
bindings/rust/libgpiod/src/line_config.rs new file +32
-0+ Errno::last(), + )); + } + +Ok (Settings ::new_with_settings (settings)) + } + +/// Get configured offsets. +pub fn offsets (&self) ->Result <Vec <Offset >> { +let mut num:u64 =0 ; +let mut offsets: *mut Offset = std::ptr::null_mut (); + +let ret = +unsafe { gpiod::gpiod_line_config_get_offsets (self.config, &mut num, &mut offsets) }; + +if ret == -1 { +Err (Error ::OperationFailed ( +OperationType ::LineConfigGetOffsets , +Errno ::last (), + )) + }else { +Ok (unsafe {from_raw_parts (offsetsas *const Offset , numas usize ).to_vec () }) + } + } +} + +impl Drop for Config { +/// Free the line config object and release all associated resources. +fn drop (&mut self) { +unsafe { gpiod::gpiod_line_config_free (self.config) } + } +}bindings/rust/libgpiod/src/line_info.rs new file +22
-0@@ -0,0 +1,180 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar <[email protected]> + +use libc::strlen; +use std::sync::Arc ; +use std::time::Duration ; +use std::{slice, str}; + +use vmm_sys_util::errno::Error as Errno ; + +use super ::{ + chip, gpiod, info,Bias ,Direction ,Drive ,Edge ,Error ,EventClock ,Offset ,OperationType , +Result , +}; + +/// Line info +/// +/// Exposes functions for retrieving kernel information about both requested and +/// free lines. Line info object contains an immutable snapshot of a line's status. +///
bindings/rust/libgpiod/src/line_info.rs line 32
Quoted 10 lines
bindings/rust/libgpiod/src/line_info.rs new file +10
-0+/// The line info contains all the publicly available information about a +/// line, which does not include the line value. The line must be requested +/// to access the line value. + +#[derive( Clone , Debug , Eq , PartialEq )] +pub struct Info { + info: *mut gpiod::gpiod_line_info , + info_event:bool , +} +
Clone/Drop wrapping a raw pointer again.
Quoted 7 lines
bindings/rust/libgpiod/src/line_info.rs new file +7
-0+impl Info { + /// Get a snapshot of information about the line and optionally start watching it for changes. + pub(crate) fn new(ichip: Arc<chip::Internal>, offset: Offset) -> Result<Self> { + let info = unsafe { gpiod::gpiod_chip_get_line_info(ichip.chip(), offset) }; + + if info.is_null() { + return Err(Error::OperationFailed(
bindings/rust/libgpiod/src/line_info.rs line 49
Quoted 10 lines
bindings/rust/libgpiod/src/line_info.rs new file +10
-0+ OperationType::ChipGetLineInfo, + Errno::last(), + )); + } + +Ok (Self { + info, + info_event:false , + }) + }
Move the guts into chip where this is called. This should just be a bare contructor:
pub(crate) fn new(info: *mut gpiod::gpiod_line_info, free_me_seymour: bool)
Quoted 6 lines
bindings/rust/libgpiod/src/line_info.rs new file +6
-0+ + pub(crate) fn new_watch(ichip: Arc<chip::Internal>, offset: Offset) -> Result<Self> { + let info = unsafe { gpiod::gpiod_chip_watch_line_info(ichip.chip(), offset) }; + + if info.is_null() { + return Err(Error::OperationFailed(
bindings/rust/libgpiod/src/line_info.rs line 65
Quoted 10 lines
bindings/rust/libgpiod/src/line_info.rs new file +10
-0+ OperationType::ChipWatchLineInfo, + Errno::last(), + )); + } + +Ok (Self { + info, + info_event:false , + }) + }
Move the guts into chip where this is called, and call the new() above.
Quoted 7 lines
bindings/rust/libgpiod/src/line_info.rs new file +7
-0+ + /// Get the Line info object associated with an event. + pub(crate) fn new_from_event(event: &info::Event) -> Result<Self> { + let info = unsafe { gpiod::gpiod_info_event_get_line_info(event.event()) }; + + if info.is_null() { + return Err(Error::OperationFailed(
bindings/rust/libgpiod/src/line_info.rs line 82
Quoted 10 lines
bindings/rust/libgpiod/src/line_info.rs new file +10
-0+ OperationType::InfoEventGetLineInfo, + Errno::last(), + )); + } + +Ok (Self { + info, + info_event:true , + }) + }
Move the guts into info_events where this is called, and call the new() above.
Quoted 118 lines
bindings/rust/libgpiod/src/line_info.rs new file +98
-0+ +/// Get the offset of the line within the GPIO chip. +/// +/// The offset uniquely identifies the line on the chip. The combination of the chip and offset +/// uniquely identifies the line within the system. + +pub fn offset (&self) ->Offset { +unsafe { gpiod::gpiod_line_info_get_offset (self.info) } + } + +/// Get GPIO line's name. +pub fn name (&self) ->Result <&str > { +// SAFETY: The string returned by libgpiod is guaranteed to live as long +// as the `struct Info`. +let name =unsafe { gpiod::gpiod_line_info_get_name (self.info) }; +if name.is_null () { +return Err (Error ::NullString ("GPIO line's name" )); + } + +// SAFETY: The string is guaranteed to be valid here by the C API. + str::from_utf8 (unsafe { slice::from_raw_parts (nameas *const u8 ,strlen (name)as usize ) }) + .map_err (Error ::StringNotUtf8 ) + } + +/// Returns True if the line is in use, false otherwise. +/// +/// The user space can't know exactly why a line is busy. It may have been +/// requested by another process or hogged by the kernel. It only matters that +/// the line is used and we can't request it. +pub fn is_used (&self) ->bool { +unsafe { gpiod::gpiod_line_info_is_used (self.info) } + } + +/// Get the GPIO line's consumer name. +pub fn consumer (&self) ->Result <&str > { +// SAFETY: The string returned by libgpiod is guaranteed to live as long +// as the `struct Info`. +let name =unsafe { gpiod::gpiod_line_info_get_consumer (self.info) }; +if name.is_null () { +return Err (Error ::NullString ("GPIO line's consumer name" )); + } + +// SAFETY: The string is guaranteed to be valid here by the C API. + str::from_utf8 (unsafe { slice::from_raw_parts (nameas *const u8 ,strlen (name)as usize ) }) + .map_err (Error ::StringNotUtf8 ) + } + +/// Get the GPIO line's direction. +pub fn direction (&self) ->Result <Direction > { +Direction ::new (unsafe { gpiod::gpiod_line_info_get_direction (self.info) }as u32 ) + } + +/// Returns true if the line is "active-low", false otherwise. +pub fn is_active_low (&self) ->bool { +unsafe { gpiod::gpiod_line_info_is_active_low (self.info) } + } + +/// Get the GPIO line's bias setting. +pub fn bias (&self) ->Result <Option <Bias >> { +Bias ::new (unsafe { gpiod::gpiod_line_info_get_bias (self.info) }as u32 ) + } + +/// Get the GPIO line's drive setting. +pub fn drive (&self) ->Result <Drive > { +Drive ::new (unsafe { gpiod::gpiod_line_info_get_drive (self.info) }as u32 ) + } + +/// Get the current edge detection setting of the line. +pub fn edge_detection (&self) ->Result <Option <Edge >> { +Edge ::new (unsafe { gpiod::gpiod_line_info_get_edge_detection (self.info) }as u32 ) + } + +/// Get the current event clock setting used for edge event timestamps. +pub fn event_clock (&self) ->Result <EventClock > { +EventClock ::new (unsafe { gpiod::gpiod_line_info_get_event_clock (self.info) }as u32 ) + } + +/// Returns true if the line is debounced (either by hardware or by the +/// kernel software debouncer), false otherwise. +pub fn is_debounced (&self) ->bool { +unsafe { gpiod::gpiod_line_info_is_debounced (self.info) } + } + +/// Get the debounce period of the line. +pub fn debounce_period (&self) ->Duration { +Duration ::from_micros (unsafe { gpiod::gpiod_line_info_get_debounce_period_us (self.info) }) + } +} + +impl Drop for Info { +fn drop (&mut self) { +// We must not free the Line info object created from `struct info::Event` by calling +// libgpiod API. +if !self.info_event { +unsafe { gpiod::gpiod_line_info_free (self.info) } + } + } +}bindings/rust/libgpiod/src/line_request.rs new file +14
-0@@ -0,0 +1,246 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar <[email protected]> + +use libc::EINVAL ; +use std::os::raw::c_ulong; +use std::sync::Arc ; +use std::time::Duration ; + +use vmm_sys_util::errno::Error as Errno ; + +use super ::{ + chip, edge, gpiod, line, request,Error ,Offset ,OperationType ,Result ,Value ,ValueMap ,
bindings/rust/libgpiod/src/line_request.rs line 24
Quoted 10 lines
bindings/rust/libgpiod/src/line_request.rs new file +10
-0+}; + +/// Line request operations +/// +/// Allows interaction with a set of requested lines. +#[derive( Clone , Debug , Eq , PartialEq )] +pub struct Request { + request: *mut gpiod::gpiod_line_request , +} +
Clone/Drop wrapping a raw pointer again.
Does it make sense to be able to clone a request?
As per Chip, concerns being about reading events concurrently, as well as the freeing of the underlying resources.
Quoted 4 lines
bindings/rust/libgpiod/src/line_request.rs new file +4
-0+impl Request { +/// Request a set of lines for exclusive usage. +pub (crate )fn new ( + ichip: &Arc<chip::Internal>,
bindings/rust/libgpiod/src/line_request.rs line 38
Quoted 10 lines
bindings/rust/libgpiod/src/line_request.rs new file +10
-0+ rconfig: &request::Config, + lconfig: &line::Config, + ) -> Result<Self> { + let request = unsafe { + gpiod::gpiod_chip_request_lines(ichip.chip(), rconfig.config(), lconfig.config()) + }; + + if request.is_null() { + return Err(Error::OperationFailed( + OperationType::LineRequest,
^
ChipRequestLinesbindings/rust/libgpiod/src/line_request.rs line 44
Quoted 6 lines
bindings/rust/libgpiod/src/line_request.rs new file +6
-0+ Errno::last(), + )); + } + +Ok (Self { request }) + }
As with other constructors, this should be a bare constructor, and the guts should be in Chip where this function is called.
Quoted 104 lines
bindings/rust/libgpiod/src/line_request.rs new file +104
-0+ +/// Get the number of lines in the request. +pub fn num_lines (&self) ->usize { +unsafe { gpiod::gpiod_line_request_get_num_lines (self.request)as usize } + } + +/// Get the offsets of lines in the request. +pub fn offsets (&self) ->Vec <Offset > { +let mut offsets =vec! [0 ; self.num_lines ()as usize ]; + +unsafe { gpiod::gpiod_line_request_get_offsets (self.request, offsets.as_mut_ptr ()) }; + offsets + } + +/// Get the value (0 or 1) of a single line associated with the request. +pub fn value (&self, offset:Offset ) ->Result <Value > { +let value =unsafe { gpiod::gpiod_line_request_get_value (self.request, offset) }; + +if value !=0 && value !=1 { +Err (Error ::OperationFailed ( +OperationType ::LineRequestGetVal , +Errno ::last (), + )) + }else { +Value ::new (value) + } + } + +/// Get values of a subset of lines associated with the request. +pub fn values_subset (&self, offsets: &[Offset ]) ->Result <ValueMap > { +let mut values =vec! [0 ; offsets.len ()]; + +let ret =unsafe { + gpiod::gpiod_line_request_get_values_subset ( + self.request, + offsets.len ()as c_ulong , + offsets.as_ptr (), + values.as_mut_ptr (), + ) + }; + +if ret == -1 { +Err (Error ::OperationFailed ( +OperationType ::LineRequestGetValSubset , +Errno ::last (), + )) + }else { +let mut map =ValueMap ::new (); + +for (i, val)in values.iter ().enumerate () { + map.insert (offsets[i].into (),Value ::new (*val)?); + } + +Ok (map) + } + } + +/// Get values of all lines associated with the request. +pub fn values (&self) ->Result <ValueMap > { + self.values_subset (&self.offsets ()) + } + +/// Set the value of a single line associated with the request. +pub fn set_value (&self, offset:Offset , value:Value ) ->Result <()> { +let ret = +unsafe { gpiod::gpiod_line_request_set_value (self.request, offset, value.value ()) }; + +if ret == -1 { +Err (Error ::OperationFailed ( +OperationType ::LineRequestSetVal , +Errno ::last (), + )) + }else { +Ok (()) + } + } + +/// Get values of a subset of lines associated with the request. +pub fn set_values_subset (&self, map:ValueMap ) ->Result <()> { +let mut offsets =Vec ::new (); +let mut values =Vec ::new (); + +for (offset, value)in map { + offsets.push (offsetas u32 ); + values.push (value.value ()); + } + +let ret =unsafe { + gpiod::gpiod_line_request_set_values_subset ( + self.request, + offsets.len ()as c_ulong , + offsets.as_ptr (), + values.as_ptr (), + ) + }; + +if ret == -1 { +Err (Error ::OperationFailed ( +OperationType ::LineRequestSetValSubset , +Errno ::last (), + )) + }else { +Ok (()) + }
bindings/rust/libgpiod/src/line_request.rs line 158
Quoted 10 lines
bindings/rust/libgpiod/src/line_request.rs new file +10
-0+ } + +/// Get values of all lines associated with the request. +pub fn set_values (&self, values: &[Value ]) ->Result <()> { +if values.len () != self.num_lines ()as usize { +return Err (Error ::OperationFailed ( +OperationType ::LineRequestSetVal , +Errno ::new (EINVAL ), + )); + }
The error implies a C call failed, but that isn't the case. Make it a plain InvalidArgument?
Quoted 28 lines
bindings/rust/libgpiod/src/line_request.rs new file +28
-0+ + let mut new_values = Vec::new(); + for value in values { + new_values.push(value.value()); + } + + let ret = + unsafe { gpiod::gpiod_line_request_set_values(self.request, new_values.as_ptr()) }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::LineRequestSetVal, + Errno::last(), + )) + } else { + Ok(()) + } + } + + /// Update the configuration of lines associated with the line request. + pub fn reconfigure_lines(&self, lconfig: &line::Config) -> Result<()> { + let ret = + unsafe { gpiod::gpiod_line_request_reconfigure_lines(self.request, lconfig.config()) }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::LineRequestReconfigLines, + Errno::last(),
bindings/rust/libgpiod/src/line_request.rs line 196
Quoted 10 lines
bindings/rust/libgpiod/src/line_request.rs new file +10
-0+ )) + }else { +Ok (()) + } + } + +/// Get the file descriptor associated with the line request. +pub fn fd (&self) ->u32 { +unsafe { gpiod::gpiod_line_request_get_fd (self.request)as u32 } + }
impl AsRawFd?
Quoted 15 lines
bindings/rust/libgpiod/src/line_request.rs new file +15
-0+ + /// Wait for edge events on any of the lines associated with the request. + pub fn wait_edge_event(&self, timeout: Option<Duration>) -> Result<bool> { + let timeout = match timeout { + Some(x) => x.as_nanos() as i64, + // Block indefinitely + None => -1, + }; + + let ret = unsafe { gpiod::gpiod_line_request_wait_edge_event(self.request, timeout) }; + + match ret { + -1 => Err(Error::OperationFailed( + OperationType::LineRequestWaitEdgeEvent, + Errno::last(),
bindings/rust/libgpiod/src/line_request.rs line 221
Quoted 10 lines
bindings/rust/libgpiod/src/line_request.rs new file +10
-0+ )), + 0 => Ok(false), + _ => Ok(true), + } + } + +/// 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: &mut edge::event::Buffer ) ->Result <u32 > {
What is to stop the user requesting events between the len returned here and the end of the buffer? Why knows what state they are in.
Would be better to return an object that can only access the relevant events in the buffer.
I've got an alternative for the event buffer implementation - will describe that at the end.
Quoted 50 lines
bindings/rust/libgpiod/src/line_request.rs new file +25
-0+let ret =unsafe { + gpiod::gpiod_line_request_read_edge_event ( + self.request, + buffer.buffer (), + buffer.capacity ()as u64 , + ) + }; + +if ret == -1 { +Err (Error ::OperationFailed ( +OperationType ::LineRequestReadEdgeEvent , +Errno ::last (), + )) + }else { +Ok (retas u32 ) + } + } +} + +impl Drop for Request { +/// Release the requested lines and free all associated resources. +fn drop (&mut self) { +unsafe { gpiod::gpiod_line_request_release (self.request) } + } +}bindings/rust/libgpiod/src/line_settings.rs new file +19
-0@@ -0,0 +1,277 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar <[email protected]> + +use std::time::Duration ; + +use vmm_sys_util::errno::Error as Errno ; + +use super ::{ + gpiod,Bias ,Direction ,Drive ,Edge ,Error ,EventClock ,OperationType ,Result ,SettingKind , +SettingVal ,Value , +}; + +/// Line settings objects. +/// +/// Line settings object contains a set of line properties that can be used +/// when requesting lines or reconfiguring an existing request. +///
bindings/rust/libgpiod/src/line_settings.rs line 29
Quoted 10 lines
bindings/rust/libgpiod/src/line_settings.rs new file +10
-0+/// Mutators in general can only fail if the new property value is invalid. The +/// return values can be safely ignored - the object remains valid even after +/// a mutator fails and simply uses the sane default appropriate for given +/// property. + +#[derive( Clone , Debug , Eq , PartialEq )] +pub struct Settings { + settings: *mut gpiod::gpiod_line_settings , +} +
Clone/Drop wrapping a raw pointer again.
Quoted 13 lines
bindings/rust/libgpiod/src/line_settings.rs new file +13
-0+impl Settings { +/// Create a new line settings object. +pub fn new () ->Result <Self > { +let settings =unsafe { gpiod::gpiod_line_settings_new () }; + +if settings.is_null () { +return Err (Error ::OperationFailed ( +OperationType ::LineSettingsNew , +Errno ::last (), + )); + } + +Ok (Self { settings })
bindings/rust/libgpiod/src/line_settings.rs line 52
Quoted 10 lines
bindings/rust/libgpiod/src/line_settings.rs new file +10
-0+ } + +pub fn new_with_settings (settings: *mut gpiod::gpiod_line_settings ) ->Self { +Self { settings } + } + +/// Private helper, Returns gpiod_line_settings +pub (crate )fn settings (&self) -> *mut gpiod::gpiod_line_settings { + self.settings + }
Just make self.settings pub(crate)?
Quoted 8 lines
bindings/rust/libgpiod/src/line_settings.rs new file +8
-0+ +/// Resets the line settings object to its default values. +pub fn reset (&mut self) { +unsafe { gpiod::gpiod_line_settings_reset (self.settings) } + } + +/// Makes copy of the settings object. +pub fn settings_clone (&self) ->Result <Self > {
bindings/rust/libgpiod/src/line_settings.rs line 70
Quoted 10 lines
bindings/rust/libgpiod/src/line_settings.rs new file +10
-0+let settings =unsafe { gpiod::gpiod_line_settings_copy (self.settings) }; +if settings.is_null () { +return Err (Error ::OperationFailed ( +OperationType ::LineSettingsCopy , +Errno ::last (), + )); + } + +Ok (Self { settings }) + }
This clone is fine - the derived one is not.
bindings/rust/libgpiod/src/line_settings.rs line 73
Quoted 3 lines
bindings/rust/libgpiod/src/line_settings.rs new file +3
-0+ +/// Set line prop setting. +pub fn set_prop (&mut self, values: &[SettingVal ]) ->Result <()> {
Rename values to props, particularly as values is one of the props.
Quoted 5 lines
bindings/rust/libgpiod/src/line_settings.rs new file +5
-0+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)?,
bindings/rust/libgpiod/src/line_settings.rs line 88
Quoted 10 lines
bindings/rust/libgpiod/src/line_settings.rs new file +10
-0+ 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 (()) + }
Make mutators -> Result<&mut Self> so they can be chained.
Quoted 23 lines
bindings/rust/libgpiod/src/line_settings.rs new file +23
-0+ +/// Get the line prop setting. +pub fn prop (&self, property:SettingKind ) ->Result <SettingVal > { +Ok (match property { +SettingKind ::Direction =>SettingVal ::Direction (self.direction ()?), +SettingKind ::EdgeDetection =>SettingVal ::EdgeDetection (self.edge_detection ()?), +SettingKind ::Bias =>SettingVal ::Bias (self.bias ()?), +SettingKind ::Drive =>SettingVal ::Drive (self.drive ()?), +SettingKind ::ActiveLow =>SettingVal ::ActiveLow (self.active_low ()), +SettingKind ::DebouncePeriod =>SettingVal ::DebouncePeriod (self.debounce_period ()?), +SettingKind ::EventClock =>SettingVal ::EventClock (self.event_clock ()?), +SettingKind ::OutputValue =>SettingVal ::OutputValue (self.output_value ()?), + }) + } + +/// Set the line direction. +fn set_direction (&mut self, direction:Direction ) ->Result <()> { +let ret =unsafe { + gpiod::gpiod_line_settings_set_direction ( + self.settings, + direction.gpiod_direction ()as i32 , + ) + };
bindings/rust/libgpiod/src/line_settings.rs line 121
Quoted 10 lines
bindings/rust/libgpiod/src/line_settings.rs new file +10
-0+ +if ret == -1 { +Err (Error ::OperationFailed ( +OperationType ::LineSettingsSetDirection , +Errno ::last (), + )) + }else { +Ok (()) + } + }
These mutators might as well be pub as well. Then the caller has the option of using the set_prop(&[settings]) or chaining mutators. Similarly the accessors.
Quoted 177 lines
bindings/rust/libgpiod/src/line_settings.rs new file +156
-0+ +/// Get the direction setting. +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 <()> { +let ret =unsafe { + gpiod::gpiod_line_settings_set_edge_detection ( + self.settings, +Edge ::gpiod_edge (edge)as i32 , + ) + }; + +if ret == -1 { +Err (Error ::OperationFailed ( +OperationType ::LineSettingsSetEdgeDetection , +Errno ::last (), + )) + }else { +Ok (()) + } + } + +/// Get the edge event detection setting. +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 <()> { +let ret =unsafe { + gpiod::gpiod_line_settings_set_bias (self.settings,Bias ::gpiod_bias (bias)as i32 ) + }; + +if ret == -1 { +Err (Error ::OperationFailed ( +OperationType ::LineSettingsSetBias , +Errno ::last (), + )) + }else { +Ok (()) + } + } + +/// Get the bias setting. +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 <()> { +let ret =unsafe { + gpiod::gpiod_line_settings_set_drive (self.settings, drive.gpiod_drive ()as i32 ) + }; + +if ret == -1 { +Err (Error ::OperationFailed ( +OperationType ::LineSettingsSetDrive , +Errno ::last (), + )) + }else { +Ok (()) + } + } + +/// Get the drive setting. +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) } + } + +/// Check the active-low setting. +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 ) { +unsafe { + gpiod::gpiod_line_settings_set_debounce_period_us ( + self.settings, + period.as_micros ()as u64 , + ) + } + } + +/// Get the debounce period. +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 <()> { +let ret =unsafe { + gpiod::gpiod_line_settings_set_event_clock (self.settings, clock.gpiod_clock ()as i32 ) + }; + +if ret == -1 { +Err (Error ::OperationFailed ( +OperationType ::LineSettingsSetEventClock , +Errno ::last (), + )) + }else { +Ok (()) + } + } + +/// Get the event clock setting. +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 <()> { +let ret = +unsafe { gpiod::gpiod_line_settings_set_output_value (self.settings, value.value ()) }; + +if ret == -1 { +Err (Error ::OperationFailed ( +OperationType ::LineSettingsSetOutputValue , +Errno ::last (), + )) + }else { +Ok (()) + } + } + +/// Get the output value, 0 or 1. +fn output_value (&self) ->Result <Value > { +let value =unsafe { gpiod::gpiod_line_settings_get_output_value (self.settings) }; + +if value !=0 && value !=1 { +Err (Error ::OperationFailed ( +OperationType ::LineSettingsGetOutVal , +Errno ::last (), + )) + }else { +Value ::new (value) + } + } +} + +impl Drop for Settings { +/// Free the line settings object and release all associated resources. +fn drop (&mut self) { +unsafe { gpiod::gpiod_line_settings_free (self.settings) } + } +}bindings/rust/libgpiod/src/request_config.rs new file +15
-0@@ -0,0 +1,96 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar <[email protected]> + +use libc::strlen; +use std::os::raw::{c_char, c_ulong}; +use std::{slice, str}; + +use vmm_sys_util::errno::Error as Errno ; + +use super ::{gpiod,Error ,OperationType ,Result }; + +/// Request configuration objects +///
bindings/rust/libgpiod/src/request_config.rs line 25
Quoted 10 lines
bindings/rust/libgpiod/src/request_config.rs new file +10
-0+/// Request config objects are used to pass a set of options to the kernel at +/// the time of the line request. The mutators don't return error values. If the +/// values are invalid, in general they are silently adjusted to acceptable +/// ranges. + +#[derive( Clone , Debug , Eq , PartialEq )] +pub struct Config { + config: *mut gpiod::gpiod_request_config , +} +
And one last time...
Clone/Drop wrapping a raw pointer again.
Quoted 9 lines
bindings/rust/libgpiod/src/request_config.rs new file +9
-0+impl Config { +/// Create a new request config object. +pub fn new () ->Result <Self > { +let config =unsafe { gpiod::gpiod_request_config_new () }; +if config.is_null () { +return Err (Error ::OperationFailed ( +OperationType ::RequestConfigNew , +Errno ::last (), + ));
bindings/rust/libgpiod/src/request_config.rs line 44
Quoted 10 lines
bindings/rust/libgpiod/src/request_config.rs new file +10
-0+ } + +Ok (Self { config }) + } + +/// Private helper, Returns gpiod_request_config +pub (crate )fn config (&self) -> *mut gpiod::gpiod_request_config { + self.config + } +
Make self.config pub(crate) instead?
Quoted 55 lines
bindings/rust/libgpiod/src/request_config.rs new file +52
-0+/// Set the consumer name for the request. +/// +/// If the consumer string is too long, it will be truncated to the max +/// accepted length. +pub fn set_consumer (&self, consumer: &str ) { +// Null-terminate the string +let consumer = consumer.to_owned () +"\0" ; + +unsafe { + gpiod::gpiod_request_config_set_consumer ( + self.config, + consumer.as_ptr ()as *const c_char , + ) + } + } + +/// Get the consumer name configured in the request config. +pub fn consumer (&self) ->Result <&str > { +// SAFETY: The string returned by libgpiod is guaranteed to live as long +// as the `struct Config`. +let consumer =unsafe { gpiod::gpiod_request_config_get_consumer (self.config) }; +if consumer.is_null () { +return Err (Error ::OperationFailed ( +OperationType ::RequestConfigGetConsumer , +Errno ::last (), + )); + } + +// SAFETY: The string is guaranteed to be valid here by the C API. + str::from_utf8 (unsafe { + slice::from_raw_parts (consumeras *const u8 ,strlen (consumer)as usize ) + }) + .map_err (Error ::StringNotUtf8 ) + } + +/// Set the size of the kernel event buffer for the request. +pub fn set_event_buffer_size (&self, size:usize ) { +unsafe { gpiod::gpiod_request_config_set_event_buffer_size (self.config, sizeas c_ulong ) } + } + +/// Get the edge event buffer size setting for the request config. +pub fn event_buffer_size (&self) ->usize { +unsafe { gpiod::gpiod_request_config_get_event_buffer_size (self.config)as usize } + } +} + +impl Drop for Config { +/// Free the request config object and release all associated resources. +fn drop (&mut self) { +unsafe { gpiod::gpiod_request_config_free (self.config) } + } +}Signature · 1 line
2.31.1.272.g89b43f80a514
Overall I don't see any problems that require major rework to the scale of previous versions. My main itches that need to be scratched being dangling pointers and to a lesser degree thread safey.
I find the event_buffer solution to be less safe than it could be. I've got an alternative, based on the my earlier PoC/suggestion, that returns an snaphot of events when you read events from the request into the buffer. I've put that in a branch on github [1] if you want to take a look.
I went though a few variations wrt how to deal with the event cloning. Not implementing Clone on the events means they can't be stored in a Vec, and so storing them in the Buffer was problematic.
My initial solution was for the event to only wrap the C pointer and have distinct event types for buffered and cloned, implemented using traits - which is bit of a pain as the user then needs to import the trait themselves to use the events :-(.
Then tried a macro to add impl Event on the two event types, which looks ugly to me.
Finally went with having Event be a new type of BaseEvent, which does impl Clone, so the event buffer can store them (and <cough> cast them to Event as appropriate). Slightly more evil under the hood than I would like, but provides the cleanest implementation.
The Events snapshot should impl Iterator to make it easier to use, though I haven't added that yet.
And the error returned by Events::event() is wrong - just cut and pasted one in there - probably should be InvalidArguments?
Anyway, the end result is that it is not possible to read an invalid or uninitialised event from Rust, which was the point.
It also better handles lifetimes, so it isn't necessary to explicitly drop events before re-using the buffer, and the event_clone() method can be an instance method rather than a class method (to borrow some Python terminology).
I've added an events.rs to the examples to demonstrate how the changes impact usage in each case.
Cheers,
Kent.
[1] https://github.com/warthog618/libgpiod/commits/rust_v7_mods
Re: [PATCH V7 3/8] libgpiod: Add rust wrapper crate
Hi Kent,
Thanks a lot for putting so much effort in reviewing the series, yes it makes me invest more time to the bindings, but it is totally worth it. Excellent work.
I have pushed all the changed I have made so far in v8 branch in my repository in the top few commits, in case you want to have a look.
Quoted 4 lines · Kent Gibson
Quoted 1 line · Viresh Kumar
+use
vmm_sys_util::errno::Erroras Errno;Why
vmm_sys_util::errno::Errorrather than, say,errno::Errno?
I used it in another project, vhost-device related to virtio, and so ended up using here. The main advantage of using this over errno is that we can do Errno::last() to get the error number of the last operation.
Do you have a better alternative to that ?
Quoted 6 lines
Quoted 6 lines
bindings/rust/libgpiod/src/chip.rs new file +6
-0+/// GPIO chip +/// +/// A GPIO chip object is associated with an open file descriptor to the GPIO +/// character device. It exposes basic information about the chip and allows +/// callers to retrieve information about each line, watch lines for state +/// changes and make line requests.
Quoted 8 lines
bindings/rust/libgpiod/src/chip.rs line 63
Quoted 6 lines
bindings/rust/libgpiod/src/chip.rs new file +6
-0+#[derive( Clone , Debug , Eq , PartialEq )] +pub struct Chip { + ichip:Arc <Internal >, + info:Info , +} +Doesn't info need an Arc as well? (see struct Info below).
Why would that be required ? Internal required it as it can be stored in two structures, Chip and Info, and should get freed after both of them are out of scope.
Though Info have a single owner/user, i.e. Chip and once Chip is out of scope, Info can be freed as well.
Quoted 1 line
Actually, does it make sense to be able to Clone the Chip?
I have removed Clone now, since Info couldn't be cloned anymore.
Quoted 4 lines
Two threads could then watch different lines and
wait_info_event()from the one chip.
That isn't going to work, at least not the way you would want.
Quoted 8 lines
bindings/rust/libgpiod/src/chip.rs line 66
Quoted 3 lines
bindings/rust/libgpiod/src/chip.rs new file +3
-0+unsafe impl Send for Chip {} +unsafe impl Sync for Chip {} +Send makes sense to me, but not Sync, due to the waiting for info events. Similarly for
line_request::Request.
At least not without some additional synchronisation/locking, and I'm not sure we want to go there.
Hmm, I added those for tests and now for the multi-threaded example I have written. And I do get what you are saying, it is tricky to share the same chip from two threads.
What should I do? Remove those example/tests and remove Sync ?
Quoted 10 lines
bindings/rust/libgpiod/src/chip.rs line 72
Quoted 6 lines
bindings/rust/libgpiod/src/chip.rs new file +6
-0+impl Chip { +/// Find a chip by path. +pub fn open <P :AsRef <Path >>(path: &P ) ->Result <Self > { +let ichip =Arc ::new (Internal ::open (path)?); +let info =Info ::new (ichip.clone ())?; +info is not always necessary, e.g. if the user just want to request a line. Maybe lazy load it? Assuming you want to keep a copy cached with the Chip at all.
Yeah, but then I will be required to add an additional check to below routines to see if info is already available or not, and populate it if required.
Or detach Info from Chip and remove below wrappers ?
Quoted 38 lines
Quoted 38 lines
bindings/rust/libgpiod/src/chip.rs new file +36
-0+Ok (Self { ichip, info }) + } + +/// Get the chip name as represented in the kernel. +pub fn name (&self) ->Result <&str > { + self.info.name () + } + +/// Get the chip label as represented in the kernel. +pub fn label (&self) ->Result <&str > { + self.info.label () + } + +/// Get the number of GPIO lines exposed by the chip. +pub fn num_lines (&self) ->usize { + self.info.num_lines () + } +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 > { +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 {
Quoted 8 lines
bindings/rust/libgpiod/src/event_buffer.rs line 38
Quoted 2 lines
bindings/rust/libgpiod/src/event_buffer.rs new file +2
-0+ self.buffer + }Just make self.buffer pub(crate)? Why is the idea of passing a raw pointer around making my skin crawl?
Or even better, move the guts ofline_request::Request::read_edge_events()into a helper function here that it can call so it doesn't need access to self.buffer.
But then this piece of code would need to access self.request as the API is:
gpiod::gpiod_line_request_read_edge_event(
self.request,
buffer.buffer,
buffer.capacity().try_into().unwrap(),
)It needs raw pointers to both buffer and request.
Quoted 6 lines
Quoted 6 lines
bindings/rust/libgpiod/src/event_buffer.rs new file +6
-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 } + } +
Quoted 8 lines
bindings/rust/libgpiod/src/event_buffer.rs line 48
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, index) + }What is to prevent reading an event from a slot that has not yet been populated? I realise doing that is possible in C, but it should be prevented in the Rust bindings.
Good point, I have done this now:
event_buffer.rs:
/// Set the number of events read into the event buffer.
pub fn set_event_count(&mut self, count: usize) {
self.event_count = count
}
/// Read an event stored in the buffer.
pub fn event(&self, index: usize) -> Result<request::Event> {
if index >= self.event_count {
Err(Error::InvalidArguments)
} else {
request::Event::new(self, index)
}
}line_request.rs:
/// 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: &mut request::Buffer) -> Result<u32> {
// SAFETY: `gpiod_line_request` is guaranteed to be valid here.
let ret = unsafe {
gpiod::gpiod_line_request_read_edge_event(
self.request,
buffer.buffer,
buffer.capacity().try_into().unwrap(),
)
};
if ret == -1 {
Err(Error::OperationFailed(
OperationType::LineRequestReadEdgeEvent,
Errno::last(),
))
} else {
// Set count of events read in the buffer
buffer.set_event_count(ret as usize);
Ok(ret as u32)
}
}Good enough ?
Quoted 5 lines
Not sure why
event_bufferneeds to be nested inside an event module.event_buffershould be in the request module.
edge_eventis a tougher call, either line or request. Probably the latter.
Is this okay now ? I had to rename chip.rs to chip_internal.rs in order to include info_event within it.
mod chip_internal;
mod info_event;
pub mod chip {
/// GPIO chip related definitions.
pub use crate::chip_internal::*;
pub mod info {
/// GPIO chip info event related definitions.
pub use crate::info_event::*;
}
}mod edge_event;
mod event_buffer;
mod line_request;
mod request_config;
/// GPIO chip request related definitions.
pub mod request {
pub use crate::edge_event::*;
pub use crate::event_buffer::*;
pub use crate::line_request::*;
pub use crate::request_config::*;
}mod line_config;
mod line_info;
mod line_settings;
/// GPIO chip line related definitions.
pub mod line {
pub use crate::line_config::*;
pub use crate::line_info::*;
pub use crate::line_settings::*;
}Quoted 7 lines
Quoted 7 lines
bindings/rust/libgpiod/src/lib.rs new file +7
-0+/// Iterator for GPIO devices. +pub fn gpiochip_devices <P :AsRef <Path >>(path: &P ) ->Result <Vec <chip::Chip >> { +let mut chips =Vec ::new (); + +for entryin fs::read_dir (path).map_err (|_|Error ::IoError )?.flatten () { +let path = entry.path (); +
Quoted 8 lines
bindings/rust/libgpiod/src/lib.rs line 454
Quoted 4 lines
bindings/rust/libgpiod/src/lib.rs new file +4
-0+if is_gpiochip_device (&path) { + chips.push (chip::Chip ::open (&path)?); + } + }If there is a symlink to a device both in the path, or multiple symlinks to the same device, you will get the same chip several times in the returned Vec?
Probably yeah.
Quoted 1 line
Some mention of the order in which devices are returned?
Documentation of read_dir() says:
The order in which this iterator returns entries is platform and filesystem dependent.
mentioned same in comment now.
Quoted 2 lines
If you are returning a Vec then sorted might be nice, else state they are not sorted.
What should we here then ?
Quoted 7 lines
Quoted 7 lines
bindings/rust/libgpiod/src/lib.rs new file +7
-0+/// Get the API version of the library as a human-readable string. +pub fn version_string () ->Result <&'staticstr > { +// SAFETY: The string returned by libgpiod is guaranteed to live forever. +let version =unsafe { gpiod::gpiod_version_string () }; + +if version.is_null () { +return Err (Error ::NullString ("GPIO library version" ));
Quoted 8 lines
bindings/rust/libgpiod/src/lib.rs line 471
Quoted 6 lines
bindings/rust/libgpiod/src/lib.rs new file +6
-0+ } + +// SAFETY: The string is guaranteed to be valid here by the C API. + str::from_utf8 (unsafe { slice::from_raw_parts (versionas *const u8 ,strlen (version)as usize ) }) + .map_err (Error ::StringNotUtf8 ) +}What if the Rust bindings version differ from the libgpiod version?
Hmm, not sure which version should we return here. I think it should just be libgpiod's version. Though I am fine with whatever you and Bartosz decide.
Quoted 7 lines
bindings/rust/libgpiod/src/line_request.rs line 221
Quoted 4 lines
bindings/rust/libgpiod/src/line_request.rs new file +4
-0+/// 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: &mut edge::event::Buffer ) ->Result <u32 > {What is to stop the user requesting events between the len returned here and the end of the buffer? Why knows what state they are in.
I have shared a change for the same earlier, is that enough ?
Quoted 29 lines
Overall I don't see any problems that require major rework to the scale of previous versions. My main itches that need to be scratched being dangling pointers and to a lesser degree thread safey.
I find the
event_buffersolution to be less safe than it could be. I've got an alternative, based on the my earlier PoC/suggestion, that returns an snaphot of events when you read events from the request into the buffer. I've put that in a branch on github [1] if you want to take a look.I went though a few variations wrt how to deal with the event cloning. Not implementing Clone on the events means they can't be stored in a Vec, and so storing them in the Buffer was problematic.
My initial solution was for the event to only wrap the C pointer and have distinct event types for buffered and cloned, implemented using traits - which is bit of a pain as the user then needs to import the trait themselves to use the events :-(.
Then tried a macro to add impl Event on the two event types, which looks ugly to me.
Finally went with having Event be a new type of BaseEvent, which does impl Clone, so the event buffer can store them (and <cough> cast them to Event as appropriate). Slightly more evil under the hood than I would like, but provides the cleanest implementation.The Events snapshot should impl Iterator to make it easier to use, though I haven't added that yet.
And the error returned byEvents::event()is wrong - just cut and pasted one in there - probably should be InvalidArguments?
Quoted 8 lines
Anyway, the end result is that it is not possible to read an invalid or uninitialised event from Rust, which was the point.
It also better handles lifetimes, so it isn't necessary to explicitly drop events before re-using the buffer, and theevent_clone()method can be an instance method rather than a class method (to borrow some Python terminology).
I've added anevents.rsto the examples to demonstrate how the changes impact usage in each case.
I haven't looked into that in great details yet, just skimmed through it. I am not sure I understand them completely yet and I am not sure if it is worth the complexity, may be it is. I will try to get through the changes again next week though.
Will it be possible to get on with the current implementation and once this is merged you send patches on top of it which can then get reviewed properly by others too, who know Rust better than me ?
Signature · 1 line
viresh
Re: [PATCH V7 3/8] libgpiod: Add rust wrapper crate
Quoted 13 lines · Viresh Kumar
Hi Kent,
Thanks a lot for putting so much effort in reviewing the series, yes it makes me invest more time to the bindings, but it is totally worth it. Excellent work.
I have pushed all the changed I have made so far in v8 branch in my repository in the top few commits, in case you want to have a look.
Quoted 3 lines · Kent Gibson
Quoted 1 line · Viresh Kumar
+use
vmm_sys_util::errno::Erroras Errno;
Quoted 9 lines
Quoted 1 line
Why
vmm_sys_util::errno::Errorrather than, say,errno::Errno?I used it in another project, vhost-device related to virtio, and so ended up using here. The main advantage of using this over errno is that we can do
Errno::last()to get the error number of the last operation.Do you have a better alternative to that ?
The one I mentioned is the top match for errno on crates.io[1]. That provides the same functionality, and that is all it does. All other things being equal I would go for the simplest dependency.
[1] https://crates.io/crates/errno
Quoted 19 lines
Quoted 6 lines
Quoted 6 lines
bindings/rust/libgpiod/src/chip.rs new file +6
-0+/// GPIO chip +/// +/// A GPIO chip object is associated with an open file descriptor to the GPIO +/// character device. It exposes basic information about the chip and allows +/// callers to retrieve information about each line, watch lines for state +/// changes and make line requests. Quoted 8 lines
bindings/rust/libgpiod/src/chip.rs line 63
Quoted 6 lines
bindings/rust/libgpiod/src/chip.rs new file +6
-0+#[derive( Clone , Debug , Eq , PartialEq )] +pub struct Chip { + ichip:Arc <Internal >, + info:Info , +} +Doesn't info need an Arc as well? (see struct Info below).
Why would that be required ? Internal required it as it can be stored in two structures, Chip and Info, and should get freed after both of them are out of scope.
Quoted 7 lines
Though Info have a single owner/user, i.e. Chip and once Chip is out of scope, Info can be freed as well.
Quoted 1 line
Actually, does it make sense to be able to Clone the Chip?
I have removed Clone now, since Info couldn't be cloned anymore.
Exactly - if Info can't clone then you could Arc it. But if the Chip can't clone then problem solved.
Quoted 13 lines
Quoted 4 lines
Two threads could then watch different lines and
wait_info_event()from the one chip.
That isn't going to work, at least not the way you would want.Quoted 8 lines
bindings/rust/libgpiod/src/chip.rs line 66
Quoted 3 lines
bindings/rust/libgpiod/src/chip.rs new file +3
-0+unsafe impl Send for Chip {} +unsafe impl Sync for Chip {} +Send makes sense to me, but not Sync, due to the waiting for info events. Similarly for
line_request::Request.
At least not without some additional synchronisation/locking, and I'm not sure we want to go there.
Quoted 6 lines
Hmm, I added those for tests and now for the multi-threaded example I have written. And I do get what you are saying, it is tricky to share the same chip from two threads.
What should I do? Remove those example/tests and remove Sync ?
I would remove Sync support until you are satisfied you have a working solution for it.
Quoted 11 lines
Quoted 10 lines
bindings/rust/libgpiod/src/chip.rs line 72
Quoted 6 lines
bindings/rust/libgpiod/src/chip.rs new file +6
-0+impl Chip { +/// Find a chip by path. +pub fn open <P :AsRef <Path >>(path: &P ) ->Result <Self > { +let ichip =Arc ::new (Internal ::open (path)?); +let info =Info ::new (ichip.clone ())?; +info is not always necessary, e.g. if the user just want to request a line. Maybe lazy load it? Assuming you want to keep a copy cached with the Chip at all.
Quoted 6 lines
Yeah, but then I will be required to add an additional check to below routines to see if info is already available or not, and populate it if required.
Or detach Info from Chip and remove below wrappers ?
The usual way is to add a helper function that returns the lazy loaded object. If it hasn't been loaded yet then it does that first. And your wrapper functions use that to access the info.
Or, as you suggest, just let the user get the Info and use its accessors.
Either way works for me.
Quoted 47 lines
Quoted 38 lines
Quoted 38 lines
bindings/rust/libgpiod/src/chip.rs new file +36
-0+Ok (Self { ichip, info }) + } + +/// Get the chip name as represented in the kernel. +pub fn name (&self) ->Result <&str > { + self.info.name () + } + +/// Get the chip label as represented in the kernel. +pub fn label (&self) ->Result <&str > { + self.info.label () + } + +/// Get the number of GPIO lines exposed by the chip. +pub fn num_lines (&self) ->usize { + self.info.num_lines () + } +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 > { +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 {Quoted 8 lines
bindings/rust/libgpiod/src/event_buffer.rs line 38
Quoted 2 lines
bindings/rust/libgpiod/src/event_buffer.rs new file +2
-0+ self.buffer + }Just make self.buffer pub(crate)? Why is the idea of passing a raw pointer around making my skin crawl?
Or even better, move the guts ofline_request::Request::read_edge_events()into a helper function here that it can call so it doesn't need access to self.buffer.
Quoted 9 lines
But then this piece of code would need to access self.request as the API is:
gpiod::gpiod_line_request_read_edge_event( self.request, buffer.buffer, buffer.capacity().try_into().unwrap(), )
Yep - you pass the Request to the Buffer, rather than have the Request doing the work on the buffer.
IIRC my branch includes my version of this.
Quoted 17 lines
It needs raw pointers to both buffer and request.
Quoted 6 lines
Quoted 6 lines
bindings/rust/libgpiod/src/event_buffer.rs new file +6
-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 } + } +Quoted 8 lines
bindings/rust/libgpiod/src/event_buffer.rs line 48
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, index) + }What is to prevent reading an event from a slot that has not yet been populated? I realise doing that is possible in C, but it should be prevented in the Rust bindings.
Quoted 9 lines
Good point, I have done this now:
event_buffer.rs:/// Set the number of events read into the event buffer. pub fn set_event_count(&mut self, count: usize) { self.event_count = count }
pub(crate) else the user can mess with the threshold.
Quoted 37 lines
/// Read an event stored in the buffer. pub fn event(&self, index: usize) -> Result<request::Event> { if index >= self.event_count { Err(Error::InvalidArguments) } else { request::Event::new(self, index) } }
line_request.rs:/// 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: &mut request::Buffer) -> Result<u32> { // SAFETY: `gpiod_line_request` is guaranteed to be valid here. let ret = unsafe { gpiod::gpiod_line_request_read_edge_event( self.request, buffer.buffer, buffer.capacity().try_into().unwrap(), ) }; if ret == -1 { Err(Error::OperationFailed( OperationType::LineRequestReadEdgeEvent, Errno::last(), )) } else { // Set count of events read in the buffer buffer.set_event_count(ret as usize); Ok(ret as u32) } }
Quoted 2 lines
Good enough ?
Better.
Quoted 12 lines
Quoted 5 lines
Not sure why
event_bufferneeds to be nested inside an event module.event_buffershould be in the request module.
edge_eventis a tougher call, either line or request. Probably the latter.Is this okay now ? I had to rename
chip.rstochip_internal.rsin order to includeinfo_eventwithin it.mod
chip_internal;
modinfo_event;
Quoted 10 lines
pub mod chip {
/// GPIO chip related definitions.
pub use crate::chip_internal::*;
pub mod info {
/// GPIO chip info event related definitions.
pub use crate::info_event::*;
}
}Couldn't you just add those pubs to the old chip file? So you don't need to rename it to chip_internal.
Quoted 40 lines
mod
edge_event;
modevent_buffer;
modline_request;
modrequest_config;/// GPIO chip request related definitions.
pub mod request { pub use crate::edge_event::*; pub use crate::event_buffer::*; pub use crate::line_request::*; pub use crate::request_config::*; }mod
line_config;
modline_info;
modline_settings;/// GPIO chip line related definitions.
pub mod line { pub use crate::line_config::*; pub use crate::line_info::*; pub use crate::line_settings::*; }Quoted 7 lines
Quoted 7 lines
bindings/rust/libgpiod/src/lib.rs new file +7
-0+/// Iterator for GPIO devices. +pub fn gpiochip_devices <P :AsRef <Path >>(path: &P ) ->Result <Vec <chip::Chip >> { +let mut chips =Vec ::new (); + +for entryin fs::read_dir (path).map_err (|_|Error ::IoError )?.flatten () { +let path = entry.path (); +Quoted 8 lines
bindings/rust/libgpiod/src/lib.rs line 454
Quoted 4 lines
bindings/rust/libgpiod/src/lib.rs new file +4
-0+if is_gpiochip_device (&path) { + chips.push (chip::Chip ::open (&path)?); + } + }If there is a symlink to a device both in the path, or multiple symlinks to the same device, you will get the same chip several times in the returned Vec?
Quoted 2 lines
Probably yeah.
Lets say I want to implement a search for line by name, and check that the line name is unique. That will fail if there is a symlink to the relevant device, cos it will see a spurious duplicate.
How would I do that then?
Quoted 4 lines
Quoted 1 line
Some mention of the order in which devices are returned?
Documentation of
read_dir()says:
Quoted 9 lines
The order in which this iterator returns entries is platform and filesystem dependent.
mentioned same in comment now.
Quoted 2 lines
If you are returning a Vec then sorted might be nice, else state they are not sorted.
What should we here then ?
From my PoV, the two options are either
a)wrap read_dir and return an actual iterator and live with the disorder
or b) return a Vec and sort it before you return it
Returning an unsorted Vec is the worst of both worlds.
Quoted 16 lines
Quoted 7 lines
Quoted 7 lines
bindings/rust/libgpiod/src/lib.rs new file +7
-0+/// Get the API version of the library as a human-readable string. +pub fn version_string () ->Result <&'staticstr > { +// SAFETY: The string returned by libgpiod is guaranteed to live forever. +let version =unsafe { gpiod::gpiod_version_string () }; + +if version.is_null () { +return Err (Error ::NullString ("GPIO library version" ));Quoted 8 lines
bindings/rust/libgpiod/src/lib.rs line 471
Quoted 6 lines
bindings/rust/libgpiod/src/lib.rs new file +6
-0+ } + +// SAFETY: The string is guaranteed to be valid here by the C API. + str::from_utf8 (unsafe { slice::from_raw_parts (versionas *const u8 ,strlen (version)as usize ) }) + .map_err (Error ::StringNotUtf8 ) +}What if the Rust bindings version differ from the libgpiod version?
Quoted 4 lines
Hmm, not sure which version should we return here. I think it should just be libgpiod's version. Though I am fine with whatever you and Bartosz decide.
You need both - separately.
Quoted 48 lines
Quoted 7 lines
bindings/rust/libgpiod/src/line_request.rs line 221
Quoted 4 lines
bindings/rust/libgpiod/src/line_request.rs new file +4
-0+/// 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: &mut edge::event::Buffer ) ->Result <u32 > {What is to stop the user requesting events between the len returned here and the end of the buffer? Why knows what state they are in.
I have shared a change for the same earlier, is that enough ?
Quoted 29 lines
Overall I don't see any problems that require major rework to the scale of previous versions. My main itches that need to be scratched being dangling pointers and to a lesser degree thread safey.
I find the
event_buffersolution to be less safe than it could be. I've got an alternative, based on the my earlier PoC/suggestion, that returns an snaphot of events when you read events from the request into the buffer. I've put that in a branch on github [1] if you want to take a look.I went though a few variations wrt how to deal with the event cloning. Not implementing Clone on the events means they can't be stored in a Vec, and so storing them in the Buffer was problematic.
My initial solution was for the event to only wrap the C pointer and have distinct event types for buffered and cloned, implemented using traits - which is bit of a pain as the user then needs to import the trait themselves to use the events :-(.
Then tried a macro to add impl Event on the two event types, which looks ugly to me.
Finally went with having Event be a new type of BaseEvent, which does impl Clone, so the event buffer can store them (and <cough> cast them to Event as appropriate). Slightly more evil under the hood than I would like, but provides the cleanest implementation.The Events snapshot should impl Iterator to make it easier to use, though I haven't added that yet.
And the error returned byEvents::event()is wrong - just cut and pasted one in there - probably should be InvalidArguments?Quoted 8 lines
Anyway, the end result is that it is not possible to read an invalid or uninitialised event from Rust, which was the point.
It also better handles lifetimes, so it isn't necessary to explicitly drop events before re-using the buffer, and theevent_clone()method can be an instance method rather than a class method (to borrow some Python terminology).
I've added anevents.rsto the examples to demonstrate how the changes impact usage in each case.
Quoted 5 lines
I haven't looked into that in great details yet, just skimmed through it. I am not sure I understand them completely yet and I am not sure if it is worth the complexity, may be it is. I will try to get through the changes again next week though.
Clearly I think it is or I wouldn't've bothered?
So not sure what you would like me to add, and I'm not going to push for it any more than I already have. If you have the time and inclination then give it a look.
Quoted 4 lines
Will it be possible to get on with the current implementation and once this is merged you send patches on top of it which can then get reviewed properly by others too, who know Rust better than me ?
The Rust bindings are your baby, and I don't have any plans to be patching it. And Bart is the arbiter of what gets merged, so that decision is his.
Cheers,
Kent.
Re: [PATCH V7 3/8] libgpiod: Add rust wrapper crate
Quoted 355 lines · Kent Gibson
Quoted 13 lines · Viresh Kumar
Hi Kent,
Thanks a lot for putting so much effort in reviewing the series, yes it makes me invest more time to the bindings, but it is totally worth it. Excellent work.
I have pushed all the changed I have made so far in v8 branch in my repository in the top few commits, in case you want to have a look.
Quoted 3 lines · Kent Gibson
Quoted 1 line · Viresh Kumar
+use
vmm_sys_util::errno::Erroras Errno;Quoted 9 lines
Quoted 1 line
Why
vmm_sys_util::errno::Errorrather than, say,errno::Errno?I used it in another project, vhost-device related to virtio, and so ended up using here. The main advantage of using this over errno is that we can do
Errno::last()to get the error number of the last operation.Do you have a better alternative to that ?
The one I mentioned is the top match for errno on crates.io[1]. That provides the same functionality, and that is all it does. All other things being equal I would go for the simplest dependency.
[1] https://crates.io/crates/errno
Quoted 19 lines
Quoted 6 lines
Quoted 6 lines
bindings/rust/libgpiod/src/chip.rs new file +6
-0+/// GPIO chip +/// +/// A GPIO chip object is associated with an open file descriptor to the GPIO +/// character device. It exposes basic information about the chip and allows +/// callers to retrieve information about each line, watch lines for state +/// changes and make line requests. Quoted 8 lines
bindings/rust/libgpiod/src/chip.rs line 63
Quoted 6 lines
bindings/rust/libgpiod/src/chip.rs new file +6
-0+#[derive( Clone , Debug , Eq , PartialEq )] +pub struct Chip { + ichip:Arc <Internal >, + info:Info , +} +Doesn't info need an Arc as well? (see struct Info below).
Why would that be required ? Internal required it as it can be stored in two structures, Chip and Info, and should get freed after both of them are out of scope.
Quoted 7 lines
Though Info have a single owner/user, i.e. Chip and once Chip is out of scope, Info can be freed as well.
Quoted 1 line
Actually, does it make sense to be able to Clone the Chip?
I have removed Clone now, since Info couldn't be cloned anymore.
Exactly - if Info can't clone then you could Arc it. But if the Chip can't clone then problem solved.
Quoted 13 lines
Quoted 4 lines
Two threads could then watch different lines and
wait_info_event()from the one chip.
That isn't going to work, at least not the way you would want.Quoted 8 lines
bindings/rust/libgpiod/src/chip.rs line 66
Quoted 3 lines
bindings/rust/libgpiod/src/chip.rs new file +3
-0+unsafe impl Send for Chip {} +unsafe impl Sync for Chip {} +Send makes sense to me, but not Sync, due to the waiting for info events. Similarly for
line_request::Request.
At least not without some additional synchronisation/locking, and I'm not sure we want to go there.Quoted 6 lines
Hmm, I added those for tests and now for the multi-threaded example I have written. And I do get what you are saying, it is tricky to share the same chip from two threads.
What should I do? Remove those example/tests and remove Sync ?
I would remove Sync support until you are satisfied you have a working solution for it.
Quoted 11 lines
Quoted 10 lines
bindings/rust/libgpiod/src/chip.rs line 72
Quoted 6 lines
bindings/rust/libgpiod/src/chip.rs new file +6
-0+impl Chip { +/// Find a chip by path. +pub fn open <P :AsRef <Path >>(path: &P ) ->Result <Self > { +let ichip =Arc ::new (Internal ::open (path)?); +let info =Info ::new (ichip.clone ())?; +info is not always necessary, e.g. if the user just want to request a line. Maybe lazy load it? Assuming you want to keep a copy cached with the Chip at all.
Quoted 6 lines
Yeah, but then I will be required to add an additional check to below routines to see if info is already available or not, and populate it if required.
Or detach Info from Chip and remove below wrappers ?
The usual way is to add a helper function that returns the lazy loaded object. If it hasn't been loaded yet then it does that first. And your wrapper functions use that to access the info.
Or, as you suggest, just let the user get the Info and use its accessors.
Either way works for me.
Quoted 47 lines
Quoted 38 lines
Quoted 38 lines
bindings/rust/libgpiod/src/chip.rs new file +36
-0+Ok (Self { ichip, info }) + } + +/// Get the chip name as represented in the kernel. +pub fn name (&self) ->Result <&str > { + self.info.name () + } + +/// Get the chip label as represented in the kernel. +pub fn label (&self) ->Result <&str > { + self.info.label () + } + +/// Get the number of GPIO lines exposed by the chip. +pub fn num_lines (&self) ->usize { + self.info.num_lines () + } +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 > { +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 {Quoted 8 lines
bindings/rust/libgpiod/src/event_buffer.rs line 38
Quoted 2 lines
bindings/rust/libgpiod/src/event_buffer.rs new file +2
-0+ self.buffer + }Just make self.buffer pub(crate)? Why is the idea of passing a raw pointer around making my skin crawl?
Or even better, move the guts ofline_request::Request::read_edge_events()into a helper function here that it can call so it doesn't need access to self.buffer.Quoted 9 lines
But then this piece of code would need to access self.request as the API is:
gpiod::gpiod_line_request_read_edge_event( self.request, buffer.buffer, buffer.capacity().try_into().unwrap(), )Yep - you pass the Request to the Buffer, rather than have the Request doing the work on the buffer.
IIRC my branch includes my version of this.
Quoted 17 lines
It needs raw pointers to both buffer and request.
Quoted 6 lines
Quoted 6 lines
bindings/rust/libgpiod/src/event_buffer.rs new file +6
-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 } + } +Quoted 8 lines
bindings/rust/libgpiod/src/event_buffer.rs line 48
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, index) + }What is to prevent reading an event from a slot that has not yet been populated? I realise doing that is possible in C, but it should be prevented in the Rust bindings.
Quoted 9 lines
Good point, I have done this now:
event_buffer.rs:/// Set the number of events read into the event buffer. pub fn set_event_count(&mut self, count: usize) { self.event_count = count }pub(crate) else the user can mess with the threshold.
Quoted 37 lines
/// Read an event stored in the buffer. pub fn event(&self, index: usize) -> Result<request::Event> { if index >= self.event_count { Err(Error::InvalidArguments) } else { request::Event::new(self, index) } }
line_request.rs:/// 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: &mut request::Buffer) -> Result<u32> { // SAFETY: `gpiod_line_request` is guaranteed to be valid here. let ret = unsafe { gpiod::gpiod_line_request_read_edge_event( self.request, buffer.buffer, buffer.capacity().try_into().unwrap(), ) }; if ret == -1 { Err(Error::OperationFailed( OperationType::LineRequestReadEdgeEvent, Errno::last(), )) } else { // Set count of events read in the buffer buffer.set_event_count(ret as usize); Ok(ret as u32) } }Quoted 2 lines
Good enough ?
Better.
Quoted 12 lines
Quoted 5 lines
Not sure why
event_bufferneeds to be nested inside an event module.event_buffershould be in the request module.
edge_eventis a tougher call, either line or request. Probably the latter.Is this okay now ? I had to rename
chip.rstochip_internal.rsin order to includeinfo_eventwithin it.mod
chip_internal;
modinfo_event;Quoted 10 lines
pub mod chip { /// GPIO chip related definitions. pub use crate::chip_internal::*; pub mod info { /// GPIO chip info event related definitions. pub use crate::info_event::*; } }Couldn't you just add those pubs to the old chip file? So you don't need to rename it to
chip_internal.Quoted 40 lines
mod
edge_event;
modevent_buffer;
modline_request;
modrequest_config;/// GPIO chip request related definitions.
pub mod request { pub use crate::edge_event::*; pub use crate::event_buffer::*; pub use crate::line_request::*; pub use crate::request_config::*; }mod
line_config;
modline_info;
modline_settings;/// GPIO chip line related definitions.
pub mod line { pub use crate::line_config::*; pub use crate::line_info::*; pub use crate::line_settings::*; }Quoted 7 lines
Quoted 7 lines
bindings/rust/libgpiod/src/lib.rs new file +7
-0+/// Iterator for GPIO devices. +pub fn gpiochip_devices <P :AsRef <Path >>(path: &P ) ->Result <Vec <chip::Chip >> { +let mut chips =Vec ::new (); + +for entryin fs::read_dir (path).map_err (|_|Error ::IoError )?.flatten () { +let path = entry.path (); +Quoted 8 lines
bindings/rust/libgpiod/src/lib.rs line 454
Quoted 4 lines
bindings/rust/libgpiod/src/lib.rs new file +4
-0+if is_gpiochip_device (&path) { + chips.push (chip::Chip ::open (&path)?); + } + }If there is a symlink to a device both in the path, or multiple symlinks to the same device, you will get the same chip several times in the returned Vec?
Quoted 2 lines
Probably yeah.
Lets say I want to implement a search for line by name, and check that the line name is unique. That will fail if there is a symlink to the relevant device, cos it will see a spurious duplicate.
How would I do that then?Quoted 4 lines
Quoted 1 line
Some mention of the order in which devices are returned?
Documentation of
read_dir()says:Quoted 9 lines
The order in which this iterator returns entries is platform and filesystem dependent.
mentioned same in comment now.
Quoted 2 lines
If you are returning a Vec then sorted might be nice, else state they are not sorted.
What should we here then ?
From my PoV, the two options are either
a)wrapread_dirand return an actual iterator and live with the disorder
or b) return a Vec and sort it before you return itReturning an unsorted Vec is the worst of both worlds.
Quoted 16 lines
Quoted 15 lines
bindings/rust/libgpiod/src/lib.rs line 471
Quoted 13 lines
bindings/rust/libgpiod/src/lib.rs new file +13
-0+/// Get the API version of the library as a human-readable string. +pub fn version_string () ->Result <&'staticstr > { +// SAFETY: The string returned by libgpiod is guaranteed to live forever. +let version =unsafe { gpiod::gpiod_version_string () }; + +if version.is_null () { +return Err (Error ::NullString ("GPIO library version" )); + } + +// SAFETY: The string is guaranteed to be valid here by the C API. + str::from_utf8 (unsafe { slice::from_raw_parts (versionas *const u8 ,strlen (version)as usize ) }) + .map_err (Error ::StringNotUtf8 ) +}What if the Rust bindings version differ from the libgpiod version?
Quoted 7 lines
Quoted 4 lines
Hmm, not sure which version should we return here. I think it should just be libgpiod's version. Though I am fine with whatever you and Bartosz decide.
You need both - separately.
For C++ and Python, the API version stays consistent across all three trees for simplicity. There are separate ABI numbers for libgpiod, libgpiodcxx and libgpiosim. Rust doesn't seem to have this kind of differentiation between ABI and API so I'd say we should have a single API version but see below about the idea for the contrib/ directory.
Quoted 59 lines
Quoted 48 lines
Quoted 7 lines
bindings/rust/libgpiod/src/line_request.rs line 221
Quoted 4 lines
bindings/rust/libgpiod/src/line_request.rs new file +4
-0+/// 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: &mut edge::event::Buffer ) ->Result <u32 > {What is to stop the user requesting events between the len returned here and the end of the buffer? Why knows what state they are in.
I have shared a change for the same earlier, is that enough ?
Quoted 29 lines
Overall I don't see any problems that require major rework to the scale of previous versions. My main itches that need to be scratched being dangling pointers and to a lesser degree thread safey.
I find the
event_buffersolution to be less safe than it could be. I've got an alternative, based on the my earlier PoC/suggestion, that returns an snaphot of events when you read events from the request into the buffer. I've put that in a branch on github [1] if you want to take a look.I went though a few variations wrt how to deal with the event cloning. Not implementing Clone on the events means they can't be stored in a Vec, and so storing them in the Buffer was problematic.
My initial solution was for the event to only wrap the C pointer and have distinct event types for buffered and cloned, implemented using traits - which is bit of a pain as the user then needs to import the trait themselves to use the events :-(.
Then tried a macro to add impl Event on the two event types, which looks ugly to me.
Finally went with having Event be a new type of BaseEvent, which does impl Clone, so the event buffer can store them (and <cough> cast them to Event as appropriate). Slightly more evil under the hood than I would like, but provides the cleanest implementation.The Events snapshot should impl Iterator to make it easier to use, though I haven't added that yet.
And the error returned byEvents::event()is wrong - just cut and pasted one in there - probably should be InvalidArguments?Quoted 8 lines
Anyway, the end result is that it is not possible to read an invalid or uninitialised event from Rust, which was the point.
It also better handles lifetimes, so it isn't necessary to explicitly drop events before re-using the buffer, and theevent_clone()method can be an instance method rather than a class method (to borrow some Python terminology).
I've added anevents.rsto the examples to demonstrate how the changes impact usage in each case.Quoted 5 lines
I haven't looked into that in great details yet, just skimmed through it. I am not sure I understand them completely yet and I am not sure if it is worth the complexity, may be it is. I will try to get through the changes again next week though.
Clearly I think it is or I wouldn't've bothered?
So not sure what you would like me to add, and I'm not going to push for it any more than I already have. If you have the time and inclination then give it a look.
Quoted 9 lines
Quoted 4 lines
Will it be possible to get on with the current implementation and once this is merged you send patches on top of it which can then get reviewed properly by others too, who know Rust better than me ?
The Rust bindings are your baby, and I don't have any plans to be patching it. And Bart is the arbiter of what gets merged, so that decision is his.
I was thinking about it lately and figured that - since I don't know Rust very well and cannot reliably maintain that part myself - how about we create a 'contrib' directory in the libgpiod tree for this kind of "third-party" bindings? The requirements for stability and correctness in there would be more relaxed compared to the main tree?
And yes, we can think about pulling it into master soon - as soon as the python bindings land and we ditch the next/libgpiod-v2.0 branch.
Bart
Re: [PATCH V7 3/8] libgpiod: Add rust wrapper crate
Quoted 13 lines · Bartosz Golaszewski
Quoted 12 lines · Kent Gibson
Quoted 9 lines · Viresh Kumar
Hi Kent,
Quoted 2 lines
What if the Rust bindings version differ from the libgpiod version?
Hmm, not sure which version should we return here. I think it should just be libgpiod's version. Though I am fine with whatever you and Bartosz decide.
Quoted 9 lines
Quoted 2 lines
You need both - separately.
For C++ and Python, the API version stays consistent across all three trees for simplicity. There are separate ABI numbers for libgpiod, libgpiodcxx and libgpiosim. Rust doesn't seem to have this kind of differentiation between ABI and API so I'd say we should have a single API version but see below about the idea for the contrib/ directory.
My thinking was that if the Rust bindings end up being compiled into the app then it may be difficult to identify the version it was built with, as opposed to the version of libgpiod it is running against. Not sure you can always determine that from the Cargo.toml or Cargo.lock, or that those will always be available after the fact.
Not a biggy if the two always remain compatible, but if a libgpiod change inadvertently triggers some fault in the Rust bindings then it could be a PITA just to determine the versions involved.
Quoted 10 lines
Quoted 9 lines
Quoted 4 lines
Will it be possible to get on with the current implementation and once this is merged you send patches on top of it which can then get reviewed properly by others too, who know Rust better than me ?
The Rust bindings are your baby, and I don't have any plans to be patching it. And Bart is the arbiter of what gets merged, so that decision is his.
Quoted 6 lines
I was thinking about it lately and figured that - since I don't know Rust very well and cannot reliably maintain that part myself - how about we create a 'contrib' directory in the libgpiod tree for this kind of "third-party" bindings? The requirements for stability and correctness in there would be more relaxed compared to the main tree?
The problem there is you thinking that you need to be able to personally support every line of libgpiod, and that it is not a team effort.
Wrt the Rust bindings, I was assuming that either Viresh would provide support, or as his work appears to be on behalf of Linaro that they would have an interest in maintaining it.
Similarly, I was assuming that I would be providing support for the proposed tool changes, should they ever get merged. Though, based on the little feedback to date, I'm dubious about that ever happening. I could always split them off into a separate project, but maintaining an autoconf based setup doesn't interest me, particularly when the Go and Rust alternatives are one-liners that just work, so probably not.
I was thinking that some forum for libgpiod, or more broadly GPIO user space development, would help increase community involvement, in both directions, and that might help address the Bartosz doesn't scale problem. But I don't have anything useful to add on that. The only relevant experience I have is with github, and while it would probably suffice I would explore other options first, with gitlab being the most obvious alternative. No help there, I know.
Sorry for not replying sooner. I was hoping to do a respin in a more positive light, but that has eluded me and time marches on.
Cheers,
Kent.
Re: [PATCH V7 3/8] libgpiod: Add rust wrapper crate
Quoted 55 lines · Kent Gibson
Quoted 13 lines · Bartosz Golaszewski
Quoted 12 lines · Kent Gibson
Quoted 9 lines · Viresh Kumar
Hi Kent,
Quoted 2 lines
What if the Rust bindings version differ from the libgpiod version?
Hmm, not sure which version should we return here. I think it should just be libgpiod's version. Though I am fine with whatever you and Bartosz decide.
Quoted 9 lines
Quoted 2 lines
You need both - separately.
For C++ and Python, the API version stays consistent across all three trees for simplicity. There are separate ABI numbers for libgpiod, libgpiodcxx and libgpiosim. Rust doesn't seem to have this kind of differentiation between ABI and API so I'd say we should have a single API version but see below about the idea for the contrib/ directory.
My thinking was that if the Rust bindings end up being compiled into the app then it may be difficult to identify the version it was built with, as opposed to the version of libgpiod it is running against. Not sure you can always determine that from the Cargo.toml or Cargo.lock, or that those will always be available after the fact.
Not a biggy if the two always remain compatible, but if a libgpiod change inadvertently triggers some fault in the Rust bindings then it could be a PITA just to determine the versions involved.
Quoted 10 lines
Quoted 9 lines
Quoted 4 lines
Will it be possible to get on with the current implementation and once this is merged you send patches on top of it which can then get reviewed properly by others too, who know Rust better than me ?
The Rust bindings are your baby, and I don't have any plans to be patching it. And Bart is the arbiter of what gets merged, so that decision is his.
Quoted 6 lines
I was thinking about it lately and figured that - since I don't know Rust very well and cannot reliably maintain that part myself - how about we create a 'contrib' directory in the libgpiod tree for this kind of "third-party" bindings? The requirements for stability and correctness in there would be more relaxed compared to the main tree?
The problem there is you thinking that you need to be able to personally support every line of libgpiod, and that it is not a team effort.
Quoted 7 lines
Wrt the Rust bindings, I was assuming that either Viresh would provide support, or as his work appears to be on behalf of Linaro that they would have an interest in maintaining it.
Similarly, I was assuming that I would be providing support for the proposed tool changes, should they ever get merged. Though, based on the little feedback to date, I'm dubious about that ever happening.
Hey, this is literally the next item on my TODO list. I've just merged the v2 API into master and am getting ready to dive head-first into your patches.
Bart
Quoted 17 lines
I could always split them off into a separate project, but maintaining an autoconf based setup doesn't interest me, particularly when the Go and Rust alternatives are one-liners that just work, so probably not.
I was thinking that some forum for libgpiod, or more broadly GPIO user space development, would help increase community involvement, in both directions, and that might help address the Bartosz doesn't scale problem. But I don't have anything useful to add on that. The only relevant experience I have is with github, and while it would probably suffice I would explore other options first, with gitlab being the most obvious alternative. No help there, I know.
Sorry for not replying sooner. I was hoping to do a respin in a more positive light, but that has eluded me and time marches on.
Cheers,
Kent.
Re: [PATCH V7 3/8] libgpiod: Add rust wrapper crate
In reply to Kent GibsonQuoted 3 lines · Kent Gibson
Wrt the Rust bindings, I was assuming that either Viresh would provide support, or as his work appears to be on behalf of Linaro that they would have an interest in maintaining it.
I will surely help in maintaining the Rust part. Not an issue.
Signature · 1 line
viresh
Re: [PATCH V7 3/8] libgpiod: Add rust wrapper crate
Quoted 8 lines · Viresh Kumar
Quoted 3 lines · Kent Gibson
Wrt the Rust bindings, I was assuming that either Viresh would provide support, or as his work appears to be on behalf of Linaro that they would have an interest in maintaining it.
I will surely help in maintaining the Rust part. Not an issue.
Sounds like a plan. I'm going through Kent's gpio-tools patches and rust bindings are next in line.
Bart
Re: [PATCH V7 3/8] libgpiod: Add rust wrapper crate
Quoted 7 lines · Bartosz Golaszewski
Quoted 6 lines · Viresh Kumar
Quoted 3 lines · Kent Gibson
Wrt the Rust bindings, I was assuming that either Viresh would provide support, or as his work appears to be on behalf of Linaro that they would have an interest in maintaining it.
Quoted 6 lines
Quoted 2 lines
I will surely help in maintaining the Rust part. Not an issue.
Sounds like a plan. I'm going through Kent's gpio-tools patches and rust bindings are next in line.
The rust bindings might make it in before they do - you may find that they are a bit more different from the old tools than you were expecting.
For my part I'm just wrapping up a quick pass over rust v8, before looking for where the python landed.
Cheers,
Kent.
Re: [PATCH V7 3/8] libgpiod: Add rust wrapper crate
Quoted 12 lines · Kent Gibson
Quoted 10 lines · Bartosz Golaszewski
Quoted 8 lines · Viresh Kumar
Quoted 3 lines · Kent Gibson
Wrt the Rust bindings, I was assuming that either Viresh would provide support, or as his work appears to be on behalf of Linaro that they would have an interest in maintaining it.
I will surely help in maintaining the Rust part. Not an issue.
Quoted 7 lines
Quoted 3 lines
Sounds like a plan. I'm going through Kent's gpio-tools patches and rust bindings are next in line.
The rust bindings might make it in before they do - you may find that they are a bit more different from the old tools than you were expecting.
Yeah, I can tell that. :)
I have lots of non-functional minor things to point out - mostly coding style etc. - that I will probably just fix locally when applying.
One thing that stands out though is the dependency on libedit - do you think we could make the gpioset interactive mode a configurable feature with its own configure switch that could be left out if we don't want to depend on any external libraries?
Bart
Quoted 6 lines
For my part I'm just wrapping up a quick pass over rust v8, before looking for where the python landed.
Cheers,
Kent.
Re: [PATCH V7 3/8] libgpiod: Add rust wrapper crate
Quoted 21 lines · Bartosz Golaszewski
Quoted 12 lines · Kent Gibson
Quoted 10 lines · Bartosz Golaszewski
Quoted 8 lines · Viresh Kumar
Quoted 3 lines · Kent Gibson
Wrt the Rust bindings, I was assuming that either Viresh would provide support, or as his work appears to be on behalf of Linaro that they would have an interest in maintaining it.
I will surely help in maintaining the Rust part. Not an issue.
Quoted 7 lines
Quoted 3 lines
Sounds like a plan. I'm going through Kent's gpio-tools patches and rust bindings are next in line.
The rust bindings might make it in before they do - you may find that they are a bit more different from the old tools than you were expecting.
Quoted 6 lines
Yeah, I can tell that. :)
I have lots of non-functional minor things to point out - mostly coding style etc. - that I will probably just fix locally when applying.
Looks like there will be a v5 for the optional interactive set, if nothing else, so let me know and I can fix them as well.
Quoted 4 lines
One thing that stands out though is the dependency on libedit - do you think we could make the gpioset interactive mode a configurable feature with its own configure switch that could be left out if we don't want to depend on any external libraries?
Well, libedit was your preferred choice.
But, yeah, making the interactive mode optional is a good idea.
Any preference on the name for the config flag?
--enable-gpioset-interactive ?
One other major thing I had been considering, and implemented in my Rust version[1], is renaming the tools, since it isn't intuitively obvious, to me anyway, what gpiodetect, gpioinfo, gpiomon, and gpiowatch do.
I went with:
gpiodetect -> gpiochip
gpioinfo -> gpioline gpiomon -> gpioedges gpiowatch -> gpionotify
where the names try to indicate the information returned, rather than how it is collected.
And yeah, I got stuck with gpiowatch/gpionotify as I couldn't come up with a short meaningful name for line info changed events. Suggestions welcome. And lice is not an option.
gpioget and gpioset remain unchanged as they are already as clear as can be.
Would that work for you, or would you prefer to stick with the existing names?
Cheers,
Kent.
Re: [PATCH V7 3/8] libgpiod: Add rust wrapper crate
Quoted 33 lines · Kent Gibson
Quoted 21 lines · Bartosz Golaszewski
Quoted 12 lines · Kent Gibson
Quoted 10 lines · Bartosz Golaszewski
Quoted 8 lines · Viresh Kumar
Quoted 3 lines · Kent Gibson
Wrt the Rust bindings, I was assuming that either Viresh would provide support, or as his work appears to be on behalf of Linaro that they would have an interest in maintaining it.
I will surely help in maintaining the Rust part. Not an issue.
Quoted 7 lines
Quoted 3 lines
Sounds like a plan. I'm going through Kent's gpio-tools patches and rust bindings are next in line.
The rust bindings might make it in before they do - you may find that they are a bit more different from the old tools than you were expecting.
Quoted 6 lines
Yeah, I can tell that. :)
I have lots of non-functional minor things to point out - mostly coding style etc. - that I will probably just fix locally when applying.
Looks like there will be a v5 for the optional interactive set, if nothing else, so let me know and I can fix them as well.
Quoted 6 lines
Quoted 4 lines
One thing that stands out though is the dependency on libedit - do you think we could make the gpioset interactive mode a configurable feature with its own configure switch that could be left out if we don't want to depend on any external libraries?
Well, libedit was your preferred choice.
It's still is - it's not about the choice of the library but about keeping it possible to build a set of command-line tools with no dependencies other than libc.
Quoted 5 lines
But, yeah, making the interactive mode optional is a good idea.
Any preference on the name for the config flag?
--enable-gpioset-interactive ?
Sounds great.
Quoted 16 lines
One other major thing I had been considering, and implemented in my Rust version[1], is renaming the tools, since it isn't intuitively obvious, to me anyway, what gpiodetect, gpioinfo, gpiomon, and gpiowatch do.
I went with:
gpiodetect -> gpiochipgpioinfo -> gpioline gpiomon -> gpioedges gpiowatch -> gpionotifywhere the names try to indicate the information returned, rather than how it is collected.
And yeah, I got stuck with gpiowatch/gpionotify as I couldn't come up with a short meaningful name for line info changed events. Suggestions welcome. And lice is not an option.
Quoted 5 lines
gpioget and gpioset remain unchanged as they are already as clear as can be.
Would that work for you, or would you prefer to stick with the existing names?
I don't know if it is because I'm used to the previous names but none of the first three make much sense to me. I think it's better for the names to indicate what the program is doing, not what it's returning.
On the other hand - gpionotify is great, go for it!
Bart
Quoted 5 lines
Cheers,
Kent.
Re: [PATCH V7 3/8] libgpiod: Add rust wrapper crate
Quoted 72 lines · Bartosz Golaszewski
Quoted 33 lines · Kent Gibson
Quoted 21 lines · Bartosz Golaszewski
Quoted 12 lines · Kent Gibson
Quoted 10 lines · Bartosz Golaszewski
Quoted 8 lines · Viresh Kumar
Quoted 3 lines · Kent Gibson
Wrt the Rust bindings, I was assuming that either Viresh would provide support, or as his work appears to be on behalf of Linaro that they would have an interest in maintaining it.
I will surely help in maintaining the Rust part. Not an issue.
Quoted 7 lines
Quoted 3 lines
Sounds like a plan. I'm going through Kent's gpio-tools patches and rust bindings are next in line.
The rust bindings might make it in before they do - you may find that they are a bit more different from the old tools than you were expecting.
Quoted 6 lines
Yeah, I can tell that. :)
I have lots of non-functional minor things to point out - mostly coding style etc. - that I will probably just fix locally when applying.
Looks like there will be a v5 for the optional interactive set, if nothing else, so let me know and I can fix them as well.
Quoted 6 lines
Quoted 4 lines
One thing that stands out though is the dependency on libedit - do you think we could make the gpioset interactive mode a configurable feature with its own configure switch that could be left out if we don't want to depend on any external libraries?
Well, libedit was your preferred choice.
It's still is - it's not about the choice of the library but about keeping it possible to build a set of command-line tools with no dependencies other than libc.
Quoted 5 lines
But, yeah, making the interactive mode optional is a good idea.
Any preference on the name for the config flag?
--enable-gpioset-interactive ?Sounds great.
Quoted 19 lines
One other major thing I had been considering, and implemented in my Rust version[1], is renaming the tools, since it isn't intuitively obvious, to me anyway, what gpiodetect, gpioinfo, gpiomon, and gpiowatch do.
I went with:
gpiodetect -> gpiochipgpioinfo -> gpioline gpiomon -> gpioedges gpiowatch -> gpionotifywhere the names try to indicate the information returned, rather than how it is collected.
And yeah, I got stuck with gpiowatch/gpionotify as I couldn't come up with a short meaningful name for line info changed events. Suggestions welcome. And lice is not an option.gpioget and gpioset remain unchanged as they are already as clear as can be.
Quoted 9 lines
Quoted 2 lines
Would that work for you, or would you prefer to stick with the existing names?
I don't know if it is because I'm used to the previous names but none of the first three make much sense to me. I think it's better for the names to indicate what the program is doing, not what it's returning.
On the other hand - gpionotify is great, go for it!
Ok, now I'm confused - only rename gpiowatch?
Anyway, we probably should pick this conversation up as part of the tools review, not the Rust bindings, so let me know there.
Cheers,
Kent.
Re: [PATCH V7 3/8] libgpiod: Add rust wrapper crate
Quoted 77 lines · Kent Gibson
Quoted 75 lines · Bartosz Golaszewski
Quoted 33 lines · Kent Gibson
Quoted 21 lines · Bartosz Golaszewski
Quoted 12 lines · Kent Gibson
Quoted 10 lines · Bartosz Golaszewski
Quoted 8 lines · Viresh Kumar
Quoted 3 lines · Kent Gibson
Wrt the Rust bindings, I was assuming that either Viresh would provide support, or as his work appears to be on behalf of Linaro that they would have an interest in maintaining it.
I will surely help in maintaining the Rust part. Not an issue.
Quoted 7 lines
Quoted 3 lines
Sounds like a plan. I'm going through Kent's gpio-tools patches and rust bindings are next in line.
The rust bindings might make it in before they do - you may find that they are a bit more different from the old tools than you were expecting.
Quoted 6 lines
Yeah, I can tell that. :)
I have lots of non-functional minor things to point out - mostly coding style etc. - that I will probably just fix locally when applying.
Looks like there will be a v5 for the optional interactive set, if nothing else, so let me know and I can fix them as well.
Quoted 6 lines
Quoted 4 lines
One thing that stands out though is the dependency on libedit - do you think we could make the gpioset interactive mode a configurable feature with its own configure switch that could be left out if we don't want to depend on any external libraries?
Well, libedit was your preferred choice.
It's still is - it's not about the choice of the library but about keeping it possible to build a set of command-line tools with no dependencies other than libc.
Quoted 5 lines
But, yeah, making the interactive mode optional is a good idea.
Any preference on the name for the config flag?
--enable-gpioset-interactive ?Sounds great.
Quoted 21 lines
One other major thing I had been considering, and implemented in my Rust version[1], is renaming the tools, since it isn't intuitively obvious, to me anyway, what gpiodetect, gpioinfo, gpiomon, and gpiowatch do.
I went with:
gpiodetect -> gpiochipgpioinfo -> gpioline gpiomon -> gpioedges gpiowatch -> gpionotifywhere the names try to indicate the information returned, rather than how it is collected.
And yeah, I got stuck with gpiowatch/gpionotify as I couldn't come up with a short meaningful name for line info changed events. Suggestions welcome. And lice is not an option.gpioget and gpioset remain unchanged as they are already as clear as can be.
Would that work for you, or would you prefer to stick with the existing names?
Quoted 8 lines
Quoted 6 lines
I don't know if it is because I'm used to the previous names but none of the first three make much sense to me. I think it's better for the names to indicate what the program is doing, not what it's returning.
On the other hand - gpionotify is great, go for it!
Ok, now I'm confused - only rename gpiowatch?
Yes, gpiowatch -> gpionotify.
Quoted 4 lines
Anyway, we probably should pick this conversation up as part of the tools review, not the Rust bindings, so let me know there.
Makes sense. I'm off most of this week, but I will get there, I promise. :)
Bartosz
Re: [PATCH V7 3/8] libgpiod: Add rust wrapper crate
In reply to Viresh KumarQuoted 4 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]>
One more thing - I overlooked trying to build on 32-bit while reviewing. (in this case on a Raspberry Pi 4)
Unfortunately that fails:
Compiling libgpiod v0.1.0 (/home/pi/libgpiod/bindings/rust/libgpiod)
error[E0308]: mismatched types
--> libgpiod/src/edge_event.rs:32:88
|
32 | let event = unsafe { gpiod::gpiod_edge_event_buffer_get_event(buffer.buffer(), index) };
| ---------------------------------------- ^^^^^ expected `u32`, found `u64`
| |
| arguments to this function are incorrect
|
note: function defined here
--> /home/pi/libgpiod/bindings/rust/libgpiod-sys/src/bindings.rs:1151:12
|
1151 | pub fn gpiod_edge_event_buffer_get_event(
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: you can convert a `u64` to a `u32` and panic if the converted value doesn't fit
|
32 | let event = unsafe { gpiod::gpiod_edge_event_buffer_get_event(buffer.buffer(), index.try_into().unwrap()) };
| ++++++++++++++++++++error[E0308]: mismatched types
--> libgpiod/src/edge_event.rs:81:18
|
80 | pub fn global_seqno(&self) -> u64 {
| --- expected `u64` because of return type
81 | unsafe { gpiod::gpiod_edge_event_get_global_seqno(self.event) }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `u64`, found `u32`
|
help: you can convert a `u32` to a `u64`
|
81 | unsafe { gpiod::gpiod_edge_event_get_global_seqno(self.event).into() }
| +++++++error[E0308]: mismatched types
--> libgpiod/src/edge_event.rs:89:18
|
88 | pub fn line_seqno(&self) -> u64 {
| --- expected `u64` because of return type
89 | unsafe { gpiod::gpiod_edge_event_get_line_seqno(self.event) }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `u64`, found `u32`
|
help: you can convert a `u32` to a `u64`
|
89 | unsafe { gpiod::gpiod_edge_event_get_line_seqno(self.event).into() }
| +++++++error[E0308]: mismatched types
--> libgpiod/src/line_config.rs:100:72
|
100 | unsafe { gpiod::gpiod_line_config_get_offsets(self.config, &mut num, &mut offsets) };
| ------------------------------------ ^^^^^^^^ expected `u32`, found `u64`
| |
| arguments to this function are incorrect
| = note: expected raw pointer *mut u32
found mutable reference &mut u64
note: function defined here
--> /home/pi/libgpiod/bindings/rust/libgpiod-sys/src/bindings.rs:847:12
|
847 | pub fn gpiod_line_config_get_offsets(
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^error[E0308]: mismatched types
--> libgpiod/src/line_info.rs:168:40
|
168 | Duration::from_micros(unsafe { gpiod::gpiod_line_info_get_debounce_period_us(self.info) })
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `u64`, found `u32`
|
help: you can convert a `u32` to a `u64`
|
168 | Duration::from_micros(unsafe { gpiod::gpiod_line_info_get_debounce_period_us(self.info).into() })
| +++++++error[E0308]: mismatched types
--> libgpiod/src/line_request.rs:226:17
|
223 | gpiod::gpiod_line_request_read_edge_event(
| ----------------------------------------- arguments to this function are incorrect
...
226 | buffer.capacity() as u64,
| ^^^^^^^^^^^^^^^^^^^^^^^^ expected `u32`, found `u64`
|
note: function defined here
--> /home/pi/libgpiod/bindings/rust/libgpiod-sys/src/bindings.rs:1058:12
|
1058 | pub fn gpiod_line_request_read_edge_event(
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: you can convert a `u64` to a `u32` and panic if the converted value doesn't fit
|
226 | (buffer.capacity() as u64).try_into().unwrap(),
| + +++++++++++++++++++++error[E0308]: mismatched types
--> libgpiod/src/line_settings.rs:209:17
|
207 | gpiod::gpiod_line_settings_set_debounce_period_us(
| ------------------------------------------------- arguments to this function are incorrect
208 | self.settings,
209 | period.as_micros() as u64,
| ^^^^^^^^^^^^^^^^^^^^^^^^^ expected `u32`, found `u64`
|
note: function defined here
--> /home/pi/libgpiod/bindings/rust/libgpiod-sys/src/bindings.rs:746:12
|
746 | pub fn gpiod_line_settings_set_debounce_period_us(
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: you can convert a `u64` to a `u32` and panic if the converted value doesn't fit
|
209 | (period.as_micros() as u64).try_into().unwrap(),
| + +++++++++++++++++++++error[E0308]: mismatched types
--> libgpiod/src/line_settings.rs:217:13
|
217 | gpiod::gpiod_line_settings_get_debounce_period_us(self.settings)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `u64`, found `u32`
|
help: you can convert a `u32` to a `u64`
|
217 | gpiod::gpiod_line_settings_get_debounce_period_us(self.settings).into()
| +++++++For more information about this error, try rustc --explain E0308.
error: could not compile libgpiod due to 8 previous errors
---
An example of one of the relevant generated signatures is:
pub fn gpiod_edge_event_buffer_get_event(
buffer: *mut gpiod_edge_event_buffer,
index: ::std::os::raw::c_ulong,
) -> *mut gpiod_edge_event;On 32-bit c_ulong maps to u32, not u64.
Cheers,
Kent.
Re: [PATCH V7 3/8] libgpiod: Add rust wrapper crate
Quoted 2 lines · Kent Gibson
One more thing - I overlooked trying to build on 32-bit while reviewing. (in this case on a Raspberry Pi 4)
I missed running this :(
cargo build --target arm-unknown-linux-gnueabi
I have these changes on the top and yet I have an error which I am not sure how to fix here without special if/else code per target.
bindings/rust/gpiosim/src/sim.rs +1 -1
@@ -186,7 +186,7 @@ impl SimBank { fn set_num_lines(&self, num: u64) -> Result<()> { // SAFETY: `gpiosim_bank` is guaranteed to be valid here. - let ret = unsafe { gpiosim_bank_set_num_lines(self.bank, num) }; + let ret = unsafe { gpiosim_bank_set_num_lines(self.bank, num.try_into().unwrap()) }; if ret == -1 { Err(Error::OperationFailed( OperationType::SimBankSetNumLines,
bindings/rust/libgpiod/src/edge_event.rs +2 -2
@@ -76,7 +76,7 @@ impl<'b> Event<'b> {/// associated line request. pub fn global_seqno (&self) ->u64 {// SAFETY: `gpiod_edge_event` is guaranteed to be valid here. -unsafe { gpiod::gpiod_edge_event_get_global_seqno (self.event) } +unsafe { gpiod::gpiod_edge_event_get_global_seqno (self.event)as u64 } }/// Get the event sequence number specific to concerned line.
@@ -85,7 +85,7 @@ impl<'b> Event<'b> {/// lifetime of the associated line request. pub fn line_seqno (&self) ->u64 {// SAFETY: `gpiod_edge_event` is guaranteed to be valid here. -unsafe { gpiod::gpiod_edge_event_get_line_seqno (self.event) } +unsafe { gpiod::gpiod_edge_event_get_line_seqno (self.event)as u64 } } }
bindings/rust/libgpiod/src/line_info.rs +3 -1
@@ -144,7 +144,9 @@ impl Info {/// Get the debounce period of the line. pub fn debounce_period (&self) ->Duration {// SAFETY: `gpiod_line_info` is guaranteed to be valid here. -Duration ::from_micros (unsafe { gpiod::gpiod_line_info_get_debounce_period_us (self.info) }) +Duration ::from_micros (unsafe { + gpiod::gpiod_line_info_get_debounce_period_us (self.info).into () + }) } }
bindings/rust/libgpiod/src/line_settings.rs +2 -2
@@ -220,7 +220,7 @@ impl Settings {unsafe { gpiod::gpiod_line_settings_set_debounce_period_us ( self.settings, - period.as_micros ()as u64 , + (period.as_micros ()as usize ).try_into ().unwrap (), ); }
@@ -231,7 +231,7 @@ impl Settings {pub fn debounce_period (&self) ->Result <Duration > {// SAFETY: `gpiod_line_settings` is guaranteed to be valid here. Ok (Duration ::from_micros (unsafe { - gpiod::gpiod_line_settings_get_debounce_period_us (self.settings) + gpiod::gpiod_line_settings_get_debounce_period_us (self.settings).into () })) }
bindings/rust/libgpiod/tests/chip.rs +4 -1
@@ -90,7 +90,10 @@ mod chip {// Failure assert_eq! ( chip.line_offset_from_name ("nonexistent" ).unwrap_err (), -ChipError ::OperationFailed (OperationType ::ChipGetLineOffsetFromName ,Errno ::new (ENOENT )) +ChipError ::OperationFailed ( +OperationType ::ChipGetLineOffsetFromName , +Errno ::new (ENOENT ), + ) ); } }
$
-------------------------8<-------------------------
Current failures:
error[E0308]: mismatched types
--> libgpiod/src/line_config.rs:105:78
|
105 | let ret = unsafe { gpiod::gpiod_line_config_get_offsets(self.config, &mut num, &mut ptr) };
| ------------------------------------ ^^^^^^^^ expected `u32`, found `u64`
| |
| arguments to this function are incorrect
| = note: expected raw pointer *mut u32
found mutable reference &mut u64
note: function defined here
--> /mnt/ssd/all/work/repos/virtio/rust/libgpiod/bindings/rust/libgpiod-sys/src/bindings.rs:847:12
|
847 | pub fn gpiod_line_config_get_offsets(
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^For more information about this error, try rustc --explain E0308.
error: could not compile libgpiod due to previous error
Signature · 1 line
viresh
PATCH v7 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
Re: [PATCH V7 4/8] libgpiod: Add rust examples
Quoted 4 lines · Viresh Kumar
Add examples for the usage of the rust bindings, quite similar to the ones in cxx bindings.
Signed-off-by Viresh Kumar <[email protected]>
Quoted 17 lines
--- 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
Add an example that exercises the event buffer, demonstrating that its is not possible to read new events while the previous set are still in use.
I added something along those lines, based on gpiomon, in my mods branch mentioned in patch 3.
If multi-threading is supported, perhaps some examples demonstrating that as well. Even if there are tests that already cover it - major features should have explicit examples, not require rooting around through the test suite.
Quoted 101 lines
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 +18
-0@@ -0,0 +1,42 @@ +// 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 gpioget tool. + +use std::env; + +use libgpiod::{chip::Chip , line, request,Direction ,Error ,Offset ,Result ,SettingVal }; + +fn main () ->Result <()> { +let args:Vec <String > = env::args ().collect (); +if args.len () <3 { +println! ("Usage: {} <chip> <line_offset0> ..." , args[0 ]); +return Err (Error ::InvalidArguments ); + } +
bindings/rust/libgpiod/examples/gpioget.rs line 28
Quoted 10 lines
bindings/rust/libgpiod/examples/gpioget.rs new file +10
-0+let mut lsettings = line::Settings ::new ()?; +let lconfig = line::Config ::new ()?; +let mut offsets =Vec ::<Offset >::new (); + +for argin &args[2 ..] { +let offset = arg.parse ::<Offset >().map_err (|_|Error ::InvalidArguments )?; + offsets.push (offset); + } + + lsettings.set_prop (&[SettingVal ::Direction (Direction ::Input )])?;
This is an example of where
lsettings.set_direction(Direction::Input)?;
would be simpler and clearer.
Cheers,
Kent.
PATCH v7 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
Re: [PATCH V7 5/8] libgpiod: Add gpiosim rust crate
Quoted 34 lines · Viresh Kumar
This adds gpiosim rust crate, which provides helpers to emulate GPIO chips.
Signed-off-by Viresh Kumar <[email protected]>--- 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.rsbindings/rust/Cargo.toml +2
-1@@ -2,5 +2,6 @@ members = [ "libgpiod-sys", - "libgpiod" + "libgpiod", + "gpiosim" ]bindings/rust/gpiosim/Cargo.toml new file +0
-0
bindings/rust/gpiosim/Cargo.toml line 5
Quoted 5 lines
bindings/rust/gpiosim/Cargo.toml new file +5
-0@@ -0,0 +1,18 @@ +[package] +name = "gpiosim" +version = "0.1.0" +edition = "2018" +
license and other keys...
Quoted 68 lines
bindings/rust/gpiosim/Cargo.toml new file +13
-0+# 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@@ -0,0 +1,43 @@ +#[cfg(feature = "generate" )] +extern crate bindgen; +#[cfg(feature = "generate" )] +use std::env; +#[cfg(feature = "generate" )] +use std::path::PathBuf ; + +#[cfg(feature = "generate" )] +fn generate_bindings () { +// Tell cargo to invalidate the built crate whenever following files change +println! ("cargo:rerun-if-changed=../../../tests/gpiosim/gpiosim.h" ); + +// The bindgen::Builder is the main entry point +// to bindgen, and lets you build up options for +// the resulting bindings. +let bindings = bindgen::Builder ::default () +// The input header we would like to generate +// bindings for. + .header ("../../../tests/gpiosim/gpiosim.h" ) +// Tell cargo to invalidate the built crate whenever any of the +// included header files changed. + .parse_callbacks (Box ::new (bindgen::CargoCallbacks )) +// Finish the builder and generate the bindings. + .generate () +// Unwrap the Result and panic on failure. + .expect ("Unable to generate bindings" ); + +// Write the bindings to the $OUT_DIR/bindings.rs file. +let out_path =PathBuf ::from (env::var ("OUT_DIR" ).unwrap ()); + bindings + .write_to_file (out_path.join ("bindings.rs" )) + .expect ("Couldn't write bindings!" ); +} + +fn main () { +#[cfg(feature = "generate" )] +generate_bindings (); + +println! ("cargo:rustc-link-lib=kmod" ); +println! ("cargo:rustc-link-lib=mount" ); +println! ("cargo:rustc-link-search=./../../tests/gpiosim/.libs/" ); +println! ("cargo:rustc-link-lib=static=gpiosim" ); +}bindings/rust/gpiosim/src/lib.rs new file +0
-0
bindings/rust/gpiosim/src/lib.rs line 2
Quoted 2 lines
bindings/rust/gpiosim/src/lib.rs new file +2
-0@@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0 +
GPL-2.0. Not so critical for the gpiosim, but still...
Quoted 7 lines
bindings/rust/gpiosim/src/lib.rs new file +7
-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" )); +
bindings/rust/gpiosim/src/lib.rs line 19
Quoted 10 lines
bindings/rust/gpiosim/src/lib.rs new file +10
-0+#[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 , +};
Create enums for pull, direction, and value and accept/return those rather than passing around these consts as i32.
bindings/rust/gpiosim/src/lib.rs line 23
Quoted 4 lines
bindings/rust/gpiosim/src/lib.rs new file +4
-0+ +#[allow(dead_code)] +mod sim; +
Handle the warning inside the module rather than the blanket allow.
Rename Sim.ctx to _ctx to clear the warning.
bindings/rust/gpiosim/src/lib.rs line 25
Quoted 2 lines
bindings/rust/gpiosim/src/lib.rs new file +2
-0+#[allow(unused_imports)] +pub use sim::*;
Which imports are unused and why?
I don't see any warnings after removing this allow.
Quoted 21 lines
bindings/rust/gpiosim/src/sim.rs new file +15
-0@@ -0,0 +1,323 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar <[email protected]> + +use libc::strlen; +use std::os::raw::c_char; +use std::path::PathBuf ; +use std::{slice, str}; + +use vmm_sys_util::errno::Error as Errno ; + +use libgpiod::{Error ,Offset ,OperationType ,Result }; + +use crate ::*;
bindings/rust/gpiosim/src/sim.rs line 25
Quoted 10 lines
bindings/rust/gpiosim/src/sim.rs new file +10
-0+ +/// Sim Ctx +#[derive( Debug )] +struct SimCtx { + ctx: *mut gpiosim_ctx , +} + +unsafe impl Send for SimCtx {} +unsafe impl Sync for SimCtx {} +
Is gpiosim thread safe?
bindings/rust/gpiosim/src/sim.rs line 28
Quoted 3 lines
bindings/rust/gpiosim/src/sim.rs new file +3
-0+impl SimCtx { +fn new () ->Result <Self > { +let ctx =unsafe {gpiosim_ctx_new () };
SAFETY comments for all unsafe.
Cheers,
Kent.
Re: [PATCH V7 5/8] libgpiod: Add gpiosim rust crate
Quoted 11 lines · Kent Gibson
bindings/rust/gpiosim/src/sim.rs line 25
Quoted 9 lines
bindings/rust/gpiosim/src/sim.rs new file +9
-0+/// Sim Ctx +#[derive( Debug )] +struct SimCtx { + ctx: *mut gpiosim_ctx , +} + +unsafe impl Send for SimCtx {} +unsafe impl Sync for SimCtx {} +Is gpiosim thread safe?
There is no locking in place at the moment, if two threads try to do conflicting operations at the same time. Nothing will stop them I believe.
Though this module is only used for carrying out the tests at the moment, where we are generating events from parallel threads. I am not sure if we should make it more complex, for example with locking. Or is there something else we can do ?
Signature · 1 line
viresh
PATCH v7 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 v7 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 | 296 ++++++++++ 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, 1832 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 +296 -0
296 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
Re: [PATCH V7 7/8] libgpiod: Add rust tests
Quoted 5 lines · Viresh Kumar
Add tests for the rust bindings, quite similar to the ones in cxx bindings.
Signed-off-by Viresh Kumar <[email protected]>---
<snip>
bindings/rust/libgpiod/tests/line_info.rs line 232
Quoted 3 lines
bindings/rust/libgpiod/tests/line_info.rs new file +3
-0+ +#[test] +fn clock () {
My preference would be event_clock(), but I can live with clock().
Quoted 16 lines
bindings/rust/libgpiod/tests/line_info.rs new file +16
-0+let mut config =TestConfig ::new (NGPIO ).unwrap (); + config.lconfig_add_settings (&[0 ]); + config.request_lines ().unwrap (); +let info = config.chip ().line_info (0 ).unwrap (); +assert_eq! (info.event_clock ().unwrap (),EventClock ::Monotonic ); + +let mut config =TestConfig ::new (NGPIO ).unwrap (); + config.lconfig_clock (EventClock ::Monotonic ); + config.lconfig_add_settings (&[0 ]); + config.request_lines ().unwrap (); +let info = config.chip ().line_info (0 ).unwrap (); +assert_eq! (info.event_clock ().unwrap (),EventClock ::Monotonic ); + +let mut config =TestConfig ::new (NGPIO ).unwrap (); + config.lconfig_clock (EventClock ::Realtime ); + config.lconfig_add_settings (&[0 ]);
bindings/rust/libgpiod/tests/line_info.rs line 258
Quoted 10 lines
bindings/rust/libgpiod/tests/line_info.rs new file +10
-0+ config.request_lines ().unwrap (); +let info = config.chip ().line_info (0 ).unwrap (); +assert_eq! (info.event_clock ().unwrap (),EventClock ::Realtime ); + +let mut config =TestConfig ::new (NGPIO ).unwrap (); + config.lconfig_clock (EventClock ::HTE ); + config.lconfig_add_settings (&[0 ]); + config.request_lines ().unwrap (); +let info = config.chip ().line_info (0 ).unwrap (); +assert_eq! (info.event_clock ().unwrap (),EventClock ::HTE );
I was surprised to find HTE tests passing on a kernel without CONFIG_HTE. I take that as being a kernel bug (GPIO_V2_LINE_VALID_FLAGS includes the HTE flag unconditionally - which is wrong IMHO).
You probably shouldn't assume HTE works - unless you have a system that supports HTE.
Other than that the tests look good to me, though as with the Python bindings I've only skimmed them.
Cheers,
Kent.
Re: [PATCH V7 7/8] libgpiod: Add rust tests
Quoted 36 lines · Kent Gibson
Quoted 5 lines · Viresh Kumar
Add tests for the rust bindings, quite similar to the ones in cxx bindings.
Signed-off-by Viresh Kumar <[email protected]>---
<snip>
bindings/rust/libgpiod/tests/line_info.rs line 232
Quoted 3 lines
bindings/rust/libgpiod/tests/line_info.rs new file +3
-0+ +#[test] +fn clock () {My preference would be
event_clock(), but I can live withclock().Quoted 22 lines
bindings/rust/libgpiod/tests/line_info.rs new file +22
-0+let mut config =TestConfig ::new (NGPIO ).unwrap (); + config.lconfig_add_settings (&[0 ]); + config.request_lines ().unwrap (); +let info = config.chip ().line_info (0 ).unwrap (); +assert_eq! (info.event_clock ().unwrap (),EventClock ::Monotonic ); + +let mut config =TestConfig ::new (NGPIO ).unwrap (); + config.lconfig_clock (EventClock ::Monotonic ); + config.lconfig_add_settings (&[0 ]); + config.request_lines ().unwrap (); +let info = config.chip ().line_info (0 ).unwrap (); +assert_eq! (info.event_clock ().unwrap (),EventClock ::Monotonic ); + +let mut config =TestConfig ::new (NGPIO ).unwrap (); + config.lconfig_clock (EventClock ::Realtime ); + config.lconfig_add_settings (&[0 ]); + config.request_lines ().unwrap (); +let info = config.chip ().line_info (0 ).unwrap (); +assert_eq! (info.event_clock ().unwrap (),EventClock ::Realtime ); + +let mut config =TestConfig ::new (NGPIO ).unwrap (); + config.lconfig_clock (EventClock ::HTE );
Quoted 9 lines
bindings/rust/libgpiod/tests/line_info.rs line 258
Quoted 4 lines
bindings/rust/libgpiod/tests/line_info.rs new file +4
-0+ config.lconfig_add_settings (&[0 ]); + config.request_lines ().unwrap (); +let info = config.chip ().line_info (0 ).unwrap (); +assert_eq! (info.event_clock ().unwrap (),EventClock ::HTE );I was surprised to find HTE tests passing on a kernel without
CONFIG_HTE. I take that as being a kernel bug (GPIO_V2_LINE_VALID_FLAGSincludes the HTE flag unconditionally - which is wrong IMHO).
Bah, I was running an old-ish kernel (5.19) in my test setup.
A check for this was added in my HTE tidy up -
commit 272ddba0047 "gpiolib: cdev: compile out HTE unless CONFIG_HTE selected"
So the HTE tests now do fail with a more recent kernel (6.0+) - unless you have HTE support.
Cheers,
Kent.
Quoted 8 lines
You probably shouldn't assume HTE works - unless you have a system that supports HTE.
Other than that the tests look good to me, though as with the Python bindings I've only skimmed them.
Cheers,
Kent.
Re: [PATCH V7 7/8] libgpiod: Add rust tests
In reply to Kent GibsonQuoted 6 lines · Kent Gibson
I was surprised to find HTE tests passing on a kernel without
CONFIG_HTE. I take that as being a kernel bug (GPIO_V2_LINE_VALID_FLAGSincludes the HTE flag unconditionally - which is wrong IMHO).You probably shouldn't assume HTE works - unless you have a system that supports HTE.
Should I drop them ? Or run them conditionally ? How ?
Quoted 2 lines
Other than that the tests look good to me, though as with the Python bindings I've only skimmed them.
Signature · 1 line
viresh
Re: [PATCH V7 7/8] libgpiod: Add rust tests
Quoted 10 lines · Viresh Kumar
Quoted 6 lines · Kent Gibson
I was surprised to find HTE tests passing on a kernel without
CONFIG_HTE. I take that as being a kernel bug (GPIO_V2_LINE_VALID_FLAGSincludes the HTE flag unconditionally - which is wrong IMHO).You probably shouldn't assume HTE works - unless you have a system that supports HTE.
Should I drop them ? Or run them conditionally ? How ?
The Rust test framework is pretty simple, so there is no way to conditionally skip tests, AFAIAA. And the only generic way to tell if your kernel supports HTE without probing it, which makes testing it pointless.
So I don't see any alternative but to drop them.
Though I would be happy to learn otherwise.
Cheers,
Kent.
Re: [PATCH V7 7/8] libgpiod: Add rust tests
Quoted 13 lines · Kent Gibson
Quoted 10 lines · Viresh Kumar
Quoted 6 lines · Kent Gibson
I was surprised to find HTE tests passing on a kernel without
CONFIG_HTE. I take that as being a kernel bug (GPIO_V2_LINE_VALID_FLAGSincludes the HTE flag unconditionally - which is wrong IMHO).You probably shouldn't assume HTE works - unless you have a system that supports HTE.
Should I drop them ? Or run them conditionally ? How ?
Quoted 8 lines
The Rust test framework is pretty simple, so there is no way to conditionally skip tests, AFAIAA. And the only generic way to tell if your kernel supports HTE without probing it, which makes testing it pointless.
So I don't see any alternative but to drop them.Though I would be happy to learn otherwise.
In C tests I just skip them using g_test_skip() which generates an info message. I'm surprised to learn one can't skip tests in rust, it sounds like a very basic functionality. I'd say let's drop them for now then.
Bart
Re: [PATCH V7 7/8] libgpiod: Add rust tests
Quoted 5 lines · Bartosz Golaszewski
In C tests I just skip them using
g_test_skip()which generates an info message. I'm surprised to learn one can't skip tests in rust, it sounds like a very basic functionality. I'd say let's drop them for now then.
A #[test] can be unconditionally ignored with #[ignore], but they can still be run if requested via --ignored or --include-ignored.
They are still always built, though, which sometimes is not what one wants. But if possible, it is good to keep them that way, since that guarantees they remain buildable.
Cheers,
Miguel
PATCH v7 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 10 lines · Viresh Kumar
Here is another version of rust bindings for libgpiod v2.0, based of the next/libgpiod-2.0.
Pushed here:
https://github.com/vireshk/libgpiod v7
[I have pushed v6 and v5 there too, in case someone wants to look at the changes].
Fixing Wedson's address (the old one is not valid anymore, which is why I guess he got dropped at some point in other threads) and Cc'ing others in case they want to take a look.
Cheers,
Miguel
Quoted 11 lines · Miguel Ojeda
Quoted 9 lines · On Fri, Oct 14, 2022 at 12:47 PM Viresh Kumar [email protected] wrote:
Here is another version of rust bindings for libgpiod v2.0, based of the next/libgpiod-2.0.
Pushed here:
https://github.com/vireshk/libgpiod v7
[I have pushed v6 and v5 there too, in case someone wants to look at the changes].
It looks pretty good to me. I do have a couple of minor suggestions though.
https://github.com/vireshk/libgpiod/blob/3e7fb99173856a3995360fc3fad51220c4b4e90e/bindings/rust/libgpiod/Cargo.toml#L10 should use 0.2.39 without >= to ensure that publishing a 0.3 version of libc with breaking changes won't break the build. Cargo treats "0.2.39" as ">=0.2.39, <0.3".
At https://github.com/vireshk/libgpiod/blob/3e7fb99173856a3995360fc3fad51220c4b4e90e/bindings/rust/libgpiod/Cargo.toml#L13 the vmm-sys-utils dependency should be unpinned from "=0.10.0" to "0.10.0". Otherwise having any crate depend on a newer semver compatible version will cause a build error. While Cargo allows multiple semver incompatible versions of a crate, it doesn't allow multiple semver compatible versions as it wouldn't know which version to use for a crate that says it works with both versions.
At https://github.com/vireshk/libgpiod/blob/3e7fb99173856a3995360fc3fad51220c4b4e90e/bindings/rust/libgpiod/src/lib.rs#L469 and elsewhere you might want to use CStr::from_ptr(version). This does the strlen call for you and you can convert it to an &str using .to_str().
At https://github.com/vireshk/libgpiod/blob/3e7fb99173856a3995360fc3fad51220c4b4e90e/bindings/rust/libgpiod/src/chip.rs#L171 you could use CString and use the .as_ptr() method to get a null-terminated string. Not sure if it would be nicer that what you currently have though.
At https://github.com/vireshk/libgpiod/blob/3e7fb99173856a3995360fc3fad51220c4b4e90e/bindings/rust/libgpiod/src/edge_event.rs#L46 the lifetimes are unclear. Is Event allowed to outlive the buffer? Can you add a lifetime annotation like fn event_clone<'a>(event: &Event<'a>) -> Result<Event<'a>> if it isn't allowed to outlive the buffer or fn event_clone<'a, 'b>(event: &Event<'a>) -> Result<Event<'b>> if it is allowed to outlive the buffer. I'm not sure which of the two the lifetime elision rules cause the current code to be equivalent of, but even if it is correct, explicitly stating the lifetime here is clearer IMHO.
As for the question about test skipping elsewhere in this thread, rust has the #[ignore] attribute to ignore tests at compile time (with a command line flag to run ignored tests anyway), but nothing to ignore tests at runtime unfortunately.
Cheers,
Björn
nb: I can't reply to a mail I didn't receive directly right now due to mail provider limitations. I'm working on sorting this out, but for now I'm going to reply to the mail I did receive directly.
Hi Björn,
I have bounced (mutt's feature) the initial emails to your and other email ids that Miguel added. The patches should be in your inbox now.
Quoted 11 lines · Björn Roy Baron
At
https://github.com/vireshk/libgpiod/blob/3e7fb99173856a3995360fc3fad51220c4b4e90e/bindings/rust/libgpiod/src/lib.rs#L469
and elsewhere you might want to useCStr::from_ptr(version). This does thestrlencall for you and you can convert it to an&strusing.to_str().At
https://github.com/vireshk/libgpiod/blob/3e7fb99173856a3995360fc3fad51220c4b4e90e/bindings/rust/libgpiod/src/chip.rs#L171
you could useCStringand use the.as_ptr()method to get a null-terminated string. Not sure if it would be nicer that what you currently have though.
These two were nice. Thanks.
Quoted 10 lines
At
https://github.com/vireshk/libgpiod/blob/3e7fb99173856a3995360fc3fad51220c4b4e90e/bindings/rust/libgpiod/src/edge_event.rs#L46
the lifetimes are unclear. Is Event allowed to outlive the buffer? Can you add a lifetime annotation like fn event_clone<'a>(event:
&Event<'a>) -> Result<Event<'a>> if it isn't allowed to outlive the buffer or fn event_clone<'a, 'b>(event: &Event<'a>) -> Result<Event<'b>> if it is allowed to outlive the buffer. I'm not sure which of the two the lifetime elision rules cause the current code to be equivalent of, but even if it is correct, explicitly stating the lifetime here is clearer IMHO.
An Event created using Event::new() can't outlive the buffer, though an Event created using Event::event_clone() can.
I tried to play around it based on your suggestion and here is the diff, does it make sense ?
bindings/rust/libgpiod/src/edge_event.rs +22 -18
@@ -33,7 +33,7 @@ pub struct Event<'b> {impl <'b>Event <'b> {/// Get an event stored in the buffer. -pub (crate )fn new (buffer: &'bBuffer , index:usize ) ->Result <Self > { +pub (crate )fn new (buffer: &'bBuffer , index:usize ) ->Result <Event <'b>> {// SAFETY: The `gpiod_edge_event` returned by libgpiod is guaranteed to live as long // as the `struct Event`. let event =unsafe {
@@ -52,22 +52,6 @@ impl<'b> Event<'b> { }) } -pub fn event_clone (event: &Event ) ->Result <Self > { -// SAFETY: `gpiod_edge_event` is guaranteed to be valid here. -let event =unsafe { gpiod::gpiod_edge_event_copy (event.event) }; -if event.is_null () { -return Err (Error ::OperationFailed ( -OperationType ::EdgeEventCopy , -Errno ::last (), - )); - } - -Ok (Self { - buffer:None , - event, - }) - } -/// Get the event type. pub fn event_type (&self) ->Result <EdgeKind > {// SAFETY: `gpiod_edge_event` is guaranteed to be valid here.
@@ -105,6 +89,27 @@ impl<'b> Event<'b> { } } +impl <'e, 'b>Event <'e> { +pub fn event_clone (event: &Event <'b>) ->Result <Event <'e>> +where + 'e: 'b, + { +// SAFETY: `gpiod_edge_event` is guaranteed to be valid here. +let event =unsafe { gpiod::gpiod_edge_event_copy (event.event) }; +if event.is_null () { +return Err (Error ::OperationFailed ( +OperationType ::EdgeEventCopy , +Errno ::last (), + )); + } + +Ok (Self { + buffer:None , + event, + }) + } +} + --
viresh
Quoted 4 lines · Viresh Kumar
Hi Björn,
I have bounced (mutt's feature) the initial emails to your and other email ids that Miguel added. The patches should be in your inbox now.
Thanks! I receive the patches.
Quoted 41 lines · Björn Roy Baron
Quoted 7 lines
At
https://github.com/vireshk/libgpiod/blob/3e7fb99173856a3995360fc3fad51220c4b4e90e/bindings/rust/libgpiod/src/lib.rs#L469
and elsewhere you might want to useCStr::from_ptr(version). This does thestrlencall for you and you can convert it to an&strusing.to_str().Quoted 5 lines
At
https://github.com/vireshk/libgpiod/blob/3e7fb99173856a3995360fc3fad51220c4b4e90e/bindings/rust/libgpiod/src/chip.rs#L171
you could useCStringand use the.as_ptr()method to get a null-terminated string. Not sure if it would be nicer that what you currently have though.These two were nice. Thanks.
Quoted 10 lines
At
https://github.com/vireshk/libgpiod/blob/3e7fb99173856a3995360fc3fad51220c4b4e90e/bindings/rust/libgpiod/src/edge_event.rs#L46
the lifetimes are unclear. Is Event allowed to outlive the buffer? Can you add a lifetime annotation like fn event_clone<'a>(event:
&Event<'a>) -> Result<Event<'a>> if it isn't allowed to outlive the buffer or fn event_clone<'a, 'b>(event: &Event<'a>) -> Result<Event<'b>> if it is allowed to outlive the buffer. I'm not sure which of the two the lifetime elision rules cause the current code to be equivalent of, but even if it is correct, explicitly stating the lifetime here is clearer IMHO.An Event created using
Event::new()can't outlive the buffer, though an Event created usingEvent::event_clone()can.I tried to play around it based on your suggestion and here is the diff, does it make sense ?
bindings/rust/libgpiod/src/edge_event.rs +0
-0
bindings/rust/libgpiod/src/edge_event.rs pub struct Event<'b> { · line 39
Quoted 8 lines
bindings/rust/libgpiod/src/edge_event.rs +1
-1@@ -33,7 +33,7 @@ pub struct Event<'b> {impl <'b>Event <'b> {/// Get an event stored in the buffer. -pub (crate )fn new (buffer: &'bBuffer , index:usize ) ->Result <Self > { +pub (crate )fn new (buffer: &'bBuffer , index:usize ) ->Result <Event <'b>> {
This looks good to me.
Quoted 31 lines
// SAFETY: The
gpiod_edge_eventreturned by libgpiod is guaranteed to live as long // as thestruct Event.
let event = unsafe {bindings/rust/libgpiod/src/edge_event.rs +0
-16@@ -52,22 +52,6 @@ impl<'b> Event<'b> { }) } -pub fn event_clone (event: &Event ) ->Result <Self > { -// SAFETY: `gpiod_edge_event` is guaranteed to be valid here. -let event =unsafe { gpiod::gpiod_edge_event_copy (event.event) }; -if event.is_null () { -return Err (Error ::OperationFailed ( -OperationType ::EdgeEventCopy , -Errno ::last (), - )); - } - -Ok (Self { - buffer:None , - event, - }) - } -/// Get the event type. pub fn
event_type(&self)-> Result<EdgeKind> {// SAFETY:
gpiod_edge_eventis guaranteed to be valid here.
bindings/rust/libgpiod/src/edge_event.rs impl<'b> Event<'b> { · line 98
Quoted 10 lines
bindings/rust/libgpiod/src/edge_event.rs +4
-0@@ -105,6 +89,27 @@ impl<'b> Event<'b> { } } +impl <'e, 'b>Event <'e> { +pub fn event_clone (event: &Event <'b>) ->Result <Event <'e>> +where + 'e: 'b,
Using Event<'b> on both sides should work fine. Event is covariant in it's lifetime parameter, so Event<'b> can be turned into Event<'e> with 'e being a shorter lifetime than 'b. What you wrote here is not incorrect, so if you prefer keeping it this way that is fine with me.
Quoted 19 lines
bindings/rust/libgpiod/src/edge_event.rs +17
-1+ { +// SAFETY: `gpiod_edge_event` is guaranteed to be valid here. +let event =unsafe { gpiod::gpiod_edge_event_copy (event.event) }; +if event.is_null () { +return Err (Error ::OperationFailed ( +OperationType ::EdgeEventCopy , +Errno ::last (), + )); + } + +Ok (Self { + buffer:None , + event, + }) + } +} + --
Quoted 1 line
viresh
Cheers,
Björn
Quoted 13 lines · Björn Roy Baron
Quoted 6 lines
impl<'b> Event<'b> {
/// Get an event stored in the buffer.
- pub(crate) fnnew(buffer: &'b Buffer, index: usize)-> Result<Self> {+ pub(crate) fn
new(buffer: &'b Buffer, index: usize)-> Result<Event<'b>> {This looks good to me.
Quoted 4 lines
+impl<'e, 'b> Event<'e> {
+ pub fn
event_clone(event: &Event<'b>)-> Result<Event<'e>>
Quoted 8 lines
Quoted 2 lines
+ where
+ 'e: 'b,Using
Event<'b>on both sides should work fine.Eventis covariant in it's lifetime parameter, soEvent<'b>can be turned intoEvent<'e>with'ebeing a shorter lifetime than'b. What you wrote here is not incorrect, so if you prefer keeping it this way that is fine with me.
That doesn't let the cloned event to live past read_edge_events().
error[E0502]: cannot borrow buffer as mutable because it is also borrowed as immutable
--> libgpiod/examples/gpio_events.rs:70:50
| 64 | let event = buffer.event(0)?; | --------------- immutable borrow occurs here
...
70 | let count = request.read_edge_events(&mut buffer)?;
| ^^^^^^^^^^^ mutable borrow occurs here
...
86 | } | - immutable borrow might be used here, when `cloned_event` is dropped and runs the `Drop` code for type `libgpiod::request::Event`
Signature · 1 line
viresh
Quoted 40 lines · Viresh Kumar
Quoted 13 lines · Björn Roy Baron
Quoted 6 lines
impl<'b> Event<'b> {
/// Get an event stored in the buffer.
- pub(crate) fnnew(buffer: &'b Buffer, index: usize)-> Result<Self> {+ pub(crate) fn
new(buffer: &'b Buffer, index: usize)-> Result<Event<'b>> {This looks good to me.
Quoted 4 lines
+impl<'e, 'b> Event<'e> {
+ pub fn
event_clone(event: &Event<'b>)-> Result<Event<'e>>Quoted 8 lines
Quoted 2 lines
+ where
+ 'e: 'b,Using
Event<'b>on both sides should work fine.Eventis covariant in it's lifetime parameter, soEvent<'b>can be turned intoEvent<'e>with'ebeing a shorter lifetime than'b. What you wrote here is not incorrect, so if you prefer keeping it this way that is fine with me.That doesn't let the cloned event to live past
read_edge_events().error[E0502]: cannot borrow
bufferas mutable because it is also borrowed as immutable -->libgpiod/examples/gpio_events.rs:70:50|
64 | let event =buffer.event(0)?;
| --------------- immutable borrow occurs here
...
70 | let count =request.read_edge_events(&mut buffer)?;
| ^^^^^^^^^^^ mutable borrow occurs here
...
86 | }
| - immutable borrow might be used here, whencloned_eventis dropped and runs theDropcode for typelibgpiod::request::Event
Quoted 2 lines
--
viresh
I would have expected that to work fine, but as it doesn't work keeping your code with two separate lifetimes is fine.
Cheers,
Bjorn