Skip to contentSkip to conversation

libgpiod: Add Rust bindings

PATCH Superseded Viresh Kumar 4 participants 66 messages Jul 8, 2022 – Aug 2, 2022 GPIO

v4 of 10 Superseded Latest is v10 →

lore
Share & tools
lore
#0 lore raw

v4 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 master

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: Add pre generated rust bindings
libgpiod-sys: Add support to generate gpiosim bindings
libgpiod: Add rust wrapper crate
libgpiod: Add rust examples
libgpiod: Derive debug traits for few definitions
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                     |   15 +
 bindings/rust/Makefile.am                    |   18 +
 bindings/rust/examples/gpiodetect.rs         |   37 +
 bindings/rust/examples/gpiofind.rs           |   42 +
 bindings/rust/examples/gpioget.rs            |   42 +
 bindings/rust/examples/gpioinfo.rs           |   89 +
 bindings/rust/examples/gpiomon.rs            |   68 +
 bindings/rust/examples/gpioset.rs            |   52 +
 bindings/rust/libgpiod-sys/Cargo.toml        |   16 +
 bindings/rust/libgpiod-sys/README.md         |   10 +
 bindings/rust/libgpiod-sys/build.rs          |   84 +
 bindings/rust/libgpiod-sys/gpiosim_wrapper.h |    1 +
 bindings/rust/libgpiod-sys/src/bindings.rs   | 1920 ++++++++++++++++++
 bindings/rust/libgpiod-sys/src/lib.rs        |   20 +
 bindings/rust/libgpiod-sys/wrapper.h         |    2 +
 bindings/rust/src/chip.rs                    |  184 ++
 bindings/rust/src/chip_info.rs               |   70 +
 bindings/rust/src/edge_event.rs              |  105 +
 bindings/rust/src/event_buffer.rs            |   88 +
 bindings/rust/src/info_event.rs              |   68 +
 bindings/rust/src/lib.rs                     |  323 +++
 bindings/rust/src/line_config.rs             |  493 +++++
 bindings/rust/src/line_info.rs               |  190 ++
 bindings/rust/src/line_request.rs            |  249 +++
 bindings/rust/src/request_config.rs          |  122 ++
 bindings/rust/tests/chip.rs                  |   96 +
 bindings/rust/tests/common/config.rs         |  117 ++
 bindings/rust/tests/common/mod.rs            |   16 +
 bindings/rust/tests/common/sim.rs            |  306 +++
 bindings/rust/tests/edge_event.rs            |  389 ++++
 bindings/rust/tests/info_event.rs            |  126 ++
 bindings/rust/tests/line_config.rs           |  187 ++
 bindings/rust/tests/line_info.rs             |   90 +
 bindings/rust/tests/line_request.rs          |  234 +++
 bindings/rust/tests/request_config.rs        |   42 +
 configure.ac                                 |   16 +
 40 files changed, 5943 insertions(+), 11 deletions(-)
 create mode 100644 bindings/rust/Cargo.toml
 create mode 100644 bindings/rust/Makefile.am
 create mode 100644 bindings/rust/examples/gpiodetect.rs
 create mode 100644 bindings/rust/examples/gpiofind.rs
 create mode 100644 bindings/rust/examples/gpioget.rs
 create mode 100644 bindings/rust/examples/gpioinfo.rs
 create mode 100644 bindings/rust/examples/gpiomon.rs
 create mode 100644 bindings/rust/examples/gpioset.rs
 create mode 100644 bindings/rust/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/gpiosim_wrapper.h
 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-sys/wrapper.h
 create mode 100644 bindings/rust/src/chip.rs
 create mode 100644 bindings/rust/src/chip_info.rs
 create mode 100644 bindings/rust/src/edge_event.rs
 create mode 100644 bindings/rust/src/event_buffer.rs
 create mode 100644 bindings/rust/src/info_event.rs
 create mode 100644 bindings/rust/src/lib.rs
 create mode 100644 bindings/rust/src/line_config.rs
 create mode 100644 bindings/rust/src/line_info.rs
 create mode 100644 bindings/rust/src/line_request.rs
 create mode 100644 bindings/rust/src/request_config.rs
 create mode 100644 bindings/rust/tests/chip.rs
 create mode 100644 bindings/rust/tests/common/config.rs
 create mode 100644 bindings/rust/tests/common/mod.rs
 create mode 100644 bindings/rust/tests/common/sim.rs
 create mode 100644 bindings/rust/tests/edge_event.rs
 create mode 100644 bindings/rust/tests/info_event.rs
 create mode 100644 bindings/rust/tests/line_config.rs
 create mode 100644 bindings/rust/tests/line_info.rs
 create mode 100644 bindings/rust/tests/line_request.rs
 create mode 100644 bindings/rust/tests/request_config.rs
Signature · 1 line
2.31.1.272.g89b43f80a514
#1 lore raw

PATCH v4 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/libgpiod-sys/Cargo.toml | 15 ++++++
 bindings/rust/libgpiod-sys/build.rs   | 69 +++++++++++++++++++++++++++
 bindings/rust/libgpiod-sys/src/lib.rs | 20 ++++++++
 bindings/rust/libgpiod-sys/wrapper.h  |  2 +
 5 files changed, 111 insertions(+)
 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
 create mode 100644 bindings/rust/libgpiod-sys/wrapper.h
.gitignore +5 -0
index 58e1c5fc7e00..9541482d5efb 100644
@@ -33,3 +33,8 @@ stamp-h1
 # profiling
 *.gcda
 *.gcno
+
+# Added by cargo
+
+target
+Cargo.lock
bindings/rust/libgpiod-sys/Cargo.toml new file +15 -0
new file mode 100644
index 000000000000..77f82719d269
@@ -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 +69 -0

69 lines not on this page · raw message

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

Re: [PATCH V4 1/8] libgpiod: Add libgpiod-sys rust crate

Quoted 4 lines · Viresh Kumar

This adds libgpiod-sys rust crate, which provides FFI (foreign function interface) bindings for libgpiod APIs.

Just a quick qualifier before we get started - I'm relatively new to Rust and this the first Rust code I've reviewed, so my opinions may not reflect current idiomatic Rust or may even be complete rubbish.

Quoted 111 lines
---
 .gitignore                            |  5 ++
 bindings/rust/libgpiod-sys/Cargo.toml | 15 ++++++
 bindings/rust/libgpiod-sys/build.rs   | 69 +++++++++++++++++++++++++++
 bindings/rust/libgpiod-sys/src/lib.rs | 20 ++++++++
 bindings/rust/libgpiod-sys/wrapper.h  |  2 +
 5 files changed, 111 insertions(+)
 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
 create mode 100644 bindings/rust/libgpiod-sys/wrapper.h
.gitignore +5 -0
index 58e1c5fc7e00..9541482d5efb 100644
@@ -33,3 +33,8 @@ stamp-h1
 # profiling
 *.gcda
 *.gcno
+
+# Added by cargo
+
+target
+Cargo.lock
bindings/rust/libgpiod-sys/Cargo.toml new file +15 -0
new file mode 100644
index 000000000000..77f82719d269
@@ -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 +59 -0
new file mode 100644
index 000000000000..bbcd30f79d23
@@ -0,0 +1,69 @@
+#[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(files: &Vec<&str>) {
+    // Tell cargo to invalidate the built crate whenever following files change
+    println!("cargo:rerun-if-changed=wrapper.h");
+
+    for file in files {
+        println!("cargo:rerun-if-changed={}", file);
+    }
+
+    // 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("wrapper.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 build_gpiod(files: Vec<&str>) {
+    // Tell Cargo that if the given file changes, to rerun this build script.
+    println!("cargo:rerun-if-changed=../../../lib/");
+
+    // Use the `cc` crate to build a C file and statically link it.
+    cc::Build::new()
+        .files(files)
+        .define("_GNU_SOURCE", None)
+        .define("GPIOD_VERSION_STR", "\"libgpio-sys\"")
+        .include("../../../include")
+        .compile("gpiod");
+}
+
+fn main() {
+    let files = vec![
+        "../../../lib/chip.c",
+        "../../../lib/chip-info.c",
+        "../../../lib/edge-event.c",
+        "../../../lib/info-event.c",
+        "../../../lib/internal.c",
+        "../../../lib/line-config.c",

bindings/rust/libgpiod-sys/build.rs line 69

Quoted 10 lines
bindings/rust/libgpiod-sys/build.rs new file +10 -0
+        "../../../lib/line-info.c",
+        "../../../lib/line-request.c",
+        "../../../lib/misc.c",
+        "../../../lib/request-config.c",
+    ];
+
+    #[cfg(feature = "generate")]
+    generate_bindings(&files);
+    build_gpiod(files);
+}

Shouldn't bindings wrap libgpiod and dynamically link against it rather than building and linking statically?

Quoted 8 lines
bindings/rust/libgpiod-sys/src/lib.rs new file +2 -0
new file mode 100644
index 000000000000..3384863a567c
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-2.0
+

bindings/rust/libgpiod-sys/src/lib.rs line 12

Quoted 10 lines
bindings/rust/libgpiod-sys/src/lib.rs new file +10 -0
+#[allow(
+    clippy::all,
+    deref_nullptr,
+    dead_code,
+    non_camel_case_types,
+    non_upper_case_globals,
+    non_snake_case,
+    improper_ctypes
+)]
+

Are all these really necessary?
Builds mostly clean for me with just:

+ non_camel_case_types,
+ non_upper_case_globals,

Both non_snake_case and deref_nullptr are only required for tests.

The deref_nullptr masks several warnings like this:

warning: dereferencing a null pointer
--> src/bindings.rs:121:14

    |
121 |             &(*(::std::ptr::null::<max_align_t>())).__clang_max_align_nonce1 as *const _ as usize
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this code causes undefined behavior when executed
    |

= note: #[warn(deref_nullptr)] on by default

which is code generated by bindgen, which is a bit of a worry. It is only used for alignment tests, but you'd think they would disable the warning just around that code themselves.

Disabling deref_nullptr globally for all builds is at best poor form. Perhaps only disable it for test builds, i.e.

#[cfg_attr(test, allow(deref_nullptr, non_snake_case))]

Quoted 14 lines
bindings/rust/libgpiod-sys/src/lib.rs new file +8 -0
+mod bindings_raw {
+    #[cfg(feature = "generate")]
+    include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
+
+    #[cfg(not(feature = "generate"))]
+    include!("bindings.rs");
+}
+pub use bindings_raw::*;
bindings/rust/libgpiod-sys/wrapper.h new file +0 -0
new file mode 100644
index 000000000000..7bc1158b7d90

bindings/rust/libgpiod-sys/wrapper.h line 2

Quoted 2 lines
bindings/rust/libgpiod-sys/wrapper.h new file +2 -0
@@ -0,0 +1,2 @@
+#include <string.h>
+#include "../../../include/gpiod.h"

The string.h is just to provide strlen() for the wrapper crate?? (but also pulls in the other string functions)
The wrapper crate already depends on libc - why not use libc::strlen() there and drop this include here?

And then wrapper.h becomes redundant - call bindgen on gpiod.h directly.

Cheers,
Kent.

#3 lore raw

Re: [PATCH V4 1/8] libgpiod: Add libgpiod-sys rust crate

Quoted 14 lines · Kent Gibson
Quoted 13 lines · Viresh Kumar
bindings/rust/libgpiod-sys/build.rs new file +13 -0
+fn main() {
+    let files = vec![
+        "../../../lib/chip.c",
+        "../../../lib/chip-info.c",
+        "../../../lib/edge-event.c",
+        "../../../lib/info-event.c",
+        "../../../lib/internal.c",
+        "../../../lib/line-config.c",
+        "../../../lib/line-info.c",
+        "../../../lib/line-request.c",
+        "../../../lib/misc.c",
+        "../../../lib/request-config.c",
+    ];
Quoted 8 lines

bindings/rust/libgpiod-sys/build.rs line 69

Quoted 5 lines
bindings/rust/libgpiod-sys/build.rs new file +5 -0
+
+    #[cfg(feature = "generate")]
+    generate_bindings(&files);
+    build_gpiod(files);
+}

Shouldn't bindings wrap libgpiod and dynamically link against it rather than building and linking statically?

There are few problems I faced, because of which I had to do it this way.

- I couldn't find a way to do a "Make" for libgpiod from here and then link to the resultant library.

- libgpiod may not be automatically installed in the environment where the end user of these Rust APIs exists. So I had to build it.

- And then the API is changing a lot, maybe down the line once it is stable enough we can change this to something else.

Quoted 12 lines
Quoted 12 lines
bindings/rust/libgpiod-sys/src/lib.rs new file +6 -0
new file mode 100644
index 000000000000..3384863a567c
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#[allow(
+    clippy::all,
+    deref_nullptr,
+    dead_code,
Quoted 8 lines

bindings/rust/libgpiod-sys/src/lib.rs line 12

Quoted 6 lines
bindings/rust/libgpiod-sys/src/lib.rs new file +6 -0
+    non_camel_case_types,
+    non_upper_case_globals,
+    non_snake_case,
+    improper_ctypes
+)]
+

Are all these really necessary?

Actually not, thanks for pointing this out.

Quoted 6 lines

Builds mostly clean for me with just:

+ non_camel_case_types,
+ non_upper_case_globals,

Both non_snake_case and deref_nullptr are only required for tests.

and if you want to run sanity checks with "fmt" or "clippy".

Quoted 8 lines

The deref_nullptr masks several warnings like this:

warning: dereferencing a null pointer
--> src/bindings.rs:121:14

    |
121 |             &(*(::std::ptr::null::<max_align_t>())).__clang_max_align_nonce1 as *const _ as usize
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this code causes undefined behavior when executed
    |
Quoted 7 lines

= note: #[warn(deref_nullptr)] on by default

which is code generated by bindgen, which is a bit of a worry. It is only used for alignment tests, but you'd think they would disable the warning just around that code themselves.

Disabling deref_nullptr globally for all builds is at best poor form.

Even with this these will get disabled only for the code present in libgpiod-sys crate, file bindgen.rs (the automatically generated one). This won't cause the warnings to be skipped for the libgpiod rust wrappers in the libgpiod crate.

Quoted 3 lines

Perhaps only disable it for test builds, i.e.

#[cfg_attr(test, allow(deref_nullptr, non_snake_case))]

I also run following normally:

cargo fmt --all -- --check; cargo clippy --workspace --bins --examples --benches --all-features -- -D warnings

to do sanity checks, etc. And this will also generate warnings, not just tests.

Quoted 13 lines
Quoted 13 lines
bindings/rust/libgpiod-sys/src/lib.rs new file +8 -0
+mod bindings_raw {
+    #[cfg(feature = "generate")]
+    include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
+
+    #[cfg(not(feature = "generate"))]
+    include!("bindings.rs");
+}
+pub use bindings_raw::*;
bindings/rust/libgpiod-sys/wrapper.h new file +0 -0
new file mode 100644
index 000000000000..7bc1158b7d90
Quoted 8 lines

bindings/rust/libgpiod-sys/wrapper.h line 2

Quoted 3 lines
bindings/rust/libgpiod-sys/wrapper.h new file +2 -0
@@ -0,0 +1,2 @@
+#include <string.h>
+#include "../../../include/gpiod.h"

The string.h is just to provide strlen() for the wrapper crate?? (but also pulls in the other string functions)
The wrapper crate already depends on libc - why not use libc::strlen() there and drop this include here?

Right, done.

Quoted 1 line

And then wrapper.h becomes redundant - call bindgen on gpiod.h directly.

Rust documentation specifically suggests wrapper.h to be created [1], maybe it it is better to just keep it, even if we have a single entry in there.

Signature · 3 lines
viresh

[1] https://rust-lang.github.io/rust-bindgen/tutorial-2.html
#4 lore raw

Re: [PATCH V4 1/8] libgpiod: Add libgpiod-sys rust crate

Quoted 26 lines · Viresh Kumar
Quoted 14 lines · Kent Gibson
Quoted 13 lines · Viresh Kumar
bindings/rust/libgpiod-sys/build.rs new file +13 -0
+fn main() {
+    let files = vec![
+        "../../../lib/chip.c",
+        "../../../lib/chip-info.c",
+        "../../../lib/edge-event.c",
+        "../../../lib/info-event.c",
+        "../../../lib/internal.c",
+        "../../../lib/line-config.c",
+        "../../../lib/line-info.c",
+        "../../../lib/line-request.c",
+        "../../../lib/misc.c",
+        "../../../lib/request-config.c",
+    ];
Quoted 8 lines

bindings/rust/libgpiod-sys/build.rs line 69

Quoted 5 lines
bindings/rust/libgpiod-sys/build.rs new file +5 -0
+
+    #[cfg(feature = "generate")]
+    generate_bindings(&files);
+    build_gpiod(files);
+}

Shouldn't bindings wrap libgpiod and dynamically link against it rather than building and linking statically?

There are few problems I faced, because of which I had to do it this way.

Quoted 9 lines

- I couldn't find a way to do a "Make" for libgpiod from here and then link to the resultant library.

- libgpiod may not be automatically installed in the environment where the end user of these Rust APIs exists. So I had to build it.

- And then the API is changing a lot, maybe down the line once it is stable enough we can change this to something else.

Sure, it is a problem, but static isn't the solution.
You should be able to get the appropriate paths from autoconf, but I would refer you to Bart on that.

Wrt, "API changing a lot", autoconf make build dependencies should sort that out for you.

Quoted 46 lines
Quoted 12 lines
Quoted 12 lines
bindings/rust/libgpiod-sys/src/lib.rs new file +6 -0
new file mode 100644
index 000000000000..3384863a567c
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#[allow(
+    clippy::all,
+    deref_nullptr,
+    dead_code,
Quoted 8 lines

bindings/rust/libgpiod-sys/src/lib.rs line 12

Quoted 6 lines
bindings/rust/libgpiod-sys/src/lib.rs new file +6 -0
+    non_camel_case_types,
+    non_upper_case_globals,
+    non_snake_case,
+    improper_ctypes
+)]
+

Are all these really necessary?

Actually not, thanks for pointing this out.

Quoted 6 lines

Builds mostly clean for me with just:

+ non_camel_case_types,
+ non_upper_case_globals,

Both non_snake_case and deref_nullptr are only required for tests.

and if you want to run sanity checks with "fmt" or "clippy".

Quoted 14 lines

The deref_nullptr masks several warnings like this:

warning: dereferencing a null pointer
--> src/bindings.rs:121:14

    |
121 |             &(*(::std::ptr::null::<max_align_t>())).__clang_max_align_nonce1 as *const _ as usize
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this code causes undefined behavior when executed
    |

= note: #[warn(deref_nullptr)] on by default

which is code generated by bindgen, which is a bit of a worry. It is only used for alignment tests, but you'd think they would disable the warning just around that code themselves.

Quoted 6 lines
Quoted 1 line

Disabling deref_nullptr globally for all builds is at best poor form.

Even with this these will get disabled only for the code present in libgpiod-sys crate, file bindgen.rs (the automatically generated one). This won't cause the warnings to be skipped for the libgpiod rust wrappers in the libgpiod crate.

By "all builds" I meant build/tests/fmt/clippy etc of this module, not others.

My concern being that a subsequent bindgen may introduce a problem into the generated code that the allows would hide. So try to keep them restricted to the problem at hand as much as possible.

Quoted 10 lines
Quoted 3 lines

Perhaps only disable it for test builds, i.e.

#[cfg_attr(test, allow(deref_nullptr, non_snake_case))]

I also run following normally:

cargo fmt --all -- --check; cargo clippy --workspace --bins --examples --benches --all-features -- -D warnings

to do sanity checks, etc. And this will also generate warnings, not just tests.

So
#[cfg_attr(any(test,fmt,clippy), allow(deref_nullptr, non_snake_case))] ?

Quoted 22 lines
Quoted 13 lines
Quoted 13 lines
bindings/rust/libgpiod-sys/src/lib.rs new file +8 -0
+mod bindings_raw {
+    #[cfg(feature = "generate")]
+    include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
+
+    #[cfg(not(feature = "generate"))]
+    include!("bindings.rs");
+}
+pub use bindings_raw::*;
bindings/rust/libgpiod-sys/wrapper.h new file +0 -0
new file mode 100644
index 000000000000..7bc1158b7d90
Quoted 8 lines

bindings/rust/libgpiod-sys/wrapper.h line 2

Quoted 3 lines
bindings/rust/libgpiod-sys/wrapper.h new file +2 -0
@@ -0,0 +1,2 @@
+#include <string.h>
+#include "../../../include/gpiod.h"

The string.h is just to provide strlen() for the wrapper crate?? (but also pulls in the other string functions)
The wrapper crate already depends on libc - why not use libc::strlen() there and drop this include here?

Quoted 7 lines

Right, done.

Quoted 1 line

And then wrapper.h becomes redundant - call bindgen on gpiod.h directly.

Rust documentation specifically suggests wrapper.h to be created [1], maybe it it is better to just keep it, even if we have a single entry in there.

Specifically the tutoral says:
"The wrapper.h file will include all the various headers containing declarations of structs and functions we would like bindings for."

If you do need to bundle several headers then fair enough, but I don't see any benefit in this case - gpiod.h contains all that.

The tutorial is probably written that way so it is easy for them to refer to the general "wrapper.h", but there is nothing in bindgen that requires it.

Cheers,
Kent.

#5 lore raw

Re: [PATCH V4 1/8] libgpiod: Add libgpiod-sys rust crate

Quoted 3 lines · Kent Gibson

Sure, it is a problem, but static isn't the solution.
You should be able to get the appropriate paths from autoconf, but I would refer you to Bart on that.

Sure, if someone can suggest a better way of doing this, I am up for it. I just don't know how to do it as of now.

Quoted 2 lines

By "all builds" I meant build/tests/fmt/clippy etc of this module, not others.

Ahh.

Quoted 3 lines

My concern being that a subsequent bindgen may introduce a problem into the generated code that the allows would hide. So try to keep them restricted to the problem at hand as much as possible.

I agree.

Quoted 2 lines

#[cfg_attr(any(test,fmt,clippy), allow(deref_nullptr, non_snake_case))] ?

Finally just this was enough for fmt/clippy too :)

#[cfg_attr(test, allow(deref_nullptr, non_snake_case))]

Quoted 10 lines

Specifically the tutoral says:
"The wrapper.h file will include all the various headers containing declarations of structs and functions we would like bindings for."

If you do need to bundle several headers then fair enough, but I don't see any benefit in this case - gpiod.h contains all that.

The tutorial is probably written that way so it is easy for them to refer to the general "wrapper.h", but there is nothing in bindgen that requires it.

Sure nothing will break if the file isn't there. Okay removed it now:

bindings/rust/libgpiod-sys/build.rs +2 -2
index 147daaf6b1da..96f832134431 100644
@@ -8,7 +8,7 @@ use std::path::PathBuf;
 #[cfg(feature = "generate")]
 fn generate_bindings(files: &Vec<&str>) {
     // Tell cargo to invalidate the built crate whenever following files change
-    println!("cargo:rerun-if-changed=wrapper.h");
+    println!("cargo:rerun-if-changed=../../../include/gpiod.h");
 
     for file in files {
         println!("cargo:rerun-if-changed={}", file);
@@ -24,7 +24,7 @@ fn generate_bindings(files: &Vec<&str>) {
     let mut builder = bindgen::Builder::default()
         // The input header we would like to generate
         // bindings for.
-        .header("wrapper.h");
+        .header("../../../include/gpiod.h");
 
     if cfg!(feature = "gpiosim") {
         builder = builder.header("gpiosim_wrapper.h");
bindings/rust/libgpiod-sys/wrapper.h deleted +0 -1
deleted file mode 100644
index 32290711642a..000000000000
@@ -1 +0,0 @@
-#include "../../../include/gpiod.h"
Signature · 1 line
viresh
#6 lore raw

Re: [PATCH V4 1/8] libgpiod: Add libgpiod-sys rust crate

In reply to Kent Gibson
Quoted 33 lines · Kent Gibson
Quoted 32 lines · Viresh Kumar
Quoted 14 lines · Kent Gibson
Quoted 13 lines · Viresh Kumar
bindings/rust/libgpiod-sys/build.rs new file +13 -0
+fn main() {
+    let files = vec![
+        "../../../lib/chip.c",
+        "../../../lib/chip-info.c",
+        "../../../lib/edge-event.c",
+        "../../../lib/info-event.c",
+        "../../../lib/internal.c",
+        "../../../lib/line-config.c",
+        "../../../lib/line-info.c",
+        "../../../lib/line-request.c",
+        "../../../lib/misc.c",
+        "../../../lib/request-config.c",
+    ];
Quoted 8 lines

bindings/rust/libgpiod-sys/build.rs line 69

Quoted 5 lines
bindings/rust/libgpiod-sys/build.rs new file +5 -0
+
+    #[cfg(feature = "generate")]
+    generate_bindings(&files);
+    build_gpiod(files);
+}

Shouldn't bindings wrap libgpiod and dynamically link against it rather than building and linking statically?

There are few problems I faced, because of which I had to do it this way.

- I couldn't find a way to do a "Make" for libgpiod from here and then link to the resultant library.

- libgpiod may not be automatically installed in the environment where the end user of these Rust APIs exists. So I had to build it.

Quoted 7 lines
Quoted 3 lines

- And then the API is changing a lot, maybe down the line once it is stable enough we can change this to something else.

Sure, it is a problem, but static isn't the solution.
You should be able to get the appropriate paths from autoconf, but I would refer you to Bart on that.

I am still looking for some help on how to link this dynamically.

FWIW, the problem is that the user crates, like vhost-device, will mention libgpiod as a dependency crate and likely won't have libgpiod installed in environment. So build.rs here needs to do some magic so the definitions are all available to the users.

Signature · 1 line
viresh
#7 lore raw

Re: [PATCH V4 1/8] libgpiod: Add libgpiod-sys rust crate

Quoted 42 lines · Viresh Kumar
Quoted 33 lines · Kent Gibson
Quoted 32 lines · Viresh Kumar
Quoted 14 lines · Kent Gibson
Quoted 13 lines · Viresh Kumar
bindings/rust/libgpiod-sys/build.rs new file +13 -0
+fn main() {
+    let files = vec![
+        "../../../lib/chip.c",
+        "../../../lib/chip-info.c",
+        "../../../lib/edge-event.c",
+        "../../../lib/info-event.c",
+        "../../../lib/internal.c",
+        "../../../lib/line-config.c",
+        "../../../lib/line-info.c",
+        "../../../lib/line-request.c",
+        "../../../lib/misc.c",
+        "../../../lib/request-config.c",
+    ];
Quoted 8 lines

bindings/rust/libgpiod-sys/build.rs line 69

Quoted 5 lines
bindings/rust/libgpiod-sys/build.rs new file +5 -0
+
+    #[cfg(feature = "generate")]
+    generate_bindings(&files);
+    build_gpiod(files);
+}

Shouldn't bindings wrap libgpiod and dynamically link against it rather than building and linking statically?

There are few problems I faced, because of which I had to do it this way.

- I couldn't find a way to do a "Make" for libgpiod from here and then link to the resultant library.

- libgpiod may not be automatically installed in the environment where the end user of these Rust APIs exists. So I had to build it.

Quoted 7 lines
Quoted 3 lines

- And then the API is changing a lot, maybe down the line once it is stable enough we can change this to something else.

Sure, it is a problem, but static isn't the solution.
You should be able to get the appropriate paths from autoconf, but I would refer you to Bart on that.

Quoted 7 lines

I am still looking for some help on how to link this dynamically.

FWIW, the problem is that the user crates, like vhost-device, will mention libgpiod as a dependency crate and likely won't have libgpiod installed in environment. So build.rs here needs to do some magic so the definitions are all available to the users.

The Rust bindings themselves should be building against the local build tree, so well known relative paths.

For users, require they have libgpiod installed and use pkg_config to locate it?

Is that what you mean?

Else, how do other Rust crates wrapping dynamic C libraries do it?

Cheers,
Kent.

#8 lore raw

Re: [PATCH V4 1/8] libgpiod: Add libgpiod-sys rust crate

Quoted 2 lines · Kent Gibson

The Rust bindings themselves should be building against the local build tree, so well known relative paths.

Right, when we build everything with "make" we better use the already built library. I agree.

Quoted 4 lines

For users, require they have libgpiod installed and use pkg_config to locate it?

Is that what you mean?

Since we need the latest APIs, we can't trust the packages provided by distributions for now. i.e. "apt-get install libgpiod-dev" won't install the latest stuff we need.

The only other option to get it working in environments like rust-vmm-containers (which tests the vhost-device crate currently) is to build / install libgpiod first. As I have understood, people don't really like it there (Maintainers of rust-vmm-containers) as this will have further dependencies and require more tools.

I even tried to generate the libgpiod-sys bindings on the fly first, but it required more tooling and there were issues with Musl build specifically. They suggested to use prebuild bindings as a solution, which I have now.

What about we have two separate features:

- "default" one will be used with "make" and will use prebuild library.

- "generate" one will be used by user crates and we will build the files there like it is done now.

Quoted 1 line

Else, how do other Rust crates wrapping dynamic C libraries do it?

I think for standard libraries that are stable, the -sys crates just contain the pre-built bindings and wrappers and expect the library to be already installed.

Signature · 1 line
viresh
#9 lore raw

Re: [PATCH V4 1/8] libgpiod: Add libgpiod-sys rust crate

Quoted 7 lines · Viresh Kumar
Quoted 2 lines · Kent Gibson

The Rust bindings themselves should be building against the local build tree, so well known relative paths.

Right, when we build everything with "make" we better use the already built library. I agree.

Quoted 9 lines
Quoted 4 lines

For users, require they have libgpiod installed and use pkg_config to locate it?

Is that what you mean?

Since we need the latest APIs, we can't trust the packages provided by distributions for now. i.e. "apt-get install libgpiod-dev" won't install the latest stuff we need.

For user builds with the Rust bindings you always require an appropriate version of libgpiod to be already installed, or it is the users problem to arrange that.
For the time being that means it needs to be built and installed from source. I don't see an alternative to that, or a problem with it. In the longer term it will be provided by the packaging system for the platform.

I may well be missing something, but I don't see the problem, at least nothing that requires addressing.

Cheers,
Kent.

Quoted 26 lines

The only other option to get it working in environments like rust-vmm-containers (which tests the vhost-device crate currently) is to build / install libgpiod first. As I have understood, people don't really like it there (Maintainers of rust-vmm-containers) as this will have further dependencies and require more tools.

I even tried to generate the libgpiod-sys bindings on the fly first, but it required more tooling and there were issues with Musl build specifically. They suggested to use prebuild bindings as a solution, which I have now.

What about we have two separate features:

- "default" one will be used with "make" and will use prebuild library.

- "generate" one will be used by user crates and we will build the files there like it is done now.

Quoted 1 line

Else, how do other Rust crates wrapping dynamic C libraries do it?

I think for standard libraries that are stable, the -sys crates just contain the pre-built bindings and wrappers and expect the library to be already installed.

Signature · 1 line
viresh
#10 lore raw

PATCH v4 2/8 libgpiod: Add pre generated rust bindings

In reply to Viresh Kumar

This adds a copy of pre generated bindings and adds the suggested way of updating those in README.

---
 bindings/rust/libgpiod-sys/README.md       |   10 +
 bindings/rust/libgpiod-sys/src/bindings.rs | 1920 ++++++++++++++++++++
 2 files changed, 1930 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 +10 -0
new file mode 100644
index 000000000000..ea037d6d7803
@@ -0,0 +1,10 @@
+# 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. Commit changes in `src/bindings.rs`
bindings/rust/libgpiod-sys/src/bindings.rs new file +1920 -0

1,920 lines not on this page · raw message

Signature · 1 line
2.31.1.272.g89b43f80a514
#11 lore raw

Re: [PATCH V4 2/8] libgpiod: Add pre generated rust bindings

Quoted 17 lines · Viresh Kumar

This adds a copy of pre generated bindings and adds the suggested way of updating those in README.

---
 bindings/rust/libgpiod-sys/README.md       |   10 +
 bindings/rust/libgpiod-sys/src/bindings.rs | 1920 ++++++++++++++++++++
 2 files changed, 1930 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 +0 -0
new file mode 100644
index 000000000000..ea037d6d7803

bindings/rust/libgpiod-sys/README.md line 10

Quoted 10 lines
bindings/rust/libgpiod-sys/README.md new file +10 -0
@@ -0,0 +1,10 @@
+# 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. Commit changes in `src/bindings.rs`

Those instructions do not force the regeneration of the bindings. I assume the rust build system is being clever and determining nothing has changed and short circuiting, but I would like to regnerate them to be sure. How do I do that?

How is the --features approach better than just bindgen -o src/bindings.rs ../../../include/gpiod.h ?

Why not build this all the time rather than hiding it behind a feature?

bindings/rust/libgpiod-sys/src/bindings.rs line 2

Quoted 8 lines
bindings/rust/libgpiod-sys/src/bindings.rs new file +2 -0
new file mode 100644
index 000000000000..930eb894f66f
@@ -0,0 +1,1920 @@
+/* automatically generated by rust-bindgen 0.59.2 */
+

Yet the Cargo.toml specifies 0.59.1.
So you didn't generate this with the instructions above then?

Having said that, I can confirm that

bindgen -o src/bindings.rs wrapper.h

with the appropriate bindgen, regenerates the pre-generated file.

Cheers,
Kent.

#12 lore raw

Re: [PATCH V4 2/8] libgpiod: Add pre generated rust bindings

Quoted 9 lines · Kent Gibson

bindings/rust/libgpiod-sys/README.md line 10

Quoted 6 lines · Viresh Kumar
bindings/rust/libgpiod-sys/README.md new file +6 -0
+## 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. Commit changes in `src/bindings.rs`

Those instructions do not force the regeneration of the bindings.

It does, just that the new file that got generated lands somewhere like this:

target/debug/build/libgpiod-sys-769f98853e1c0550/out/bindings.rs

and the end user crate will use this instead of the one in src/.

Quoted 3 lines

I assume the rust build system is being clever and determining nothing has changed and short circuiting, but I would like to regnerate them to be sure. How do I do that?

If you use the "generate" feature, you will end up building it again.

Quoted 3 lines

How is the --features approach better than just bindgen -o src/bindings.rs ../../../include/gpiod.h ?

This lets the user crates control it from their Cargo.toml file, libgpiod doesn't do it right now but it can like:

libgpiod-sys = { path = "libgpiod-sys", features = ["generate"] }

Quoted 1 line

Why not build this all the time rather than hiding it behind a feature?

That's what I did initially but there are environments, like rust-vmm containers [1], where all tools aren't available to run a bindgen for each architecture/platform type. I ran into a lot of issues there and the maintainers suggested to do this instead as the bindings won't change a lot later on.

FWIW, in my use case vhost-device crate [2] is using libgpiod, which uses libgpiod-sys and the vhost-device updates get tested with [1] automatically by bots.

Quoted 11 lines

bindings/rust/libgpiod-sys/src/bindings.rs line 2

Quoted 8 lines
bindings/rust/libgpiod-sys/src/bindings.rs new file +2 -0
new file mode 100644
index 000000000000..930eb894f66f
@@ -0,0 +1,1920 @@
+/* automatically generated by rust-bindgen 0.59.2 */
+

Yet the Cargo.toml specifies 0.59.1.
So you didn't generate this with the instructions above then?

I did.

The dependency like: bindgen = "0.59.1", doesn't force a specific version but actually a range. An update is allowed if the new version number does not modify the left-most non-zero digit in the major, minor, patch grouping [3].

Quoted 5 lines

Having said that, I can confirm that

bindgen -o src/bindings.rs wrapper.h

with the appropriate bindgen, regenerates the pre-generated file.

Signature · 5 lines
viresh

[1] https://github.com/rust-vmm/rust-vmm-container
[2] https://github.com/rust-vmm/vhost-device/tree/main/gpio
[3] https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html
#13 lore raw

Re: [PATCH V4 2/8] libgpiod: Add pre generated rust bindings

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

bindings/rust/libgpiod-sys/README.md line 10

Quoted 6 lines · Viresh Kumar
bindings/rust/libgpiod-sys/README.md new file +6 -0
+## 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. Commit changes in `src/bindings.rs`

Those instructions do not force the regeneration of the bindings.

Quoted 6 lines

It does, just that the new file that got generated lands somewhere like this:

target/debug/build/libgpiod-sys-769f98853e1c0550/out/bindings.rs

and the end user crate will use this instead of the one in src/.

But the instructions don't mention any of that, and the implication is that src/bindings.rs will be regenerated.

Cheers,
Kent.

#14 lore raw

Re: [PATCH V4 2/8] libgpiod: Add pre generated rust bindings

Quoted 14 lines · Kent Gibson
Quoted 13 lines · Viresh Kumar
Quoted 9 lines · Kent Gibson

Review

Quoted 6 lines · Viresh Kumar
(quoted diff) +6 -0
+## 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. Commit changes in `src/bindings.rs`

Those instructions do not force the regeneration of the bindings.

It does, just that the new file that got generated lands somewhere like this:

Quoted 7 lines
Quoted 4 lines

target/debug/build/libgpiod-sys-769f98853e1c0550/out/bindings.rs

and the end user crate will use this instead of the one in src/.

But the instructions don't mention any of that, and the implication is that src/bindings.rs will be regenerated.

Something like this is okay ?

bindings/rust/libgpiod-sys/README.md +2 -1
index ea037d6d7803..7d4583519e87 100644
@@ -7,4 +7,5 @@ Automatically generated Rust FFI bindings via
        <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. Commit changes in `src/bindings.rs`
+3. Copy the bindings 'cp target/debug/build/libgpiod-sys-###/out/bindings.rs src/bindings.rs'
+4. Commit changes in `src/bindings.rs`
Signature · 1 line
viresh
#15 lore raw

Re: [PATCH V4 2/8] libgpiod: Add pre generated rust bindings

Quoted 31 lines · Viresh Kumar
Quoted 14 lines · Kent Gibson
Quoted 13 lines · Viresh Kumar
Quoted 9 lines · Kent Gibson

Review

Quoted 6 lines · Viresh Kumar
(quoted diff) +6 -0
+## 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. Commit changes in `src/bindings.rs`

Those instructions do not force the regeneration of the bindings.

It does, just that the new file that got generated lands somewhere like this:

Quoted 7 lines
Quoted 4 lines

target/debug/build/libgpiod-sys-769f98853e1c0550/out/bindings.rs

and the end user crate will use this instead of the one in src/.

But the instructions don't mention any of that, and the implication is that src/bindings.rs will be regenerated.

Something like this is okay ?

bindings/rust/libgpiod-sys/README.md +0 -0
index ea037d6d7803..7d4583519e87 100644

bindings/rust/libgpiod-sys/README.md Automatically generated Rust FFI bindings via · line 11

Quoted 6 lines
bindings/rust/libgpiod-sys/README.md +2 -1
@@ -7,4 +7,5 @@ Automatically generated Rust FFI bindings via
        <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. Commit changes in `src/bindings.rs`
+3. Copy the bindings 'cp target/debug/build/libgpiod-sys-###/out/bindings.rs src/bindings.rs'
+4. Commit changes in `src/bindings.rs`

Definitely clearer.

Still not sure how all this is easier than just calling bindgen directly, but ok. I guess at least it specifies the appropriate-ish bindgen version.

Cheers,
Kent.

#16 lore raw

PATCH v4 3/8 libgpiod-sys: Add support to generate gpiosim bindings

In reply to Viresh Kumar

Add support to generate gpiosim bindings.

---
 bindings/rust/libgpiod-sys/Cargo.toml        |  1 +
 bindings/rust/libgpiod-sys/build.rs          | 19 +++++++++++++++++--
 bindings/rust/libgpiod-sys/gpiosim_wrapper.h |  1 +
 3 files changed, 19 insertions(+), 2 deletions(-)
 create mode 100644 bindings/rust/libgpiod-sys/gpiosim_wrapper.h
bindings/rust/libgpiod-sys/Cargo.toml +1 -0
index 77f82719d269..73b6761d16dd 100644
@@ -9,6 +9,7 @@ edition = "2018"
 
 [features]
 generate = [ "bindgen" ]
+gpiosim = [ "generate", "bindgen" ]
 
 [build-dependencies]
 bindgen = { version = "0.59.1", optional = true }
bindings/rust/libgpiod-sys/build.rs +17 -2
index bbcd30f79d23..147daaf6b1da 100644
@@ -14,13 +14,25 @@ fn generate_bindings(files: &Vec<&str>) {
         println!("cargo:rerun-if-changed={}", file);
     }
 
+    if cfg!(feature = "gpiosim") {
+        println!("cargo:rerun-if-changed=gpiosim_wrapper.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()
+    let mut builder = bindgen::Builder::default()
         // The input header we would like to generate
         // bindings for.
-        .header("wrapper.h")
+        .header("wrapper.h");
+
+    if cfg!(feature = "gpiosim") {
+        builder = builder.header("gpiosim_wrapper.h");
+        println!("cargo:rustc-link-lib=kmod");
+        println!("cargo:rustc-link-lib=mount");
+    }
+
+    let bindings = builder
         // Tell cargo to invalidate the built crate whenever any of the
         // included header files changed.
         .parse_callbacks(Box::new(bindgen::CargoCallbacks))
@@ -46,6 +58,7 @@ fn build_gpiod(files: Vec<&str>) {
         .define("_GNU_SOURCE", None)
         .define("GPIOD_VERSION_STR", "\"libgpio-sys\"")
         .include("../../../include")
+        .include("/usr/include/libmount")
         .compile("gpiod");
 }
 
@@ -61,6 +74,8 @@ fn main() {
         "../../../lib/line-request.c",
         "../../../lib/misc.c",
         "../../../lib/request-config.c",
+        #[cfg(feature = "gpiosim")]
+        "../../../tests/gpiosim/gpiosim.c",
     ];
 
     #[cfg(feature = "generate")]
bindings/rust/libgpiod-sys/gpiosim_wrapper.h new file +1 -0
new file mode 100644
index 000000000000..47dc12a87917
@@ -0,0 +1 @@
+#include "../../../tests/gpiosim/gpiosim.h"
Signature · 1 line
2.31.1.272.g89b43f80a514
#17 lore raw

Re: [PATCH V4 3/8] libgpiod-sys: Add support to generate gpiosim bindings

Quoted 15 lines · Viresh Kumar

Add support to generate gpiosim bindings.

---
 bindings/rust/libgpiod-sys/Cargo.toml        |  1 +
 bindings/rust/libgpiod-sys/build.rs          | 19 +++++++++++++++++--
 bindings/rust/libgpiod-sys/gpiosim_wrapper.h |  1 +
 3 files changed, 19 insertions(+), 2 deletions(-)
 create mode 100644 bindings/rust/libgpiod-sys/gpiosim_wrapper.h
bindings/rust/libgpiod-sys/Cargo.toml +0 -0
index 77f82719d269..73b6761d16dd 100644

bindings/rust/libgpiod-sys/Cargo.toml edition = "2018" · line 13

Quoted 5 lines
bindings/rust/libgpiod-sys/Cargo.toml +1 -0
@@ -9,6 +9,7 @@ edition = "2018"
 
 [features]
 generate = [ "bindgen" ]
+gpiosim = [ "generate", "bindgen" ]
 

Should gpiosim be a feature or a separate library/crate? I would expect it to be a separate crate and for libgpiod to have a dev-dependency on it (it is only required for tests, right?).

Quoted 58 lines
bindings/rust/libgpiod-sys/Cargo.toml +0 -0
 [build-dependencies]
 bindgen = { version = "0.59.1", optional = true }
bindings/rust/libgpiod-sys/build.rs +17 -2
index bbcd30f79d23..147daaf6b1da 100644
@@ -14,13 +14,25 @@ fn generate_bindings(files: &Vec<&str>) {
         println!("cargo:rerun-if-changed={}", file);
     }
 
+    if cfg!(feature = "gpiosim") {
+        println!("cargo:rerun-if-changed=gpiosim_wrapper.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()
+    let mut builder = bindgen::Builder::default()
         // The input header we would like to generate
         // bindings for.
-        .header("wrapper.h")
+        .header("wrapper.h");
+
+    if cfg!(feature = "gpiosim") {
+        builder = builder.header("gpiosim_wrapper.h");
+        println!("cargo:rustc-link-lib=kmod");
+        println!("cargo:rustc-link-lib=mount");
+    }
+
+    let bindings = builder
         // Tell cargo to invalidate the built crate whenever any of the
         // included header files changed.
         .parse_callbacks(Box::new(bindgen::CargoCallbacks))
@@ -46,6 +58,7 @@ fn build_gpiod(files: Vec<&str>) {
         .define("_GNU_SOURCE", None)
         .define("GPIOD_VERSION_STR", "\"libgpio-sys\"")
         .include("../../../include")
+        .include("/usr/include/libmount")
         .compile("gpiod");
 }
 
@@ -61,6 +74,8 @@ fn main() {
         "../../../lib/line-request.c",
         "../../../lib/misc.c",
         "../../../lib/request-config.c",
+        #[cfg(feature = "gpiosim")]
+        "../../../tests/gpiosim/gpiosim.c",
     ];
 
     #[cfg(feature = "generate")]
bindings/rust/libgpiod-sys/gpiosim_wrapper.h new file +0 -0
new file mode 100644
index 000000000000..47dc12a87917

bindings/rust/libgpiod-sys/gpiosim_wrapper.h line 1

Quoted 1 line
bindings/rust/libgpiod-sys/gpiosim_wrapper.h new file +1 -0
@@ -0,0 +1 @@
+#include "../../../tests/gpiosim/gpiosim.h"

Why bother with this wrapper - just bindgen that header directly?

Cheers,
Kent.

#18 lore raw

Re: [PATCH V4 3/8] libgpiod-sys: Add support to generate gpiosim bindings

Quoted 17 lines · Kent Gibson
Quoted 16 lines · Viresh Kumar

Add support to generate gpiosim bindings.

---
 bindings/rust/libgpiod-sys/Cargo.toml        |  1 +
 bindings/rust/libgpiod-sys/build.rs          | 19 +++++++++++++++++--
 bindings/rust/libgpiod-sys/gpiosim_wrapper.h |  1 +
 3 files changed, 19 insertions(+), 2 deletions(-)
 create mode 100644 bindings/rust/libgpiod-sys/gpiosim_wrapper.h
bindings/rust/libgpiod-sys/Cargo.toml +0 -0
index 77f82719d269..73b6761d16dd 100644
@@ -9,6 +9,7 @@ edition = "2018"
 
Quoted 6 lines

bindings/rust/libgpiod-sys/Cargo.toml edition = "2018" · line 13

Quoted 4 lines
bindings/rust/libgpiod-sys/Cargo.toml +1 -0
 [features]
 generate = [ "bindgen" ]
+gpiosim = [ "generate", "bindgen" ]
 

Should gpiosim be a feature or a separate library/crate?

It can be. I don't have any objections to that. It will add a bit more code, i.e. a libgpiosim-sys crate in bindings/rust/ directory, but that's fine I think.

Quoted 2 lines

I would expect it to be a separate crate and for libgpiod to have a dev-dependency on it (it is only required for tests, right?).

Yes.

Quoted 10 lines

bindings/rust/libgpiod-sys/gpiosim_wrapper.h line 1

Quoted 8 lines
bindings/rust/libgpiod-sys/gpiosim_wrapper.h new file +1 -0
new file mode 100644
index 000000000000..47dc12a87917
@@ -0,0 +1 @@
+#include "../../../tests/gpiosim/gpiosim.h"

Why bother with this wrapper - just bindgen that header directly?

Whatever we decide for wrapper.h, will be done here as well.

Signature · 1 line
viresh
#19 lore raw

PATCH v4 4/8 libgpiod: Add rust wrapper crate

In reply to Viresh Kumar

Add rust wrapper crate, around the libpiod-sys crate added earlier, to provide a convenient interface for the users.

---
 bindings/rust/Cargo.toml            |  12 +
 bindings/rust/src/chip.rs           | 179 ++++++++++
 bindings/rust/src/chip_info.rs      |  69 ++++
 bindings/rust/src/edge_event.rs     | 105 ++++++
 bindings/rust/src/event_buffer.rs   |  88 +++++
 bindings/rust/src/info_event.rs     |  68 ++++
 bindings/rust/src/lib.rs            | 317 ++++++++++++++++++
 bindings/rust/src/line_config.rs    | 493 ++++++++++++++++++++++++++++
 bindings/rust/src/line_info.rs      | 189 +++++++++++
 bindings/rust/src/line_request.rs   | 248 ++++++++++++++
 bindings/rust/src/request_config.rs | 122 +++++++
 11 files changed, 1890 insertions(+)
 create mode 100644 bindings/rust/Cargo.toml
 create mode 100644 bindings/rust/src/chip.rs
 create mode 100644 bindings/rust/src/chip_info.rs
 create mode 100644 bindings/rust/src/edge_event.rs
 create mode 100644 bindings/rust/src/event_buffer.rs
 create mode 100644 bindings/rust/src/info_event.rs
 create mode 100644 bindings/rust/src/lib.rs
 create mode 100644 bindings/rust/src/line_config.rs
 create mode 100644 bindings/rust/src/line_info.rs
 create mode 100644 bindings/rust/src/line_request.rs
 create mode 100644 bindings/rust/src/request_config.rs
bindings/rust/Cargo.toml new file +12 -0
new file mode 100644
index 000000000000..d5d81486fa2f
@@ -0,0 +1,12 @@
+[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]
+libc = ">=0.2.39"
+libgpiod-sys = { path = "libgpiod-sys" }
+thiserror = "1.0"
+vmm-sys-util = "=0.9.0"
bindings/rust/src/chip.rs new file +179 -0

179 lines not on this page · raw message

bindings/rust/src/chip_info.rs new file +69 -0

69 lines not on this page · raw message

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

105 lines not on this page · raw message

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

88 lines not on this page · raw message

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

68 lines not on this page · raw message

bindings/rust/src/lib.rs new file +317 -0

317 lines not on this page · raw message

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

493 lines not on this page · raw message

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

189 lines not on this page · raw message

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

248 lines not on this page · raw message

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

122 lines not on this page · raw message

Signature · 1 line
2.31.1.272.g89b43f80a514
#20 lore raw

Re: [PATCH V4 4/8] libgpiod: Add rust wrapper crate

Quoted 35 lines · Viresh Kumar

Add rust wrapper crate, around the libpiod-sys crate added earlier, to provide a convenient interface for the users.

---
 bindings/rust/Cargo.toml            |  12 +
 bindings/rust/src/chip.rs           | 179 ++++++++++
 bindings/rust/src/chip_info.rs      |  69 ++++
 bindings/rust/src/edge_event.rs     | 105 ++++++
 bindings/rust/src/event_buffer.rs   |  88 +++++
 bindings/rust/src/info_event.rs     |  68 ++++
 bindings/rust/src/lib.rs            | 317 ++++++++++++++++++
 bindings/rust/src/line_config.rs    | 493 ++++++++++++++++++++++++++++
 bindings/rust/src/line_info.rs      | 189 +++++++++++
 bindings/rust/src/line_request.rs   | 248 ++++++++++++++
 bindings/rust/src/request_config.rs | 122 +++++++
 11 files changed, 1890 insertions(+)
 create mode 100644 bindings/rust/Cargo.toml
 create mode 100644 bindings/rust/src/chip.rs
 create mode 100644 bindings/rust/src/chip_info.rs
 create mode 100644 bindings/rust/src/edge_event.rs
 create mode 100644 bindings/rust/src/event_buffer.rs
 create mode 100644 bindings/rust/src/info_event.rs
 create mode 100644 bindings/rust/src/lib.rs
 create mode 100644 bindings/rust/src/line_config.rs
 create mode 100644 bindings/rust/src/line_info.rs
 create mode 100644 bindings/rust/src/line_request.rs
 create mode 100644 bindings/rust/src/request_config.rs
bindings/rust/Cargo.toml new file +0 -0
new file mode 100644
index 000000000000..d5d81486fa2f

bindings/rust/Cargo.toml line 5

Quoted 5 lines
bindings/rust/Cargo.toml new file +5 -0
@@ -0,0 +1,12 @@
+[package]
+name = "libgpiod"
+version = "0.1.0"
+edition = "2018"
+

Make the top level a workspace, and move libgpiod, libgpiod-sys and gpiosim into sub-directories (as libgpiod-sys already is).

Quoted 62 lines
bindings/rust/Cargo.toml new file +7 -0
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+libc = ">=0.2.39"
+libgpiod-sys = { path = "libgpiod-sys" }
+thiserror = "1.0"
+vmm-sys-util = "=0.9.0"
bindings/rust/src/chip.rs new file +49 -0
new file mode 100644
index 000000000000..50b5d6102f96
@@ -0,0 +1,179 @@
+// 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_char;
+use std::sync::Arc;
+use std::time::Duration;
+use std::{slice, str};
+
+use vmm_sys_util::errno::Error as IoError;
+
+use super::{
+    bindings, chip_info::ChipInfo, Error, InfoEvent, LineConfig, LineInfo, LineRequest,
+    RequestConfig, Result,
+};
+
+/// 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.
+pub(crate) struct ChipInternal {
+    chip: *mut bindings::gpiod_chip,
+}
+
+impl ChipInternal {
+    /// Find a chip by path.
+    pub(crate) fn open(path: &str) -> Result<Self> {
+        // Null-terminate the string
+        let path = path.to_owned() + "\0";
+
+        let chip = unsafe { bindings::gpiod_chip_open(path.as_ptr() as *const c_char) };
+        if chip.is_null() {
+            return Err(Error::OperationFailed("Gpio Chip open", IoError::last()));
+        }
+
+        Ok(Self { chip })
+    }
+
+    /// Private helper, Returns gpiod_chip
+    pub(crate) fn chip(&self) -> *mut bindings::gpiod_chip {
+        self.chip
+    }
+}
+
+impl Drop for ChipInternal {
+    /// Close the chip and release all associated resources.

bindings/rust/src/chip.rs line 59

Quoted 10 lines
bindings/rust/src/chip.rs new file +10 -0
+    fn drop(&mut self) {
+        unsafe { bindings::gpiod_chip_close(self.chip) }
+    }
+}
+
+pub struct Chip {
+    ichip: Arc<ChipInternal>,
+    info: ChipInfo,
+}
+

Chip is undocumented. (check the generated docs)

Quoted 4 lines
bindings/rust/src/chip.rs new file +4 -0
+impl Chip {
+    /// Find a chip by path.
+    pub fn open(path: &str) -> Result<Self> {
+        let ichip = Arc::new(ChipInternal::open(path)?);

bindings/rust/src/chip.rs line 73

Quoted 10 lines
bindings/rust/src/chip.rs new file +10 -0
+        let info = ChipInfo::new(ichip.clone())?;
+
+        Ok(Self { ichip, info })
+    }
+
+    /// Get the chip name as represented in the kernel.
+    pub fn get_name(&self) -> Result<&str> {
+        self.info.name()
+    }
+

Drop the "get_" prefix from accessor methods (mutators are still prefixed with "set_")[1]

[1] https://rust-lang.github.io/api-guidelines/naming.html

bindings/rust/src/chip.rs line 85

Quoted 12 lines
bindings/rust/src/chip.rs new file +12 -0
+    /// Get the chip label as represented in the kernel.
+    pub fn get_label(&self) -> Result<&str> {
+        self.info.label()
+    }
+
+    /// Get the number of GPIO lines exposed by the chip.
+    pub fn get_num_lines(&self) -> u32 {
+        self.info.num_lines()
+    }
+
+    /// Get the path used to find the chip.
+    pub fn get_path(&self) -> Result<&str> {

It seems absurd that a method that simply returns the path provided to open() requires a Result, but that is a consequence of wrapping.

I was considering suggesting caching a copy in struct Chip, but all the other methods require Results so at least this is consistent :-(.

Yay, more unwrapping than Xmas past.

bindings/rust/src/chip.rs line 88

Quoted 3 lines
bindings/rust/src/chip.rs new file +3 -0
+        // SAFETY: The string returned by libgpiod is guaranteed to live as long
+        // as the `struct Chip`.
+        let path = unsafe { bindings::gpiod_chip_get_path(self.ichip.chip()) };

Trusting that it is never NULL?
Add a null check to be sure.

Quoted 30 lines
bindings/rust/src/chip.rs new file +30 -0
+
+        // SAFETY: The string is guaranteed to be valid here.
+        str::from_utf8(unsafe {
+            slice::from_raw_parts(path as *const u8, bindings::strlen(path) as usize)
+        })
+        .map_err(Error::InvalidString)
+    }
+
+    /// Get information about the chip.
+    pub fn info(&self) -> Result<ChipInfo> {
+        ChipInfo::new(self.ichip.clone())
+    }
+
+    /// Get a snapshot of information about the line.
+    pub fn line_info(&self, offset: u32) -> Result<LineInfo> {
+        LineInfo::new(self.ichip.clone(), offset, false)
+    }
+
+    /// Get the current snapshot of information about the line at given offset
+    /// and optionally start watching it for future changes.
+    pub fn watch_line_info(&self, offset: u32) -> Result<LineInfo> {
+        LineInfo::new(self.ichip.clone(), offset, true)
+    }
+
+    /// 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 get_fd(&self) -> Result<u32> {
+        let fd = unsafe { bindings::gpiod_chip_get_fd(self.ichip.chip()) };

bindings/rust/src/chip.rs line 128

Quoted 10 lines
bindings/rust/src/chip.rs new file +10 -0
+
+        if fd < 0 {
+            Err(Error::OperationFailed("Gpio Chip get-fd", IoError::last()))
+        } else {
+            Ok(fd as u32)
+        }
+    }
+
+    /// Wait for line status events on any of the watched lines on the chip.
+    pub fn wait_info_event(&self, timeout: Duration) -> Result<()> {

Durations cannot be negative, so caller cannot make this block indefinitely. Make timeout an Option? (no timeout => block)

Quoted 4 lines
bindings/rust/src/chip.rs new file +4 -0
+        let ret = unsafe {
+            bindings::gpiod_chip_wait_info_event(self.ichip.chip(), timeout.as_nanos() as i64)
+        };
+

bindings/rust/src/chip.rs line 142

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

Better to return Result<bool> indicating if an event is present rather than a timeout error?

bindings/rust/src/chip.rs line 150

Quoted 8 lines
bindings/rust/src/chip.rs new file +8 -0
+    /// 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<InfoEvent> {
+        InfoEvent::new(&self.ichip)
+    }
+
+    /// Map a GPIO line's name to its offset within the chip.
+    pub fn find_line(&self, name: &str) -> Result<u32> {

Rename to line_offset_from_name() to match the renaming in the C API.

Quoted 42 lines
bindings/rust/src/chip.rs new file +29 -0
+        // Null-terminate the string
+        let name = name.to_owned() + "\0";
+
+        let ret = unsafe {
+            bindings::gpiod_chip_get_line_offset_from_name(
+                self.ichip.chip(),
+                name.as_ptr() as *const c_char,
+            )
+        };
+
+        if ret == -1 {
+            Err(Error::OperationFailed(
+                "Gpio Chip find-line",
+                IoError::last(),
+            ))
+        } else {
+            Ok(ret as u32)
+        }
+    }
+
+    /// Request a set of lines for exclusive usage.
+    pub fn request_lines(
+        &self,
+        rconfig: &RequestConfig,
+        lconfig: &LineConfig,
+    ) -> Result<LineRequest> {
+        LineRequest::new(&self.ichip, rconfig, lconfig)
+    }
+}
bindings/rust/src/chip_info.rs new file +7 -0
new file mode 100644
index 000000000000..950368b54c6f
@@ -0,0 +1,69 @@
+// 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::{slice, str};

bindings/rust/src/chip_info.rs line 17

Quoted 10 lines
bindings/rust/src/chip_info.rs new file +10 -0
+
+use vmm_sys_util::errno::Error as IoError;
+
+use super::{bindings, ChipInternal, Error, Result};
+
+/// GPIO chip Information
+pub struct ChipInfo {
+    info: *mut bindings::gpiod_chip_info,
+}
+

Consider modules and namespaces rather than lumping everything in the gpiod space.

e.g. gpiod::ChipInfo -> gpiod::chip::Info

Quoted 10 lines
bindings/rust/src/chip_info.rs new file +10 -0
+impl ChipInfo {
+    /// Find a GPIO chip by path.
+    pub(crate) fn new(chip: Arc<ChipInternal>) -> Result<Self> {
+        let info = unsafe { bindings::gpiod_chip_get_info(chip.chip()) };
+        if info.is_null() {
+            return Err(Error::OperationFailed(
+                "Gpio Chip get info",
+                IoError::last(),
+            ));
+        }

bindings/rust/src/chip_info.rs line 37

Quoted 10 lines
bindings/rust/src/chip_info.rs new file +10 -0
+
+        Ok(Self { info })
+    }
+
+    /// 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 { bindings::gpiod_chip_info_get_name(self.info) };
+

Again, trusting the C not to return NULL.
Similarly elsewhere - don't trust the C library.

Quoted 62 lines
bindings/rust/src/chip_info.rs new file +32 -0
+        // SAFETY: The string is guaranteed to be valid here.
+        str::from_utf8(unsafe {
+            slice::from_raw_parts(name as *const u8, bindings::strlen(name) as usize)
+        })
+        .map_err(Error::InvalidString)
+    }
+
+    /// 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 { bindings::gpiod_chip_info_get_label(self.info) };
+
+        // SAFETY: The string is guaranteed to be valid here.
+        str::from_utf8(unsafe {
+            slice::from_raw_parts(label as *const u8, bindings::strlen(label) as usize)
+        })
+        .map_err(Error::InvalidString)
+    }
+
+    /// Get the number of GPIO lines exposed by the chip.
+    pub(crate) fn num_lines(&self) -> u32 {
+        unsafe { bindings::gpiod_chip_info_get_num_lines(self.info) as u32 }
+    }
+}
+
+impl Drop for ChipInfo {
+    /// Close the GPIO chip info and release all associated resources.
+    fn drop(&mut self) {
+        unsafe { bindings::gpiod_chip_info_free(self.info) }
+    }
+}
bindings/rust/src/edge_event.rs new file +24 -0
new file mode 100644
index 000000000000..89bda58709d2
@@ -0,0 +1,105 @@
+// 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 IoError;
+
+use super::{bindings, EdgeEventBufferInternal, Error, LineEdgeEvent, 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.
+
+pub struct EdgeEvent {

bindings/rust/src/edge_event.rs line 34

Quoted 10 lines
bindings/rust/src/edge_event.rs new file +10 -0
+    ibuffer: Option<Arc<EdgeEventBufferInternal>>,
+    event: *mut bindings::gpiod_edge_event,
+}
+
+impl EdgeEvent {
+    /// Get an event stored in the buffer.
+    pub(crate) fn new(
+        ibuffer: &Arc<EdgeEventBufferInternal>,
+        index: u64,
+        copy: bool,

rename to copied or cloned, or flip the sense and call it buffered or contained.

Quoted 19 lines
bindings/rust/src/edge_event.rs new file +19 -0
+    ) -> Result<Self> {
+        let event = unsafe { bindings::gpiod_edge_event_buffer_get_event(ibuffer.buffer(), index) };
+        if event.is_null() {
+            return Err(Error::OperationFailed(
+                "Gpio EdgeEvent buffer-get-event",
+                IoError::last(),
+            ));
+        }
+
+        if copy {
+            let event = unsafe { bindings::gpiod_edge_event_copy(event) };
+            if event.is_null() {
+                return Err(Error::OperationFailed(
+                    "Gpio EdgeEvent copy",
+                    IoError::last(),
+                ));
+            }
+
+            Ok(Self {

bindings/rust/src/edge_event.rs line 63

Quoted 10 lines
bindings/rust/src/edge_event.rs new file +10 -0
+                ibuffer: None,
+                event,
+            })
+        } else {
+            Ok(Self {
+                ibuffer: Some(ibuffer.clone()),
+                event,
+            })
+        }
+    }

This is both a new() and a clone(). Split them out.

Quoted 61 lines
bindings/rust/src/edge_event.rs new file +42 -0
+
+    /// Get the event type.
+    pub fn get_event_type(&self) -> Result<LineEdgeEvent> {
+        LineEdgeEvent::new(unsafe { bindings::gpiod_edge_event_get_event_type(self.event) } as u32)
+    }
+
+    /// Get the timestamp of the event.
+    pub fn get_timestamp(&self) -> Duration {
+        Duration::from_nanos(unsafe { bindings::gpiod_edge_event_get_timestamp_ns(self.event) })
+    }
+
+    /// Get the offset of the line on which the event was triggered.
+    pub fn get_line_offset(&self) -> u32 {
+        unsafe { bindings::gpiod_edge_event_get_line_offset(self.event) }
+    }
+
+    /// Get the global sequence number of the event.
+    ///
+    /// Returns sequence number of the event relative to all lines in the
+    /// associated line request.
+    pub fn get_global_seqno(&self) -> u64 {
+        unsafe { bindings::gpiod_edge_event_get_global_seqno(self.event) }
+    }
+
+    /// Get the event sequence number specific to concerned line.
+    ///
+    /// Returns sequence number of the event relative to the line within the
+    /// lifetime of the associated line request.
+    pub fn get_line_seqno(&self) -> u64 {
+        unsafe { bindings::gpiod_edge_event_get_line_seqno(self.event) }
+    }
+}
+
+impl Drop for EdgeEvent {
+    /// Free the edge event.
+    fn drop(&mut self) {
+        // Free the event only if a copy is made
+        if self.ibuffer.is_none() {
+            unsafe { bindings::gpiod_edge_event_free(self.event) };
+        }
+    }
+}
bindings/rust/src/event_buffer.rs new file +13 -0
new file mode 100644
index 000000000000..bb568a80e09c
@@ -0,0 +1,88 @@
+// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
+//
+// Copyright 2022 Linaro Ltd. All Rights Reserved.
+//     Viresh Kumar <[email protected]>
+
+use std::os::raw::c_ulong;
+use std::sync::Arc;
+
+use vmm_sys_util::errno::Error as IoError;
+
+use super::{bindings, EdgeEvent, Error, Result};
+
+/// Line edge events buffer

bindings/rust/src/event_buffer.rs line 23

Quoted 10 lines
bindings/rust/src/event_buffer.rs new file +10 -0
+pub(crate) struct EdgeEventBufferInternal {
+    buffer: *mut bindings::gpiod_edge_event_buffer,
+}
+
+impl EdgeEventBufferInternal {
+    /// 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: u32) -> Result<Self> {

Use usize not u32 for array sizes throughout.

Quoted 39 lines
bindings/rust/src/event_buffer.rs new file +39 -0
+        let buffer = unsafe { bindings::gpiod_edge_event_buffer_new(capacity as c_ulong) };
+        if buffer.is_null() {
+            return Err(Error::OperationFailed(
+                "Gpio EdgeEventBuffer new",
+                IoError::last(),
+            ));
+        }
+
+        Ok(Self { buffer })
+    }
+
+    /// Private helper, Returns gpiod_edge_event_buffer
+    pub(crate) fn buffer(&self) -> *mut bindings::gpiod_edge_event_buffer {
+        self.buffer
+    }
+}
+
+impl Drop for EdgeEventBufferInternal {
+    /// Free the edge event buffer and release all associated resources.
+    fn drop(&mut self) {
+        unsafe { bindings::gpiod_edge_event_buffer_free(self.buffer) };
+    }
+}
+
+/// Line edge events buffer
+pub struct EdgeEventBuffer {
+    ibuffer: Arc<EdgeEventBufferInternal>,
+}
+
+impl EdgeEventBuffer {
+    /// 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: u32) -> Result<Self> {
+        Ok(Self {
+            ibuffer: Arc::new(EdgeEventBufferInternal::new(capacity)?),
+        })
+    }

bindings/rust/src/event_buffer.rs line 72

Quoted 10 lines
bindings/rust/src/event_buffer.rs new file +10 -0
+
+    /// Private helper, Returns gpiod_edge_event_buffer
+    pub(crate) fn buffer(&self) -> *mut bindings::gpiod_edge_event_buffer {
+        self.ibuffer.buffer()
+    }
+
+    /// Get the capacity of the event buffer.
+    pub fn get_capacity(&self) -> u32 {
+        unsafe { bindings::gpiod_edge_event_buffer_get_capacity(self.buffer()) as u32 }
+    }

Follow the Vec API and make this capacity().
num_events() becomes len().

bindings/rust/src/event_buffer.rs line 82

Quoted 10 lines
bindings/rust/src/event_buffer.rs new file +10 -0
+
+    /// Read an event stored in the buffer.
+    pub fn get_event(&self, index: u64) -> Result<EdgeEvent> {
+        EdgeEvent::new(&self.ibuffer, index, false)
+    }
+
+    /// Make copy of an edge event stored in the buffer.
+    pub fn get_event_copy(&self, index: u64) -> Result<EdgeEvent> {
+        EdgeEvent::new(&self.ibuffer, index, true)
+    }

impl Clone for EdgeEvent instead.

bindings/rust/src/event_buffer.rs line 84

Quoted 2 lines
bindings/rust/src/event_buffer.rs new file +2 -0
+
+    /// Get the number of events the buffers stores.

stores -> contains

The capacity indicates the number that can be stored. This field indicates the number the buffer currently contains.

And, as mentioned above, rename to len(), and return value should be usize.

Add a set_len() method to replace the max_events parameter in LineRequest::read_edge_event() to handle the unusual case where the user only wants to partially fill the buffer.

Quoted 100 lines
bindings/rust/src/event_buffer.rs new file +4 -0
+    pub fn get_num_events(&self) -> u32 {
+        unsafe { bindings::gpiod_edge_event_buffer_get_num_events(self.buffer()) as u32 }
+    }
+}
bindings/rust/src/info_event.rs new file +68 -0
new file mode 100644
index 000000000000..ffba23f350be
@@ -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::convert::TryFrom;
+use std::sync::Arc;
+use std::time::Duration;
+
+use vmm_sys_util::errno::Error as IoError;
+
+use super::{bindings, ChipInternal, Error, Event, LineInfo, Result};
+
+/// Line status watch events
+///
+/// Accessors for the info event objects allowing to monitor changes in GPIO
+/// line state.
+///
+/// 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.
+
+pub struct InfoEvent {
+    event: *mut bindings::gpiod_info_event,
+}
+
+impl InfoEvent {
+    /// Get a single chip's line's status change event.
+    pub(crate) fn new(ichip: &Arc<ChipInternal>) -> Result<Self> {
+        let event = unsafe { bindings::gpiod_chip_read_info_event(ichip.chip()) };
+        if event.is_null() {
+            return Err(Error::OperationFailed(
+                "Gpio InfoEvent event-read",
+                IoError::last(),
+            ));
+        }
+
+        Ok(Self { event })
+    }
+
+    /// Private helper, Returns gpiod_info_event
+    pub(crate) fn event(&self) -> *mut bindings::gpiod_info_event {
+        self.event
+    }
+
+    /// Get the event type of the status change event.
+    pub fn get_event_type(&self) -> Result<Event> {
+        Event::new(unsafe { bindings::gpiod_info_event_get_event_type(self.event) } as u32)
+    }
+
+    /// Get the timestamp of the event, read from the monotonic clock.
+    pub fn get_timestamp(&self) -> Duration {
+        Duration::from_nanos(unsafe { bindings::gpiod_info_event_get_timestamp_ns(self.event) })
+    }
+
+    /// Get the line-info object associated with the event.
+    pub fn line_info(&self) -> Result<LineInfo> {
+        LineInfo::try_from(self)
+    }
+}
+
+impl Drop for InfoEvent {
+    /// Free the info event object and release all associated resources.
+    fn drop(&mut self) {
+        unsafe { bindings::gpiod_info_event_free(self.event) }
+    }
+}
bindings/rust/src/lib.rs new file +16 -0
new file mode 100644
index 000000000000..2f2ac515d353
@@ -0,0 +1,317 @@
+// 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.
+//!
+//! The API is logically split into several parts such as: GPIO chip & line
+//! operators, GPIO events handling etc.
+
+mod chip;

bindings/rust/src/lib.rs line 26

Quoted 10 lines
bindings/rust/src/lib.rs new file +10 -0
+mod chip_info;
+mod edge_event;
+mod event_buffer;
+mod info_event;
+mod line_config;
+mod line_info;
+mod line_request;
+mod request_config;
+
+use libgpiod_sys as bindings;

I find the original name clearer.
From the end user's perspective THIS module is the bindings. If you want a short name how about "gpiod"?

Quoted 6 lines
bindings/rust/src/lib.rs new file +6 -0
+
+pub use crate::chip::*;
+pub use crate::edge_event::*;
+pub use crate::event_buffer::*;
+pub use crate::info_event::*;
+pub use crate::line_config::*;

bindings/rust/src/lib.rs line 42

Quoted 10 lines
bindings/rust/src/lib.rs new file +10 -0
+pub use crate::line_info::*;
+pub use crate::line_request::*;
+pub use crate::request_config::*;
+
+use std::os::raw::c_char;
+use std::{slice, str};
+
+use thiserror::Error as ThisError;
+use vmm_sys_util::errno::Error as IoError;
+

IoError could be confused with std::io::Error.
This is just an Errno.

bindings/rust/src/lib.rs line 49

Quoted 7 lines
bindings/rust/src/lib.rs new file +7 -0
+/// 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 find {0}")]

"find" implies a search. Where it is used it is just a get.

bindings/rust/src/lib.rs line 50

Quoted 1 line
bindings/rust/src/lib.rs new file +1 -0
+    NameNotFound(&'static str),

This is used when the C API returns a NULL char *. Not sure "NameNotFound" indicates that.
It is a specific case of InvalidValue. Maybe NullString?

bindings/rust/src/lib.rs line 52

Quoted 2 lines
bindings/rust/src/lib.rs new file +2 -0
+    #[error("Invalid String: {0:?}")]
+    InvalidString(str::Utf8Error),

Specifically the string is not UTF8, so "InvalidString" is a bit vague.

bindings/rust/src/lib.rs line 54

Quoted 2 lines
bindings/rust/src/lib.rs new file +2 -0
+    #[error("Invalid {0} value: {1}")]
+    InvalidValue(&'static str, u32),

And this is an InvalidEnumValue.

bindings/rust/src/lib.rs line 56

Quoted 2 lines
bindings/rust/src/lib.rs new file +2 -0
+    #[error("Operation {0} Failed: {1}")]
+    OperationFailed(&'static str, IoError),

Use an enum for the operation rather than a string?

And if it returns an IoError it must be an IoOperation? Else if the IoError is just an errno then call it that.

bindings/rust/src/lib.rs line 58

Quoted 2 lines
bindings/rust/src/lib.rs new file +2 -0
+    #[error("Operation Timed-out")]
+    OperationTimedOut,

No indication which operation?

Scratch that - this error should be removed.
Refer to comments where used below.

Quoted 35 lines
bindings/rust/src/lib.rs new file +35 -0
+}
+
+/// Direction settings.
+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 {
+            bindings::GPIOD_LINE_DIRECTION_AS_IS => Ok(Direction::AsIs),
+            bindings::GPIOD_LINE_DIRECTION_INPUT => Ok(Direction::Input),
+            bindings::GPIOD_LINE_DIRECTION_OUTPUT => Ok(Direction::Output),
+            _ => Err(Error::InvalidValue("direction", dir)),
+        }
+    }
+
+    fn gpiod_direction(&self) -> u32 {
+        match self {
+            Direction::AsIs => bindings::GPIOD_LINE_DIRECTION_AS_IS,
+            Direction::Input => bindings::GPIOD_LINE_DIRECTION_INPUT,
+            Direction::Output => bindings::GPIOD_LINE_DIRECTION_OUTPUT,
+        }
+    }
+}
+
+/// Internal bias settings.
+pub enum Bias {
+    /// Don't change the bias setting when applying line config.
+    AsIs,

bindings/rust/src/lib.rs line 103

Quoted 10 lines
bindings/rust/src/lib.rs new file +10 -0
+    /// The internal bias state is unknown.
+    Unknown,
+    /// The internal bias is disabled.
+    Disabled,
+    /// The internal pull-up bias is enabled.
+    PullUp,
+    /// The internal pull-down bias is enabled.
+    PullDown,
+}
+

Bias fields should be Option<Bias>
AsIs only makes sense in requests and should be None. Unknown only makes sense in responses and should be None.

Quoted 54 lines
bindings/rust/src/lib.rs new file +54 -0
+impl Bias {
+    fn new(bias: u32) -> Result<Self> {
+        match bias {
+            bindings::GPIOD_LINE_BIAS_AS_IS => Ok(Bias::AsIs),
+            bindings::GPIOD_LINE_BIAS_UNKNOWN => Ok(Bias::Unknown),
+            bindings::GPIOD_LINE_BIAS_DISABLED => Ok(Bias::Disabled),
+            bindings::GPIOD_LINE_BIAS_PULL_UP => Ok(Bias::PullUp),
+            bindings::GPIOD_LINE_BIAS_PULL_DOWN => Ok(Bias::PullDown),
+            _ => Err(Error::InvalidValue("bias", bias)),
+        }
+    }
+
+    fn gpiod_bias(&self) -> u32 {
+        match self {
+            Bias::AsIs => bindings::GPIOD_LINE_BIAS_AS_IS,
+            Bias::Unknown => bindings::GPIOD_LINE_BIAS_UNKNOWN,
+            Bias::Disabled => bindings::GPIOD_LINE_BIAS_DISABLED,
+            Bias::PullUp => bindings::GPIOD_LINE_BIAS_PULL_UP,
+            Bias::PullDown => bindings::GPIOD_LINE_BIAS_PULL_DOWN,
+        }
+    }
+}
+
+/// Drive settings.
+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 {
+            bindings::GPIOD_LINE_DRIVE_PUSH_PULL => Ok(Drive::PushPull),
+            bindings::GPIOD_LINE_DRIVE_OPEN_DRAIN => Ok(Drive::OpenDrain),
+            bindings::GPIOD_LINE_DRIVE_OPEN_SOURCE => Ok(Drive::OpenSource),
+            _ => Err(Error::InvalidValue("drive", drive)),
+        }
+    }
+
+    fn gpiod_drive(&self) -> u32 {
+        match self {
+            Drive::PushPull => bindings::GPIOD_LINE_DRIVE_PUSH_PULL,
+            Drive::OpenDrain => bindings::GPIOD_LINE_DRIVE_OPEN_DRAIN,
+            Drive::OpenSource => bindings::GPIOD_LINE_DRIVE_OPEN_SOURCE,
+        }
+    }
+}
+
+/// Edge detection settings.
+pub enum Edge {

bindings/rust/src/lib.rs line 167

Quoted 10 lines
bindings/rust/src/lib.rs new file +10 -0
+    /// Line edge detection is disabled.
+    None,
+    /// Line detects rising edge events.
+    Rising,
+    /// Line detects falling edge events.
+    Falling,
+    /// Line detects both rising and falling edge events.
+    Both,
+}
+

As per Bias, the None case should be provided by making the field an Option where used.

Quoted 13 lines
bindings/rust/src/lib.rs new file +13 -0
+impl Edge {
+    fn new(edge: u32) -> Result<Self> {
+        match edge {
+            bindings::GPIOD_LINE_EDGE_NONE => Ok(Edge::None),
+            bindings::GPIOD_LINE_EDGE_RISING => Ok(Edge::Rising),
+            bindings::GPIOD_LINE_EDGE_FALLING => Ok(Edge::Falling),
+            bindings::GPIOD_LINE_EDGE_BOTH => Ok(Edge::Both),
+            _ => Err(Error::InvalidValue("edge", edge)),
+        }
+    }
+
+    fn gpiod_edge(&self) -> u32 {
+        match self {

bindings/rust/src/lib.rs line 190

Quoted 10 lines
bindings/rust/src/lib.rs new file +10 -0
+            Edge::None => bindings::GPIOD_LINE_EDGE_NONE,
+            Edge::Rising => bindings::GPIOD_LINE_EDGE_RISING,
+            Edge::Falling => bindings::GPIOD_LINE_EDGE_FALLING,
+            Edge::Both => bindings::GPIOD_LINE_EDGE_BOTH,
+        }
+    }
+}
+
+/// Line config settings.
+pub enum Config {

Rename to SettingKind.
(see comments in line-config)

Quoted 58 lines
bindings/rust/src/lib.rs new file +58 -0
+    /// Line direction.
+    Direction,
+    /// Edge detection.
+    EdgeDetection,
+    /// Bias.
+    Bias,
+    /// Drive.
+    Drive,
+    /// Active-low setting.
+    ActiveLow,
+    /// Debounce period.
+    DebouncePeriodUs,
+    /// Event clock type.
+    EventClock,
+    /// Output value.
+    OutputValue,
+}
+
+impl Config {
+    fn new(config: u32) -> Result<Self> {
+        match config {
+            bindings::GPIOD_LINE_CONFIG_PROP_DIRECTION => Ok(Config::Direction),
+            bindings::GPIOD_LINE_CONFIG_PROP_EDGE_DETECTION => Ok(Config::EdgeDetection),
+            bindings::GPIOD_LINE_CONFIG_PROP_BIAS => Ok(Config::Bias),
+            bindings::GPIOD_LINE_CONFIG_PROP_DRIVE => Ok(Config::Drive),
+            bindings::GPIOD_LINE_CONFIG_PROP_ACTIVE_LOW => Ok(Config::ActiveLow),
+            bindings::GPIOD_LINE_CONFIG_PROP_DEBOUNCE_PERIOD_US => Ok(Config::DebouncePeriodUs),
+            bindings::GPIOD_LINE_CONFIG_PROP_EVENT_CLOCK => Ok(Config::EventClock),
+            bindings::GPIOD_LINE_CONFIG_PROP_OUTPUT_VALUE => Ok(Config::OutputValue),
+            _ => Err(Error::InvalidValue("config", config)),
+        }
+    }
+}
+
+/// Event clock settings.
+pub enum EventClock {
+    /// Line uses the monotonic clock for edge event timestamps.
+    Monotonic,
+    /// Line uses the realtime clock for edge event timestamps.
+    Realtime,
+}
+
+impl EventClock {
+    fn new(clock: u32) -> Result<Self> {
+        match clock {
+            bindings::GPIOD_LINE_EVENT_CLOCK_MONOTONIC => Ok(EventClock::Monotonic),
+            bindings::GPIOD_LINE_EVENT_CLOCK_REALTIME => Ok(EventClock::Realtime),
+            _ => Err(Error::InvalidValue("event clock", clock)),
+        }
+    }
+
+    fn gpiod_clock(&self) -> u32 {
+        match self {
+            EventClock::Monotonic => bindings::GPIOD_LINE_EVENT_CLOCK_MONOTONIC,
+            EventClock::Realtime => bindings::GPIOD_LINE_EVENT_CLOCK_REALTIME,
+        }
+    }
+}

bindings/rust/src/lib.rs line 258

Quoted 10 lines
bindings/rust/src/lib.rs new file +10 -0
+
+/// Line status change event types.
+pub enum Event {
+    /// Line has been requested.
+    LineRequested,
+    /// Previously requested line has been released.
+    LineReleased,
+    /// Line configuration has changed.
+    LineConfigChanged,
+}

This is not an Event. It is a LineInfoChange or LineInfoChangeKind.

And then the "Line" prefix is redundant.

Quoted 11 lines
bindings/rust/src/lib.rs new file +11 -0
+
+impl Event {
+    fn new(event: u32) -> Result<Self> {
+        match event {
+            bindings::GPIOD_INFO_EVENT_LINE_REQUESTED => Ok(Event::LineRequested),
+            bindings::GPIOD_INFO_EVENT_LINE_RELEASED => Ok(Event::LineReleased),
+            bindings::GPIOD_INFO_EVENT_LINE_CONFIG_CHANGED => Ok(Event::LineConfigChanged),
+            _ => Err(Error::InvalidValue("event", event)),
+        }
+    }
+}

bindings/rust/src/lib.rs line 279

Quoted 10 lines
bindings/rust/src/lib.rs new file +10 -0
+
+#[derive(Copy, Clone)]
+/// Edge event types.
+pub enum LineEdgeEvent {
+    /// Rising edge event.
+    Rising,
+    /// Falling edge event.
+    Falling,
+}
+

Similarly LineEdge or LineEdgeKind.

Quoted 13 lines
bindings/rust/src/lib.rs new file +13 -0
+impl LineEdgeEvent {
+    fn new(event: u32) -> Result<Self> {
+        match event {
+            bindings::GPIOD_EDGE_EVENT_RISING_EDGE => Ok(LineEdgeEvent::Rising),
+            bindings::GPIOD_EDGE_EVENT_FALLING_EDGE => Ok(LineEdgeEvent::Falling),
+            _ => Err(Error::InvalidValue("edge event", event)),
+        }
+    }
+}
+
+/// Various libgpiod-related functions.
+
+/// Check if the file pointed to by path is a GPIO chip character device.

bindings/rust/src/lib.rs line 302

Quoted 10 lines
bindings/rust/src/lib.rs new file +10 -0
+///
+/// Returns true if the file exists and is a GPIO chip character device or a
+/// symbolic link to it.
+pub fn gpiod_is_gpiochip_device(path: &str) -> bool {
+    // Null-terminate the string
+    let path = path.to_owned() + "\0";
+
+    unsafe { bindings::gpiod_is_gpiochip_device(path.as_ptr() as *const c_char) }
+}
+

The "gpiod_" prefix is redundant for top level functions in the crate.

Quoted 44 lines
bindings/rust/src/lib.rs new file +15 -0
+/// Get the API version of the library as a human-readable string.
+pub fn gpiod_version_string() -> Result<&'static str> {
+    // SAFETY: The string returned by libgpiod is guaranteed to live forever.
+    let version = unsafe { bindings::gpiod_version_string() };
+
+    if version.is_null() {
+        return Err(Error::NameNotFound("GPIO library version"));
+    }
+
+    // SAFETY: The string is guaranteed to be valid here.
+    str::from_utf8(unsafe {
+        slice::from_raw_parts(version as *const u8, bindings::strlen(version) as usize)
+    })
+    .map_err(Error::InvalidString)
+}
bindings/rust/src/line_config.rs new file +23 -0
new file mode 100644
index 000000000000..0fc8d0b736a3
@@ -0,0 +1,493 @@
+// 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::time::Duration;
+
+use vmm_sys_util::errno::Error as IoError;
+
+use super::{bindings, Bias, Config, Direction, Drive, Edge, Error, EventClock, Result};
+
+/// 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 instantiated with a set of sane defaults
+/// for all supported configuration settings. Those defaults can be modified by
+/// the caller. Default values can be overridden by applying different values

bindings/rust/src/line_config.rs line 33

Quoted 10 lines
bindings/rust/src/line_config.rs new file +10 -0
+/// for specific lines. When making a request or reconfiguring an existing one,
+/// the overridden settings for specific lines take precedence. For lines
+/// without an override the requested default settings are used.
+///
+/// For every setting there are two mutators (one setting the default and one
+/// for the per-line override), two getters (one for reading the global
+/// default and one for retrieving the effective value for the line),
+/// a function for testing if a setting is overridden for the line
+/// and finally a function for clearing the overrides (per line).
+///

Add a Setting enum that has a variant for each setting. Then you only need 3 mutators total.
And the user can define configs as a list of Settings. So perhaps the mutators should accept &[Setting]. And &[offsets] rather than just offset.

Similarly, gets can be consolidated into:

get_prop_offset(self, offset, SettingKind) -> Result<Setting>

and
get_prop_default(self, SettingKind) -> Result<Setting>

(simplified signatures)

As per my comments to the Python bindings, I question the value of the default/override/clear pattern over a straight up set, but this is at least consistent with the other bindings.

Quoted 249 lines
bindings/rust/src/line_config.rs new file +249 -0
+/// The mutators don't return errors. If the set of options is too complex to
+/// be translated into kernel uAPI structures then an error will be returned at
+/// the time of the request or reconfiguration. If an invalid value was passed
+/// to any of the mutators then the default value will be silently used instead.
+///
+/// Operating on lines in struct LineConfig has no immediate effect on real
+/// GPIOs, it only manipulates the config object in memory.  Those changes are
+/// only applied to the hardware at the time of the request or reconfiguration.
+///
+/// Overrides for lines that don't end up being requested are silently ignored
+/// both in LineRequest::new() as well as in LineRequest::reconfigure_lines().
+///
+/// In cases where all requested lines are using the one configuration, the
+/// line overrides can be entirely ignored when preparing the configuration.
+
+pub struct LineConfig {
+    config: *mut bindings::gpiod_line_config,
+}
+
+impl LineConfig {
+    /// Create a new line config object.
+    pub fn new() -> Result<Self> {
+        let config = unsafe { bindings::gpiod_line_config_new() };
+
+        if config.is_null() {
+            return Err(Error::OperationFailed(
+                "Gpio LineConfig new",
+                IoError::last(),
+            ));
+        }
+
+        Ok(Self { config })
+    }
+
+    /// Private helper, Returns gpiod_line_config
+    pub(crate) fn config(&self) -> *mut bindings::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 { bindings::gpiod_line_config_reset(self.config) }
+    }
+
+    /// Set the default line direction.
+    pub fn set_direction_default(&mut self, direction: Direction) {
+        unsafe {
+            bindings::gpiod_line_config_set_direction_default(
+                self.config,
+                direction.gpiod_direction() as i32,
+            )
+        }
+    }
+
+    /// Set the direction for a line.
+    pub fn set_direction_override(&mut self, direction: Direction, offset: u32) {
+        unsafe {
+            bindings::gpiod_line_config_set_direction_override(
+                self.config,
+                direction.gpiod_direction() as i32,
+                offset,
+            )
+        }
+    }
+
+    /// Clear the direction for a line.
+    pub fn clear_direction_override(&mut self, offset: u32) {
+        unsafe { bindings::gpiod_line_config_clear_direction_override(self.config, offset) }
+    }
+
+    /// Check if the direction is overridden for a line.
+    pub fn direction_is_overridden(&self, offset: u32) -> bool {
+        unsafe { bindings::gpiod_line_config_direction_is_overridden(self.config, offset) }
+    }
+
+    /// Get the default direction setting.
+    pub fn get_direction_default(&self) -> Result<Direction> {
+        Direction::new(
+            unsafe { bindings::gpiod_line_config_get_direction_default(self.config) } as u32,
+        )
+    }
+
+    /// Get the direction of a given line.
+    ///
+    /// Direction setting for the line if the config object were used in a request.
+    pub fn get_direction_offset(&self, offset: u32) -> Result<Direction> {
+        Direction::new(unsafe {
+            bindings::gpiod_line_config_get_direction_offset(self.config, offset)
+        } as u32)
+    }
+
+    /// Set the default edge event detection setting.
+    pub fn set_edge_detection_default(&mut self, edge: Edge) {
+        unsafe {
+            bindings::gpiod_line_config_set_edge_detection_default(
+                self.config,
+                edge.gpiod_edge() as i32,
+            )
+        }
+    }
+
+    /// Set the edge event detection for a single line.
+    pub fn set_edge_detection_override(&mut self, edge: Edge, offset: u32) {
+        unsafe {
+            bindings::gpiod_line_config_set_edge_detection_override(
+                self.config,
+                edge.gpiod_edge() as i32,
+                offset,
+            )
+        }
+    }
+
+    /// Clear the edge event detection for a single line.
+    pub fn clear_edge_detection_override(&mut self, offset: u32) {
+        unsafe { bindings::gpiod_line_config_clear_edge_detection_override(self.config, offset) }
+    }
+
+    /// Check if the edge event detection is overridden for a line.
+    pub fn edge_detection_is_overridden(&self, offset: u32) -> bool {
+        unsafe { bindings::gpiod_line_config_edge_detection_is_overridden(self.config, offset) }
+    }
+
+    /// Get the default edge event detection setting.
+    pub fn get_edge_detection_default(&self) -> Result<Edge> {
+        Edge::new(
+            unsafe { bindings::gpiod_line_config_get_edge_detection_default(self.config) } as u32,
+        )
+    }
+
+    /// Get the edge event detection setting for a given line.
+    ///
+    /// Edge event detection setting for the line if the config object were used in a request.
+    pub fn get_edge_detection_offset(&self, offset: u32) -> Result<Edge> {
+        Edge::new(unsafe {
+            bindings::gpiod_line_config_get_edge_detection_offset(self.config, offset)
+        } as u32)
+    }
+
+    /// Set the default bias setting.
+    pub fn set_bias_default(&mut self, bias: Bias) {
+        unsafe {
+            bindings::gpiod_line_config_set_bias_default(self.config, bias.gpiod_bias() as i32)
+        }
+    }
+
+    /// Set the bias for a single line.
+    pub fn set_bias_override(&mut self, bias: Bias, offset: u32) {
+        unsafe {
+            bindings::gpiod_line_config_set_bias_override(
+                self.config,
+                bias.gpiod_bias() as i32,
+                offset,
+            )
+        }
+    }
+
+    /// Clear the bias for a single line.
+    pub fn clear_bias_override(&mut self, offset: u32) {
+        unsafe { bindings::gpiod_line_config_clear_bias_override(self.config, offset) }
+    }
+
+    /// Check if the bias is overridden for a line.
+    pub fn bias_is_overridden(&self, offset: u32) -> bool {
+        unsafe { bindings::gpiod_line_config_bias_is_overridden(self.config, offset) }
+    }
+
+    /// Get the default bias setting.
+    pub fn get_bias_default(&self) -> Result<Bias> {
+        Bias::new(unsafe { bindings::gpiod_line_config_get_bias_default(self.config) } as u32)
+    }
+
+    /// Get the bias setting for a given line.
+    ///
+    /// Bias setting used for the line if the config object were used in a request.
+    pub fn get_bias_offset(&self, offset: u32) -> Result<Bias> {
+        Bias::new(
+            unsafe { bindings::gpiod_line_config_get_bias_offset(self.config, offset) } as u32,
+        )
+    }
+
+    /// Set the default drive setting.
+    pub fn set_drive_default(&mut self, drive: Drive) {
+        unsafe {
+            bindings::gpiod_line_config_set_drive_default(self.config, drive.gpiod_drive() as i32)
+        }
+    }
+
+    /// Set the drive for a single line.
+    pub fn set_drive_override(&mut self, drive: Drive, offset: u32) {
+        unsafe {
+            bindings::gpiod_line_config_set_drive_override(
+                self.config,
+                drive.gpiod_drive() as i32,
+                offset,
+            )
+        }
+    }
+
+    /// clear the drive for a single line.
+    pub fn clear_drive_override(&mut self, offset: u32) {
+        unsafe { bindings::gpiod_line_config_clear_drive_override(self.config, offset) }
+    }
+
+    /// Check if the drive is overridden for a line.
+    pub fn drive_is_overridden(&self, offset: u32) -> bool {
+        unsafe { bindings::gpiod_line_config_drive_is_overridden(self.config, offset) }
+    }
+
+    /// Get the default drive setting.
+    pub fn get_drive_default(&self) -> Result<Drive> {
+        Drive::new(unsafe { bindings::gpiod_line_config_get_drive_default(self.config) } as u32)
+    }
+
+    /// Get the drive setting for a given line.
+    ///
+    /// The offset of the line for which to read the drive setting. Drive setting for the line if
+    /// the config object were used in a request.
+    pub fn get_drive_offset(&self, offset: u32) -> Result<Drive> {
+        Drive::new(
+            unsafe { bindings::gpiod_line_config_get_drive_offset(self.config, offset) } as u32,
+        )
+    }
+
+    /// Set default active-low setting.
+    pub fn set_active_low_default(&mut self, active_low: bool) {
+        unsafe { bindings::gpiod_line_config_set_active_low_default(self.config, active_low) }
+    }
+
+    /// Set active-low setting for a single line.
+    pub fn set_active_low_override(&mut self, active_low: bool, offset: u32) {
+        unsafe {
+            bindings::gpiod_line_config_set_active_low_override(self.config, active_low, offset)
+        }
+    }
+
+    /// Clear a single line's active-low setting.
+    pub fn clear_active_low_override(&mut self, offset: u32) {
+        unsafe { bindings::gpiod_line_config_clear_active_low_override(self.config, offset) }
+    }
+
+    /// Check if the active-low is overridden for a line.
+    pub fn active_low_is_overridden(&mut self, offset: u32) -> bool {
+        unsafe { bindings::gpiod_line_config_active_low_is_overridden(self.config, offset) }
+    }
+
+    /// Check the default active-low setting.
+    pub fn get_active_low_default(&self) -> bool {
+        unsafe { bindings::gpiod_line_config_get_active_low_default(self.config) }

bindings/rust/src/line_config.rs line 292

Quoted 10 lines
bindings/rust/src/line_config.rs new file +10 -0
+    }
+
+    /// Check the active-low setting of a line.
+    ///
+    /// Active-low setting for the line if the config object were used in a request.
+    pub fn get_active_low_offset(&self, offset: u32) -> bool {
+        unsafe { bindings::gpiod_line_config_get_active_low_offset(self.config, offset) }
+    }
+
+    /// Set the deafult debounce period setting.

default

Quoted 91 lines
bindings/rust/src/line_config.rs new file +91 -0
+    pub fn set_debounce_period_default(&mut self, period: Duration) {
+        unsafe {
+            bindings::gpiod_line_config_set_debounce_period_us_default(
+                self.config,
+                period.as_micros() as u64,
+            )
+        }
+    }
+
+    /// Set the debounce period for a single line.
+    pub fn set_debounce_period_override(&mut self, period: Duration, offset: u32) {
+        unsafe {
+            bindings::gpiod_line_config_set_debounce_period_us_override(
+                self.config,
+                period.as_micros() as u64,
+                offset,
+            )
+        }
+    }
+
+    /// Clear the debounce period for a single line.
+    pub fn clear_debounce_period_override(&mut self, offset: u32) {
+        unsafe {
+            bindings::gpiod_line_config_clear_debounce_period_us_override(self.config, offset)
+        }
+    }
+
+    /// Check if the debounce period setting is overridden.
+    pub fn debounce_period_is_overridden(&self, offset: u32) -> bool {
+        unsafe { bindings::gpiod_line_config_debounce_period_us_is_overridden(self.config, offset) }
+    }
+
+    /// Get the default debounce period.
+    pub fn get_debounce_period_default(&self) -> Result<Duration> {
+        Ok(Duration::from_micros(unsafe {
+            bindings::gpiod_line_config_get_debounce_period_us_default(self.config)
+        }))
+    }
+
+    /// Get the debounce period for a given line.
+    ///
+    /// Debounce period for the line if the config object were used in a request, 0 if debouncing
+    /// is disabled.
+    pub fn get_debounce_period_offset(&self, offset: u32) -> Result<Duration> {
+        Ok(Duration::from_micros(unsafe {
+            bindings::gpiod_line_config_get_debounce_period_us_offset(self.config, offset)
+        }))
+    }
+
+    /// Set the default event clock setting.
+    pub fn set_event_clock_default(&mut self, clock: EventClock) {
+        unsafe {
+            bindings::gpiod_line_config_set_event_clock_default(
+                self.config,
+                clock.gpiod_clock() as i32,
+            )
+        }
+    }
+
+    /// Set the event clock for a single line.
+    pub fn set_event_clock_override(&mut self, clock: EventClock, offset: u32) {
+        unsafe {
+            bindings::gpiod_line_config_set_event_clock_override(
+                self.config,
+                clock.gpiod_clock() as i32,
+                offset,
+            )
+        }
+    }
+
+    /// Clear the event clock for a single line.
+    pub fn clear_event_clock_override(&mut self, offset: u32) {
+        unsafe { bindings::gpiod_line_config_clear_event_clock_override(self.config, offset) }
+    }
+
+    /// Check if the event clock is overridden for a line.
+    pub fn event_clock_is_overridden(&mut self, offset: u32) -> bool {
+        unsafe { bindings::gpiod_line_config_event_clock_is_overridden(self.config, offset) }
+    }
+
+    /// Get the default event clock setting.
+    pub fn get_event_clock_default(&self) -> Result<EventClock> {
+        EventClock::new(
+            unsafe { bindings::gpiod_line_config_get_event_clock_default(self.config) } as u32,
+        )
+    }
+
+    /// Get the event clock setting for a given line.
+    ///
+    /// Event clock setting for the line if the config object were used in a request.
+    pub fn get_event_clock_offset(&self, offset: u32) -> Result<EventClock> {

bindings/rust/src/line_config.rs line 393

Quoted 10 lines
bindings/rust/src/line_config.rs new file +10 -0
+        EventClock::new(unsafe {
+            bindings::gpiod_line_config_get_event_clock_offset(self.config, offset)
+        } as u32)
+    }
+
+    /// Set the default output value setting.
+    pub fn set_output_value_default(&mut self, value: u32) {
+        unsafe { bindings::gpiod_line_config_set_output_value_default(self.config, value as i32) }
+    }
+

Make value an enum (active/inactive) not a u32 throughout. Perhaps give offsets a type as well.

Quoted 125 lines
bindings/rust/src/line_config.rs new file +100 -0
+    /// Set the output value for a line.
+    pub fn set_output_value_override(&mut self, value: u32, offset: u32) {
+        unsafe {
+            bindings::gpiod_line_config_set_output_value_override(self.config, value as i32, offset)
+        }
+    }
+
+    /// Set the output values for a set of lines.
+    pub fn set_output_values(&mut self, offsets: &[u32], values: &[i32]) -> Result<()> {
+        if offsets.len() != values.len() {
+            return Err(Error::OperationFailed(
+                "Gpio LineConfig array size mismatch",
+                IoError::new(EINVAL),
+            ));
+        }
+
+        unsafe {
+            bindings::gpiod_line_config_set_output_values(
+                self.config,
+                values.len() as c_ulong,
+                offsets.as_ptr(),
+                values.as_ptr(),
+            );
+        }
+
+        Ok(())
+    }
+
+    /// Clear the output value for a line.
+    pub fn clear_output_value_override(&mut self, offset: u32) {
+        unsafe { bindings::gpiod_line_config_clear_output_value_override(self.config, offset) }
+    }
+
+    /// Check if the output value is overridden for a line.
+    pub fn output_value_is_overridden(&self, offset: u32) -> bool {
+        unsafe { bindings::gpiod_line_config_output_value_is_overridden(self.config, offset) }
+    }
+
+    /// Get the default output value, 0 or 1.
+    pub fn get_output_value_default(&self) -> Result<u32> {
+        let value = unsafe { bindings::gpiod_line_config_get_output_value_default(self.config) };
+
+        if value != 0 && value != 1 {
+            Err(Error::OperationFailed(
+                "Gpio LineConfig get-output-value",
+                IoError::last(),
+            ))
+        } else {
+            Ok(value as u32)
+        }
+    }
+
+    /// Get the output value configured for a given line, 0 or 1.
+    pub fn get_output_value_offset(&self, offset: u32) -> Result<u32> {
+        let value =
+            unsafe { bindings::gpiod_line_config_get_output_value_offset(self.config, offset) };
+
+        if value != 0 && value != 1 {
+            Err(Error::OperationFailed(
+                "Gpio LineConfig get-output-value",
+                IoError::last(),
+            ))
+        } else {
+            Ok(value as u32)
+        }
+    }
+
+    /// Get the list of overridden offsets and the corresponding types of overridden settings.
+    pub fn get_overrides(&self) -> Result<Vec<(u32, Config)>> {
+        let num = unsafe { bindings::gpiod_line_config_get_num_overrides(self.config) } as usize;
+        if num == 0 {
+            return Ok(Vec::new());
+        }
+
+        let mut overrides = Vec::new();
+        let mut offset = vec![0_u32; num];
+        let mut props = vec![0_i32; num];
+
+        unsafe {
+            bindings::gpiod_line_config_get_overrides(
+                self.config,
+                offset.as_mut_ptr(),
+                props.as_mut_ptr(),
+            )
+        };
+
+        for i in 0..num {
+            overrides.push((offset[i], Config::new(props[i] as u32)?));
+        }
+
+        Ok(overrides)
+    }
+}
+
+impl Drop for LineConfig {
+    /// Free the line config object and release all associated resources.
+    fn drop(&mut self) {
+        unsafe { bindings::gpiod_line_config_free(self.config) }
+    }
+}
bindings/rust/src/line_info.rs new file +19 -0
new file mode 100644
index 000000000000..70b6bd6a84bb
@@ -0,0 +1,189 @@
+// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
+//
+// Copyright 2022 Linaro Ltd. All Rights Reserved.
+//     Viresh Kumar <[email protected]>
+
+use std::convert::TryFrom;
+use std::sync::Arc;
+use std::time::Duration;
+use std::{slice, str};
+
+use vmm_sys_util::errno::Error as IoError;
+
+use super::{
+    bindings, Bias, ChipInternal, Direction, Drive, Edge, Error, EventClock, InfoEvent, Result,
+};
+
+/// Line info
+///
+/// Exposes functions for retrieving kernel information about both requested and

bindings/rust/src/line_info.rs line 29

Quoted 10 lines
bindings/rust/src/line_info.rs new file +10 -0
+/// free lines.  Line info object contains an immutable snapshot of a line's status.
+///
+/// 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.
+
+pub struct LineInfo {
+    info: *mut bindings::gpiod_line_info,
+    ichip: Option<Arc<ChipInternal>>,
+    free: bool,

This flag makes no sense - the info always needs to be freed no matter which path, watched or not, was taken to get it from the C API.

Implement a gpiowatch example to demonstrate using this part of the API.

Quoted 15 lines
bindings/rust/src/line_info.rs new file +15 -0
+}
+
+impl LineInfo {
+    /// Get a snapshot of information about the line and optionally start watching it for changes.
+    pub(crate) fn new(ichip: Arc<ChipInternal>, offset: u32, watch: bool) -> Result<Self> {
+        let info = if watch {
+            unsafe { bindings::gpiod_chip_watch_line_info(ichip.chip(), offset) }
+        } else {
+            unsafe { bindings::gpiod_chip_get_line_info(ichip.chip(), offset) }
+        };
+
+        if info.is_null() {
+            return Err(Error::OperationFailed(
+                "Gpio LineInfo line-info",
+                IoError::last(),

bindings/rust/src/line_info.rs line 54

Quoted 10 lines
bindings/rust/src/line_info.rs new file +10 -0
+            ));
+        }
+
+        Ok(Self {
+            info,
+            ichip: if watch { Some(ichip) } else { None },
+            free: watch,
+        })
+    }
+

Why bother implementing the get and watch cases in the one function? Move the corresponding path into where it is called in the chip methods and remove this method.

bindings/rust/src/line_info.rs line 64

Quoted 10 lines
bindings/rust/src/line_info.rs new file +10 -0
+    /// Stop watching the line
+    pub fn unwatch(&mut self) {
+        if let Some(ichip) = &self.ichip {
+            unsafe {
+                bindings::gpiod_chip_unwatch_line_info(ichip.chip(), self.get_offset());
+            }
+            self.ichip = None;
+        }
+    }
+

This should be a method of the chip, not the info.

Quoted 85 lines
bindings/rust/src/line_info.rs new file +85 -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 get_offset(&self) -> u32 {
+        unsafe { bindings::gpiod_line_info_get_offset(self.info) }
+    }
+
+    /// Get GPIO line's name.
+    pub fn get_name(&self) -> Result<&str> {
+        // SAFETY: The string returned by libgpiod is guaranteed to live as long
+        // as the `struct LineInfo`.
+        let name = unsafe { bindings::gpiod_line_info_get_name(self.info) };
+        if name.is_null() {
+            return Err(Error::NameNotFound("GPIO line's name"));
+        }
+
+        // SAFETY: The string is guaranteed to be valid here.
+        str::from_utf8(unsafe {
+            slice::from_raw_parts(name as *const u8, bindings::strlen(name) as usize)
+        })
+        .map_err(Error::InvalidString)
+    }
+
+    /// 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 { bindings::gpiod_line_info_is_used(self.info) }
+    }
+
+    /// Get the GPIO line's consumer name.
+    pub fn get_consumer(&self) -> Result<&str> {
+        // SAFETY: The string returned by libgpiod is guaranteed to live as long
+        // as the `struct LineInfo`.
+        let name = unsafe { bindings::gpiod_line_info_get_consumer(self.info) };
+        if name.is_null() {
+            return Err(Error::NameNotFound("GPIO line's consumer name"));
+        }
+
+        // SAFETY: The string is guaranteed to be valid here.
+        str::from_utf8(unsafe {
+            slice::from_raw_parts(name as *const u8, bindings::strlen(name) as usize)
+        })
+        .map_err(Error::InvalidString)
+    }
+
+    /// Get the GPIO line's direction.
+    pub fn get_direction(&self) -> Result<Direction> {
+        Direction::new(unsafe { bindings::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 { bindings::gpiod_line_info_is_active_low(self.info) }
+    }
+
+    /// Get the GPIO line's bias setting.
+    pub fn get_bias(&self) -> Result<Bias> {
+        Bias::new(unsafe { bindings::gpiod_line_info_get_bias(self.info) } as u32)
+    }
+
+    /// Get the GPIO line's drive setting.
+    pub fn get_drive(&self) -> Result<Drive> {
+        Drive::new(unsafe { bindings::gpiod_line_info_get_drive(self.info) } as u32)
+    }
+
+    /// Get the current edge detection setting of the line.
+    pub fn get_edge_detection(&self) -> Result<Edge> {
+        Edge::new(unsafe { bindings::gpiod_line_info_get_edge_detection(self.info) } as u32)
+    }
+
+    /// Get the current event clock setting used for edge event timestamps.
+    pub fn get_event_clock(&self) -> Result<EventClock> {
+        EventClock::new(unsafe { bindings::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 { bindings::gpiod_line_info_is_debounced(self.info) }
+    }

bindings/rust/src/line_info.rs line 159

Quoted 10 lines
bindings/rust/src/line_info.rs new file +10 -0
+
+    /// Get the debounce period of the line.
+    pub fn get_debounce_period(&self) -> Duration {
+        Duration::from_micros(unsafe {
+            bindings::gpiod_line_info_get_debounce_period_us(self.info)
+        })
+    }
+}
+
+impl TryFrom<&InfoEvent> for LineInfo {

Is try_from appropriate for getting a contained object? "from" normally refers to a type conversion.

Quoted 16 lines
bindings/rust/src/line_info.rs new file +16 -0
+    type Error = Error;
+
+    /// Get the Line info object associated with a event.
+    fn try_from(event: &InfoEvent) -> Result<Self> {
+        let info = unsafe { bindings::gpiod_info_event_get_line_info(event.event()) };
+        if info.is_null() {
+            return Err(Error::OperationFailed(
+                "Gpio LineInfo try-from",
+                IoError::last(),
+            ));
+        }
+
+        Ok(Self {
+            info,
+            ichip: None,
+            free: false,

bindings/rust/src/line_info.rs line 185

Quoted 10 lines
bindings/rust/src/line_info.rs new file +10 -0
+        })
+    }
+}
+
+impl Drop for LineInfo {
+    fn drop(&mut self) {
+        // We must not free the Line info object created from `struct InfoEvent` by calling
+        // libgpiod API.
+        if self.free {
+            self.unwatch();

Why does dropping a LineInfo unwatch the line???

Quoted 78 lines
bindings/rust/src/line_info.rs new file +4 -0
+            unsafe { bindings::gpiod_line_info_free(self.info) }
+        }
+    }
+}
bindings/rust/src/line_request.rs new file +68 -0
new file mode 100644
index 000000000000..bb338e72671d
@@ -0,0 +1,248 @@
+// 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 IoError;
+
+use super::{bindings, ChipInternal, EdgeEventBuffer, Error, LineConfig, RequestConfig, Result};
+
+/// Line request operations
+///
+/// Allows interaction with a set of requested lines.
+pub struct LineRequest {
+    request: *mut bindings::gpiod_line_request,
+}
+
+impl LineRequest {
+    /// Request a set of lines for exclusive usage.
+    pub(crate) fn new(
+        ichip: &Arc<ChipInternal>,
+        rconfig: &RequestConfig,
+        lconfig: &LineConfig,
+    ) -> Result<Self> {
+        let request = unsafe {
+            bindings::gpiod_chip_request_lines(ichip.chip(), rconfig.config(), lconfig.config())
+        };
+
+        if request.is_null() {
+            return Err(Error::OperationFailed(
+                "Gpio LineRequest request-lines",
+                IoError::last(),
+            ));
+        }
+
+        Ok(Self { request })
+    }
+
+    /// Get the number of lines in the request.
+    pub fn get_num_lines(&self) -> u32 {
+        unsafe { bindings::gpiod_line_request_get_num_lines(self.request) as u32 }
+    }
+
+    /// Get the offsets of lines in the request.
+    pub fn get_offsets(&self) -> Vec<u32> {
+        let mut offsets = vec![0; self.get_num_lines() as usize];
+
+        unsafe { bindings::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 get_value(&self, offset: u32) -> Result<u32> {
+        let value = unsafe { bindings::gpiod_line_request_get_value(self.request, offset) };
+
+        if value != 0 && value != 1 {
+            Err(Error::OperationFailed(
+                "Gpio LineRequest get-value",
+                IoError::last(),
+            ))
+        } else {
+            Ok(value as u32)
+        }
+    }

bindings/rust/src/line_request.rs line 78

Quoted 10 lines
bindings/rust/src/line_request.rs new file +10 -0
+
+    /// Get values of a subset of lines associated with the request.
+    pub fn get_values_subset(&self, offsets: &[u32], values: &mut Vec<i32>) -> Result<()> {
+        if offsets.len() != values.len() {
+            return Err(Error::OperationFailed(
+                "Gpio LineRequest array size mismatch",
+                IoError::new(EINVAL),
+            ));
+        }
+

Returned values are awkward to use as the user has to index into them using the index corresponding to the offset in offsets. Provide a Values type that maps offset to value, e.g. using an IntMap, and pass that in instead of separate offsets and values arrays.

Same applies to set_values_subset().

Quoted 18 lines
bindings/rust/src/line_request.rs new file +18 -0
+        let ret = unsafe {
+            bindings::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(
+                "Gpio LineRequest get-values-subset",
+                IoError::last(),
+            ))
+        } else {
+            Ok(())
+        }
+    }

bindings/rust/src/line_request.rs line 106

Quoted 10 lines
bindings/rust/src/line_request.rs new file +10 -0
+
+    /// Get values of all lines associated with the request.
+    pub fn get_values(&self, values: &mut Vec<i32>) -> Result<()> {
+        if values.len() != self.get_num_lines() as usize {
+            return Err(Error::OperationFailed(
+                "Gpio LineRequest array size mismatch",
+                IoError::new(EINVAL),
+            ));
+        }
+

Incorporating the get_values_subset() comments above, this becomes a special case of that, so merge the two into one function.

Similarly the sets.

Quoted 103 lines
bindings/rust/src/line_request.rs new file +103 -0
+        let ret =
+            unsafe { bindings::gpiod_line_request_get_values(self.request, values.as_mut_ptr()) };
+
+        if ret == -1 {
+            Err(Error::OperationFailed(
+                "Gpio LineRequest get-values",
+                IoError::last(),
+            ))
+        } else {
+            Ok(())
+        }
+    }
+
+    /// Set the value of a single line associated with the request.
+    pub fn set_value(&self, offset: u32, value: i32) -> Result<()> {
+        let ret = unsafe { bindings::gpiod_line_request_set_value(self.request, offset, !!value) };
+
+        if ret == -1 {
+            Err(Error::OperationFailed(
+                "Gpio LineRequest set-value",
+                IoError::last(),
+            ))
+        } else {
+            Ok(())
+        }
+    }
+
+    /// Get values of a subset of lines associated with the request.
+    pub fn set_values_subset(&self, offsets: &[u32], values: &[i32]) -> Result<()> {
+        if offsets.len() != values.len() {
+            return Err(Error::OperationFailed(
+                "Gpio LineRequest array size mismatch",
+                IoError::new(EINVAL),
+            ));
+        }
+
+        let ret = unsafe {
+            bindings::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(
+                "Gpio LineRequest set-values-subset",
+                IoError::last(),
+            ))
+        } else {
+            Ok(())
+        }
+    }
+
+    /// Get values of all lines associated with the request.
+    pub fn set_values(&self, values: &[i32]) -> Result<()> {
+        if values.len() != self.get_num_lines() as usize {
+            return Err(Error::OperationFailed(
+                "Gpio LineRequest array size mismatch",
+                IoError::new(EINVAL),
+            ));
+        }
+
+        let ret = unsafe { bindings::gpiod_line_request_set_values(self.request, values.as_ptr()) };
+
+        if ret == -1 {
+            Err(Error::OperationFailed(
+                "Gpio LineRequest set-values",
+                IoError::last(),
+            ))
+        } else {
+            Ok(())
+        }
+    }
+
+    /// Update the configuration of lines associated with the line request.
+    pub fn reconfigure_lines(&self, lconfig: &LineConfig) -> Result<()> {
+        let ret = unsafe {
+            bindings::gpiod_line_request_reconfigure_lines(self.request, lconfig.config())
+        };
+
+        if ret == -1 {
+            Err(Error::OperationFailed(
+                "Gpio LineRequest reconfigure-lines",
+                IoError::last(),
+            ))
+        } else {
+            Ok(())
+        }
+    }
+
+    /// Get the file descriptor associated with the line request.
+    pub fn get_fd(&self) -> u32 {
+        unsafe { bindings::gpiod_line_request_get_fd(self.request) as u32 }
+    }
+
+    /// Wait for edge events on any of the lines associated with the request.
+    pub fn wait_edge_event(&self, timeout: Duration) -> Result<()> {
+        let ret = unsafe {
+            bindings::gpiod_line_request_wait_edge_event(self.request, timeout.as_nanos() as i64)
+        };
+

bindings/rust/src/line_request.rs line 219

Quoted 10 lines
bindings/rust/src/line_request.rs new file +10 -0
+        match ret {
+            -1 => Err(Error::OperationFailed(
+                "Gpio LineRequest edge-event-wait",
+                IoError::last(),
+            )),
+            0 => Err(Error::OperationTimedOut),
+            _ => Ok(()),
+        }
+    }
+

Same comment on Duration/blocking as per info events. Same comment on return type as per info events.

bindings/rust/src/line_request.rs line 223

Quoted 4 lines
bindings/rust/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_event(&self, buffer: &EdgeEventBuffer, max_events: u32) -> Result<u32> {

Should be read_edge_events() as it reads multiple. (same comment applies to the C API, btw)

max_events should be derived from buffer len (assuming EdgeEventBuffer has a Vec-like API for len and capacity).

Quoted 130 lines
bindings/rust/src/line_request.rs new file +25 -0
+        let ret = unsafe {
+            bindings::gpiod_line_request_read_edge_event(
+                self.request,
+                buffer.buffer(),
+                max_events as c_ulong,
+            )
+        };
+
+        if ret == -1 {
+            Err(Error::OperationFailed(
+                "Gpio LineRequest edge-event-read",
+                IoError::last(),
+            ))
+        } else {
+            Ok(ret as u32)
+        }
+    }
+}
+
+impl Drop for LineRequest {
+    /// Release the requested lines and free all associated resources.
+    fn drop(&mut self) {
+        unsafe { bindings::gpiod_line_request_release(self.request) }
+    }
+}
bindings/rust/src/request_config.rs new file +99 -0
new file mode 100644
index 000000000000..3e667d28cc1f
@@ -0,0 +1,122 @@
+// 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_char, c_ulong};
+use std::{slice, str};
+
+use vmm_sys_util::errno::Error as IoError;
+
+use super::{bindings, Error, Result};
+
+/// Request configuration objects
+///
+/// Request config objects are used to pass a set of options to the kernel at
+/// the time of the line request. Similarly to the line-config - the mutators
+/// don't return error values. If the values are invalid, in general they are
+/// silently adjusted to acceptable ranges.
+
+pub struct RequestConfig {
+    config: *mut bindings::gpiod_request_config,
+}
+
+impl RequestConfig {
+    /// Create a new request config object.
+    pub fn new() -> Result<Self> {
+        let config = unsafe { bindings::gpiod_request_config_new() };
+        if config.is_null() {
+            return Err(Error::OperationFailed(
+                "Gpio RequestConfig new",
+                IoError::last(),
+            ));
+        }
+
+        Ok(Self { config })
+    }
+
+    /// Private helper, Returns gpiod_request_config
+    pub(crate) fn config(&self) -> *mut bindings::gpiod_request_config {
+        self.config
+    }
+
+    /// 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 {
+            bindings::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 get_consumer(&self) -> Result<&str> {
+        // SAFETY: The string returned by libgpiod is guaranteed to live as long
+        // as the `struct RequestConfig`.
+        let consumer = unsafe { bindings::gpiod_request_config_get_consumer(self.config) };
+        if consumer.is_null() {
+            return Err(Error::OperationFailed(
+                "Gpio RequestConfig get-consumer",
+                IoError::last(),
+            ));
+        }
+
+        // SAFETY: The string is guaranteed to be valid here.
+        str::from_utf8(unsafe {
+            slice::from_raw_parts(consumer as *const u8, bindings::strlen(consumer) as usize)
+        })
+        .map_err(Error::InvalidString)
+    }
+
+    /// Set the offsets of the lines to be requested.
+    ///
+    /// If too many offsets were specified, the offsets above the limit accepted
+    /// by the kernel (64 lines) are silently dropped.
+    pub fn set_offsets(&self, offsets: &[u32]) {
+        unsafe {
+            bindings::gpiod_request_config_set_offsets(
+                self.config,
+                offsets.len() as c_ulong,
+                offsets.as_ptr(),
+            )
+        }
+    }
+
+    /// Get the offsets of lines in the request config.
+    pub fn get_offsets(&self) -> Vec<u32> {
+        let num = unsafe { bindings::gpiod_request_config_get_num_offsets(self.config) };
+        let mut offsets = vec![0; num as usize];
+
+        unsafe { bindings::gpiod_request_config_get_offsets(self.config, offsets.as_mut_ptr()) };
+        offsets
+    }

bindings/rust/src/request_config.rs line 109

Quoted 10 lines
bindings/rust/src/request_config.rs new file +10 -0
+
+    /// Set the size of the kernel event buffer for the request.
+    ///
+    /// The kernel may adjust the value if it's too high. If set to 0, the
+    /// default value will be used.
+    pub fn set_event_buffer_size(&self, size: u32) {
+        unsafe {
+            bindings::gpiod_request_config_set_event_buffer_size(self.config, size as c_ulong)
+        }
+    }

The kernel may adjust the value regardless - this value is a tentative suggestion to the kernel (the kernel buffers have to be sized in 2^N, so it generally rounds up to the next power of 2, within limits).

bindings/rust/src/request_config.rs line 114

Quoted 5 lines
bindings/rust/src/request_config.rs new file +5 -0
+
+    /// Get the edge event buffer size for the request config.
+    pub fn get_event_buffer_size(&self) -> u32 {
+        unsafe { bindings::gpiod_request_config_get_event_buffer_size(self.config) as u32 }
+    }

You might want to note that this just reads the value from the config. The actual value used by the kernel is not made available to user space.

Quoted 11 lines
bindings/rust/src/request_config.rs new file +8 -0
+}
+
+impl Drop for RequestConfig {
+    /// Free the request config object and release all associated resources.
+    fn drop(&mut self) {
+        unsafe { bindings::gpiod_request_config_free(self.config) }
+    }
+}
Signature · 1 line
2.31.1.272.g89b43f80a514

Cheers,
Kent.

#21 lore raw

Re: [PATCH V4 4/8] libgpiod: Add rust wrapper crate

Quoted 5 lines · Kent Gibson

bindings/rust/src/chip.rs line 85

Quoted 2 lines · Viresh Kumar
bindings/rust/src/chip.rs new file +2 -0
+    /// Get the path used to find the chip.
+    pub fn get_path(&self) -> Result<&str> {
Quoted 7 lines

It seems absurd that a method that simply returns the path provided to open() requires a Result, but that is a consequence of wrapping.

I was considering suggesting caching a copy in struct Chip, but all the other methods require Results so at least this is consistent :-(.

Yay, more unwrapping than Xmas past.

:)

Quoted 5 lines

bindings/rust/src/chip.rs line 88

Quoted 3 lines
bindings/rust/src/chip.rs new file +3 -0
+        // SAFETY: The string returned by libgpiod is guaranteed to live as long
+        // as the `struct Chip`.
+        let path = unsafe { bindings::gpiod_chip_get_path(self.ichip.chip()) };

Trusting that it is never NULL?

I believe we discussed that early on (few months back) and decided this will never be NULL and since the Rust wrappers are pretty much part of the same repository, we can take that as a guarantee. An out-of-this-repository user can't really assume that.

Quoted 1 line

Add a null check to be sure.

But I am fine with this as well.

Quoted 5 lines

bindings/rust/src/chip.rs line 128

Quoted 2 lines
bindings/rust/src/chip.rs new file +2 -0
+    /// Wait for line status events on any of the watched lines on the chip.
+    pub fn wait_info_event(&self, timeout: Duration) -> Result<()> {

Durations cannot be negative, so caller cannot make this block indefinitely. Make timeout an Option? (no timeout => block)

I didn't know we want the always blocking capability as well. Yeah, Option sounds like the right approach.

So in that case we just pass -1 to gpiod_chip_wait_info_event() ?

Quoted 14 lines

bindings/rust/src/chip_info.rs line 17

Quoted 10 lines
bindings/rust/src/chip.rs new file +3 -0
+        let ret = unsafe {
+            bindings::gpiod_chip_wait_info_event(self.ichip.chip(), timeout.as_nanos() as i64)
+        };
bindings/rust/src/chip_info.rs +5 -0
+/// GPIO chip Information
+pub struct ChipInfo {
+    info: *mut bindings::gpiod_chip_info,
+}
+

Consider modules and namespaces rather than lumping everything in the gpiod space.

Quoted 1 line

e.g. gpiod::ChipInfo -> gpiod::chip::Info

The modules are already there, as file names. So what we really have is gpiod::chip_info::ChipInfo (yeah it isn't great for sure). But then it looked tougher/complex/unnecessary for end users to know the internals of a dependency and so I did this:

pub use crate::chip::*;
pub use crate::edge_event::*;
pub use crate::event_buffer::*;
pub use crate::info_event::*;
pub use crate::line_config::*;
pub use crate::line_info::*;
pub use crate::line_request::*;
pub use crate::request_config::*;

which puts everything under gpiod::*. I think it is easier for users that way. The modules are fine for in-crate management, but for end user they shouldn't be visible.

Quoted 9 lines

bindings/rust/src/lib.rs line 56

Quoted 7 lines
bindings/rust/src/lib.rs +5 -0
+/// Error codes for libgpiod operations
+#[derive(Copy, Clone, Debug, PartialEq, ThisError)]
+pub enum Error {
 
+    #[error("Operation {0} Failed: {1}")]
+    OperationFailed(&'static str, IoError),

Use an enum for the operation rather than a string?

Not sure I understood this.

Quoted 14 lines

And if it returns an IoError it must be an IoOperation? Else if the IoError is just an errno then call it that.

Quoted 11 lines
bindings/rust/src/lib.rs +1 -0
+}
bindings/rust/src/line_info.rs +8 -0
+/// 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.
+///
+/// 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.
Quoted 8 lines

bindings/rust/src/line_info.rs line 29

Quoted 5 lines
bindings/rust/src/line_info.rs +5 -0
+
+pub struct LineInfo {
+    info: *mut bindings::gpiod_line_info,
+    ichip: Option<Arc<ChipInternal>>,
+    free: bool,

This flag makes no sense - the info always needs to be freed no matter which path, watched or not, was taken to get it from the C API.

Not the one created with gpiod_info_event_get_line_info(), else it will result in double free.

Quoted 11 lines

bindings/rust/src/line_info.rs line 64

Quoted 10 lines
bindings/rust/src/line_info.rs +10 -0
+    /// Stop watching the line
+    pub fn unwatch(&mut self) {
+        if let Some(ichip) = &self.ichip {
+            unsafe {
+                bindings::gpiod_chip_unwatch_line_info(ichip.chip(), self.get_offset());
+            }
+            self.ichip = None;
+        }
+    }
+
Quoted 1 line

This should be a method of the chip, not the info.

I think there were some issues with my understanding of the whole watch thing. Is there any existing example of gpiowatch somewhere ?

Quoted 4 lines
Quoted 1 line

+impl TryFrom<&InfoEvent> for LineInfo {

Is try_from appropriate for getting a contained object? "from" normally refers to a type conversion.

Not sure, but as most of new() is going away here, I will likely move this to info_event as well. No try-from with that.

Quoted 8 lines

bindings/rust/src/line_info.rs line 185

Quoted 6 lines
bindings/rust/src/line_info.rs +6 -0
+impl Drop for LineInfo {
+    fn drop(&mut self) {
+        // We must not free the Line info object created from `struct InfoEvent` by calling
+        // libgpiod API.
+        if self.free {
+            self.unwatch();

Why does dropping a LineInfo unwatch the line???

Because I thought it is related to the Line's info and we won't wanna watch once line info is gone. Again, it is my wrong interpretation.

Quoted 10 lines

bindings/rust/src/line_info.rs

Quoted 9 lines
bindings/rust/src/line_info.rs +9 -0
+    /// Get values of a subset of lines associated with the request.
+    pub fn get_values_subset(&self, offsets: &[u32], values: &mut Vec<i32>) -> Result<()> {
+        if offsets.len() != values.len() {
+            return Err(Error::OperationFailed(
+                "Gpio LineRequest array size mismatch",
+                IoError::new(EINVAL),
+            ));
+        }
+
Quoted 4 lines

Returned values are awkward to use as the user has to index into them using the index corresponding to the offset in offsets. Provide a Values type that maps offset to value, e.g. using an IntMap, and pass that in instead of separate offsets and values arrays.

We would need to separate out the offsets then as that's what gpiod_line_request_get_values_subset() needs. Maybe take offsets, like now, as an argument and return Result<IntMap> ?

Quoted 1 line

Same applies to set_values_subset().

That would be fine here though.

Quoted 14 lines

bindings/rust/src/line_info.rs

Quoted 9 lines
bindings/rust/src/line_info.rs +9 -0
+    /// Set the size of the kernel event buffer for the request.
+    ///
+    /// The kernel may adjust the value if it's too high. If set to 0, the
+    /// default value will be used.
+    pub fn set_event_buffer_size(&self, size: u32) {
+        unsafe {
+            bindings::gpiod_request_config_set_event_buffer_size(self.config, size as c_ulong)
+        }
+    }

The kernel may adjust the value regardless - this value is a tentative suggestion to the kernel (the kernel buffers have to be sized in 2^N, so it generally rounds up to the next power of 2, within limits).

Quoted 8 lines

bindings/rust/src/line_info.rs

Quoted 5 lines
bindings/rust/src/line_info.rs +5 -0
+
+    /// Get the edge event buffer size for the request config.
+    pub fn get_event_buffer_size(&self) -> u32 {
+        unsafe { bindings::gpiod_request_config_get_event_buffer_size(self.config) as u32 }
+    }

You might want to note that this just reads the value from the config. The actual value used by the kernel is not made available to user space.

Do you want me to add these two comments for the above two routines ?

Other comments, which I removed, look acceptable to me. Will try to incorporate all that in the next version.

Thanks a lot for the very thorough review.

Signature · 1 line
viresh
#22 lore raw

Re: [PATCH V4 4/8] libgpiod: Add rust wrapper crate

Quoted 22 lines · Viresh Kumar
Quoted 5 lines · Kent Gibson

bindings/rust/src/chip.rs line 85

Quoted 2 lines · Viresh Kumar
bindings/rust/src/chip.rs new file +2 -0
+    /// Get the path used to find the chip.
+    pub fn get_path(&self) -> Result<&str> {
Quoted 7 lines

It seems absurd that a method that simply returns the path provided to open() requires a Result, but that is a consequence of wrapping.

I was considering suggesting caching a copy in struct Chip, but all the other methods require Results so at least this is consistent :-(.

Yay, more unwrapping than Xmas past.

:)

Quoted 5 lines

bindings/rust/src/chip.rs line 88

Quoted 3 lines
bindings/rust/src/chip.rs new file +3 -0
+        // SAFETY: The string returned by libgpiod is guaranteed to live as long
+        // as the `struct Chip`.
+        let path = unsafe { bindings::gpiod_chip_get_path(self.ichip.chip()) };

Trusting that it is never NULL?

Quoted 9 lines

I believe we discussed that early on (few months back) and decided this will never be NULL and since the Rust wrappers are pretty much part of the same repository, we can take that as a guarantee. An out-of-this-repository user can't really assume that.

Quoted 1 line

Add a null check to be sure.

But I am fine with this as well.

It should never return NULL. At the moment.
I would prefer to have NULL checks for all cases, not assume anything about the C implementation, and to be consistent with other places where you do NULL checks. As it stands when I see this I need to go check the C to see if this is a reasonable exception or not. And I'm lazy.

Quoted 41 lines
Quoted 5 lines

bindings/rust/src/chip.rs line 128

Quoted 2 lines
bindings/rust/src/chip.rs new file +2 -0
+    /// Wait for line status events on any of the watched lines on the chip.
+    pub fn wait_info_event(&self, timeout: Duration) -> Result<()> {

Durations cannot be negative, so caller cannot make this block indefinitely. Make timeout an Option? (no timeout => block)

I didn't know we want the always blocking capability as well. Yeah, Option sounds like the right approach.

So in that case we just pass -1 to gpiod_chip_wait_info_event() ?

Quoted 14 lines

bindings/rust/src/chip_info.rs line 17

Quoted 10 lines
bindings/rust/src/chip.rs new file +3 -0
+        let ret = unsafe {
+            bindings::gpiod_chip_wait_info_event(self.ichip.chip(), timeout.as_nanos() as i64)
+        };
bindings/rust/src/chip_info.rs +5 -0
+/// GPIO chip Information
+pub struct ChipInfo {
+    info: *mut bindings::gpiod_chip_info,
+}
+

Consider modules and namespaces rather than lumping everything in the gpiod space.

Quoted 1 line

e.g. gpiod::ChipInfo -> gpiod::chip::Info

The modules are already there, as file names. So what we really have is gpiod::chip_info::ChipInfo (yeah it isn't great for sure). But then it looked tougher/complex/unnecessary for end users to know the internals of a dependency and so I did this:

pub use crate::chip::*;
pub use crate::edge_event::*;
pub use crate::event_buffer::*;
pub use crate::info_event::*;
pub use crate::line_config::*;
pub use crate::line_info::*;
pub use crate::line_request::*;
pub use crate::request_config::*;

Quoted 4 lines

which puts everything under gpiod::*. I think it is easier for users that way. The modules are fine for in-crate management, but for end user they shouldn't be visible.

The main problem I have with putting everything in the top level is the generated docs. You get everything dumped on you, so all structs, enums and functions, and it isn't clear to the user what the logical starting point is.
If things are tiered then you can introduce them more gradually, or keep them out of their way if they are unlikely to use them (e.g. ChipInfo, InfoEvent).

Quoted 5 lines
Quoted 4 lines
Quoted 4 lines
bindings/rust/src/lib.rs +3 -0
+/// Error codes for libgpiod operations
+#[derive(Copy, Clone, Debug, PartialEq, ThisError)]
+pub enum Error {
Quoted 7 lines
Quoted 4 lines

bindings/rust/src/lib.rs line 56

Quoted 2 lines
bindings/rust/src/lib.rs +2 -0
+    #[error("Operation {0} Failed: {1}")]
+    OperationFailed(&'static str, IoError),

Use an enum for the operation rather than a string?

Not sure I understood this.

Rather than passing a static str, define an enum with a variant to identify the operation. Like std::io::ErrorKind (not sure that is the best example).

Quoted 23 lines
Quoted 14 lines

And if it returns an IoError it must be an IoOperation? Else if the IoError is just an errno then call it that.

Quoted 11 lines
bindings/rust/src/lib.rs +1 -0
+}
bindings/rust/src/line_info.rs +8 -0
+/// 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.
+///
+/// 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.
Quoted 8 lines

bindings/rust/src/line_info.rs line 29

Quoted 5 lines
bindings/rust/src/line_info.rs +5 -0
+
+pub struct LineInfo {
+    info: *mut bindings::gpiod_line_info,
+    ichip: Option<Arc<ChipInternal>>,
+    free: bool,

This flag makes no sense - the info always needs to be freed no matter which path, watched or not, was taken to get it from the C API.

Quoted 3 lines

Not the one created with gpiod_info_event_get_line_info(), else it will result in double free.

Ah, down in the try_from. Fair enough. Though the other two cases both need to be freed. And I would prefer the field name changed to match the EdgeEvent - which ever way you go with that.

Quoted 11 lines
Quoted 11 lines
Quoted 10 lines
bindings/rust/src/line_info.rs +10 -0
+    /// Stop watching the line
+    pub fn unwatch(&mut self) {
+        if let Some(ichip) = &self.ichip {
+            unsafe {
+                bindings::gpiod_chip_unwatch_line_info(ichip.chip(), self.get_offset());
+            }
+            self.ichip = None;
+        }
+    }
+
Quoted 5 lines
Quoted 1 line

This should be a method of the chip, not the info.

I think there were some issues with my understanding of the whole watch thing. Is there any existing example of gpiowatch somewhere ?

There is one in my proposed tool changes for v2[1]

[1] https://lore.kernel.org/linux-gpio/[email protected]/

Quoted 15 lines
Quoted 4 lines
Quoted 1 line

+impl TryFrom<&InfoEvent> for LineInfo {

Is try_from appropriate for getting a contained object? "from" normally refers to a type conversion.

Not sure, but as most of new() is going away here, I will likely move this to info_event as well. No try-from with that.

Quoted 7 lines
Quoted 6 lines
bindings/rust/src/line_info.rs +6 -0
+impl Drop for LineInfo {
+    fn drop(&mut self) {
+        // We must not free the Line info object created from `struct InfoEvent` by calling
+        // libgpiod API.
+        if self.free {
+            self.unwatch();
Quoted 5 lines
Quoted 1 line

Why does dropping a LineInfo unwatch the line???

Because I thought it is related to the Line's info and we won't wanna watch once line info is gone. Again, it is my wrong interpretation.

Yeah, no. You can get LineInfoChange events from the chip similar to the EdgeEvents from a line request. unwatching when here is akin to disabling edge detection when you free an edge event.

Watching and unwatching line info are at chip scope, and your bindings should never unwatch themselves - only wrap the C and provide a method on the Chip for the user to call.

Quoted 10 lines
Quoted 10 lines
Quoted 9 lines
bindings/rust/src/line_info.rs +9 -0
+    /// Get values of a subset of lines associated with the request.
+    pub fn get_values_subset(&self, offsets: &[u32], values: &mut Vec<i32>) -> Result<()> {
+        if offsets.len() != values.len() {
+            return Err(Error::OperationFailed(
+                "Gpio LineRequest array size mismatch",
+                IoError::new(EINVAL),
+            ));
+        }
+
Quoted 9 lines
Quoted 4 lines

Returned values are awkward to use as the user has to index into them using the index corresponding to the offset in offsets. Provide a Values type that maps offset to value, e.g. using an IntMap, and pass that in instead of separate offsets and values arrays.

We would need to separate out the offsets then as that's what gpiod_line_request_get_values_subset() needs. Maybe take offsets, like now, as an argument and return Result<IntMap> ?

Don't use IntMap raw, use it as the basis of a Values type.

You can pull the keys from the map to determine which lines to get. If the map is empty get them all.
Hmmm, that is assuming you have the requested offsets on hand at that point.

Quoted 24 lines
Quoted 1 line

Same applies to set_values_subset().

That would be fine here though.

Quoted 20 lines

bindings/rust/src/line_info.rs

Quoted 9 lines
bindings/rust/src/line_info.rs +9 -0
+    /// Set the size of the kernel event buffer for the request.
+    ///
+    /// The kernel may adjust the value if it's too high. If set to 0, the
+    /// default value will be used.
+    pub fn set_event_buffer_size(&self, size: u32) {
+        unsafe {
+            bindings::gpiod_request_config_set_event_buffer_size(self.config, size as c_ulong)
+        }
+    }

The kernel may adjust the value regardless - this value is a tentative suggestion to the kernel (the kernel buffers have to be sized in 2^N, so it generally rounds up to the next power of 2, within limits).

Quoted 5 lines
bindings/rust/src/line_info.rs +5 -0
+
+    /// Get the edge event buffer size for the request config.
+    pub fn get_event_buffer_size(&self) -> u32 {
+        unsafe { bindings::gpiod_request_config_get_event_buffer_size(self.config) as u32 }
+    }
Quoted 5 lines
Quoted 2 lines

You might want to note that this just reads the value from the config. The actual value used by the kernel is not made available to user space.

Do you want me to add these two comments for the above two routines ?

Not add verbatim. Maybe work them in.
Probably should look at revising the C API comments and then just mirror that.

Quoted 5 lines

Other comments, which I removed, look acceptable to me. Will try to incorporate all that in the next version.

Thanks a lot for the very thorough review.

No problem. Thanks for the effort you've put into them.

Cheers,
Kent.

#23 lore raw

Re: [PATCH V4 4/8] libgpiod: Add rust wrapper crate

Quoted 6 lines · Kent Gibson

It should never return NULL. At the moment.
I would prefer to have NULL checks for all cases, not assume anything about the C implementation, and to be consistent with other places where you do NULL checks. As it stands when I see this I need to go check the C to see if this is a reasonable exception or not. And I'm lazy.

Ideally the C side would document the guarantees explicitly instead, and then the Rust side can rely on them.

In any case, if a given C API never returned an invalid pointer and suddenly it starts doing so in some cases, I would consider that a breaking change in practice, which would likely break C users too.

A potential compromise meanwhile is debug_assert! to at least test those assumptions.

Cheers,
Miguel

#24 lore raw

Re: [PATCH V4 4/8] libgpiod: Add rust wrapper crate

Quoted 11 lines · Miguel Ojeda
Quoted 6 lines · Kent Gibson

It should never return NULL. At the moment.
I would prefer to have NULL checks for all cases, not assume anything about the C implementation, and to be consistent with other places where you do NULL checks. As it stands when I see this I need to go check the C to see if this is a reasonable exception or not. And I'm lazy.

Ideally the C side would document the guarantees explicitly instead, and then the Rust side can rely on them.

Unfortunately the C header doesn't currently provide any guarantee - except in the cases where it CAN return NULL.
But we can fix that.

Quoted 4 lines

In any case, if a given C API never returned an invalid pointer and suddenly it starts doing so in some cases, I would consider that a breaking change in practice, which would likely break C users too.

Not sure I'm onboard with that. Unless the API has a contract not to return a NULL then it is free to at a later date. The user should always assume that NULL is a possibility, even if they have never seen one.

But in practice you are probably right.

Quoted 3 lines

A potential compromise meanwhile is debug_assert! to at least test those assumptions.

I'd be fine with that.
I'd also be satisfied with a comment in the Rust that the C guarantees a non-NULL where that is the case. That would at least demonstrate that the possibility has been duly considered.

Cheers,
Kent.

#25 lore raw

Re: [PATCH V4 4/8] libgpiod: Add rust wrapper crate

Quoted 4 lines · Kent Gibson

Unfortunately the C header doesn't currently provide any guarantee - except in the cases where it CAN return NULL.
But we can fix that.

Yeah, fixing that is what I was suggesting, since it is a possibility here, and would improve things for C users too.

Quoted 6 lines

Not sure I'm onboard with that. Unless the API has a contract not to return a NULL then it is free to at a later date. The user should always assume that NULL is a possibility, even if they have never seen one.

But in practice you are probably right.

I definitely agree that a client should aim to avoid assuming anything.

However, if we are strict, given C pointers are unconstrained, all pointers would be useless unless told otherwise, because checking for NULL is not a guarantee of validity either.

Also, if an C API just says "returns the name", for instance, it is reasonable to assume it is a valid name because it is not said otherwise (e.g. it does not say "returns the name, if available" nor "returns an optional name").

And, of course, eventually consumers will end up relying on your particular implementation no matter what, and returning invalid pointers where there weren't before is a very dangerous idea for a C library.

Quoted 4 lines

I'd be fine with that.
I'd also be satisfied with a comment in the Rust that the C guarantees a non-NULL where that is the case. That would at least demonstrate that the possibility has been duly considered.

I think the current SAFETY comment already intends to imply that, but yeah, it could be clarified.

In any case, I would say it always returns a valid pointer, not "non-NULL", since the latter does not really show it is a valid pointer (it could point to a non-NULL, bad address).

Cheers,
Miguel

#26 lore raw

Re: [PATCH V4 4/8] libgpiod: Add rust wrapper crate

Quoted 27 lines · Miguel Ojeda
Quoted 4 lines · Kent Gibson

Unfortunately the C header doesn't currently provide any guarantee - except in the cases where it CAN return NULL.
But we can fix that.

Yeah, fixing that is what I was suggesting, since it is a possibility here, and would improve things for C users too.

Quoted 6 lines

Not sure I'm onboard with that. Unless the API has a contract not to return a NULL then it is free to at a later date. The user should always assume that NULL is a possibility, even if they have never seen one.

But in practice you are probably right.

I definitely agree that a client should aim to avoid assuming anything.

However, if we are strict, given C pointers are unconstrained, all pointers would be useless unless told otherwise, because checking for NULL is not a guarantee of validity either.

Also, if an C API just says "returns the name", for instance, it is reasonable to assume it is a valid name because it is not said otherwise (e.g. it does not say "returns the name, if available" nor "returns an optional name").

Quoted 5 lines

And, of course, eventually consumers will end up relying on your particular implementation no matter what, and returning invalid pointers where there weren't before is a very dangerous idea for a C library.

All true, but practically speaking the only cases we need to be concerned with here are NULL and valid. NULL is the only one we can detect for certain, and we have no alternative but to assume valid if not.

Quoted 8 lines
Quoted 4 lines

I'd be fine with that.
I'd also be satisfied with a comment in the Rust that the C guarantees a non-NULL where that is the case. That would at least demonstrate that the possibility has been duly considered.

I think the current SAFETY comment already intends to imply that, but yeah, it could be clarified.

The comment is:

    // SAFETY: The string is guaranteed to be valid here.

and that is whether there a NULL check or not, so it isn't clear what the source of the guarantee is.
I would prefer:

    // SAFETY: The string is guaranteed to be valid by the C API.

and updating the C header to explicitly state it returns a valid pointer. It currently says "Pointer to a human-readable string" which could be taken to mean valid, but making it "Valid pointer to..." would more clearly place the onus of it actually being valid on the C library.

Cheers,
Kent.

Quoted 6 lines

In any case, I would say it always returns a valid pointer, not "non-NULL", since the latter does not really show it is a valid pointer (it could point to a non-NULL, bad address).

Cheers,
Miguel

#27 lore raw

Re: [PATCH V4 4/8] libgpiod: Add rust wrapper crate

Quoted 9 lines · Kent Gibson

The comment is:

    // SAFETY: The string is guaranteed to be valid here.

and that is whether there a NULL check or not, so it isn't clear what the source of the guarantee is.
I would prefer:

    // SAFETY: The string is guaranteed to be valid by the C API.

I believe this is what we settled with now. I will make updates accordingly.

Quoted 4 lines

and updating the C header to explicitly state it returns a valid pointer. It currently says "Pointer to a human-readable string" which could be taken to mean valid, but making it "Valid pointer to..." would more clearly place the onus of it actually being valid on the C library.

I will let you guys handle the C API :)

Signature · 1 line
viresh
#28 lore raw

Re: [PATCH V4 4/8] libgpiod: Add rust wrapper crate

In reply to Kent Gibson
Quoted 6 lines · Kent Gibson
Quoted 6 lines
Quoted 6 lines
Quoted 5 lines
bindings/rust/src/request_config.rs new file +5 -0
+
+    /// Get the edge event buffer size for the request config.
+    pub fn get_event_buffer_size(&self) -> u32 {
+        unsafe { bindings::gpiod_request_config_get_event_buffer_size(self.config) as u32 }
+    }
Quoted 9 lines
Quoted 5 lines
Quoted 2 lines

You might want to note that this just reads the value from the config. The actual value used by the kernel is not made available to user space.

Do you want me to add these two comments for the above two routines ?

Not add verbatim. Maybe work them in.
Probably should look at revising the C API comments and then just mirror that.

Just to be clear, I don't think we want to get into the details as to how the kernel interprets that setting, so that is just informational for you in case you find the kernel doesn't buffer exactly the number you expected. The description for the set covers the case where you might get less than you expected. But in some cases you may get more.

The key difference is that the C API description says:
@return Edge event buffer size setting from the request config.

                                ^^^^^^^

which indicates you are getting the setting value back, not whatever the buffer size in the kernel actually is.
That is missing from the one-liner description, which is all you copied. So if you are going to condense it to one line then add "setting" in there.

Cheers,
Kent.

#29 lore raw

Re: [PATCH V4 4/8] libgpiod: Add rust wrapper crate

In reply to Kent Gibson
Quoted 27 lines · Kent Gibson
Quoted 21 lines · Viresh Kumar
Quoted 5 lines · Kent Gibson

On Fri, Jul 08, 2022 at 05:04:57PM +0530, Viresh Kumar wrote:
Consider modules and namespaces rather than lumping everything in the gpiod space.

e.g. gpiod::ChipInfo -> gpiod::chip::Info

The modules are already there, as file names. So what we really have is gpiod::chip_info::ChipInfo (yeah it isn't great for sure). But then it looked tougher/complex/unnecessary for end users to know the internals of a dependency and so I did this:

pub use crate::chip::*;
pub use crate::edge_event::*;
pub use crate::event_buffer::*;
pub use crate::info_event::*;
pub use crate::line_config::*;
pub use crate::line_info::*;
pub use crate::line_request::*;
pub use crate::request_config::*;

Quoted 4 lines

which puts everything under gpiod::*. I think it is easier for users that way. The modules are fine for in-crate management, but for end user they shouldn't be visible.

Quoted 7 lines

The main problem I have with putting everything in the top level is the generated docs. You get everything dumped on you, so all structs, enums and functions, and it isn't clear to the user what the logical starting point is.
If things are tiered then you can introduce them more gradually, or keep them out of their way if they are unlikely to use them (e.g. ChipInfo, InfoEvent).

I am not sure what to do about this. I was suggested earlier to dump it all at the top level namespace so it is easier/shorter for users, which also won't need to know the internal namespaces/modules of libgpiod.

Looking at structures, there are just 8 of them which are exposed in docs now, which isn't a lot really. And then we have 12 enums now.

Signature · 1 line
viresh
#30 lore raw

Re: [PATCH V4 4/8] libgpiod: Add rust wrapper crate

Quoted 36 lines · Viresh Kumar
Quoted 27 lines · Kent Gibson
Quoted 21 lines · Viresh Kumar
Quoted 5 lines · Kent Gibson

On Fri, Jul 08, 2022 at 05:04:57PM +0530, Viresh Kumar wrote:
Consider modules and namespaces rather than lumping everything in the gpiod space.

e.g. gpiod::ChipInfo -> gpiod::chip::Info

The modules are already there, as file names. So what we really have is gpiod::chip_info::ChipInfo (yeah it isn't great for sure). But then it looked tougher/complex/unnecessary for end users to know the internals of a dependency and so I did this:

pub use crate::chip::*;
pub use crate::edge_event::*;
pub use crate::event_buffer::*;
pub use crate::info_event::*;
pub use crate::line_config::*;
pub use crate::line_info::*;
pub use crate::line_request::*;
pub use crate::request_config::*;

Quoted 4 lines

which puts everything under gpiod::*. I think it is easier for users that way. The modules are fine for in-crate management, but for end user they shouldn't be visible.

Quoted 7 lines

The main problem I have with putting everything in the top level is the generated docs. You get everything dumped on you, so all structs, enums and functions, and it isn't clear to the user what the logical starting point is.
If things are tiered then you can introduce them more gradually, or keep them out of their way if they are unlikely to use them (e.g. ChipInfo, InfoEvent).

Quoted 4 lines

I am not sure what to do about this. I was suggested earlier to dump it all at the top level namespace so it is easier/shorter for users, which also won't need to know the internal namespaces/modules of libgpiod.

libgpiod has no namespaces/modules - cos it is C.
The users do need to understand how things fit together, and the flat namespace doesn't help there - everything is equal.

True, "_" is shorter than "::". Can't argue with that ;-).

In Rust the user can rename the import whatever they want, so I'm not sure the "easier/shorter" argument holds much water.

Quoted 3 lines

Looking at structures, there are just 8 of them which are exposed in docs now, which isn't a lot really. And then we have 12 enums now.

This isn't a question of numbers, it is a question of whether to indicate structure, or to flatten everything.

I find the structured approach both clearer and more idiomatic in Rust, so that gets my vote.

Cheers,
Kent.

#31 lore raw

Re: [PATCH V4 4/8] libgpiod: Add rust wrapper crate

Quoted 2 lines · Kent Gibson

True, "_" is shorter than "::". Can't argue with that ;-).

Not sure about that in non-monospace fonts -- just kidding ;P

(Though I know an experienced C programmer that uses non-monospaced fonts for code)

Quoted 2 lines

I find the structured approach both clearer and more idiomatic in Rust, so that gets my vote.

Yeah, we are also following that approach in the kernel.

Cheers,
Miguel

#32 lore raw

Re: [PATCH V4 4/8] libgpiod: Add rust wrapper crate

In reply to Kent Gibson
Quoted 9 lines · Kent Gibson
Quoted 1 line · Viresh Kumar

+ /// Get the number of events the buffers stores.

stores -> contains

The capacity indicates the number that can be stored. This field indicates the number the buffer currently contains.

Quoted 6 lines

And, as mentioned above, rename to len(), and return value should be usize.

Add a set_len() method to replace the max_events parameter in LineRequest::read_edge_event() to handle the unusual case where the user only wants to partially fill the buffer.

So capacity is max that can be stored, len is what is currently present. What read_edge_event() needs is how much we want it to read, which should be <= capacity.

If we remove the parameter to read_edge_event(), then it must fetch the value from the buffer itself, which should be buffer.capacity() if user hasn't called set_len(), else it will be that special value the user sets.

What do you want to call the value (to be added as a field to struct EdgeEventBuffer) and the helper routine to fetch it ?

Maybe we should name it "max_events" and call the helpers as max_events() and set_max_events() instead of set_len()?

Quoted 4 lines
Quoted 4 lines
bindings/rust/src/event_buffer.rs new file +4 -0
+    pub fn get_num_events(&self) -> u32 {
+        unsafe { bindings::gpiod_edge_event_buffer_get_num_events(self.buffer()) as u32 }
+    }
+}
Signature · 1 line
viresh
#33 lore raw

Re: [PATCH V4 4/8] libgpiod: Add rust wrapper crate

Quoted 17 lines · Viresh Kumar
Quoted 9 lines · Kent Gibson
Quoted 1 line · Viresh Kumar

+ /// Get the number of events the buffers stores.

stores -> contains

The capacity indicates the number that can be stored. This field indicates the number the buffer currently contains.

Quoted 6 lines

And, as mentioned above, rename to len(), and return value should be usize.

Add a set_len() method to replace the max_events parameter in LineRequest::read_edge_event() to handle the unusual case where the user only wants to partially fill the buffer.

Quoted 8 lines

So capacity is max that can be stored, len is what is currently present. What read_edge_event() needs is how much we want it to read, which should be <= capacity.

If we remove the parameter to read_edge_event(), then it must fetch the value from the buffer itself, which should be buffer.capacity() if user hasn't called set_len(), else it will be that special value the user sets.

Ah, so the the problem there is after the read_edge_event() the len() is supposed to indicate the number of events in the buffer, so if we use that to size the read then the user will have to re-set_len() on each read
- so we are effectively back to providing the length to read on each call.

So not a good plan - my bad.

Quoted 6 lines

What do you want to call the value (to be added as a field to struct EdgeEventBuffer) and the helper routine to fetch it ?

Maybe we should name it "max_events" and call the helpers as max_events() and set_max_events() instead of set_len()?

I'm not even sure it makes sense to have an option to partially fill the buffer. It is there in the C as we have to provide the buffer size, and so can always elect to provide a smaller size, but I'm not sure there is any use case for it - certainly none that I am aware of. Perhaps we should always fill the buffer to capacity.

Then we can stick with the Vec len/capacity pattern, so capacity() indicates how many can be written during a read and len() indicates how many are available in the buffer. No set_len() required.

The goal was to provide an interface that is more idiomatic than just wrapping the C API verbatim, so the idea was to provide something with a Vec feel.

That is what I tried to do with mine[1], though that totally hides the filling of the buffer from the user - filling it on demand when the user requests an event (the buffer also being an iterator) when the buffer is empty.
Something like that may be doable using the libpgiod API.

Kent. [1]https://warthog618.github.io/gpiocdev-rs/gpiocdev/request/struct.EdgeEventBuffer.html

#34 lore raw

PATCH v4 5/8 libgpiod: Add rust examples

In reply to Viresh Kumar

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

---
 bindings/rust/examples/gpiodetect.rs | 37 ++++++++++++
 bindings/rust/examples/gpiofind.rs   | 42 +++++++++++++
 bindings/rust/examples/gpioget.rs    | 42 +++++++++++++
 bindings/rust/examples/gpioinfo.rs   | 89 ++++++++++++++++++++++++++++
 bindings/rust/examples/gpiomon.rs    | 68 +++++++++++++++++++++
 bindings/rust/examples/gpioset.rs    | 52 ++++++++++++++++
 6 files changed, 330 insertions(+)
 create mode 100644 bindings/rust/examples/gpiodetect.rs
 create mode 100644 bindings/rust/examples/gpiofind.rs
 create mode 100644 bindings/rust/examples/gpioget.rs
 create mode 100644 bindings/rust/examples/gpioinfo.rs
 create mode 100644 bindings/rust/examples/gpiomon.rs
 create mode 100644 bindings/rust/examples/gpioset.rs
bindings/rust/examples/gpiodetect.rs new file +37 -0
new file mode 100644
index 000000000000..82307e4eecea
@@ -0,0 +1,37 @@
+// 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::fs;
+use std::path::Path;
+
+use libgpiod::{gpiod_is_gpiochip_device, Chip};
+
+fn main() {
+    let args: Vec<String> = env::args().collect();
+    if args.len() > 1 {
+        println!("Usage: {}", args[0]);
+        return;
+    }
+
+    for entry in fs::read_dir(Path::new("/dev")).unwrap() {
+        let pathbuf = entry.unwrap().path();
+        let path = pathbuf.to_str().unwrap();
+
+        if gpiod_is_gpiochip_device(path) {
+            let chip = Chip::open(path).unwrap();
+            let ngpio = chip.get_num_lines();
+
+            println!(
+                "{} [{}] ({})",
+                chip.get_name().unwrap(),
+                chip.get_label().unwrap(),
+                ngpio
+            );
+        }
+    }
+}
bindings/rust/examples/gpiofind.rs new file +42 -0

42 lines not on this page · raw message

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

42 lines not on this page · raw message

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

89 lines not on this page · raw message

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

68 lines not on this page · raw message

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

52 lines not on this page · raw message

Signature · 1 line
2.31.1.272.g89b43f80a514
#35 lore raw

Re: [PATCH V4 5/8] libgpiod: Add rust examples

Quoted 36 lines · Viresh Kumar

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

---
 bindings/rust/examples/gpiodetect.rs | 37 ++++++++++++
 bindings/rust/examples/gpiofind.rs   | 42 +++++++++++++
 bindings/rust/examples/gpioget.rs    | 42 +++++++++++++
 bindings/rust/examples/gpioinfo.rs   | 89 ++++++++++++++++++++++++++++
 bindings/rust/examples/gpiomon.rs    | 68 +++++++++++++++++++++
 bindings/rust/examples/gpioset.rs    | 52 ++++++++++++++++
 6 files changed, 330 insertions(+)
 create mode 100644 bindings/rust/examples/gpiodetect.rs
 create mode 100644 bindings/rust/examples/gpiofind.rs
 create mode 100644 bindings/rust/examples/gpioget.rs
 create mode 100644 bindings/rust/examples/gpioinfo.rs
 create mode 100644 bindings/rust/examples/gpiomon.rs
 create mode 100644 bindings/rust/examples/gpioset.rs
bindings/rust/examples/gpiodetect.rs new file +11 -0
new file mode 100644
index 000000000000..82307e4eecea
@@ -0,0 +1,37 @@
+// 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::fs;
+use std::path::Path;
+

bindings/rust/examples/gpiodetect.rs line 21

Quoted 10 lines
bindings/rust/examples/gpiodetect.rs new file +10 -0
+use libgpiod::{gpiod_is_gpiochip_device, Chip};
+
+fn main() {
+    let args: Vec<String> = env::args().collect();
+    if args.len() > 1 {
+        println!("Usage: {}", args[0]);
+        return;
+    }
+
+    for entry in fs::read_dir(Path::new("/dev")).unwrap() {

use .flatten() to have the iterator unwrap the entry so it is actually an entry, not a Result.

bindings/rust/examples/gpiodetect.rs line 24

Quoted 3 lines
bindings/rust/examples/gpiodetect.rs new file +3 -0
+        let pathbuf = entry.unwrap().path();
+        let path = pathbuf.to_str().unwrap();
+

is_gpiochip_device() and Chip::open() (and ChipInternal) should accept anything that can be converted into a &Path, e.g. a PathBuf, so the path variable becomes redundant.

e.g.

    pub(crate) fn open<P: AsRef<std::path::Path>>(path: &P) -> Result<Self> {
        // Null-terminate the string
        let path = path.as_ref().to_string_lossy() + "\0";
        ...

and then example code becomes:

    for entry in fs::read_dir(Path::new("/dev")).unwrap().flatten() {
        let path = entry.path();

        if gpiod_is_gpiochip_device(&path) {
            let chip = Chip::open(&path).unwrap();
            ...

(renaming pathbuf to path)

Similarly other examples.

bindings/rust/examples/gpiodetect.rs line 28

Quoted 4 lines
bindings/rust/examples/gpiodetect.rs new file +4 -0
+        if gpiod_is_gpiochip_device(path) {
+            let chip = Chip::open(path).unwrap();
+            let ngpio = chip.get_num_lines();
+

Why does ngpio get a variable, unlike name and label?

bindings/rust/examples/gpiodetect.rs line 37

Quoted 9 lines
bindings/rust/examples/gpiodetect.rs new file +9 -0
+            println!(
+                "{} [{}] ({})",
+                chip.get_name().unwrap(),
+                chip.get_label().unwrap(),
+                ngpio
+            );
+        }
+    }
+}

Avoid using unwrap(). Have main return a Result and use ?. Not so important here, but below for helper functions returning Results allows the caller to determine how to handle the error. And it reads better.

Quoted 21 lines
bindings/rust/examples/gpiofind.rs new file +15 -0
new file mode 100644
index 000000000000..bbbd7a87ece8
@@ -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 gpiofind tool.
+
+use std::env;
+use std::fs;
+use std::path::Path;
+
+use libgpiod::{gpiod_is_gpiochip_device, Chip};
+
+fn main() {
+    let args: Vec<String> = env::args().collect();

bindings/rust/examples/gpiofind.rs line 25

Quoted 10 lines
bindings/rust/examples/gpiofind.rs new file +10 -0
+    if args.len() != 2 {
+        println!("Usage: {} <line-name>", args[0]);
+        return;
+    }
+
+    for entry in fs::read_dir(Path::new("/dev")).unwrap() {
+        let pathbuf = entry.unwrap().path();
+        let path = pathbuf.to_str().unwrap();
+
+        if gpiod_is_gpiochip_device(path) {

Perhaps have the bindings provide an iterator that returns the paths of available gpiochips?

Quoted 202 lines
bindings/rust/examples/gpiofind.rs new file +17 -0
+            let chip = Chip::open(path).unwrap();
+
+            let offset = chip.find_line(&args[1]);
+            if offset.is_ok() {
+                println!(
+                    "Line {} found: Chip: {}, offset: {}",
+                    args[1],
+                    chip.get_name().unwrap(),
+                    offset.unwrap()
+                );
+                return;
+            }
+        }
+    }
+
+    println!("Failed to find line: {}", args[1]);
+}
bindings/rust/examples/gpioget.rs new file +42 -0
new file mode 100644
index 000000000000..c3bc35fcfdb6
@@ -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, Direction, LineConfig, RequestConfig};
+
+fn main() {
+    let args: Vec<String> = env::args().collect();
+    if args.len() < 3 {
+        println!("Usage: {} <chip> <line_offset0> ...", args[0]);
+        return;
+    }
+
+    let mut config = LineConfig::new().unwrap();
+    let mut offsets = Vec::<u32>::new();
+
+    for arg in &args[2..] {
+        let offset = arg.parse::<u32>().unwrap();
+
+        offsets.push(offset);
+        config.set_direction_override(Direction::Input, offset);
+    }
+
+    let path = format!("/dev/gpiochip{}", args[1]);
+    let chip = Chip::open(&path).unwrap();
+
+    let rconfig = RequestConfig::new().unwrap();
+    rconfig.set_consumer(&args[0]);
+    rconfig.set_offsets(&offsets);
+
+    let request = chip.request_lines(&rconfig, &config).unwrap();
+
+    let mut values: Vec<i32> = vec![0; offsets.len()];
+    request.get_values(&mut values).unwrap();
+
+    println!("{:?}", values);
+}
bindings/rust/examples/gpioinfo.rs new file +89 -0
new file mode 100644
index 000000000000..bd30d9096ce8
@@ -0,0 +1,89 @@
+// 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 gpioinfo tool.
+
+use std::env;
+use std::fs;
+use std::path::Path;
+
+use libgpiod::{gpiod_is_gpiochip_device, Chip, Direction};
+
+fn line_info(chip: &Chip, offset: u32) {
+    let info = chip.line_info(offset).unwrap();
+    let off = info.get_offset();
+
+    let name = match info.get_name() {
+        Ok(name) => name,
+        _ => "unused",
+    };
+
+    let consumer = match info.get_consumer() {
+        Ok(name) => name,
+        _ => "unnamed",
+    };
+
+    let low = if info.is_active_low() {
+        "active-low"
+    } else {
+        "active-high"
+    };
+
+    let dir = match info.get_direction().unwrap() {
+        Direction::AsIs => "None",
+        Direction::Input => "Input",
+        Direction::Output => "Output",
+    };
+
+    println!(
+        "\tline {:>3}\
+              \t{:>10}\
+              \t{:>10}\
+              \t{:>6}\
+              \t{:>14}",
+        off, name, consumer, dir, low
+    );
+}
+
+fn chip_info(path: &str) {
+    if gpiod_is_gpiochip_device(path) {
+        let chip = Chip::open(path).unwrap();
+        let ngpio = chip.get_num_lines();
+
+        println!("GPIO Chip name: {}", chip.get_name().unwrap());
+        println!("\tlabel: {}", chip.get_label().unwrap());
+        println!("\tpath: {}", chip.get_path().unwrap());
+        println!("\tngpio: {}\n", ngpio);
+
+        println!("\tLine information:");
+
+        for offset in 0..ngpio {
+            line_info(&chip, offset);
+        }
+        println!("\n");
+    }
+}
+
+fn main() {
+    let args: Vec<String> = env::args().collect();
+    if args.len() > 2 {
+        println!("Usage: {}", args[0]);
+        return;
+    }
+
+    if args.len() == 1 {
+        for entry in fs::read_dir(Path::new("/dev")).unwrap() {
+            let pathbuf = entry.unwrap().path();
+            let path = pathbuf.to_str().unwrap();
+
+            chip_info(path);
+        }
+    } else {
+        let index = args[1].parse::<u32>().unwrap();
+        let path = format!("/dev/gpiochip{}", index);
+
+        chip_info(&path);
+    }
+}
bindings/rust/examples/gpiomon.rs new file +36 -0
new file mode 100644
index 000000000000..872907b386f3
@@ -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]>
+//
+// Simplified Rust implementation of the gpiomon tool.
+
+use std::env;
+use std::time::Duration;
+
+use libgpiod::{Chip, Edge, EdgeEventBuffer, Error, LineConfig, LineEdgeEvent, RequestConfig};
+
+fn usage(name: &str) {
+    println!("Usage: {} <chip> <offset0> ...", name);
+}
+
+fn main() {
+    let args: Vec<String> = env::args().collect();
+    if args.len() < 3 {
+        usage(&args[0]);
+        return;
+    }
+
+    let mut config = LineConfig::new().unwrap();
+    let mut offsets = Vec::<u32>::new();
+
+    for arg in &args[2..] {
+        let offset = arg.parse::<u32>().unwrap();
+
+        offsets.push(offset);
+    }
+
+    config.set_edge_detection_default(Edge::Both);
+
+    let path = format!("/dev/gpiochip{}", args[1]);
+    let chip = Chip::open(&path).unwrap();

bindings/rust/examples/gpiomon.rs line 46

Quoted 10 lines
bindings/rust/examples/gpiomon.rs new file +10 -0
+
+    let rconfig = RequestConfig::new().unwrap();
+    rconfig.set_offsets(&offsets);
+
+    let buffer = EdgeEventBuffer::new(1).unwrap();
+    let request = chip.request_lines(&rconfig, &config).unwrap();
+
+    loop {
+        match request.wait_edge_event(Duration::new(1, 0)) {
+            Err(Error::OperationTimedOut) => continue,

timeout/continue required as you can't (currently) block indefinitely?

Quoted 69 lines
bindings/rust/examples/gpiomon.rs new file +22 -0
+            Err(x) => {
+                println!("{:?}", x);
+                return;
+            }
+            Ok(()) => (),
+        }
+
+        let count = request.read_edge_event(&buffer, 1).unwrap();
+        if count == 1 {
+            let event = buffer.get_event(0).unwrap();
+            println!(
+                "line: {} type: {}, time: {:?}",
+                event.get_line_offset(),
+                match event.get_event_type().unwrap() {
+                    LineEdgeEvent::Rising => "Rising",
+                    LineEdgeEvent::Falling => "Falling",
+                },
+                event.get_timestamp()
+            );
+        }
+    }
+}
bindings/rust/examples/gpioset.rs new file +41 -0
new file mode 100644
index 000000000000..ef70e8edbaae
@@ -0,0 +1,52 @@
+// 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 the gpioset tool.
+
+use std::env;
+
+use libgpiod::{Chip, Direction, LineConfig, RequestConfig};
+
+fn usage(name: &str) {
+    println!("Usage: {} <chip> <line_offset0>=<value0> ...", name);
+}
+
+fn main() {
+    let args: Vec<String> = env::args().collect();
+    if args.len() < 3 {
+        usage(&args[0]);
+        return;
+    }
+
+    let mut config = LineConfig::new().unwrap();
+    let mut offsets = Vec::<u32>::new();
+    let mut values = Vec::<i32>::new();
+
+    for arg in &args[2..] {
+        let pair: Vec<&str> = arg.split('=').collect();
+        if pair.len() != 2 {
+            usage(&args[0]);
+            return;
+        }
+
+        let offset = pair[0].parse::<u32>().unwrap();
+        let value = pair[1].parse::<u32>().unwrap();
+
+        offsets.push(offset);
+        values.push(value as i32);
+    }
+
+    config.set_direction_default(Direction::Output);

bindings/rust/examples/gpioset.rs line 51

Quoted 10 lines
bindings/rust/examples/gpioset.rs new file +10 -0
+    config.set_output_values(&offsets, &values).unwrap();
+
+    let path = format!("/dev/gpiochip{}", args[1]);
+    let chip = Chip::open(&path).unwrap();
+
+    let rconfig = RequestConfig::new().unwrap();
+    rconfig.set_consumer(&args[0]);
+    rconfig.set_offsets(&offsets);
+
+    chip.request_lines(&rconfig, &config).unwrap();

Wait rather than exiting immediately?

Quoted 4 lines
bindings/rust/examples/gpioset.rs new file +1 -0
+}
Signature · 1 line
2.31.1.272.g89b43f80a514

And, as mentioned elsewhere, add a gpiowatch example.

Cheers,
Kent.

#36 lore raw

Re: [PATCH V4 5/8] libgpiod: Add rust examples

Quoted 9 lines · Kent Gibson

bindings/rust/examples/gpioset.rs line 51

Quoted 6 lines · Viresh Kumar
bindings/rust/examples/gpioset.rs +5 -0
+    let rconfig = RequestConfig::new().unwrap();
+    rconfig.set_consumer(&args[0]);
+    rconfig.set_offsets(&offsets);
+
+    chip.request_lines(&rconfig, &config).unwrap();

Wait rather than exiting immediately?

Wait for what exactly ?

Quoted 1 line

And, as mentioned elsewhere, add a gpiowatch example.

Will do, sure. Any existing example of the same will help though.

Signature · 1 line
viresh
#37 lore raw

Re: [PATCH V4 5/8] libgpiod: Add rust examples

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

bindings/rust/examples/gpioset.rs line 51

Quoted 6 lines · Viresh Kumar
bindings/rust/examples/gpioset.rs +5 -0
+    let rconfig = RequestConfig::new().unwrap();
+    rconfig.set_consumer(&args[0]);
+    rconfig.set_offsets(&offsets);
+
+    chip.request_lines(&rconfig, &config).unwrap();

Wait rather than exiting immediately?

Quoted 2 lines

Wait for what exactly ?

For long enough for the user to check that the line is set, as there is the possibility that when the line is released it will be set back to its default value and you will get users reporting that your set doesn't work ;-).

So wait for a keypress?

Quoted 4 lines
Quoted 1 line

And, as mentioned elsewhere, add a gpiowatch example.

Will do, sure. Any existing example of the same will help though.

There is an example in my proposed tool changes for v2[1]. It is very similar to gpiomon, just on the chip and with line info instead of edge events.

Though that example is more complicated than you need as it supports named lines and operating over multiple chips.
Hopefully you can see past that.
The basic operation is watch the line, then wait for the chip fd to be readable and read the info changed event from it.

You would only unwatch if you no longer had an interest in the line, which I expect will be the case in the D-BUS daemon that Bart has suggested.

Cheers,
Kent.

[1]https://lore.kernel.org/linux-gpio/[email protected]/

#38 lore raw

Re: [PATCH V4 5/8] libgpiod: Add rust examples

Quoted 6 lines · Kent Gibson

For long enough for the user to check that the line is set, as there is the possibility that when the line is released it will be set back to its default value and you will get users reporting that your set doesn't work ;-).

So wait for a keypress?

I thought we wanted to avoid human-intervention to the tests :)

Can't we just read the values of the lines again here somehow and match ? I don't expect/want the user to do this for running the test. Not to mention, the user may eventually be a bot running the tests for each commit added to the tree.

Signature · 1 line
viresh
#39 lore raw

Re: [PATCH V4 5/8] libgpiod: Add rust examples

Quoted 10 lines · Viresh Kumar
Quoted 6 lines · Kent Gibson

For long enough for the user to check that the line is set, as there is the possibility that when the line is released it will be set back to its default value and you will get users reporting that your set doesn't work ;-).

So wait for a keypress?

I thought we wanted to avoid human-intervention to the tests :)

This is for an example gpioset, right?
You have tests for your examples?

And even then you could either inject a keypress or just kill the process. (e.g. the tests for the proposed tool changes do that)

Cheers,
Kent.

Quoted 5 lines

Can't we just read the values of the lines again here somehow and match ? I don't expect/want the user to do this for running the test. Not to mention, the user may eventually be a bot running the tests for each commit added to the tree.

#40 lore raw

Re: [PATCH V4 5/8] libgpiod: Add rust examples

Quoted 1 line · Kent Gibson

This is for an example gpioset, right?

Damn, got confused :(

Signature · 1 line
viresh
#41 lore raw

PATCH v4 6/8 libgpiod: Derive debug traits for few definitions

In reply to Viresh Kumar

These are required for tests, which will be added by a later commit.

---
 bindings/rust/src/chip.rs      | 5 +++++
 bindings/rust/src/chip_info.rs | 1 +
 bindings/rust/src/lib.rs       | 8 +++++++-
 bindings/rust/src/line_info.rs | 1 +
 4 files changed, 14 insertions(+), 1 deletion(-)
bindings/rust/src/chip.rs +5 -0
index 50b5d6102f96..ecff4b003cd9 100644
@@ -21,6 +21,7 @@ use super::{
 /// 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(Debug)]
 pub(crate) struct ChipInternal {
     chip: *mut bindings::gpiod_chip,
 }
@@ -52,11 +53,15 @@ impl Drop for ChipInternal {
     }
 }
 
+#[derive(Debug)]
 pub struct Chip {
     ichip: Arc<ChipInternal>,
     info: ChipInfo,
 }
 
+unsafe impl Send for Chip {}
+unsafe impl Sync for Chip {}
+
 impl Chip {
     /// Find a chip by path.
     pub fn open(path: &str) -> Result<Self> {
bindings/rust/src/chip_info.rs +1 -0
index 950368b54c6f..7188f91a92a6 100644
@@ -11,6 +11,7 @@ use vmm_sys_util::errno::Error as IoError;
 use super::{bindings, ChipInternal, Error, Result};
 
 /// GPIO chip Information
+#[derive(Debug)]
 pub struct ChipInfo {
     info: *mut bindings::gpiod_chip_info,
 }
bindings/rust/src/lib.rs +7 -1
index 2f2ac515d353..63b0b82281b7 100644
@@ -59,6 +59,7 @@ pub enum Error {
 }
 
 /// Direction settings.
+#[derive(Debug, PartialEq)]
 pub enum Direction {
     /// Request the line(s), but don't change direction.
     AsIs,
@@ -88,6 +89,7 @@ impl Direction {
 }
 
 /// Internal bias settings.
+#[derive(Debug, PartialEq)]
 pub enum Bias {
     /// Don't change the bias setting when applying line config.
     AsIs,
@@ -125,6 +127,7 @@ impl Bias {
 }
 
 /// Drive settings.
+#[derive(Debug, PartialEq)]
 pub enum Drive {
     /// Drive setting is push-pull.
     PushPull,
@@ -154,6 +157,7 @@ impl Drive {
 }
 
 /// Edge detection settings.
+#[derive(Debug, PartialEq)]
 pub enum Edge {
     /// Line edge detection is disabled.
     None,
@@ -223,6 +227,7 @@ impl Config {
 }
 
 /// Event clock settings.
+#[derive(Debug, PartialEq)]
 pub enum EventClock {
     /// Line uses the monotonic clock for edge event timestamps.
     Monotonic,
@@ -248,6 +253,7 @@ impl EventClock {
 }
 
 /// Line status change event types.
+#[derive(Debug, PartialEq)]
 pub enum Event {
     /// Line has been requested.
     LineRequested,
@@ -268,7 +274,7 @@ impl Event {
     }
 }
 
-#[derive(Copy, Clone)]
+#[derive(Copy, Clone, Debug, PartialEq)]
 /// Edge event types.
 pub enum LineEdgeEvent {
     /// Rising edge event.
bindings/rust/src/line_info.rs +1 -0
index 70b6bd6a84bb..426bd16aa616 100644
@@ -23,6 +23,7 @@ use super::{
 /// line, which does not include the line value.  The line must be requested
 /// to access the line value.
 
+#[derive(Debug)]
 pub struct LineInfo {
     info: *mut bindings::gpiod_line_info,
     ichip: Option<Arc<ChipInternal>>,
Signature · 1 line
2.31.1.272.g89b43f80a514
#42 lore raw

Re: [PATCH V4 6/8] libgpiod: Derive debug traits for few definitions

Quoted 2 lines · Viresh Kumar

These are required for tests, which will be added by a later commit.

Squash this patch into patch 4, as you know you will need them eventually. All public types should implement Debug[1].

They should also implement Clone, Eq, PartialEq and Default where that makes sense. And Copy if you are sure you wont add something non-Copyable to them in the future.

Cheers,
Kent.

[1] https://rust-lang.github.io/api-guidelines/debuggability.html

Quoted 124 lines
---
 bindings/rust/src/chip.rs      | 5 +++++
 bindings/rust/src/chip_info.rs | 1 +
 bindings/rust/src/lib.rs       | 8 +++++++-
 bindings/rust/src/line_info.rs | 1 +
 4 files changed, 14 insertions(+), 1 deletion(-)
bindings/rust/src/chip.rs +5 -0
index 50b5d6102f96..ecff4b003cd9 100644
@@ -21,6 +21,7 @@ use super::{
 /// 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(Debug)]
 pub(crate) struct ChipInternal {
     chip: *mut bindings::gpiod_chip,
 }
@@ -52,11 +53,15 @@ impl Drop for ChipInternal {
     }
 }
 
+#[derive(Debug)]
 pub struct Chip {
     ichip: Arc<ChipInternal>,
     info: ChipInfo,
 }
 
+unsafe impl Send for Chip {}
+unsafe impl Sync for Chip {}
+
 impl Chip {
     /// Find a chip by path.
     pub fn open(path: &str) -> Result<Self> {
bindings/rust/src/chip_info.rs +1 -0
index 950368b54c6f..7188f91a92a6 100644
@@ -11,6 +11,7 @@ use vmm_sys_util::errno::Error as IoError;
 use super::{bindings, ChipInternal, Error, Result};
 
 /// GPIO chip Information
+#[derive(Debug)]
 pub struct ChipInfo {
     info: *mut bindings::gpiod_chip_info,
 }
bindings/rust/src/lib.rs +7 -1
index 2f2ac515d353..63b0b82281b7 100644
@@ -59,6 +59,7 @@ pub enum Error {
 }
 
 /// Direction settings.
+#[derive(Debug, PartialEq)]
 pub enum Direction {
     /// Request the line(s), but don't change direction.
     AsIs,
@@ -88,6 +89,7 @@ impl Direction {
 }
 
 /// Internal bias settings.
+#[derive(Debug, PartialEq)]
 pub enum Bias {
     /// Don't change the bias setting when applying line config.
     AsIs,
@@ -125,6 +127,7 @@ impl Bias {
 }
 
 /// Drive settings.
+#[derive(Debug, PartialEq)]
 pub enum Drive {
     /// Drive setting is push-pull.
     PushPull,
@@ -154,6 +157,7 @@ impl Drive {
 }
 
 /// Edge detection settings.
+#[derive(Debug, PartialEq)]
 pub enum Edge {
     /// Line edge detection is disabled.
     None,
@@ -223,6 +227,7 @@ impl Config {
 }
 
 /// Event clock settings.
+#[derive(Debug, PartialEq)]
 pub enum EventClock {
     /// Line uses the monotonic clock for edge event timestamps.
     Monotonic,
@@ -248,6 +253,7 @@ impl EventClock {
 }
 
 /// Line status change event types.
+#[derive(Debug, PartialEq)]
 pub enum Event {
     /// Line has been requested.
     LineRequested,
@@ -268,7 +274,7 @@ impl Event {
     }
 }
 
-#[derive(Copy, Clone)]
+#[derive(Copy, Clone, Debug, PartialEq)]
 /// Edge event types.
 pub enum LineEdgeEvent {
     /// Rising edge event.
bindings/rust/src/line_info.rs +1 -0
index 70b6bd6a84bb..426bd16aa616 100644
@@ -23,6 +23,7 @@ use super::{
 /// line, which does not include the line value.  The line must be requested
 /// to access the line value.
 
+#[derive(Debug)]
 pub struct LineInfo {
     info: *mut bindings::gpiod_line_info,
     ichip: Option<Arc<ChipInternal>>,
Signature · 1 line
2.31.1.272.g89b43f80a514
#43 lore raw

Re: [PATCH V4 6/8] libgpiod: Derive debug traits for few definitions

Quoted 10 lines · Kent Gibson
Quoted 2 lines · Viresh Kumar

These are required for tests, which will be added by a later commit.

Squash this patch into patch 4, as you know you will need them eventually. All public types should implement Debug[1].

They should also implement Clone, Eq, PartialEq and Default where that makes sense. And Copy if you are sure you wont add something non-Copyable to them in the future.

Sure, will define few more for all public structures.

Signature · 1 line
viresh
#44 lore raw

PATCH v4 7/8 libgpiod: Add rust tests

In reply to Viresh Kumar

Add tests for the rust bindings, quite similar to the ones in cxx bindings.

This is how "cargo test" prints results:

     Running tests/chip.rs (target/debug/deps/chip-b19008e6b9a10d2f)

running 6 tests
test chip::create::nonexistent_file_failure ... ok test chip::create::no_dev_file_failure ... ok test chip::create::non_gpio_char_dev_file_failure ... ok test chip::create::existing ... ok
test chip::configure::line_lookup ... ok
test chip::configure::verify ... ok

test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.14s

     Running tests/edge_event.rs (target/debug/deps/edge_event-5897a36c91efe0d4)

running 11 tests
test edge_event::buffer_settings::default_capacity ... ok test edge_event::buffer_settings::user_defined_capacity ... ok test edge_event::buffer_settings::max_capacity ... ok test edge_event::failure::dir_out_edge_failure ... ok test edge_event::failure::wait_timeout ... ok
test edge_event::verify::both_edges ... ok
test edge_event::verify::multiple_events ... ok
test edge_event::verify::edge_sequence ... ok
test edge_event::verify::falling_edge ... ok
test edge_event::verify::over_capacity ... ok
test edge_event::verify::rising_edge ... ok

test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.98s

     Running tests/info_event.rs (target/debug/deps/info_event-707c734e0820381c)

running 3 tests
test info_event::watch::verify ... ok
test info_event::watch::failure ... ok
test info_event::watch::reconfigure ... ok

test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.26s

     Running tests/line_config.rs (target/debug/deps/line_config-d376966750bf38a0)

running 9 tests
test line_config::overrides::active_low ... ok test line_config::overrides::bias ... ok test line_config::default::verify ... ok test line_config::overrides::debounce_period ... ok test line_config::overrides::direction ... ok test line_config::overrides::drive ... ok test line_config::overrides::edge_detection ... ok test line_config::overrides::event_clock ... ok test line_config::overrides::output_value ... ok

test result: ok. 9 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s

     Running tests/line_info.rs (target/debug/deps/line_info-5069196cfaa74ab7)

running 2 tests

[40570.967204] gpio-1992 (hog): hogged as output/high
[40570.974774] gpio-1956 (hog4): hogged as output/low
[40570.975314] gpio-1955 (hog3): hogged as output/high

test line_info::properties::verify ... ok
test line_info::basic::verify ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.31s

Running tests/line_request.rs (target/debug/deps/line_request-914b9fd8c84e2c4b)

running 7 tests
test line_request::invalid_arguments::no_offsets ... ok test line_request::verify::read_values ... ok
test line_request::verify::set_bias ... ok
test line_request::verify::reconfigure_output_values ... ok test line_request::verify::empty_consumer ... ok test line_request::verify::set_output_values ... ok test line_request::verify::custom_consumer ... ok

test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.48s

Running tests/request_config.rs (target/debug/deps/request_config-99e366517cc0feda)

running 2 tests
test request_config::verify::default ... ok
test request_config::verify::initialized ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s

Doc-tests libgpiod

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

---
 bindings/rust/Cargo.toml              |   3 +
 bindings/rust/src/line_request.rs     |   1 +
 bindings/rust/tests/chip.rs           |  96 +++++++
 bindings/rust/tests/common/config.rs  | 117 ++++++++
 bindings/rust/tests/common/mod.rs     |  16 ++
 bindings/rust/tests/common/sim.rs     | 306 ++++++++++++++++++++
 bindings/rust/tests/edge_event.rs     | 389 ++++++++++++++++++++++++++
 bindings/rust/tests/info_event.rs     | 126 +++++++++
 bindings/rust/tests/line_config.rs    | 187 +++++++++++++
 bindings/rust/tests/line_info.rs      |  90 ++++++
 bindings/rust/tests/line_request.rs   | 234 ++++++++++++++++
 bindings/rust/tests/request_config.rs |  42 +++
 12 files changed, 1607 insertions(+)
 create mode 100644 bindings/rust/tests/chip.rs
 create mode 100644 bindings/rust/tests/common/config.rs
 create mode 100644 bindings/rust/tests/common/mod.rs
 create mode 100644 bindings/rust/tests/common/sim.rs
 create mode 100644 bindings/rust/tests/edge_event.rs
 create mode 100644 bindings/rust/tests/info_event.rs
 create mode 100644 bindings/rust/tests/line_config.rs
 create mode 100644 bindings/rust/tests/line_info.rs
 create mode 100644 bindings/rust/tests/line_request.rs
 create mode 100644 bindings/rust/tests/request_config.rs
bindings/rust/Cargo.toml +3 -0
index d5d81486fa2f..e2d6d5bb91b6 100644
@@ -10,3 +10,6 @@ libc = ">=0.2.39"
 libgpiod-sys = { path = "libgpiod-sys" }
 thiserror = "1.0"
 vmm-sys-util = "=0.9.0"
+
+[dev-dependencies]
+libgpiod-sys = { path = "libgpiod-sys", features = ["gpiosim"] }
bindings/rust/src/line_request.rs +1 -0
index bb338e72671d..c1dbbb397e73 100644
@@ -15,6 +15,7 @@ use super::{bindings, ChipInternal, EdgeEventBuffer, Error, LineConfig, RequestC
 /// Line request operations
 ///
 /// Allows interaction with a set of requested lines.
+#[derive(Debug)]
 pub struct LineRequest {
     request: *mut bindings::gpiod_line_request,
 }
bindings/rust/tests/chip.rs new file +96 -0

96 lines not on this page · raw message

bindings/rust/tests/common/config.rs new file +117 -0

117 lines not on this page · raw message

bindings/rust/tests/common/mod.rs new file +16 -0
new file mode 100644
index 000000000000..2dc37986396b
@@ -0,0 +1,16 @@
+// 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 sim;
+
+#[allow(unused_imports)]
+pub(crate) use sim::*;
+
+#[allow(dead_code)]
+mod config;
+
+#[allow(unused_imports)]
+pub(crate) use config::*;
bindings/rust/tests/common/sim.rs new file +306 -0

306 lines not on this page · raw message

bindings/rust/tests/edge_event.rs new file +389 -0

389 lines not on this page · raw message

bindings/rust/tests/info_event.rs new file +126 -0

126 lines not on this page · raw message

bindings/rust/tests/line_config.rs new file +187 -0

187 lines not on this page · raw message

bindings/rust/tests/line_info.rs new file +90 -0

90 lines not on this page · raw message

bindings/rust/tests/line_request.rs new file +234 -0

234 lines not on this page · raw message

bindings/rust/tests/request_config.rs new file +42 -0

42 lines not on this page · raw message

Signature · 1 line
2.31.1.272.g89b43f80a514
#45 lore raw

Re: [PATCH V4 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.

This is how "cargo test" prints results:

Don't include the test results in the commit message.
Those are more of a release artifact than a commit artifact. It is assumed the tests pass for you or you wouldn't be submitting them.

Quoted 133 lines
     Running tests/chip.rs (target/debug/deps/chip-b19008e6b9a10d2f)

running 6 tests
test chip::create::nonexistent_file_failure ... ok test chip::create::no_dev_file_failure ... ok test chip::create::non_gpio_char_dev_file_failure ... ok test chip::create::existing ... ok
test chip::configure::line_lookup ... ok
test chip::configure::verify ... ok

test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.14s

     Running tests/edge_event.rs (target/debug/deps/edge_event-5897a36c91efe0d4)

running 11 tests
test edge_event::buffer_settings::default_capacity ... ok test edge_event::buffer_settings::user_defined_capacity ... ok test edge_event::buffer_settings::max_capacity ... ok test edge_event::failure::dir_out_edge_failure ... ok test edge_event::failure::wait_timeout ... ok
test edge_event::verify::both_edges ... ok
test edge_event::verify::multiple_events ... ok
test edge_event::verify::edge_sequence ... ok
test edge_event::verify::falling_edge ... ok
test edge_event::verify::over_capacity ... ok
test edge_event::verify::rising_edge ... ok

test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.98s

     Running tests/info_event.rs (target/debug/deps/info_event-707c734e0820381c)

running 3 tests
test info_event::watch::verify ... ok
test info_event::watch::failure ... ok
test info_event::watch::reconfigure ... ok

test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.26s

     Running tests/line_config.rs (target/debug/deps/line_config-d376966750bf38a0)

running 9 tests
test line_config::overrides::active_low ... ok test line_config::overrides::bias ... ok test line_config::default::verify ... ok test line_config::overrides::debounce_period ... ok test line_config::overrides::direction ... ok test line_config::overrides::drive ... ok test line_config::overrides::edge_detection ... ok test line_config::overrides::event_clock ... ok test line_config::overrides::output_value ... ok

test result: ok. 9 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s

     Running tests/line_info.rs (target/debug/deps/line_info-5069196cfaa74ab7)

running 2 tests

[40570.967204] gpio-1992 (hog): hogged as output/high
[40570.974774] gpio-1956 (hog4): hogged as output/low
[40570.975314] gpio-1955 (hog3): hogged as output/high

test line_info::properties::verify ... ok
test line_info::basic::verify ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.31s

Running tests/line_request.rs (target/debug/deps/line_request-914b9fd8c84e2c4b)

running 7 tests
test line_request::invalid_arguments::no_offsets ... ok test line_request::verify::read_values ... ok
test line_request::verify::set_bias ... ok
test line_request::verify::reconfigure_output_values ... ok test line_request::verify::empty_consumer ... ok test line_request::verify::set_output_values ... ok test line_request::verify::custom_consumer ... ok

test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.48s

Running tests/request_config.rs (target/debug/deps/request_config-99e366517cc0feda)

running 2 tests
test request_config::verify::default ... ok
test request_config::verify::initialized ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s

Doc-tests libgpiod

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

---
 bindings/rust/Cargo.toml              |   3 +
 bindings/rust/src/line_request.rs     |   1 +
 bindings/rust/tests/chip.rs           |  96 +++++++
 bindings/rust/tests/common/config.rs  | 117 ++++++++
 bindings/rust/tests/common/mod.rs     |  16 ++
 bindings/rust/tests/common/sim.rs     | 306 ++++++++++++++++++++
 bindings/rust/tests/edge_event.rs     | 389 ++++++++++++++++++++++++++
 bindings/rust/tests/info_event.rs     | 126 +++++++++
 bindings/rust/tests/line_config.rs    | 187 +++++++++++++
 bindings/rust/tests/line_info.rs      |  90 ++++++
 bindings/rust/tests/line_request.rs   | 234 ++++++++++++++++
 bindings/rust/tests/request_config.rs |  42 +++
 12 files changed, 1607 insertions(+)
 create mode 100644 bindings/rust/tests/chip.rs
 create mode 100644 bindings/rust/tests/common/config.rs
 create mode 100644 bindings/rust/tests/common/mod.rs
 create mode 100644 bindings/rust/tests/common/sim.rs
 create mode 100644 bindings/rust/tests/edge_event.rs
 create mode 100644 bindings/rust/tests/info_event.rs
 create mode 100644 bindings/rust/tests/line_config.rs
 create mode 100644 bindings/rust/tests/line_info.rs
 create mode 100644 bindings/rust/tests/line_request.rs
 create mode 100644 bindings/rust/tests/request_config.rs
bindings/rust/Cargo.toml +3 -0
index d5d81486fa2f..e2d6d5bb91b6 100644
@@ -10,3 +10,6 @@ libc = ">=0.2.39"
 libgpiod-sys = { path = "libgpiod-sys" }
 thiserror = "1.0"
 vmm-sys-util = "=0.9.0"
+
+[dev-dependencies]
+libgpiod-sys = { path = "libgpiod-sys", features = ["gpiosim"] }
bindings/rust/src/line_request.rs +0 -0
index bb338e72671d..c1dbbb397e73 100644

bindings/rust/src/line_request.rs use super::{bindings, ChipInternal, EdgeEventBuffer, Error, LineConfig, RequestC · line 21

Quoted 7 lines
bindings/rust/src/line_request.rs +1 -0
@@ -15,6 +15,7 @@ use super::{bindings, ChipInternal, EdgeEventBuffer, Error, LineConfig, RequestC
 /// Line request operations
 ///
 /// Allows interaction with a set of requested lines.
+#[derive(Debug)]
 pub struct LineRequest {
     request: *mut bindings::gpiod_line_request,
 }

Overlooked in patch 6? Squash that into patch 4 as well.

Quoted 12 lines
bindings/rust/tests/chip.rs new file +6 -0
new file mode 100644
index 000000000000..4e64e9c7e291
@@ -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]>
+
+mod common;

bindings/rust/tests/chip.rs line 16

Quoted 10 lines
bindings/rust/tests/chip.rs new file +10 -0
+
+mod chip {
+    use libc::{ENODEV, ENOENT, ENOTTY};
+
+    use vmm_sys_util::errno::Error as IoError;
+
+    use crate::common::*;
+    use libgpiod::{Chip, Error as ChipError};
+
+    mod create {

If it is testing the open method then call it "open"?

bindings/rust/tests/chip.rs line 25

Quoted 9 lines
bindings/rust/tests/chip.rs new file +9 -0
+        use super::*;
+
+        #[test]
+        fn nonexistent_file_failure() {
+            assert_eq!(
+                Chip::open("/dev/nonexistent").unwrap_err(),
+                ChipError::OperationFailed("Gpio Chip open", IoError::new(ENOENT))
+            );
+        }

The "_failure" suffix doesn't add anything, so remove it.

Quoted 11 lines
bindings/rust/tests/chip.rs new file +11 -0
+
+        #[test]
+        fn no_dev_file_failure() {
+            assert_eq!(
+                Chip::open("/tmp").unwrap_err(),
+                ChipError::OperationFailed("Gpio Chip open", IoError::new(ENOTTY))
+            );
+        }
+
+        #[test]
+        fn non_gpio_char_dev_file_failure() {

bindings/rust/tests/chip.rs line 46

Quoted 10 lines
bindings/rust/tests/chip.rs new file +10 -0
+            assert_eq!(
+                Chip::open("/dev/null").unwrap_err(),
+                ChipError::OperationFailed("Gpio Chip open", IoError::new(ENODEV))
+            );
+        }
+
+        #[test]
+        fn existing() {
+            let sim = Sim::new(None, None, true).unwrap();
+            Chip::open(sim.dev_path()).unwrap();

Test names should indicate what the test covers. "existing" doesn't quite do that for me, though last I checked I passed. YMMV.

Use an assert variant for explicit tests.
Only use unwrap() for intermediate steps that are part of the fixture and are expected to pass unless something is very broken in the test setup. That makes it easier to distinguish the meat from the skeleton.

Quoted 10 lines
bindings/rust/tests/chip.rs new file +10 -0
+        }
+    }
+
+    mod configure {
+        use super::*;
+        const NGPIO: u64 = 16;
+        const LABEL: &str = "foobar";
+
+        #[test]
+        fn verify() {

bindings/rust/tests/chip.rs line 66

Quoted 10 lines
bindings/rust/tests/chip.rs new file +10 -0
+            let sim = Sim::new(Some(NGPIO), Some(LABEL), true).unwrap();
+            let chip = Chip::open(sim.dev_path()).unwrap();
+
+            assert_eq!(chip.get_label().unwrap(), LABEL);
+            assert_eq!(chip.get_name().unwrap(), sim.chip_name());
+            assert_eq!(chip.get_path().unwrap(), sim.dev_path());
+            assert_eq!(chip.get_num_lines(), NGPIO as u32);
+            chip.get_fd().unwrap();
+        }
+

A test for a basic open on an existing chip and a smoke test of some chip methods is called "verify", so chip::configure::verify?

Quoted 18 lines
bindings/rust/tests/chip.rs new file +18 -0
+        #[test]
+        fn line_lookup() {
+            let sim = Sim::new(Some(NGPIO), None, false).unwrap();
+            sim.set_line_name(0, "zero").unwrap();
+            sim.set_line_name(2, "two").unwrap();
+            sim.set_line_name(3, "three").unwrap();
+            sim.set_line_name(5, "five").unwrap();
+            sim.set_line_name(10, "ten").unwrap();
+            sim.set_line_name(11, "ten").unwrap();
+            sim.enable().unwrap();
+
+            let chip = Chip::open(sim.dev_path()).unwrap();
+
+            // Success case
+            assert_eq!(chip.find_line("zero").unwrap(), 0);
+            assert_eq!(chip.find_line("two").unwrap(), 2);
+            assert_eq!(chip.find_line("three").unwrap(), 3);
+            assert_eq!(chip.find_line("five").unwrap(), 5);

bindings/rust/tests/chip.rs line 94

Quoted 10 lines
bindings/rust/tests/chip.rs new file +10 -0
+
+            // Success with duplicate names, should return first entry
+            assert_eq!(chip.find_line("ten").unwrap(), 10);
+
+            // Failure
+            assert_eq!(
+                chip.find_line("nonexistent").unwrap_err(),
+                ChipError::OperationFailed("Gpio Chip find-line", IoError::new(ENOENT))
+            );
+        }

If it is testing find_line() then why not call it find_line?

Quoted 10 lines
bindings/rust/tests/chip.rs new file +2 -0
+    }
+}
bindings/rust/tests/common/config.rs new file +2 -0
new file mode 100644
index 000000000000..3abd9a8c4c8b
@@ -0,0 +1,117 @@
+// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
+//

bindings/rust/tests/common/config.rs line 12

Quoted 10 lines
bindings/rust/tests/common/config.rs new file +10 -0
+// Copyright 2022 Linaro Ltd. All Rights Reserved.
+//     Viresh Kumar <[email protected]>
+
+use std::sync::Arc;
+
+use crate::common::*;
+
+use libgpiod::{Bias, Chip, Direction, Edge, LineConfig, LineRequest, RequestConfig, Result};
+
+//#[derive(Debug)]

Uncomment or remove.

Quoted 28 lines
bindings/rust/tests/common/config.rs new file +28 -0
+pub(crate) struct TestConfig {
+    sim: Arc<Sim>,
+    chip: Option<Chip>,
+    request: Option<LineRequest>,
+    rconfig: RequestConfig,
+    lconfig: LineConfig,
+}
+
+impl TestConfig {
+    pub(crate) fn new(ngpio: u64) -> Result<Self> {
+        Ok(Self {
+            sim: Arc::new(Sim::new(Some(ngpio), None, true)?),
+            chip: None,
+            request: None,
+            rconfig: RequestConfig::new().unwrap(),
+            lconfig: LineConfig::new().unwrap(),
+        })
+    }
+
+    pub(crate) fn set_pull(&self, offsets: &[u32], pulls: &[u32]) {
+        for i in 0..pulls.len() {
+            self.sim.set_pull(offsets[i], pulls[i] as i32).unwrap();
+        }
+    }
+
+    pub(crate) fn rconfig_consumer(&self, offsets: Option<&[u32]>, consumer: Option<&str>) {
+        if let Some(offsets) = offsets {
+            self.rconfig.set_offsets(offsets);

bindings/rust/tests/common/config.rs line 50

Quoted 10 lines
bindings/rust/tests/common/config.rs new file +10 -0
+        }
+
+        if let Some(consumer) = consumer {
+            self.rconfig.set_consumer(consumer);
+        }
+    }
+
+    pub(crate) fn rconfig(&self, offsets: Option<&[u32]>) {
+        self.rconfig_consumer(offsets, None);
+    }

Almost always called with Some(&offsets), and calling it with None is a NOP.
Where called with Some just call rconfig.set_offsets(). Remove instances of calling with None.

Quoted 73 lines
bindings/rust/tests/common/config.rs new file +67 -0
+
+    pub(crate) fn lconfig(
+        &mut self,
+        dir: Option<Direction>,
+        val: Option<u32>,
+        val_override: Option<(u32, u32)>,
+        edge: Option<Edge>,
+        bias: Option<Bias>,
+    ) {
+        if let Some(bias) = bias {
+            self.lconfig.set_bias_default(bias);
+        }
+
+        if let Some(edge) = edge {
+            self.lconfig.set_edge_detection_default(edge);
+        }
+
+        if let Some(dir) = dir {
+            self.lconfig.set_direction_default(dir);
+        }
+
+        if let Some(val) = val {
+            self.lconfig.set_output_value_default(val);
+        }
+
+        if let Some((offset, val)) = val_override {
+            self.lconfig.set_output_value_override(val, offset);
+        }
+    }
+
+    pub(crate) fn lconfig_raw(&mut self) {
+        self.lconfig(None, None, None, None, None);
+    }
+
+    pub(crate) fn lconfig_edge(&mut self, edge: Option<Edge>) {
+        self.lconfig(None, None, None, edge, None);
+    }
+
+    pub(crate) fn request_lines(&mut self) -> Result<()> {
+        let chip = Chip::open(self.sim.dev_path())?;
+
+        self.request = Some(chip.request_lines(&self.rconfig, &self.lconfig)?);
+        self.chip = Some(chip);
+
+        Ok(())
+    }
+
+    pub(crate) fn sim(&self) -> Arc<Sim> {
+        self.sim.clone()
+    }
+
+    pub(crate) fn chip(&self) -> &Chip {
+        &self.chip.as_ref().unwrap()
+    }
+
+    pub(crate) fn request(&self) -> &LineRequest {
+        &self.request.as_ref().unwrap()
+    }
+}
+
+impl Drop for TestConfig {
+    fn drop(&mut self) {
+        // Explicit freeing is important to make sure "request" get freed
+        // before "sim" and "chip".
+        self.request = None;
+    }
+}
bindings/rust/tests/common/mod.rs new file +0 -0
new file mode 100644
index 000000000000..2dc37986396b

bindings/rust/tests/common/mod.rs line 10

Quoted 10 lines
bindings/rust/tests/common/mod.rs new file +10 -0
@@ -0,0 +1,16 @@
+// 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 sim;
+
+#[allow(unused_imports)]
+pub(crate) use sim::*;

This allow seems unnecessary.

bindings/rust/tests/common/mod.rs line 16

Quoted 6 lines
bindings/rust/tests/common/mod.rs new file +6 -0
+
+#[allow(dead_code)]
+mod config;
+
+#[allow(unused_imports)]
+pub(crate) use config::*;

The dead_code allows make me think there is a problem with the way the common test code is structured or there is actual dead code in there.

common/sim.rs should be part of a gpiosim crate??

Quoted 351 lines
bindings/rust/tests/common/sim.rs new file +306 -0
new file mode 100644
index 000000000000..cd5ec66c3da5
@@ -0,0 +1,306 @@
+// 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_char;
+use std::{slice, str};
+
+use vmm_sys_util::errno::Error as IoError;
+
+use libgpiod::{Error, Result};
+use libgpiod_sys as bindings;
+
+/// Sim Ctx
+#[derive(Debug)]
+struct SimCtx {
+    ctx: *mut bindings::gpiosim_ctx,
+}
+
+unsafe impl Send for SimCtx {}
+unsafe impl Sync for SimCtx {}
+
+impl SimCtx {
+    fn new() -> Result<Self> {
+        let ctx = unsafe { bindings::gpiosim_ctx_new() };
+        if ctx.is_null() {
+            return Err(Error::OperationFailed("gpio-sim ctx new", IoError::last()));
+        }
+
+        Ok(Self { ctx })
+    }
+
+    fn ctx(&self) -> *mut bindings::gpiosim_ctx {
+        self.ctx
+    }
+}
+
+impl Drop for SimCtx {
+    fn drop(&mut self) {
+        unsafe { bindings::gpiosim_ctx_unref(self.ctx) }
+    }
+}
+
+/// Sim Dev
+#[derive(Debug)]
+struct SimDev {
+    dev: *mut bindings::gpiosim_dev,
+}
+
+unsafe impl Send for SimDev {}
+unsafe impl Sync for SimDev {}
+
+impl SimDev {
+    fn new(ctx: &SimCtx) -> Result<Self> {
+        let dev = unsafe { bindings::gpiosim_dev_new(ctx.ctx()) };
+        if dev.is_null() {
+            return Err(Error::OperationFailed("gpio-sim dev new", IoError::last()));
+        }
+
+        Ok(Self { dev })
+    }
+
+    fn dev(&self) -> *mut bindings::gpiosim_dev {
+        self.dev
+    }
+
+    fn enable(&self) -> Result<()> {
+        let ret = unsafe { bindings::gpiosim_dev_enable(self.dev) };
+
+        if ret == -1 {
+            Err(Error::OperationFailed(
+                "gpio-sim dev-enable",
+                IoError::last(),
+            ))
+        } else {
+            Ok(())
+        }
+    }
+
+    fn disable(&self) -> Result<()> {
+        let ret = unsafe { bindings::gpiosim_dev_disable(self.dev) };
+
+        if ret == -1 {
+            Err(Error::OperationFailed(
+                "gpio-sim dev-disable",
+                IoError::last(),
+            ))
+        } else {
+            Ok(())
+        }
+    }
+}
+
+impl Drop for SimDev {
+    fn drop(&mut self) {
+        unsafe { bindings::gpiosim_dev_unref(self.dev) }
+    }
+}
+
+/// Sim Bank
+#[derive(Debug)]
+struct SimBank {
+    bank: *mut bindings::gpiosim_bank,
+}
+
+unsafe impl Send for SimBank {}
+unsafe impl Sync for SimBank {}
+
+impl SimBank {
+    fn new(dev: &SimDev) -> Result<Self> {
+        let bank = unsafe { bindings::gpiosim_bank_new(dev.dev()) };
+        if bank.is_null() {
+            return Err(Error::OperationFailed("gpio-sim Bank new", IoError::last()));
+        }
+
+        Ok(Self { bank })
+    }
+
+    fn chip_name(&self) -> Result<&str> {
+        // SAFETY: The string returned by gpiosim is guaranteed to live as long
+        // as the `struct SimBank`.
+        let name = unsafe { bindings::gpiosim_bank_get_chip_name(self.bank) };
+
+        // SAFETY: The string is guaranteed to be valid here.
+        str::from_utf8(unsafe {
+            slice::from_raw_parts(name as *const u8, bindings::strlen(name) as usize)
+        })
+        .map_err(Error::InvalidString)
+    }
+
+    fn dev_path(&self) -> Result<&str> {
+        // SAFETY: The string returned by gpiosim is guaranteed to live as long
+        // as the `struct SimBank`.
+        let path = unsafe { bindings::gpiosim_bank_get_dev_path(self.bank) };
+
+        // SAFETY: The string is guaranteed to be valid here.
+        str::from_utf8(unsafe {
+            slice::from_raw_parts(path as *const u8, bindings::strlen(path) as usize)
+        })
+        .map_err(Error::InvalidString)
+    }
+
+    fn val(&self, offset: u32) -> Result<u32> {
+        let ret = unsafe { bindings::gpiosim_bank_get_value(self.bank, offset) };
+
+        if ret == -1 {
+            Err(Error::OperationFailed(
+                "gpio-sim get-value",
+                IoError::last(),
+            ))
+        } else {
+            Ok(ret as u32)
+        }
+    }
+
+    fn set_label(&self, label: &str) -> Result<()> {
+        // Null-terminate the string
+        let label = label.to_owned() + "\0";
+
+        let ret =
+            unsafe { bindings::gpiosim_bank_set_label(self.bank, label.as_ptr() as *const c_char) };
+
+        if ret == -1 {
+            Err(Error::OperationFailed(
+                "gpio-sim set-label",
+                IoError::last(),
+            ))
+        } else {
+            Ok(())
+        }
+    }
+
+    fn set_num_lines(&self, num: u64) -> Result<()> {
+        let ret = unsafe { bindings::gpiosim_bank_set_num_lines(self.bank, num) };
+        if ret == -1 {
+            Err(Error::OperationFailed(
+                "gpio-sim set-num-lines",
+                IoError::last(),
+            ))
+        } else {
+            Ok(())
+        }
+    }
+
+    fn set_line_name(&self, offset: u32, name: &str) -> Result<()> {
+        // Null-terminate the string
+        let name = name.to_owned() + "\0";
+
+        let ret = unsafe {
+            bindings::gpiosim_bank_set_line_name(self.bank, offset, name.as_ptr() as *const c_char)
+        };
+
+        if ret == -1 {
+            Err(Error::OperationFailed(
+                "gpio-sim set-line-name",
+                IoError::last(),
+            ))
+        } else {
+            Ok(())
+        }
+    }
+
+    fn set_pull(&self, offset: u32, pull: i32) -> Result<()> {
+        let ret = unsafe { bindings::gpiosim_bank_set_pull(self.bank, offset, pull) };
+
+        if ret == -1 {
+            Err(Error::OperationFailed("gpio-sim set-pull", IoError::last()))
+        } else {
+            Ok(())
+        }
+    }
+
+    fn hog_line(&self, offset: u32, name: &str, dir: i32) -> Result<()> {
+        // Null-terminate the string
+        let name = name.to_owned() + "\0";
+
+        let ret = unsafe {
+            bindings::gpiosim_bank_hog_line(self.bank, offset, name.as_ptr() as *const c_char, dir)
+        };
+
+        if ret == -1 {
+            Err(Error::OperationFailed("gpio-sim hog-line", IoError::last()))
+        } else {
+            Ok(())
+        }
+    }
+}
+
+impl Drop for SimBank {
+    fn drop(&mut self) {
+        unsafe { bindings::gpiosim_bank_unref(self.bank) }
+    }
+}
+
+/// GPIO SIM
+#[derive(Debug)]
+pub(crate) struct Sim {
+    ctx: SimCtx,
+    dev: SimDev,
+    bank: SimBank,
+}
+
+unsafe impl Send for Sim {}
+unsafe impl Sync for Sim {}
+
+impl Sim {
+    pub(crate) fn new(ngpio: Option<u64>, label: Option<&str>, enable: bool) -> Result<Self> {
+        let ctx = SimCtx::new()?;
+        let dev = SimDev::new(&ctx)?;
+        let bank = SimBank::new(&dev)?;
+
+        if let Some(ngpio) = ngpio {
+            bank.set_num_lines(ngpio)?;
+        }
+
+        if let Some(label) = label {
+            bank.set_label(label)?;
+        }
+
+        if enable {
+            dev.enable()?;
+        }
+
+        Ok(Self { ctx, dev, bank })
+    }
+
+    pub(crate) fn chip_name(&self) -> &str {
+        self.bank.chip_name().unwrap()
+    }
+
+    pub fn dev_path(&self) -> &str {
+        self.bank.dev_path().unwrap()
+    }
+
+    pub(crate) fn val(&self, offset: u32) -> Result<u32> {
+        self.bank.val(offset)
+    }
+
+    pub(crate) fn set_label(&self, label: &str) -> Result<()> {
+        self.bank.set_label(label)
+    }
+
+    pub(crate) fn set_num_lines(&self, num: u64) -> Result<()> {
+        self.bank.set_num_lines(num)
+    }
+
+    pub(crate) fn set_line_name(&self, offset: u32, name: &str) -> Result<()> {
+        self.bank.set_line_name(offset, name)
+    }
+
+    pub(crate) fn set_pull(&self, offset: u32, pull: i32) -> Result<()> {
+        self.bank.set_pull(offset, pull)
+    }
+
+    pub(crate) fn hog_line(&self, offset: u32, name: &str, dir: i32) -> Result<()> {
+        self.bank.hog_line(offset, name, dir)
+    }
+
+    pub(crate) fn enable(&self) -> Result<()> {
+        self.dev.enable()
+    }
+
+    pub(crate) fn disable(&self) -> Result<()> {
+        self.dev.disable()
+    }
+}
bindings/rust/tests/edge_event.rs new file +33 -0
new file mode 100644
index 000000000000..1b05b225aab7
@@ -0,0 +1,389 @@
+// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
+//
+// Copyright 2022 Linaro Ltd. All Rights Reserved.
+//     Viresh Kumar <[email protected]>
+
+mod common;
+
+mod edge_event {
+    use libc::EINVAL;
+    use std::sync::Arc;
+    use std::thread::{sleep, spawn};
+    use std::time::Duration;
+
+    use vmm_sys_util::errno::Error as IoError;
+
+    use crate::common::*;
+    use libgpiod::{Direction, Edge, EdgeEventBuffer, Error as ChipError, LineEdgeEvent};
+    use libgpiod_sys::{GPIOSIM_PULL_DOWN, GPIOSIM_PULL_UP};
+
+    const NGPIO: u64 = 8;
+
+    mod buffer_settings {
+        use super::*;
+
+        #[test]
+        fn default_capacity() {
+            assert_eq!(EdgeEventBuffer::new(0).unwrap().get_capacity(), 64);
+        }
+
+        #[test]
+        fn user_defined_capacity() {
+            assert_eq!(EdgeEventBuffer::new(123).unwrap().get_capacity(), 123);
+        }

bindings/rust/tests/edge_event.rs line 43

Quoted 10 lines
bindings/rust/tests/edge_event.rs new file +10 -0
+
+        #[test]
+        fn max_capacity() {
+            assert_eq!(EdgeEventBuffer::new(1024 * 2).unwrap().get_capacity(), 1024);
+        }
+    }
+
+    mod failure {
+        use super::*;
+

Don't see the point of the failure/verify namespace level here.

Quoted 6 lines
bindings/rust/tests/edge_event.rs new file +6 -0
+        #[test]
+        fn wait_timeout() {
+            let mut config = TestConfig::new(NGPIO).unwrap();
+            config.rconfig(Some(&[0]));
+            config.lconfig_edge(Some(Edge::Both));
+            config.request_lines().unwrap();

bindings/rust/tests/edge_event.rs line 59

Quoted 10 lines
bindings/rust/tests/edge_event.rs new file +10 -0
+
+            // No events available
+            assert_eq!(
+                config
+                    .request()
+                    .wait_edge_event(Duration::from_millis(100))
+                    .unwrap_err(),
+                ChipError::OperationTimedOut
+            );
+        }

Is a timeout really a "failure"?

It is testing wait_edge_event(), which is a method of line_request, and so should be in the line_request test suite.

Quoted 4 lines
bindings/rust/tests/edge_event.rs new file +4 -0
+
+        #[test]
+        fn dir_out_edge_failure() {
+            let mut config = TestConfig::new(NGPIO).unwrap();

bindings/rust/tests/edge_event.rs line 73

Quoted 10 lines
bindings/rust/tests/edge_event.rs new file +10 -0
+            config.rconfig(Some(&[0]));
+            config.lconfig(Some(Direction::Output), None, None, Some(Edge::Both), None);
+
+            assert_eq!(
+                config.request_lines().unwrap_err(),
+                ChipError::OperationFailed("Gpio LineRequest request-lines", IoError::new(EINVAL))
+            );
+        }
+    }
+

This is testing a failure mode of request_lines(), not edge_events. Where is the edge_event here?

Quoted 351 lines
bindings/rust/tests/edge_event.rs new file +316 -0
+    mod verify {
+        use super::*;
+
+        // Helpers to generate events
+        fn trigger_falling_and_rising_edge(sim: Arc<Sim>, offset: u32) {
+            spawn(move || {
+                sleep(Duration::from_millis(30));
+                sim.set_pull(offset, GPIOSIM_PULL_UP as i32).unwrap();
+
+                sleep(Duration::from_millis(30));
+                sim.set_pull(offset, GPIOSIM_PULL_DOWN as i32).unwrap();
+            });
+        }
+
+        fn trigger_rising_edge_events_on_two_offsets(sim: Arc<Sim>, offset: [u32; 2]) {
+            spawn(move || {
+                sleep(Duration::from_millis(30));
+                sim.set_pull(offset[0], GPIOSIM_PULL_UP as i32).unwrap();
+
+                sleep(Duration::from_millis(30));
+                sim.set_pull(offset[1], GPIOSIM_PULL_UP as i32).unwrap();
+            });
+        }
+
+        fn trigger_multiple_events(sim: Arc<Sim>, offset: u32) {
+            sim.set_pull(offset, GPIOSIM_PULL_UP as i32).unwrap();
+            sleep(Duration::from_millis(10));
+
+            sim.set_pull(offset, GPIOSIM_PULL_DOWN as i32).unwrap();
+            sleep(Duration::from_millis(10));
+
+            sim.set_pull(offset, GPIOSIM_PULL_UP as i32).unwrap();
+            sleep(Duration::from_millis(10));
+        }
+
+        #[test]
+        fn both_edges() {
+            const GPIO: u32 = 2;
+            let buf = EdgeEventBuffer::new(0).unwrap();
+            let mut config = TestConfig::new(NGPIO).unwrap();
+            config.rconfig(Some(&[GPIO]));
+            config.lconfig_edge(Some(Edge::Both));
+            config.request_lines().unwrap();
+
+            // Generate events
+            trigger_falling_and_rising_edge(config.sim(), GPIO);
+
+            // Rising event
+            config
+                .request()
+                .wait_edge_event(Duration::from_secs(1))
+                .unwrap();
+
+            assert_eq!(
+                config
+                    .request()
+                    .read_edge_event(&buf, buf.get_capacity())
+                    .unwrap(),
+                1
+            );
+            assert_eq!(buf.get_num_events(), 1);
+
+            let event = buf.get_event(0).unwrap();
+            let ts_rising = event.get_timestamp();
+            assert_eq!(event.get_event_type().unwrap(), LineEdgeEvent::Rising);
+            assert_eq!(event.get_line_offset(), GPIO);
+
+            // Falling event
+            config
+                .request()
+                .wait_edge_event(Duration::from_secs(1))
+                .unwrap();
+
+            assert_eq!(
+                config
+                    .request()
+                    .read_edge_event(&buf, buf.get_capacity())
+                    .unwrap(),
+                1
+            );
+            assert_eq!(buf.get_num_events(), 1);
+
+            let event = buf.get_event(0).unwrap();
+            let ts_falling = event.get_timestamp();
+            assert_eq!(event.get_event_type().unwrap(), LineEdgeEvent::Falling);
+            assert_eq!(event.get_line_offset(), GPIO);
+
+            // No events available
+            assert_eq!(
+                config
+                    .request()
+                    .wait_edge_event(Duration::from_millis(100))
+                    .unwrap_err(),
+                ChipError::OperationTimedOut
+            );
+
+            assert!(ts_falling > ts_rising);
+        }
+
+        #[test]
+        fn rising_edge() {
+            const GPIO: u32 = 6;
+            let buf = EdgeEventBuffer::new(0).unwrap();
+            let mut config = TestConfig::new(NGPIO).unwrap();
+            config.rconfig(Some(&[GPIO]));
+            config.lconfig_edge(Some(Edge::Rising));
+            config.request_lines().unwrap();
+
+            // Generate events
+            trigger_falling_and_rising_edge(config.sim(), GPIO);
+
+            // Rising event
+            config
+                .request()
+                .wait_edge_event(Duration::from_secs(1))
+                .unwrap();
+
+            assert_eq!(
+                config
+                    .request()
+                    .read_edge_event(&buf, buf.get_capacity())
+                    .unwrap(),
+                1
+            );
+            assert_eq!(buf.get_num_events(), 1);
+
+            let event = buf.get_event(0).unwrap();
+            assert_eq!(event.get_event_type().unwrap(), LineEdgeEvent::Rising);
+            assert_eq!(event.get_line_offset(), GPIO);
+
+            // No events available
+            assert_eq!(
+                config
+                    .request()
+                    .wait_edge_event(Duration::from_millis(100))
+                    .unwrap_err(),
+                ChipError::OperationTimedOut
+            );
+        }
+
+        #[test]
+        fn falling_edge() {
+            const GPIO: u32 = 7;
+            let buf = EdgeEventBuffer::new(0).unwrap();
+            let mut config = TestConfig::new(NGPIO).unwrap();
+            config.rconfig(Some(&[GPIO]));
+            config.lconfig_edge(Some(Edge::Falling));
+            config.request_lines().unwrap();
+
+            // Generate events
+            trigger_falling_and_rising_edge(config.sim(), GPIO);
+
+            // Falling event
+            config
+                .request()
+                .wait_edge_event(Duration::from_secs(1))
+                .unwrap();
+
+            assert_eq!(
+                config
+                    .request()
+                    .read_edge_event(&buf, buf.get_capacity())
+                    .unwrap(),
+                1
+            );
+            assert_eq!(buf.get_num_events(), 1);
+
+            let event = buf.get_event(0).unwrap();
+            assert_eq!(event.get_event_type().unwrap(), LineEdgeEvent::Falling);
+            assert_eq!(event.get_line_offset(), GPIO);
+
+            // No events available
+            assert_eq!(
+                config
+                    .request()
+                    .wait_edge_event(Duration::from_millis(100))
+                    .unwrap_err(),
+                ChipError::OperationTimedOut
+            );
+        }
+
+        #[test]
+        fn edge_sequence() {
+            const GPIO: [u32; 2] = [0, 1];
+            let mut config = TestConfig::new(NGPIO).unwrap();
+            config.rconfig(Some(&GPIO));
+            config.lconfig_edge(Some(Edge::Both));
+            config.request_lines().unwrap();
+
+            // Generate events
+            trigger_rising_edge_events_on_two_offsets(config.sim(), GPIO);
+
+            // Rising event GPIO 0
+            let buf = EdgeEventBuffer::new(0).unwrap();
+            config
+                .request()
+                .wait_edge_event(Duration::from_secs(1))
+                .unwrap();
+
+            assert_eq!(
+                config
+                    .request()
+                    .read_edge_event(&buf, buf.get_capacity())
+                    .unwrap(),
+                1
+            );
+            assert_eq!(buf.get_num_events(), 1);
+
+            let event = buf.get_event(0).unwrap();
+            assert_eq!(event.get_event_type().unwrap(), LineEdgeEvent::Rising);
+            assert_eq!(event.get_line_offset(), GPIO[0]);
+            assert_eq!(event.get_global_seqno(), 1);
+            assert_eq!(event.get_line_seqno(), 1);
+
+            // Rising event GPIO 1
+            config
+                .request()
+                .wait_edge_event(Duration::from_secs(1))
+                .unwrap();
+
+            assert_eq!(
+                config
+                    .request()
+                    .read_edge_event(&buf, buf.get_capacity())
+                    .unwrap(),
+                1
+            );
+            assert_eq!(buf.get_num_events(), 1);
+
+            let event = buf.get_event(0).unwrap();
+            assert_eq!(event.get_event_type().unwrap(), LineEdgeEvent::Rising);
+            assert_eq!(event.get_line_offset(), GPIO[1]);
+            assert_eq!(event.get_global_seqno(), 2);
+            assert_eq!(event.get_line_seqno(), 1);
+
+            // No events available
+            assert_eq!(
+                config
+                    .request()
+                    .wait_edge_event(Duration::from_millis(100))
+                    .unwrap_err(),
+                ChipError::OperationTimedOut
+            );
+        }
+
+        #[test]
+        fn multiple_events() {
+            const GPIO: u32 = 1;
+            let buf = EdgeEventBuffer::new(0).unwrap();
+            let mut config = TestConfig::new(NGPIO).unwrap();
+            config.rconfig(Some(&[GPIO]));
+            config.lconfig_edge(Some(Edge::Both));
+            config.request_lines().unwrap();
+
+            // Generate events
+            trigger_multiple_events(config.sim(), GPIO);
+
+            // Read multiple events
+            config
+                .request()
+                .wait_edge_event(Duration::from_secs(1))
+                .unwrap();
+
+            assert_eq!(
+                config
+                    .request()
+                    .read_edge_event(&buf, buf.get_capacity())
+                    .unwrap(),
+                3
+            );
+            assert_eq!(buf.get_num_events(), 3);
+
+            let mut global_seqno = 1;
+            let mut line_seqno = 1;
+
+            // Verify sequence number of events
+            for i in 0..3 {
+                let event = buf.get_event(i).unwrap();
+                assert_eq!(event.get_line_offset(), GPIO);
+                assert_eq!(event.get_global_seqno(), global_seqno);
+                assert_eq!(event.get_line_seqno(), line_seqno);
+
+                global_seqno += 1;
+                line_seqno += 1;
+            }
+        }
+
+        #[test]
+        fn over_capacity() {
+            const GPIO: u32 = 2;
+            let buf = EdgeEventBuffer::new(2).unwrap();
+            let mut config = TestConfig::new(NGPIO).unwrap();
+            config.rconfig(Some(&[GPIO]));
+            config.lconfig_edge(Some(Edge::Both));
+            config.request_lines().unwrap();
+
+            // Generate events
+            trigger_multiple_events(config.sim(), GPIO);
+
+            // Read multiple events
+            config
+                .request()
+                .wait_edge_event(Duration::from_secs(1))
+                .unwrap();
+
+            assert_eq!(
+                config
+                    .request()
+                    .read_edge_event(&buf, buf.get_capacity())
+                    .unwrap(),
+                2
+            );
+            assert_eq!(buf.get_num_events(), 2);
+        }
+    }
+}
bindings/rust/tests/info_event.rs new file +29 -0
new file mode 100644
index 000000000000..96d8385deadf
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
+//
+// Copyright 2022 Linaro Ltd. All Rights Reserved.
+//     Viresh Kumar <[email protected]>
+
+mod common;
+
+mod info_event {
+    use libc::EINVAL;
+    use std::sync::Arc;
+    use std::thread::{sleep, spawn};
+    use std::time::Duration;
+
+    use vmm_sys_util::errno::Error as IoError;
+
+    use crate::common::*;
+    use libgpiod::{Chip, Direction, Error as ChipError, Event, LineConfig, RequestConfig};
+
+    fn request_reconfigure_line(chip: Arc<Chip>) {
+        spawn(move || {
+            sleep(Duration::from_millis(10));
+
+            let lconfig1 = LineConfig::new().unwrap();
+            let rconfig = RequestConfig::new().unwrap();
+            rconfig.set_offsets(&[7]);
+
+            let request = chip.request_lines(&rconfig, &lconfig1).unwrap();
+
+            sleep(Duration::from_millis(10));

bindings/rust/tests/info_event.rs line 39

Quoted 10 lines
bindings/rust/tests/info_event.rs new file +10 -0
+
+            let mut lconfig2 = LineConfig::new().unwrap();
+            lconfig2.set_direction_default(Direction::Output);
+
+            request.reconfigure_lines(&lconfig2).unwrap();
+
+            sleep(Duration::from_millis(10));
+        });
+    }
+

Benefit of the background thread?

Quoted 119 lines
bindings/rust/tests/info_event.rs new file +87 -0
+    mod watch {
+        use super::*;
+        const NGPIO: u64 = 8;
+        const GPIO: u32 = 7;
+
+        #[test]
+        fn failure() {
+            let sim = Sim::new(Some(NGPIO), None, true).unwrap();
+            let chip = Chip::open(sim.dev_path()).unwrap();
+
+            assert_eq!(
+                chip.watch_line_info(NGPIO as u32).unwrap_err(),
+                ChipError::OperationFailed("Gpio LineInfo line-info", IoError::new(EINVAL))
+            );
+
+            chip.watch_line_info(3).unwrap();
+
+            // No events available
+            assert_eq!(
+                chip.wait_info_event(Duration::from_millis(100))
+                    .unwrap_err(),
+                ChipError::OperationTimedOut
+            );
+        }
+
+        #[test]
+        fn verify() {
+            let sim = Sim::new(Some(NGPIO), None, true).unwrap();
+            let chip = Chip::open(sim.dev_path()).unwrap();
+            let info = chip.watch_line_info(GPIO).unwrap();
+
+            assert_eq!(info.get_offset(), GPIO);
+        }
+
+        #[test]
+        fn reconfigure() {
+            let sim = Sim::new(Some(NGPIO), None, true).unwrap();
+            let chip = Arc::new(Chip::open(sim.dev_path()).unwrap());
+            let info = chip.watch_line_info(GPIO).unwrap();
+
+            assert_eq!(info.get_direction().unwrap(), Direction::Input);
+
+            // Generate events
+            request_reconfigure_line(chip.clone());
+
+            // Line requested event
+            chip.wait_info_event(Duration::from_secs(1)).unwrap();
+            let event = chip.read_info_event().unwrap();
+            let ts_req = event.get_timestamp();
+
+            assert_eq!(event.get_event_type().unwrap(), Event::LineRequested);
+            assert_eq!(
+                event.line_info().unwrap().get_direction().unwrap(),
+                Direction::Input
+            );
+
+            // Line changed event
+            chip.wait_info_event(Duration::from_secs(1)).unwrap();
+            let event = chip.read_info_event().unwrap();
+            let ts_rec = event.get_timestamp();
+
+            assert_eq!(event.get_event_type().unwrap(), Event::LineConfigChanged);
+            assert_eq!(
+                event.line_info().unwrap().get_direction().unwrap(),
+                Direction::Output
+            );
+
+            // Line released event
+            chip.wait_info_event(Duration::from_secs(1)).unwrap();
+            let event = chip.read_info_event().unwrap();
+            let ts_rel = event.get_timestamp();
+
+            assert_eq!(event.get_event_type().unwrap(), Event::LineReleased);
+
+            // No events available
+            assert_eq!(
+                chip.wait_info_event(Duration::from_millis(100))
+                    .unwrap_err(),
+                ChipError::OperationTimedOut
+            );
+
+            // Check timestamps are really monotonic.
+            assert!(ts_rel > ts_rec);
+            assert!(ts_rec > ts_req);
+        }
+    }
+}
bindings/rust/tests/line_config.rs new file +26 -0
new file mode 100644
index 000000000000..82879324a7f0
@@ -0,0 +1,187 @@
+// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
+//
+// Copyright 2022 Linaro Ltd. All Rights Reserved.
+//     Viresh Kumar <[email protected]>
+
+mod common;
+
+mod line_config {
+    use std::time::Duration;
+
+    use libgpiod::{Bias, Direction, Drive, Edge, EventClock, LineConfig};
+
+    mod default {
+        use super::*;
+
+        #[test]
+        fn verify() {
+            let lconfig = LineConfig::new().unwrap();
+
+            assert_eq!(lconfig.get_direction_default().unwrap(), Direction::AsIs);
+            assert_eq!(lconfig.get_edge_detection_default().unwrap(), Edge::None);
+            assert_eq!(lconfig.get_bias_default().unwrap(), Bias::AsIs);
+            assert_eq!(lconfig.get_drive_default().unwrap(), Drive::PushPull);
+            assert_eq!(lconfig.get_active_low_default(), false);
+            assert_eq!(
+                lconfig.get_debounce_period_default().unwrap(),

bindings/rust/tests/line_config.rs line 36

Quoted 10 lines
bindings/rust/tests/line_config.rs new file +10 -0
+                Duration::from_millis(0)
+            );
+            assert_eq!(
+                lconfig.get_event_clock_default().unwrap(),
+                EventClock::Monotonic
+            );
+            assert_eq!(lconfig.get_output_value_default().unwrap(), 0);
+            assert_eq!(lconfig.get_overrides().unwrap().len(), 0);
+        }
+    }

The only test in the default mod is verify.
Drop the mod and rename the test to default.

Quoted 236 lines
bindings/rust/tests/line_config.rs new file +151 -0
+
+    mod overrides {
+        use super::*;
+
+        #[test]
+        fn direction() {
+            const GPIO: u32 = 0;
+            let mut lconfig = LineConfig::new().unwrap();
+
+            lconfig.set_direction_default(Direction::AsIs);
+            lconfig.set_direction_override(Direction::Input, GPIO);
+
+            assert_eq!(lconfig.direction_is_overridden(GPIO), true);
+            assert_eq!(
+                lconfig.get_direction_offset(GPIO).unwrap(),
+                Direction::Input
+            );
+
+            lconfig.clear_direction_override(GPIO);
+            assert_eq!(lconfig.direction_is_overridden(GPIO), false);
+            assert_eq!(lconfig.get_direction_offset(GPIO).unwrap(), Direction::AsIs);
+        }
+
+        #[test]
+        fn edge_detection() {
+            const GPIO: u32 = 1;
+            let mut lconfig = LineConfig::new().unwrap();
+
+            lconfig.set_edge_detection_default(Edge::None);
+            lconfig.set_edge_detection_override(Edge::Both, GPIO);
+
+            assert_eq!(lconfig.edge_detection_is_overridden(GPIO), true);
+            assert_eq!(lconfig.get_edge_detection_offset(GPIO).unwrap(), Edge::Both);
+
+            lconfig.clear_edge_detection_override(GPIO);
+            assert_eq!(lconfig.edge_detection_is_overridden(GPIO), false);
+            assert_eq!(lconfig.get_edge_detection_offset(GPIO).unwrap(), Edge::None);
+        }
+
+        #[test]
+        fn bias() {
+            const GPIO: u32 = 2;
+            let mut lconfig = LineConfig::new().unwrap();
+
+            lconfig.set_bias_default(Bias::AsIs);
+            lconfig.set_bias_override(Bias::PullDown, GPIO);
+
+            assert_eq!(lconfig.bias_is_overridden(GPIO), true);
+            assert_eq!(lconfig.get_bias_offset(GPIO).unwrap(), Bias::PullDown);
+
+            lconfig.clear_bias_override(GPIO);
+            assert_eq!(lconfig.bias_is_overridden(GPIO), false);
+            assert_eq!(lconfig.get_bias_offset(GPIO).unwrap(), Bias::AsIs);
+        }
+
+        #[test]
+        fn drive() {
+            const GPIO: u32 = 3;
+            let mut lconfig = LineConfig::new().unwrap();
+
+            lconfig.set_drive_default(Drive::PushPull);
+            lconfig.set_drive_override(Drive::OpenDrain, GPIO);
+
+            assert_eq!(lconfig.drive_is_overridden(GPIO), true);
+            assert_eq!(lconfig.get_drive_offset(GPIO).unwrap(), Drive::OpenDrain);
+
+            lconfig.clear_drive_override(GPIO);
+            assert_eq!(lconfig.drive_is_overridden(GPIO), false);
+            assert_eq!(lconfig.get_drive_offset(GPIO).unwrap(), Drive::PushPull);
+        }
+
+        #[test]
+        fn active_low() {
+            const GPIO: u32 = 4;
+            let mut lconfig = LineConfig::new().unwrap();
+
+            lconfig.set_active_low_default(false);
+            lconfig.set_active_low_override(true, GPIO);
+
+            assert_eq!(lconfig.active_low_is_overridden(GPIO), true);
+            assert_eq!(lconfig.get_active_low_offset(GPIO), true);
+
+            lconfig.clear_active_low_override(GPIO);
+            assert_eq!(lconfig.active_low_is_overridden(GPIO), false);
+            assert_eq!(lconfig.get_active_low_offset(GPIO), false);
+        }
+
+        #[test]
+        fn debounce_period() {
+            const GPIO: u32 = 5;
+            let mut lconfig = LineConfig::new().unwrap();
+
+            lconfig.set_debounce_period_default(Duration::from_millis(5));
+            lconfig.set_debounce_period_override(Duration::from_millis(3), GPIO);
+
+            assert_eq!(lconfig.debounce_period_is_overridden(GPIO), true);
+            assert_eq!(
+                lconfig.get_debounce_period_offset(GPIO).unwrap(),
+                Duration::from_millis(3)
+            );
+
+            lconfig.clear_debounce_period_override(GPIO);
+            assert_eq!(lconfig.debounce_period_is_overridden(GPIO), false);
+            assert_eq!(
+                lconfig.get_debounce_period_offset(GPIO).unwrap(),
+                Duration::from_millis(5)
+            );
+        }
+
+        #[test]
+        fn event_clock() {
+            const GPIO: u32 = 6;
+            let mut lconfig = LineConfig::new().unwrap();
+
+            lconfig.set_event_clock_default(EventClock::Monotonic);
+            lconfig.set_event_clock_override(EventClock::Realtime, GPIO);
+
+            assert_eq!(lconfig.event_clock_is_overridden(GPIO), true);
+            assert_eq!(
+                lconfig.get_event_clock_offset(GPIO).unwrap(),
+                EventClock::Realtime
+            );
+
+            lconfig.clear_event_clock_override(GPIO);
+            assert_eq!(lconfig.event_clock_is_overridden(GPIO), false);
+            assert_eq!(
+                lconfig.get_event_clock_offset(GPIO).unwrap(),
+                EventClock::Monotonic
+            );
+        }
+
+        #[test]
+        fn output_value() {
+            const GPIO: u32 = 0;
+            let mut lconfig = LineConfig::new().unwrap();
+
+            lconfig.set_output_value_default(0);
+            lconfig.set_output_value_override(1, GPIO);
+            lconfig.set_output_values(&[1, 2, 8], &[1, 1, 1]).unwrap();
+
+            for line in [0, 1, 2, 8] {
+                assert_eq!(lconfig.output_value_is_overridden(line), true);
+                assert_eq!(lconfig.get_output_value_offset(line).unwrap(), 1);
+
+                lconfig.clear_output_value_override(line);
+                assert_eq!(lconfig.output_value_is_overridden(line), false);
+                assert_eq!(lconfig.get_output_value_offset(line).unwrap(), 0);
+            }
+        }
+    }
+}
bindings/rust/tests/line_info.rs new file +79 -0
new file mode 100644
index 000000000000..f6f8f592cc85
@@ -0,0 +1,90 @@
+// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
+//
+// Copyright 2022 Linaro Ltd. All Rights Reserved.
+//     Viresh Kumar <[email protected]>
+
+mod common;
+
+mod line_info {
+    use libc::EINVAL;
+    use std::time::Duration;
+
+    use vmm_sys_util::errno::Error as IoError;
+
+    use crate::common::*;
+    use libgpiod::{Bias, Chip, Direction, Drive, Edge, Error as ChipError, EventClock};
+    use libgpiod_sys::{GPIOSIM_HOG_DIR_OUTPUT_HIGH, GPIOSIM_HOG_DIR_OUTPUT_LOW};
+
+    const NGPIO: u64 = 8;
+
+    mod basic {
+        use super::*;
+
+        #[test]
+        fn verify() {
+            const GPIO: u32 = 0;
+            const LABEL: &str = "foobar";
+            let sim = Sim::new(Some(NGPIO), None, false).unwrap();
+            sim.set_line_name(GPIO, LABEL).unwrap();
+            sim.hog_line(GPIO, "hog", GPIOSIM_HOG_DIR_OUTPUT_HIGH as i32)
+                .unwrap();
+            sim.enable().unwrap();
+
+            let chip = Chip::open(sim.dev_path()).unwrap();
+            let info = chip.line_info(GPIO).unwrap();
+
+            assert_eq!(info.get_offset(), GPIO);
+            assert_eq!(info.get_name().unwrap(), LABEL);
+            assert_eq!(info.is_used(), true);
+            assert_eq!(info.get_consumer().unwrap(), "hog");
+            assert_eq!(info.get_direction().unwrap(), Direction::Output);
+            assert_eq!(info.is_active_low(), false);
+            assert_eq!(info.get_bias().unwrap(), Bias::Unknown);
+            assert_eq!(info.get_drive().unwrap(), Drive::PushPull);
+            assert_eq!(info.get_edge_detection().unwrap(), Edge::None);
+            assert_eq!(info.get_event_clock().unwrap(), EventClock::Monotonic);
+            assert_eq!(info.is_debounced(), false);
+            assert_eq!(info.get_debounce_period(), Duration::from_millis(0));
+
+            assert_eq!(
+                chip.line_info(NGPIO as u32).unwrap_err(),
+                ChipError::OperationFailed("Gpio LineInfo line-info", IoError::new(EINVAL))
+            );
+        }
+    }
+
+    mod properties {
+        use super::*;
+
+        #[test]
+        fn verify() {
+            let sim = Sim::new(Some(NGPIO), None, false).unwrap();
+            sim.set_line_name(1, "one").unwrap();
+            sim.set_line_name(2, "two").unwrap();
+            sim.set_line_name(4, "four").unwrap();
+            sim.set_line_name(5, "five").unwrap();
+            sim.hog_line(3, "hog3", GPIOSIM_HOG_DIR_OUTPUT_HIGH as i32)
+                .unwrap();
+            sim.hog_line(4, "hog4", GPIOSIM_HOG_DIR_OUTPUT_LOW as i32)
+                .unwrap();
+            sim.enable().unwrap();
+
+            let chip = Chip::open(sim.dev_path()).unwrap();
+            chip.line_info(6).unwrap();
+
+            let info4 = chip.line_info(4).unwrap();
+            assert_eq!(info4.get_offset(), 4);
+            assert_eq!(info4.get_name().unwrap(), "four");
+            assert_eq!(info4.is_used(), true);
+            assert_eq!(info4.get_consumer().unwrap(), "hog4");

bindings/rust/tests/line_info.rs line 89

Quoted 10 lines
bindings/rust/tests/line_info.rs new file +10 -0
+            assert_eq!(info4.get_direction().unwrap(), Direction::Output);
+            assert_eq!(info4.is_active_low(), false);
+            assert_eq!(info4.get_bias().unwrap(), Bias::Unknown);
+            assert_eq!(info4.get_drive().unwrap(), Drive::PushPull);
+            assert_eq!(info4.get_edge_detection().unwrap(), Edge::None);
+            assert_eq!(info4.get_event_clock().unwrap(), EventClock::Monotonic);
+            assert_eq!(info4.is_debounced(), false);
+            assert_eq!(info4.get_debounce_period(), Duration::from_millis(0));
+        }
+    }

Test that you can read all supported values for all fields.

Quoted 230 lines
bindings/rust/tests/line_info.rs new file +1 -0
+}
bindings/rust/tests/line_request.rs new file +223 -0
new file mode 100644
index 000000000000..361ee6318d2e
@@ -0,0 +1,234 @@
+// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
+//
+// Copyright 2022 Linaro Ltd. All Rights Reserved.
+//     Viresh Kumar <[email protected]>
+
+mod common;
+
+mod line_request {
+    use libc::{EBUSY, EINVAL};
+
+    use vmm_sys_util::errno::Error as IoError;
+
+    use crate::common::*;
+    use libgpiod::{Bias, Direction, Error as ChipError, LineConfig};
+    use libgpiod_sys::{
+        GPIOSIM_PULL_DOWN, GPIOSIM_PULL_UP, GPIOSIM_VALUE_ACTIVE, GPIOSIM_VALUE_INACTIVE,
+    };
+
+    const NGPIO: u64 = 8;
+
+    mod invalid_arguments {
+        use super::*;
+
+        #[test]
+        fn no_offsets() {
+            let mut config = TestConfig::new(NGPIO).unwrap();
+            config.rconfig(None);
+            config.lconfig_raw();
+
+            assert_eq!(
+                config.request_lines().unwrap_err(),
+                ChipError::OperationFailed("Gpio LineRequest request-lines", IoError::new(EINVAL))
+            );
+        }
+
+        #[test]
+        fn duplicate_offsets() {
+            let mut config = TestConfig::new(NGPIO).unwrap();
+            config.rconfig(Some(&[2, 0, 0, 4]));
+            config.lconfig_raw();
+
+            assert_eq!(
+                config.request_lines().unwrap_err(),
+                ChipError::OperationFailed("Gpio LineRequest request-lines", IoError::new(EBUSY))
+            );
+        }
+
+        #[test]
+        fn out_of_bound_offsets() {
+            let mut config = TestConfig::new(NGPIO).unwrap();
+            config.rconfig(Some(&[2, 0, 8, 4]));
+            config.lconfig_raw();
+
+            assert_eq!(
+                config.request_lines().unwrap_err(),
+                ChipError::OperationFailed("Gpio LineRequest request-lines", IoError::new(EINVAL))
+            );
+        }
+    }
+
+    mod verify {
+        use super::*;
+
+        #[test]
+        fn custom_consumer() {
+            const GPIO: u32 = 2;
+            const CONSUMER: &str = "foobar";
+            let mut config = TestConfig::new(NGPIO).unwrap();
+            config.rconfig_consumer(Some(&[GPIO]), Some(CONSUMER));
+            config.lconfig_raw();
+            config.request_lines().unwrap();
+
+            let info = config.chip().line_info(GPIO).unwrap();
+
+            assert_eq!(info.is_used(), true);
+            assert_eq!(info.get_consumer().unwrap(), CONSUMER);
+        }
+
+        #[test]
+        fn empty_consumer() {
+            const GPIO: u32 = 2;
+            let mut config = TestConfig::new(NGPIO).unwrap();
+            config.rconfig(Some(&[GPIO]));
+            config.lconfig_raw();
+            config.request_lines().unwrap();
+
+            let info = config.chip().line_info(GPIO).unwrap();
+
+            assert_eq!(info.is_used(), true);
+            assert_eq!(info.get_consumer().unwrap(), "?");
+        }
+
+        #[test]
+        fn read_values() {
+            let offsets = [7, 1, 0, 6, 2];
+            let pulls = [
+                GPIOSIM_PULL_UP,
+                GPIOSIM_PULL_UP,
+                GPIOSIM_PULL_DOWN,
+                GPIOSIM_PULL_UP,
+                GPIOSIM_PULL_DOWN,
+            ];
+            let mut config = TestConfig::new(NGPIO).unwrap();
+            config.set_pull(&offsets, &pulls);
+            config.rconfig(Some(&offsets));
+            config.lconfig(Some(Direction::Input), None, None, None, None);
+            config.request_lines().unwrap();
+
+            let request = config.request();
+
+            // Buffer is smaller
+            let mut values: Vec<i32> = vec![0; 4];
+            assert_eq!(
+                request.get_values(&mut values).unwrap_err(),
+                ChipError::OperationFailed(
+                    "Gpio LineRequest array size mismatch",
+                    IoError::new(EINVAL),
+                )
+            );
+
+            // Buffer is larger
+            let mut values: Vec<i32> = vec![0; 6];
+            assert_eq!(
+                request.get_values(&mut values).unwrap_err(),
+                ChipError::OperationFailed(
+                    "Gpio LineRequest array size mismatch",
+                    IoError::new(EINVAL),
+                )
+            );
+
+            // Single values read properly
+            assert_eq!(request.get_value(7).unwrap(), 1);
+
+            // Values read properly
+            let mut values: Vec<i32> = vec![0; 5];
+            request.get_values(&mut values).unwrap();
+            for i in 0..values.len() {
+                assert_eq!(
+                    values[i],
+                    match pulls[i] {
+                        GPIOSIM_PULL_DOWN => 0,
+                        _ => 1,
+                    }
+                );
+            }
+
+            // Subset of values read properly
+            let mut values: Vec<i32> = vec![0; 3];
+            request.get_values_subset(&[2, 0, 6], &mut values).unwrap();
+            assert_eq!(values[0], 0);
+            assert_eq!(values[1], 0);
+            assert_eq!(values[2], 1);
+
+            // Value read properly after reconfigure
+            let mut lconfig = LineConfig::new().unwrap();
+            lconfig.set_active_low_default(true);
+            request.reconfigure_lines(&lconfig).unwrap();
+            assert_eq!(request.get_value(7).unwrap(), 0);
+        }
+
+        #[test]
+        fn set_output_values() {
+            let offsets = [0, 1, 3, 4];
+            let mut config = TestConfig::new(NGPIO).unwrap();
+            config.rconfig(Some(&offsets));
+            config.lconfig(Some(Direction::Output), Some(1), Some((4, 0)), None, None);
+            config.request_lines().unwrap();
+
+            assert_eq!(config.sim().val(0).unwrap(), GPIOSIM_VALUE_ACTIVE);
+            assert_eq!(config.sim().val(1).unwrap(), GPIOSIM_VALUE_ACTIVE);
+            assert_eq!(config.sim().val(3).unwrap(), GPIOSIM_VALUE_ACTIVE);
+
+            // Overriden
+            assert_eq!(config.sim().val(4).unwrap(), GPIOSIM_VALUE_INACTIVE);
+
+            // Default
+            assert_eq!(config.sim().val(2).unwrap(), GPIOSIM_VALUE_INACTIVE);
+        }
+
+        #[test]
+        fn reconfigure_output_values() {
+            let offsets = [0, 1, 3, 4];
+            let mut config = TestConfig::new(NGPIO).unwrap();
+            config.rconfig(Some(&offsets));
+            config.lconfig(Some(Direction::Output), Some(0), None, None, None);
+            config.request_lines().unwrap();
+            let request = config.request();
+
+            // Set single value
+            request.set_value(1, 1).unwrap();
+            assert_eq!(config.sim().val(0).unwrap(), GPIOSIM_VALUE_INACTIVE);
+            assert_eq!(config.sim().val(1).unwrap(), GPIOSIM_VALUE_ACTIVE);
+            assert_eq!(config.sim().val(3).unwrap(), GPIOSIM_VALUE_INACTIVE);
+            assert_eq!(config.sim().val(4).unwrap(), GPIOSIM_VALUE_INACTIVE);
+            request.set_value(1, 0).unwrap();
+            assert_eq!(config.sim().val(1).unwrap(), GPIOSIM_VALUE_INACTIVE);
+
+            // Set values of subset
+            request.set_values_subset(&[4, 3], &[1, 1]).unwrap();
+            assert_eq!(config.sim().val(0).unwrap(), GPIOSIM_VALUE_INACTIVE);
+            assert_eq!(config.sim().val(1).unwrap(), GPIOSIM_VALUE_INACTIVE);
+            assert_eq!(config.sim().val(3).unwrap(), GPIOSIM_VALUE_ACTIVE);
+            assert_eq!(config.sim().val(4).unwrap(), GPIOSIM_VALUE_ACTIVE);
+            request.set_values_subset(&[4, 3], &[0, 0]).unwrap();
+            assert_eq!(config.sim().val(3).unwrap(), GPIOSIM_VALUE_INACTIVE);
+            assert_eq!(config.sim().val(4).unwrap(), GPIOSIM_VALUE_INACTIVE);
+
+            // Set all values
+            request.set_values(&[1, 0, 1, 0]).unwrap();
+            assert_eq!(config.sim().val(0).unwrap(), GPIOSIM_VALUE_ACTIVE);
+            assert_eq!(config.sim().val(1).unwrap(), GPIOSIM_VALUE_INACTIVE);
+            assert_eq!(config.sim().val(3).unwrap(), GPIOSIM_VALUE_ACTIVE);
+            assert_eq!(config.sim().val(4).unwrap(), GPIOSIM_VALUE_INACTIVE);
+            request.set_values(&[0, 0, 0, 0]).unwrap();
+            assert_eq!(config.sim().val(0).unwrap(), GPIOSIM_VALUE_INACTIVE);
+            assert_eq!(config.sim().val(1).unwrap(), GPIOSIM_VALUE_INACTIVE);
+            assert_eq!(config.sim().val(3).unwrap(), GPIOSIM_VALUE_INACTIVE);
+            assert_eq!(config.sim().val(4).unwrap(), GPIOSIM_VALUE_INACTIVE);
+        }
+
+        #[test]
+        fn set_bias() {
+            let offsets = [3];

bindings/rust/tests/line_request.rs line 233

Quoted 10 lines
bindings/rust/tests/line_request.rs new file +10 -0
+            let mut config = TestConfig::new(NGPIO).unwrap();
+            config.rconfig(Some(&offsets));
+            config.lconfig(Some(Direction::Input), None, None, None, Some(Bias::PullUp));
+            config.request_lines().unwrap();
+            config.request();
+
+            // Set single value
+            assert_eq!(config.sim().val(3).unwrap(), GPIOSIM_VALUE_ACTIVE);
+        }
+    }

Test reconfigure() failure modes.

Test you can reconfigure all values for all attributes (not all values for debounce obviously - just the flags).

Similarly for request_lines (ideally in chip.rs)

Quoted 52 lines
bindings/rust/tests/line_request.rs new file +1 -0
+}
bindings/rust/tests/request_config.rs new file +42 -0
new file mode 100644
index 000000000000..e914ca8ec887
@@ -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]>
+
+mod common;
+
+mod request_config {
+    use vmm_sys_util::errno::Error as IoError;
+
+    use libgpiod::{Error as ChipError, RequestConfig};
+
+    mod verify {
+        use super::*;
+
+        #[test]
+        fn default() {
+            let rconfig = RequestConfig::new().unwrap();
+
+            assert_eq!(rconfig.get_offsets().len(), 0);
+            assert_eq!(rconfig.get_event_buffer_size(), 0);
+            assert_eq!(
+                rconfig.get_consumer().unwrap_err(),
+                ChipError::OperationFailed("Gpio RequestConfig get-consumer", IoError::new(0))
+            );
+        }
+
+        #[test]
+        fn initialized() {
+            let offsets = [0, 1, 2, 3];
+            const CONSUMER: &str = "foobar";
+            let rconfig = RequestConfig::new().unwrap();
+            rconfig.set_consumer(CONSUMER);
+            rconfig.set_offsets(&offsets);
+            rconfig.set_event_buffer_size(64);
+
+            assert_eq!(rconfig.get_offsets(), offsets);
+            assert_eq!(rconfig.get_event_buffer_size(), 64);
+            assert_eq!(rconfig.get_consumer().unwrap(), CONSUMER);
+        }
+    }
+}
Signature · 1 line
2.31.1.272.g89b43f80a514

Clippy warnings to fix:

$cargo clippy --tests
warning: this expression creates a reference which is immediately dereferenced by the compiler
--> tests/common/config.rs:103:9

    |
103 |         &self.chip.as_ref().unwrap()
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: change this to: `self.chip.as_ref().unwrap()`
    |

= note: #[warn(clippy::needless_borrow)] on by default
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow

warning: this expression creates a reference which is immediately dereferenced by the compiler
--> tests/common/config.rs:107:9

    |
107 |         &self.request.as_ref().unwrap()
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: change this to: `self.request.as_ref().unwrap()`
    |

= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow

warning: used assert_eq! with a literal bool
--> tests/line_config.rs:24:13

   |
24 |             assert_eq!(lconfig.get_active_low_default(), false);
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
   |

= note: #[warn(clippy::bool_assert_comparison)] on by default
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used assert_eq! with a literal bool
--> tests/line_config.rs:49:13

   |
49 |             assert_eq!(lconfig.direction_is_overridden(GPIO), true);
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
   |

= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used assert_eq! with a literal bool
--> tests/line_config.rs:56:13

   |
56 |             assert_eq!(lconfig.direction_is_overridden(GPIO), false);
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
   |

= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used assert_eq! with a literal bool
--> tests/line_config.rs:68:13

   |
68 |             assert_eq!(lconfig.edge_detection_is_overridden(GPIO), true);
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
   |

= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used assert_eq! with a literal bool
--> tests/line_config.rs:72:13

   |
72 |             assert_eq!(lconfig.edge_detection_is_overridden(GPIO), false);
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
   |

= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used assert_eq! with a literal bool
--> tests/line_config.rs:84:13

   |
84 |             assert_eq!(lconfig.bias_is_overridden(GPIO), true);
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
   |

= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used assert_eq! with a literal bool
--> tests/line_config.rs:88:13

   |
88 |             assert_eq!(lconfig.bias_is_overridden(GPIO), false);
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
   |

= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used assert_eq! with a literal bool
--> tests/line_config.rs:100:13

    |
100 |             assert_eq!(lconfig.drive_is_overridden(GPIO), true);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
    |

= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used assert_eq! with a literal bool
--> tests/line_config.rs:104:13

    |
104 |             assert_eq!(lconfig.drive_is_overridden(GPIO), false);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
    |

= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used assert_eq! with a literal bool
--> tests/line_config.rs:116:13

    |
116 |             assert_eq!(lconfig.active_low_is_overridden(GPIO), true);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
    |

= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used assert_eq! with a literal bool
--> tests/line_config.rs:117:13

    |
117 |             assert_eq!(lconfig.get_active_low_offset(GPIO), true);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
    |

= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used assert_eq! with a literal bool
--> tests/line_config.rs:120:13

    |
120 |             assert_eq!(lconfig.active_low_is_overridden(GPIO), false);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
    |

= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used assert_eq! with a literal bool
--> tests/line_config.rs:121:13

    |
121 |             assert_eq!(lconfig.get_active_low_offset(GPIO), false);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
    |

= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used assert_eq! with a literal bool
--> tests/line_config.rs:132:13

    |
132 |             assert_eq!(lconfig.debounce_period_is_overridden(GPIO), true);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
    |

= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used assert_eq! with a literal bool
--> tests/line_config.rs:139:13

    |
139 |             assert_eq!(lconfig.debounce_period_is_overridden(GPIO), false);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
    |

= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used assert_eq! with a literal bool
--> tests/line_config.rs:154:13

    |
154 |             assert_eq!(lconfig.event_clock_is_overridden(GPIO), true);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
    |

= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used assert_eq! with a literal bool
--> tests/line_config.rs:161:13

    |
161 |             assert_eq!(lconfig.event_clock_is_overridden(GPIO), false);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
    |

= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used assert_eq! with a literal bool
--> tests/line_config.rs:178:17

    |
178 |                 assert_eq!(lconfig.output_value_is_overridden(line), true);
    |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
    |

= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used assert_eq! with a literal bool
--> tests/line_config.rs:182:17

    |
182 |                 assert_eq!(lconfig.output_value_is_overridden(line), false);
    |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
    |

= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used assert_eq! with a literal bool
--> tests/line_info.rs:38:13

   |
38 |             assert_eq!(info.is_used(), true);
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
   |

= note: #[warn(clippy::bool_assert_comparison)] on by default
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used assert_eq! with a literal bool
--> tests/line_info.rs:41:13

   |
41 |             assert_eq!(info.is_active_low(), false);
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
   |

= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used assert_eq! with a literal bool
--> tests/line_info.rs:46:13

   |
46 |             assert_eq!(info.is_debounced(), false);
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
   |

= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used assert_eq! with a literal bool
--> tests/line_info.rs:78:13

   |
78 |             assert_eq!(info4.is_used(), true);
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
   |

= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used assert_eq! with a literal bool
--> tests/line_info.rs:81:13

   |
81 |             assert_eq!(info4.is_active_low(), false);
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
   |

= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used assert_eq! with a literal bool
--> tests/line_info.rs:86:13

   |
86 |             assert_eq!(info4.is_debounced(), false);
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
   |

= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used assert_eq! with a literal bool
--> tests/line_request.rs:75:13

   |
75 |             assert_eq!(info.is_used(), true);
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
   |

= note: #[warn(clippy::bool_assert_comparison)] on by default
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used assert_eq! with a literal bool
--> tests/line_request.rs:89:13

   |
89 |             assert_eq!(info.is_used(), true);
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
   |

= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: libgpiod (test "line_config") generated 21 warnings
warning: libgpiod (test "request_config") generated 2 warnings (2 duplicates)
warning: libgpiod (test "info_event") generated 2 warnings (2 duplicates)
warning: libgpiod (test "line_info") generated 8 warnings (2 duplicates)
warning: libgpiod (test "chip") generated 2 warnings (2 duplicates)
warning: libgpiod (test "line_request") generated 4 warnings (2 duplicates)
warning: libgpiod (test "edge_event") generated 2 warnings (2 duplicates)
Finished dev [unoptimized + debuginfo] target(s) in 0.05s

Cheers,
Kent.

#46 lore raw

Re: [PATCH V4 7/8] libgpiod: Add rust tests

Quoted 4 lines · Kent Gibson

On Fri, Jul 08, 2022 at 05:05:00PM +0530, Viresh Kumar wrote:
Don't include the test results in the commit message.
Those are more of a release artifact than a commit artifact. It is assumed the tests pass for you or you wouldn't be submitting them.

I wasn't trying to prove that I tested them :)

The idea was to show how module/test names eventually look in the output.

Maybe I could have just replied to this email and pasted it, sure commit message doesn't need it.

Quoted 13 lines
Quoted 13 lines
bindings/rust/tests/chip.rs +12 -0
+    mod configure {
+        use super::*;
+        const NGPIO: u64 = 16;
+        const LABEL: &str = "foobar";
+
+        #[test]
+        fn verify() {
+            let sim = Sim::new(Some(NGPIO), Some(LABEL), true).unwrap();
+            let chip = Chip::open(sim.dev_path()).unwrap();
+
+            assert_eq!(chip.get_label().unwrap(), LABEL);
+            assert_eq!(chip.get_name().unwrap(), sim.chip_name());
Quoted 8 lines

bindings/rust/tests/chip.rs line 66

Quoted 5 lines
bindings/rust/tests/chip.rs +5 -0
+            assert_eq!(chip.get_path().unwrap(), sim.dev_path());
+            assert_eq!(chip.get_num_lines(), NGPIO as u32);
+            chip.get_fd().unwrap();
+        }
+

A test for a basic open on an existing chip and a smoke test of some chip methods is called "verify", so chip::configure::verify?

You want me to rename this ? Sorry, got confused :(

Yeah, I am generally bad with naming, suggestions are welcome here :)

Quoted 22 lines
Quoted 22 lines
bindings/rust/tests/chip.rs +22 -0
+        #[test]
+        fn line_lookup() {
+            let sim = Sim::new(Some(NGPIO), None, false).unwrap();
+            sim.set_line_name(0, "zero").unwrap();
+            sim.set_line_name(2, "two").unwrap();
+            sim.set_line_name(3, "three").unwrap();
+            sim.set_line_name(5, "five").unwrap();
+            sim.set_line_name(10, "ten").unwrap();
+            sim.set_line_name(11, "ten").unwrap();
+            sim.enable().unwrap();
+
+            let chip = Chip::open(sim.dev_path()).unwrap();
+
+            // Success case
+            assert_eq!(chip.find_line("zero").unwrap(), 0);
+            assert_eq!(chip.find_line("two").unwrap(), 2);
+            assert_eq!(chip.find_line("three").unwrap(), 3);
+            assert_eq!(chip.find_line("five").unwrap(), 5);
+
+            // Success with duplicate names, should return first entry
+            assert_eq!(chip.find_line("ten").unwrap(), 10);
+
Quoted 8 lines

bindings/rust/tests/chip.rs line 94

Quoted 6 lines
bindings/rust/tests/chip.rs +6 -0
+            // Failure
+            assert_eq!(
+                chip.find_line("nonexistent").unwrap_err(),
+                ChipError::OperationFailed("Gpio Chip find-line", IoError::new(ENOENT))
+            );
+        }

If it is testing find_line() then why not call it find_line?

I think I picked many names from the TEST_CASE name from cxx bindings. This was written there as:

TEST_CASE("line lookup by name works", "[chip]")

and I didn't think much about it :)

Sure I can name this find_line().

Quoted 20 lines
Quoted 7 lines
bindings/rust/tests/edge_event.rs +6 -0
+        #[test]
+        fn wait_timeout() {
+            let mut config = TestConfig::new(NGPIO).unwrap();
+            config.rconfig(Some(&[0]));
+            config.lconfig_edge(Some(Edge::Both));
+            config.request_lines().unwrap();

bindings/rust/tests/edge_event.rs line 59

Quoted 10 lines
bindings/rust/tests/edge_event.rs +10 -0
+
+            // No events available
+            assert_eq!(
+                config
+                    .request()
+                    .wait_edge_event(Duration::from_millis(100))
+                    .unwrap_err(),
+                ChipError::OperationTimedOut
+            );
+        }

Is a timeout really a "failure"?

Quoted 2 lines

It is testing wait_edge_event(), which is a method of line_request, and so should be in the line_request test suite.

Copied it from cxx again, I just tried to add similar tests in similar files.

TEST_CASE("edge_event wait timeout", "[edge-event]")

Quoted 9 lines
Quoted 9 lines
bindings/rust/tests/edge_event.rs +9 -0
+
+        #[test]
+        fn dir_out_edge_failure() {
+            let mut config = TestConfig::new(NGPIO).unwrap();
+            config.rconfig(Some(&[0]));
+            config.lconfig(Some(Direction::Output), None, None, Some(Edge::Both), None);
+
+            assert_eq!(
+                config.request_lines().unwrap_err(),
Quoted 8 lines

bindings/rust/tests/edge_event.rs line 73

Quoted 5 lines
bindings/rust/tests/edge_event.rs +5 -0
+                ChipError::OperationFailed("Gpio LineRequest request-lines", IoError::new(EINVAL))
+            );
+        }
+    }
+

This is testing a failure mode of request_lines(), not edge_events. Where is the edge_event here?

I agree, I will move this out.

This needs fixing too though.

TEST_CASE("output mode and edge detection don't work together", "[edge-event]")

Quoted 39 lines
Quoted 39 lines
bindings/rust/tests/info_event.rs new file +33 -0
new file mode 100644
index 000000000000..96d8385deadf
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
+//
+// Copyright 2022 Linaro Ltd. All Rights Reserved.
+//     Viresh Kumar <[email protected]>
+
+mod common;
+
+mod info_event {
+    use libc::EINVAL;
+    use std::sync::Arc;
+    use std::thread::{sleep, spawn};
+    use std::time::Duration;
+
+    use vmm_sys_util::errno::Error as IoError;
+
+    use crate::common::*;
+    use libgpiod::{Chip, Direction, Error as ChipError, Event, LineConfig, RequestConfig};
+
+    fn request_reconfigure_line(chip: Arc<Chip>) {
+        spawn(move || {
+            sleep(Duration::from_millis(10));
+
+            let lconfig1 = LineConfig::new().unwrap();
+            let rconfig = RequestConfig::new().unwrap();
+            rconfig.set_offsets(&[7]);
+
+            let request = chip.request_lines(&rconfig, &lconfig1).unwrap();
+
+            sleep(Duration::from_millis(10));
+
+            let mut lconfig2 = LineConfig::new().unwrap();
+            lconfig2.set_direction_default(Direction::Output);
+
Quoted 8 lines

bindings/rust/tests/info_event.rs line 39

Quoted 6 lines
bindings/rust/tests/info_event.rs new file +6 -0
+            request.reconfigure_lines(&lconfig2).unwrap();
+
+            sleep(Duration::from_millis(10));
+        });
+    }
+

Benefit of the background thread?

Again copied from cxx, I think the idea here is to get the events one by one and read one before the next one is generated. i.e. to do it all in parallel, which looked fine to me.

Quoted 28 lines
Quoted 28 lines
bindings/rust/tests/info_event.rs new file +28 -0
+    mod properties {
+        use super::*;
+
+        #[test]
+        fn verify() {
+            let sim = Sim::new(Some(NGPIO), None, false).unwrap();
+            sim.set_line_name(1, "one").unwrap();
+            sim.set_line_name(2, "two").unwrap();
+            sim.set_line_name(4, "four").unwrap();
+            sim.set_line_name(5, "five").unwrap();
+            sim.hog_line(3, "hog3", GPIOSIM_HOG_DIR_OUTPUT_HIGH as i32)
+                .unwrap();
+            sim.hog_line(4, "hog4", GPIOSIM_HOG_DIR_OUTPUT_LOW as i32)
+                .unwrap();
+            sim.enable().unwrap();
+
+            let chip = Chip::open(sim.dev_path()).unwrap();
+            chip.line_info(6).unwrap();
+
+            let info4 = chip.line_info(4).unwrap();
+            assert_eq!(info4.get_offset(), 4);
+            assert_eq!(info4.get_name().unwrap(), "four");
+            assert_eq!(info4.is_used(), true);
+            assert_eq!(info4.get_consumer().unwrap(), "hog4");
+            assert_eq!(info4.get_direction().unwrap(), Direction::Output);
+            assert_eq!(info4.is_active_low(), false);
+            assert_eq!(info4.get_bias().unwrap(), Bias::Unknown);
+            assert_eq!(info4.get_drive().unwrap(), Drive::PushPull);
Quoted 8 lines

bindings/rust/tests/info_event.rs line 73

Quoted 6 lines
bindings/rust/tests/info_event.rs new file +6 -0
+            assert_eq!(info4.get_edge_detection().unwrap(), Edge::None);
+            assert_eq!(info4.get_event_clock().unwrap(), EventClock::Monotonic);
+            assert_eq!(info4.is_debounced(), false);
+            assert_eq!(info4.get_debounce_period(), Duration::from_millis(0));
+        }
+    }

Test that you can read all supported values for all fields.

Like setting bias to all possible values one by one and reading them back ? Or something else ?

Quoted 3 lines

Clippy warnings to fix:

$cargo clippy --tests

I just ran

cargo clippy --workspace --bins --examples --benches --all-features -- -D warnings

and thought it works for tests too, my bad.

Signature · 1 line
viresh
#47 lore raw

Re: [PATCH V4 7/8] libgpiod: Add rust tests

Quoted 6 lines · Viresh Kumar
Quoted 4 lines · Kent Gibson

On Fri, Jul 08, 2022 at 05:05:00PM +0530, Viresh Kumar wrote:
Don't include the test results in the commit message.
Those are more of a release artifact than a commit artifact. It is assumed the tests pass for you or you wouldn't be submitting them.

Quoted 7 lines

I wasn't trying to prove that I tested them :)

The idea was to show how module/test names eventually look in the output.

Maybe I could have just replied to this email and pasted it, sure commit message doesn't need it.

If you want to add more detail to the patch, but not to the commit message, then add it between the "---" and the file list. Or put it in the cover letter.

Quoted 22 lines
Quoted 13 lines
Quoted 13 lines
bindings/rust/tests/chip.rs +12 -0
+    mod configure {
+        use super::*;
+        const NGPIO: u64 = 16;
+        const LABEL: &str = "foobar";
+
+        #[test]
+        fn verify() {
+            let sim = Sim::new(Some(NGPIO), Some(LABEL), true).unwrap();
+            let chip = Chip::open(sim.dev_path()).unwrap();
+
+            assert_eq!(chip.get_label().unwrap(), LABEL);
+            assert_eq!(chip.get_name().unwrap(), sim.chip_name());
Quoted 8 lines

bindings/rust/tests/chip.rs line 66

Quoted 5 lines
bindings/rust/tests/chip.rs +5 -0
+            assert_eq!(chip.get_path().unwrap(), sim.dev_path());
+            assert_eq!(chip.get_num_lines(), NGPIO as u32);
+            chip.get_fd().unwrap();
+        }
+

A test for a basic open on an existing chip and a smoke test of some chip methods is called "verify", so chip::configure::verify?

Quoted 1 line

You want me to rename this ? Sorry, got confused :(

I was trying to work out your naming scheme.
Couldn't see one - sorry.

Quoted 3 lines

Yeah, I am generally bad with naming, suggestions are welcome here :)

Naming is always the hard part. Find a system that will do the worst of it for you.

I usually go with <struct>::<method> for the tests that cover a method on a struct. For complex methods that require multiple tests add another tier that described what subset of functionality or failure mode is being tested.

Quoted 31 lines
Quoted 22 lines
Quoted 22 lines
bindings/rust/tests/chip.rs +22 -0
+        #[test]
+        fn line_lookup() {
+            let sim = Sim::new(Some(NGPIO), None, false).unwrap();
+            sim.set_line_name(0, "zero").unwrap();
+            sim.set_line_name(2, "two").unwrap();
+            sim.set_line_name(3, "three").unwrap();
+            sim.set_line_name(5, "five").unwrap();
+            sim.set_line_name(10, "ten").unwrap();
+            sim.set_line_name(11, "ten").unwrap();
+            sim.enable().unwrap();
+
+            let chip = Chip::open(sim.dev_path()).unwrap();
+
+            // Success case
+            assert_eq!(chip.find_line("zero").unwrap(), 0);
+            assert_eq!(chip.find_line("two").unwrap(), 2);
+            assert_eq!(chip.find_line("three").unwrap(), 3);
+            assert_eq!(chip.find_line("five").unwrap(), 5);
+
+            // Success with duplicate names, should return first entry
+            assert_eq!(chip.find_line("ten").unwrap(), 10);
+
Quoted 8 lines

bindings/rust/tests/chip.rs line 94

Quoted 6 lines
bindings/rust/tests/chip.rs +6 -0
+            // Failure
+            assert_eq!(
+                chip.find_line("nonexistent").unwrap_err(),
+                ChipError::OperationFailed("Gpio Chip find-line", IoError::new(ENOENT))
+            );
+        }

If it is testing find_line() then why not call it find_line?

Quoted 7 lines

I think I picked many names from the TEST_CASE name from cxx bindings. This was written there as:

TEST_CASE("line lookup by name works", "[chip]")

and I didn't think much about it :)

Fair enough.

Quoted 2 lines

Sure I can name this find_line().

At least until you get around to renaming find_line to line_offset_from_name ;-).

Quoted 99 lines
Quoted 20 lines
Quoted 7 lines
bindings/rust/tests/edge_event.rs +6 -0
+        #[test]
+        fn wait_timeout() {
+            let mut config = TestConfig::new(NGPIO).unwrap();
+            config.rconfig(Some(&[0]));
+            config.lconfig_edge(Some(Edge::Both));
+            config.request_lines().unwrap();

bindings/rust/tests/edge_event.rs line 59

Quoted 10 lines
bindings/rust/tests/edge_event.rs +10 -0
+
+            // No events available
+            assert_eq!(
+                config
+                    .request()
+                    .wait_edge_event(Duration::from_millis(100))
+                    .unwrap_err(),
+                ChipError::OperationTimedOut
+            );
+        }

Is a timeout really a "failure"?

Quoted 2 lines

It is testing wait_edge_event(), which is a method of line_request, and so should be in the line_request test suite.

Copied it from cxx again, I just tried to add similar tests in similar files.

TEST_CASE("edge_event wait timeout", "[edge-event]")

Quoted 9 lines
Quoted 9 lines
bindings/rust/tests/edge_event.rs +9 -0
+
+        #[test]
+        fn dir_out_edge_failure() {
+            let mut config = TestConfig::new(NGPIO).unwrap();
+            config.rconfig(Some(&[0]));
+            config.lconfig(Some(Direction::Output), None, None, Some(Edge::Both), None);
+
+            assert_eq!(
+                config.request_lines().unwrap_err(),
Quoted 8 lines

bindings/rust/tests/edge_event.rs line 73

Quoted 5 lines
bindings/rust/tests/edge_event.rs +5 -0
+                ChipError::OperationFailed("Gpio LineRequest request-lines", IoError::new(EINVAL))
+            );
+        }
+    }
+

This is testing a failure mode of request_lines(), not edge_events. Where is the edge_event here?

I agree, I will move this out.

This needs fixing too though.

TEST_CASE("output mode and edge detection don't work together", "[edge-event]")

Quoted 39 lines
Quoted 39 lines
bindings/rust/tests/info_event.rs new file +33 -0
new file mode 100644
index 000000000000..96d8385deadf
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
+//
+// Copyright 2022 Linaro Ltd. All Rights Reserved.
+//     Viresh Kumar <[email protected]>
+
+mod common;
+
+mod info_event {
+    use libc::EINVAL;
+    use std::sync::Arc;
+    use std::thread::{sleep, spawn};
+    use std::time::Duration;
+
+    use vmm_sys_util::errno::Error as IoError;
+
+    use crate::common::*;
+    use libgpiod::{Chip, Direction, Error as ChipError, Event, LineConfig, RequestConfig};
+
+    fn request_reconfigure_line(chip: Arc<Chip>) {
+        spawn(move || {
+            sleep(Duration::from_millis(10));
+
+            let lconfig1 = LineConfig::new().unwrap();
+            let rconfig = RequestConfig::new().unwrap();
+            rconfig.set_offsets(&[7]);
+
+            let request = chip.request_lines(&rconfig, &lconfig1).unwrap();
+
+            sleep(Duration::from_millis(10));
+
+            let mut lconfig2 = LineConfig::new().unwrap();
+            lconfig2.set_direction_default(Direction::Output);
+
Quoted 8 lines

bindings/rust/tests/info_event.rs line 39

Quoted 6 lines
bindings/rust/tests/info_event.rs new file +6 -0
+            request.reconfigure_lines(&lconfig2).unwrap();
+
+            sleep(Duration::from_millis(10));
+        });
+    }
+

Benefit of the background thread?

Quoted 4 lines

Again copied from cxx, I think the idea here is to get the events one by one and read one before the next one is generated. i.e. to do it all in parallel, which looked fine to me.

Following the pattern from the other bindings is fine.

That is a point where Bart and I disagree.
He likes making the tests closer to reality.
I like keeping them simple.

They are separate paths and strictly speaking both should be tested. But if you are only going to do one, do the easy one ;-).

Anyway leave it as is - no point changing it now.

Quoted 42 lines
Quoted 28 lines
Quoted 28 lines
bindings/rust/tests/info_event.rs new file +28 -0
+    mod properties {
+        use super::*;
+
+        #[test]
+        fn verify() {
+            let sim = Sim::new(Some(NGPIO), None, false).unwrap();
+            sim.set_line_name(1, "one").unwrap();
+            sim.set_line_name(2, "two").unwrap();
+            sim.set_line_name(4, "four").unwrap();
+            sim.set_line_name(5, "five").unwrap();
+            sim.hog_line(3, "hog3", GPIOSIM_HOG_DIR_OUTPUT_HIGH as i32)
+                .unwrap();
+            sim.hog_line(4, "hog4", GPIOSIM_HOG_DIR_OUTPUT_LOW as i32)
+                .unwrap();
+            sim.enable().unwrap();
+
+            let chip = Chip::open(sim.dev_path()).unwrap();
+            chip.line_info(6).unwrap();
+
+            let info4 = chip.line_info(4).unwrap();
+            assert_eq!(info4.get_offset(), 4);
+            assert_eq!(info4.get_name().unwrap(), "four");
+            assert_eq!(info4.is_used(), true);
+            assert_eq!(info4.get_consumer().unwrap(), "hog4");
+            assert_eq!(info4.get_direction().unwrap(), Direction::Output);
+            assert_eq!(info4.is_active_low(), false);
+            assert_eq!(info4.get_bias().unwrap(), Bias::Unknown);
+            assert_eq!(info4.get_drive().unwrap(), Drive::PushPull);
Quoted 8 lines

bindings/rust/tests/info_event.rs line 73

Quoted 6 lines
bindings/rust/tests/info_event.rs new file +6 -0
+            assert_eq!(info4.get_edge_detection().unwrap(), Edge::None);
+            assert_eq!(info4.get_event_clock().unwrap(), EventClock::Monotonic);
+            assert_eq!(info4.is_debounced(), false);
+            assert_eq!(info4.get_debounce_period(), Duration::from_millis(0));
+        }
+    }

Test that you can read all supported values for all fields.

Like setting bias to all possible values one by one and reading them back ? Or something else ?

Quoted 2 lines

Clippy warnings to fix:

Quoted 8 lines
Quoted 1 line

$cargo clippy --tests

I just ran

cargo clippy --workspace --bins --examples --benches --all-features -- -D warnings

and thought it works for tests too, my bad.

Turns out not. Surprised me too.

Cheers,
Kent.

#48 lore raw

Re: [PATCH V4 7/8] libgpiod: Add rust tests

In reply to Kent Gibson
Quoted 8 lines · Kent Gibson
Quoted 8 lines
bindings/rust/tests/line_request.rs +7 -0
+        #[test]
+        fn set_bias() {
+            let offsets = [3];
+            let mut config = TestConfig::new(NGPIO).unwrap();
+            config.rconfig(Some(&offsets));
+            config.lconfig(Some(Direction::Input), None, None, None, Some(Bias::PullUp));
+            config.request_lines().unwrap();
Quoted 8 lines

bindings/rust/tests/line_request.rs line 233

Quoted 6 lines
bindings/rust/tests/line_request.rs +6 -0
+            config.request();
+
+            // Set single value
+            assert_eq!(config.sim().val(3).unwrap(), GPIOSIM_VALUE_ACTIVE);
+        }
+    }

Test reconfigure() failure modes.

What are we specifically looking to test here ? I am not sure I understood which failures modes I should target.

Quoted 2 lines

Test you can reconfigure all values for all attributes (not all values for debounce obviously - just the flags).

This I have done now, with request.reconfigure_lines().

Quoted 1 line

Similarly for request_lines (ideally in chip.rs)

Isn't this same as the above one? I am not sure again what I should test here.

I have pushed my current changes here, if it helps:

[email protected]:vireshk/libgpiod.git master

Signature · 1 line
viresh
#49 lore raw

Re: [PATCH V4 7/8] libgpiod: Add rust tests

Quoted 18 lines · Viresh Kumar
Quoted 8 lines · Kent Gibson
Quoted 8 lines
bindings/rust/tests/line_request.rs +7 -0
+        #[test]
+        fn set_bias() {
+            let offsets = [3];
+            let mut config = TestConfig::new(NGPIO).unwrap();
+            config.rconfig(Some(&offsets));
+            config.lconfig(Some(Direction::Input), None, None, None, Some(Bias::PullUp));
+            config.request_lines().unwrap();
Quoted 8 lines

bindings/rust/tests/line_request.rs line 233

Quoted 6 lines
bindings/rust/tests/line_request.rs +6 -0
+            config.request();
+
+            // Set single value
+            assert_eq!(config.sim().val(3).unwrap(), GPIOSIM_VALUE_ACTIVE);
+        }
+    }

Test reconfigure() failure modes.

Quoted 3 lines

What are we specifically looking to test here ? I am not sure I understood which failures modes I should target.

reconfigure() can return E2BIG if the configuration is too complex to encode into uAPI structures. There are only 10 attribute slots in the uAPI config, so more than 11 lines with distinct configs can't be encoded. You want to test that you propagate that error correctly.
I typically test this by giving each line a different debounce value.

Quoted 9 lines
Quoted 2 lines

Test you can reconfigure all values for all attributes (not all values for debounce obviously - just the flags).

This I have done now, with request.reconfigure_lines().

Quoted 1 line

Similarly for request_lines (ideally in chip.rs)

Isn't this same as the above one? I am not sure again what I should test here.

The request_lines() could be considered sufficient to test the conversions for both request_config and line_config objects.

But testing request_lines() and reconfigure_lines() doesn't just test those conversions, it checks that the functions don't mess with the config along the way.
If you want to take that as a given then sure, just testing the config variants for request_lines() will do.

Cheers,
Kent.

Quoted 6 lines

I have pushed my current changes here, if it helps:

[email protected]:vireshk/libgpiod.git master

Signature · 1 line
viresh
#50 lore raw

Re: [PATCH V4 7/8] libgpiod: Add rust tests

Quoted 8 lines · Kent Gibson

The request_lines() could be considered sufficient to test the conversions for both request_config and line_config objects.

But testing request_lines() and reconfigure_lines() doesn't just test those conversions, it checks that the functions don't mess with the config along the way.
If you want to take that as a given then sure, just testing the config variants for request_lines() will do.

Okay, so we want test setting various flag types in line_config and then see if request_line() fails or not and verify (read) them later. Right ?

Very similar to this is also done in tests/line_info now, where you suggested:

"Test that you can read all supported values for all fields."

So I set all possible values for fields and then read them back.

Lets see after the next version, if they are sufficient or not. I will happily add more if required then.

Thanks.

Signature · 1 line
viresh
#51 lore raw

Re: [PATCH V4 7/8] libgpiod: Add rust tests

Quoted 10 lines · Viresh Kumar
Quoted 8 lines · Kent Gibson

The request_lines() could be considered sufficient to test the conversions for both request_config and line_config objects.

But testing request_lines() and reconfigure_lines() doesn't just test those conversions, it checks that the functions don't mess with the config along the way.
If you want to take that as a given then sure, just testing the config variants for request_lines() will do.

Quoted 4 lines

Okay, so we want test setting various flag types in line_config and then see if request_line() fails or not and verify (read) them later. Right ?

Right.

Cheers,
Kent.

Quoted 14 lines

Very similar to this is also done in tests/line_info now, where you suggested:

"Test that you can read all supported values for all fields."

So I set all possible values for fields and then read them back.

Lets see after the next version, if they are sufficient or not. I will happily add more if required then.

Thanks.

Signature · 1 line
viresh
#52 lore raw

PATCH v4 8/8 libgpiod: Integrate building of rust bindings with make

In reply to Viresh Kumar

Lets 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
index 814a0f161fd2..68b5d69f9b66 100644
@@ -119,9 +119,9 @@ TOOLS
 BINDINGS
 --------
 
-High-level, object-oriented bindings for C++ and python3 are provided. They
-can be enabled by passing --enable-bindings-cxx and --enable-bindings-python
-arguments respectively to configure.
+High-level, object-oriented bindings for C++, python3 and Rust are provided.
+They can be enabled by passing --enable-bindings-cxx, --enable-bindings-python
+and --enable-bindings-rust arguments respectively to configure.
 
 C++ bindings require C++11 support and autoconf-archive collection if building
 from git.
@@ -132,6 +132,8 @@ the PYTHON_CPPFLAGS and PYTHON_LIBS variables in order to point the build
 system to the correct locations. During native builds, the configure script
 can auto-detect the location of the development files.
 
+Rust bindings require cargo support.
+
 TESTING
 -------
 
TODO +0 -8
index 8bb4d8f3ad56..cf4fd7b4a962 100644
@@ -28,14 +28,6 @@ and is partially functional.
 
 ----------
 
-* implement rust bindings
-
-With Rust gaining popularity as a low-level system's language and the
-possibility of it making its way into the linux kernel, it's probably time to
-provide Rust bindings to libgpiod as part of the project.
-
-----------
-
 * implement a simple daemon for controlling GPIOs in C together with a client
   program
 
bindings/Makefile.am +6 -0
index 8f8c762f254f..004ae23dbc58 100644
@@ -14,3 +14,9 @@ if WITH_BINDINGS_PYTHON
 SUBDIRS += python
 
 endif
+
+if WITH_BINDINGS_RUST
+
+SUBDIRS += rust
+
+endif
bindings/rust/Makefile.am new file +18 -0
new file mode 100644
index 000000000000..79a52bc691ae
@@ -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
index ab03673589e9..8458f734a606 100644
@@ -211,6 +211,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
@@ -245,6 +260,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
#53 lore raw

Re: [PATCH V4 8/8] libgpiod: Integrate building of rust bindings with make

Quoted 132 lines · Viresh Kumar

Lets 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
index 814a0f161fd2..68b5d69f9b66 100644
@@ -119,9 +119,9 @@ TOOLS
 BINDINGS
 --------
 
-High-level, object-oriented bindings for C++ and python3 are provided. They
-can be enabled by passing --enable-bindings-cxx and --enable-bindings-python
-arguments respectively to configure.
+High-level, object-oriented bindings for C++, python3 and Rust are provided.
+They can be enabled by passing --enable-bindings-cxx, --enable-bindings-python
+and --enable-bindings-rust arguments respectively to configure.
 
 C++ bindings require C++11 support and autoconf-archive collection if building
 from git.
@@ -132,6 +132,8 @@ the PYTHON_CPPFLAGS and PYTHON_LIBS variables in order to point the build
 system to the correct locations. During native builds, the configure script
 can auto-detect the location of the development files.
 
+Rust bindings require cargo support.
+
 TESTING
 -------
 
TODO +0 -8
index 8bb4d8f3ad56..cf4fd7b4a962 100644
@@ -28,14 +28,6 @@ and is partially functional.
 
 ----------
 
-* implement rust bindings
-
-With Rust gaining popularity as a low-level system's language and the
-possibility of it making its way into the linux kernel, it's probably time to
-provide Rust bindings to libgpiod as part of the project.
-
-----------
-
 * implement a simple daemon for controlling GPIOs in C together with a client
   program
 
bindings/Makefile.am +6 -0
index 8f8c762f254f..004ae23dbc58 100644
@@ -14,3 +14,9 @@ if WITH_BINDINGS_PYTHON
 SUBDIRS += python
 
 endif
+
+if WITH_BINDINGS_RUST
+
+SUBDIRS += rust
+
+endif
bindings/rust/Makefile.am new file +18 -0
new file mode 100644
index 000000000000..79a52bc691ae
@@ -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
index ab03673589e9..8458f734a606 100644
@@ -211,6 +211,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
@@ -245,6 +260,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

Wouldn't build for me on a Debian bullseye VM.
Apparently bindgen requires clang to find the system headers [1][2], and there is no dep check or warning about that.

Also not sure why the build wanted bindgen, as by default it uses the pre-generated bindings?

Anyway, for reference this was the build error without clang installed:

...
Compiling thiserror v1.0.31
Compiling libgpiod-sys v0.1.0 (/home/dev/libgpiod/bindings/rust/libgpiod-sys)
error: failed to run custom build command for libgpiod-sys v0.1.0 (/home/dev/libgpiod/bindings/rust/libgpiod-sys)

Caused by:
process didn't exit successfully: /home/dev/libgpiod/bindings/rust/target/release/build/libgpiod-sys-0fb8ce8170c88d8f/build-script-build (exit status: 101) --- stdout
cargo:rerun-if-changed=wrapper.h
cargo:rerun-if-changed=../../../lib/chip.c
cargo:rerun-if-changed=../../../lib/chip-info.c
cargo:rerun-if-changed=../../../lib/edge-event.c
cargo:rerun-if-changed=../../../lib/info-event.c
cargo:rerun-if-changed=../../../lib/internal.c
cargo:rerun-if-changed=../../../lib/line-config.c
cargo:rerun-if-changed=../../../lib/line-info.c
cargo:rerun-if-changed=../../../lib/line-request.c
cargo:rerun-if-changed=../../../lib/misc.c
cargo:rerun-if-changed=../../../lib/request-config.c
cargo:rerun-if-changed=../../../tests/gpiosim/gpiosim.c
cargo:rerun-if-changed=gpiosim_wrapper.h
cargo:rustc-link-lib=kmod
cargo:rustc-link-lib=mount

--- stderr
/usr/include/string.h:33:10: fatal error: 'stddef.h' file not found /usr/include/string.h:33:10: fatal error: 'stddef.h' file not found, err: true thread 'main' panicked at 'Unable to generate bindings: ()', libgpiod-sys/build.rs:42:10

Cheers,
Kent.

[1] https://rust-lang.github.io/rust-bindgen/requirements.html
[2] https://github.com/rust-lang/rust-bindgen/issues/242

#54 lore raw

Re: [PATCH V4 8/8] libgpiod: Integrate building of rust bindings with make

Quoted 2 lines · Kent Gibson

Wouldn't build for me on a Debian bullseye VM.
Apparently bindgen requires clang to find the system headers [1][2],

Right.

Quoted 2 lines

and
there is no dep check or warning about that.

Ahh, I need to add that somewhere then.

Quoted 2 lines

Also not sure why the build wanted bindgen, as by default it uses the pre-generated bindings?

Did you enable tests as well ? That enables "gpiosim", which enables "generate" and bindgen will be required.

I thought it will be better to build bindings everytime for tests.

Quoted 1 line

Anyway, for reference this was the build error without clang installed:

Did it work for you after clang was installed ?

Quoted 29 lines

...
Compiling thiserror v1.0.31
Compiling libgpiod-sys v0.1.0 (/home/dev/libgpiod/bindings/rust/libgpiod-sys)
error: failed to run custom build command for libgpiod-sys v0.1.0 (/home/dev/libgpiod/bindings/rust/libgpiod-sys)

Caused by:
process didn't exit successfully: /home/dev/libgpiod/bindings/rust/target/release/build/libgpiod-sys-0fb8ce8170c88d8f/build-script-build (exit status: 101) --- stdout
cargo:rerun-if-changed=wrapper.h
cargo:rerun-if-changed=../../../lib/chip.c
cargo:rerun-if-changed=../../../lib/chip-info.c
cargo:rerun-if-changed=../../../lib/edge-event.c
cargo:rerun-if-changed=../../../lib/info-event.c
cargo:rerun-if-changed=../../../lib/internal.c
cargo:rerun-if-changed=../../../lib/line-config.c
cargo:rerun-if-changed=../../../lib/line-info.c
cargo:rerun-if-changed=../../../lib/line-request.c
cargo:rerun-if-changed=../../../lib/misc.c
cargo:rerun-if-changed=../../../lib/request-config.c
cargo:rerun-if-changed=../../../tests/gpiosim/gpiosim.c
cargo:rerun-if-changed=gpiosim_wrapper.h
cargo:rustc-link-lib=kmod
cargo:rustc-link-lib=mount

--- stderr
/usr/include/string.h:33:10: fatal error: 'stddef.h' file not found /usr/include/string.h:33:10: fatal error: 'stddef.h' file not found, err: true thread 'main' panicked at 'Unable to generate bindings: ()', libgpiod-sys/build.rs:42:10

Signature · 1 line
viresh
#55 lore raw

Re: [PATCH V4 8/8] libgpiod: Integrate building of rust bindings with make

Quoted 9 lines · Viresh Kumar
Quoted 2 lines · Kent Gibson

Wouldn't build for me on a Debian bullseye VM.
Apparently bindgen requires clang to find the system headers [1][2],

Right.

Quoted 2 lines

and
there is no dep check or warning about that.

Quoted 7 lines

Ahh, I need to add that somewhere then.

Quoted 2 lines

Also not sure why the build wanted bindgen, as by default it uses the pre-generated bindings?

Did you enable tests as well ? That enables "gpiosim", which enables "generate" and bindgen will be required.

Ahh, yeah I do --enable-tests as well.

Quoted 3 lines

I thought it will be better to build bindings everytime for tests.

So you can't run tests on those platforms where bindgen is problematic?

Quoted 4 lines
Quoted 1 line

Anyway, for reference this was the build error without clang installed:

Did it work for you after clang was installed ?

Yeah, all good once clang was installed.

Cheers,
Kent.

Quoted 32 lines
Quoted 29 lines

...
Compiling thiserror v1.0.31
Compiling libgpiod-sys v0.1.0 (/home/dev/libgpiod/bindings/rust/libgpiod-sys)
error: failed to run custom build command for libgpiod-sys v0.1.0 (/home/dev/libgpiod/bindings/rust/libgpiod-sys)

Caused by:
process didn't exit successfully: /home/dev/libgpiod/bindings/rust/target/release/build/libgpiod-sys-0fb8ce8170c88d8f/build-script-build (exit status: 101) --- stdout
cargo:rerun-if-changed=wrapper.h
cargo:rerun-if-changed=../../../lib/chip.c
cargo:rerun-if-changed=../../../lib/chip-info.c
cargo:rerun-if-changed=../../../lib/edge-event.c
cargo:rerun-if-changed=../../../lib/info-event.c
cargo:rerun-if-changed=../../../lib/internal.c
cargo:rerun-if-changed=../../../lib/line-config.c
cargo:rerun-if-changed=../../../lib/line-info.c
cargo:rerun-if-changed=../../../lib/line-request.c
cargo:rerun-if-changed=../../../lib/misc.c
cargo:rerun-if-changed=../../../lib/request-config.c
cargo:rerun-if-changed=../../../tests/gpiosim/gpiosim.c
cargo:rerun-if-changed=gpiosim_wrapper.h
cargo:rustc-link-lib=kmod
cargo:rustc-link-lib=mount

--- stderr
/usr/include/string.h:33:10: fatal error: 'stddef.h' file not found /usr/include/string.h:33:10: fatal error: 'stddef.h' file not found, err: true thread 'main' panicked at 'Unable to generate bindings: ()', libgpiod-sys/build.rs:42:10

Signature · 1 line
viresh
#56 lore raw

Re: [PATCH V4 8/8] libgpiod: Integrate building of rust bindings with make

Quoted 1 line · Kent Gibson

So you can't run tests on those platforms where bindgen is problematic?

Specifically for my case, that environment (rust-vmm-container) never runs libgpiod tests, but just vhost-device/gpio tests. So it works.

Signature · 1 line
viresh
#57 lore raw

Re: [PATCH V4 8/8] libgpiod: Integrate building of rust bindings with make

Quoted 6 lines · Viresh Kumar
Quoted 1 line · Kent Gibson

So you can't run tests on those platforms where bindgen is problematic?

Specifically for my case, that environment (rust-vmm-container) never runs libgpiod tests, but just vhost-device/gpio tests. So it works.

That may work for you, but in general it would be better if the tests can be run even if the bindings can't be regenerated, so the two should be independent.

Cheers,
Kent.

#58 lore raw

Re: [PATCH V4 8/8] libgpiod: Integrate building of rust bindings with make

Quoted 11 lines · Kent Gibson
Quoted 6 lines · Viresh Kumar
Quoted 1 line · Kent Gibson

So you can't run tests on those platforms where bindgen is problematic?

Specifically for my case, that environment (rust-vmm-container) never runs libgpiod tests, but just vhost-device/gpio tests. So it works.

That may work for you, but in general it would be better if the tests can be run even if the bindings can't be regenerated, so the two should be independent.

Hmm, then we can take the same approach as for gpiod.h bindings. Use pre-compiled ones unless "generate" feature is enabled.

Will do.

Signature · 1 line
viresh
#59 lore raw
In reply to Viresh Kumar
Quoted 37 lines · Viresh Kumar

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 master

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

Quoted 10 lines

Viresh Kumar (8):
libgpiod: Add libgpiod-sys rust crate
libgpiod: Add pre generated rust bindings
libgpiod-sys: Add support to generate gpiosim bindings
libgpiod: Add rust wrapper crate
libgpiod: Add rust examples
libgpiod: Derive debug traits for few definitions
libgpiod: Add rust tests
libgpiod: Integrate building of rust bindings with make

Hey Viresh, Kent and Miguel!

Rust noob here: I have my cargo installed using rustup for my local user but I can't run it via sudo or as root (to run tests) because I'm seeing this:

error: no override and no default toolchain set

What is the right way to use cargo as root? I'm hesitant to just curl a random script and pipe it to shell as root honestly.

Bart

#60 lore raw

Hi Bartosz,

Quoted 9 lines · Bartosz Golaszewski

Rust noob here: I have my cargo installed using rustup for my local user but I can't run it via sudo or as root (to run tests) because I'm seeing this:

error: no override and no default toolchain set

What is the right way to use cargo as root? I'm hesitant to just curl a random script and pipe it to shell as root honestly.

If by "random script" you mean the rustup installer, you can always download rustup on its own, or even directly a Rust "standalone installer", which are signed and do not require rustup, from:

https://forge.rust-lang.org/infra/other-installation-methods.html#standalone

However, since it appears that you have rustup installed, maybe you don't mean that. In any case, if rustup is installed, maybe you can try something like:

rustup default stable

or similar to install a toolchain.

Cheers,
Miguel

#61 lore raw
Quoted 7 lines · Miguel Ojeda

If by "random script" you mean the rustup installer, you can always download rustup on its own, or even directly a Rust "standalone installer", which are signed and do not require rustup, from:

https://forge.rust-lang.org/infra/other-installation-methods.html#standalone

Quoted 7 lines

However, since it appears that you have rustup installed, maybe you don't mean that. In any case, if rustup is installed, maybe you can try something like:

rustup default stable

or similar to install a toolchain.

Having said that, if building as a normal user is OK (should be), then you maybe can simply run the test binary as root (building it with something like cargo --no-run as a normal user).

Cheers,
Miguel

#62 lore raw
Quoted 2 lines · Miguel Ojeda

something like cargo --no-run as a normal user).

Sorry, that was intended to be cargo test --no-run (and the test binary will be in target/debug/deps). Also note that is only for the unit tests, though, not doctests. It can get more involved for those if you want to avoid to run Cargo/rustdoc as root.

Hope that helps!

Cheers,
Miguel

#63 lore raw
Quoted 11 lines · Miguel Ojeda
Quoted 2 lines · Miguel Ojeda

something like cargo --no-run as a normal user).

Sorry, that was intended to be cargo test --no-run (and the test binary will be in target/debug/deps). Also note that is only for the unit tests, though, not doctests. It can get more involved for those if you want to avoid to run Cargo/rustdoc as root.

Quoted 4 lines

Hope that helps!

Cheers,
Miguel

This does seem to build the test-suite but there's no single executable in target/debug/deps. Instead it seems that every test section gets its own executable. Does 'cargo test' run them separately? Or is there a single binary including all of them somewhere else?

Bart

#64 lore raw
Quoted 13 lines · Bartosz Golaszewski
Quoted 11 lines · Miguel Ojeda
Quoted 2 lines · Miguel Ojeda

something like cargo --no-run as a normal user).

Sorry, that was intended to be cargo test --no-run (and the test binary will be in target/debug/deps). Also note that is only for the unit tests, though, not doctests. It can get more involved for those if you want to avoid to run Cargo/rustdoc as root.

Quoted 7 lines
Quoted 4 lines

Hope that helps!

Cheers,
Miguel

This does seem to build the test-suite but there's no single executable in target/debug/deps.

As does providing the --enable-tests to configure, btw.

Quoted 5 lines

Instead it seems that every test
section gets its own executable. Does 'cargo test' run them separately? Or is there a single binary including all of them somewhere else?

AIUI 'cargo test' builds and runs those tests individually.

As we need to run the integration tests as root, and cargo doesn't play nice unless you install it as root, and the executables include md5(?) hashes in their names, it is a major PITA to run the full suite as root. Unless you do everything as root.

Anyway to get those names from cargo?

Assuming the gpio-sim module is already loaded, is it at all possible to run tests as a low privilege user? e.g. udev rules can be added to chown the /dev/gpiochips into a gpio group. Is it possible to do something similar for the configfs and sysfs for the gpio-sim?
Or do we need a gpiosimd that will allow a user to launch and control sims without being root ;-).

Cheers,
Kent.

#65 lore raw
In reply to Bartosz Golaszewski
Quoted 6 lines · Bartosz Golaszewski

This does seem to build the test-suite but there's no single executable in target/debug/deps. Instead it seems that every test section gets its own executable. Does 'cargo test' run them separately? Or is there a single binary including all of them somewhere else?

Yeah, for tests/ tests it seems to create several binaries. For #[test]s, they are put in a single binary.

Another solution for both of those (from
https://github.com/rust-lang/cargo/issues/5999) is .cargo/config (in the project folder):

[target.x86_64-unknown-linux-gnu]
runner = 'sudo -E'

Which indeed seems to work for both tests/ and #[test]s.

However, I don't think that one works for doctests, though (at least I don't see it executing rustdoc with the runner)... But maybe you don't need privileges for those.

Cheers,
Miguel