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
This adds libgpiod-sys rust crate, which provides FFI (foreign function interface) bindings for libgpiod APIs.
Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- .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
diff --git a/.gitignore b/.gitignore index 58e1c5fc7e00..9541482d5efb 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,8 @@ stamp-h1 # profiling *.gcda *.gcno + +# Added by cargo + +target +Cargo.lock diff --git a/bindings/rust/libgpiod-sys/Cargo.toml b/bindings/rust/libgpiod-sys/Cargo.toml new file mode 100644 index 000000000000..77f82719d269 --- /dev/null +++ b/bindings/rust/libgpiod-sys/Cargo.toml @@ -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" diff --git a/bindings/rust/libgpiod-sys/build.rs b/bindings/rust/libgpiod-sys/build.rs new file mode 100644 index 000000000000..bbcd30f79d23 --- /dev/null +++ b/bindings/rust/libgpiod-sys/build.rs @@ -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", + "../../../lib/line-info.c", + "../../../lib/line-request.c", + "../../../lib/misc.c", + "../../../lib/request-config.c", + ]; + + #[cfg(feature = "generate")] + generate_bindings(&files); + build_gpiod(files); +} diff --git a/bindings/rust/libgpiod-sys/src/lib.rs b/bindings/rust/libgpiod-sys/src/lib.rs new file mode 100644 index 000000000000..3384863a567c --- /dev/null +++ b/bindings/rust/libgpiod-sys/src/lib.rs @@ -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::*; diff --git a/bindings/rust/libgpiod-sys/wrapper.h b/bindings/rust/libgpiod-sys/wrapper.h new file mode 100644 index 000000000000..7bc1158b7d90 --- /dev/null +++ b/bindings/rust/libgpiod-sys/wrapper.h @@ -0,0 +1,2 @@ +#include <string.h> +#include "../../../include/gpiod.h"
On Fri, Jul 08, 2022 at 05:04:54PM +0530, Viresh Kumar wrote:
This adds libgpiod-sys rust crate, which provides FFI (foreign function interface) bindings for libgpiod APIs.
Signed-off-by: Viresh Kumar viresh.kumar@linaro.org
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.
.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
diff --git a/.gitignore b/.gitignore index 58e1c5fc7e00..9541482d5efb 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,8 @@ stamp-h1 # profiling *.gcda *.gcno
+# Added by cargo
+target +Cargo.lock diff --git a/bindings/rust/libgpiod-sys/Cargo.toml b/bindings/rust/libgpiod-sys/Cargo.toml new file mode 100644 index 000000000000..77f82719d269 --- /dev/null +++ b/bindings/rust/libgpiod-sys/Cargo.toml @@ -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" diff --git a/bindings/rust/libgpiod-sys/build.rs b/bindings/rust/libgpiod-sys/build.rs new file mode 100644 index 000000000000..bbcd30f79d23 --- /dev/null +++ b/bindings/rust/libgpiod-sys/build.rs @@ -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",
"../../../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?
diff --git a/bindings/rust/libgpiod-sys/src/lib.rs b/bindings/rust/libgpiod-sys/src/lib.rs new file mode 100644 index 000000000000..3384863a567c --- /dev/null +++ b/bindings/rust/libgpiod-sys/src/lib.rs @@ -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
+)]
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))]
+mod bindings_raw {
- #[cfg(feature = "generate")]
- include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
- #[cfg(not(feature = "generate"))]
- include!("bindings.rs");
+} +pub use bindings_raw::*; diff --git a/bindings/rust/libgpiod-sys/wrapper.h b/bindings/rust/libgpiod-sys/wrapper.h new file mode 100644 index 000000000000..7bc1158b7d90 --- /dev/null +++ b/bindings/rust/libgpiod-sys/wrapper.h @@ -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.
On 27-07-22, 10:57, Kent Gibson wrote:
On Fri, Jul 08, 2022 at 05:04:54PM +0530, Viresh Kumar wrote:
+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",
- ];
- #[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.
diff --git a/bindings/rust/libgpiod-sys/src/lib.rs b/bindings/rust/libgpiod-sys/src/lib.rs new file mode 100644 index 000000000000..3384863a567c --- /dev/null +++ b/bindings/rust/libgpiod-sys/src/lib.rs @@ -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
+)]
Are all these really necessary?
Actually not, thanks for pointing this out.
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".
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.
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.
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.
+mod bindings_raw {
- #[cfg(feature = "generate")]
- include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
- #[cfg(not(feature = "generate"))]
- include!("bindings.rs");
+} +pub use bindings_raw::*; diff --git a/bindings/rust/libgpiod-sys/wrapper.h b/bindings/rust/libgpiod-sys/wrapper.h new file mode 100644 index 000000000000..7bc1158b7d90 --- /dev/null +++ b/bindings/rust/libgpiod-sys/wrapper.h @@ -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.
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.
On Wed, Jul 27, 2022 at 10:21:58AM +0530, Viresh Kumar wrote:
On 27-07-22, 10:57, Kent Gibson wrote:
On Fri, Jul 08, 2022 at 05:04:54PM +0530, Viresh Kumar wrote:
+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",
- ];
- #[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.
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.
diff --git a/bindings/rust/libgpiod-sys/src/lib.rs b/bindings/rust/libgpiod-sys/src/lib.rs new file mode 100644 index 000000000000..3384863a567c --- /dev/null +++ b/bindings/rust/libgpiod-sys/src/lib.rs @@ -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
+)]
Are all these really necessary?
Actually not, thanks for pointing this out.
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".
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.
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.
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))] ?
+mod bindings_raw {
- #[cfg(feature = "generate")]
- include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
- #[cfg(not(feature = "generate"))]
- include!("bindings.rs");
+} +pub use bindings_raw::*; diff --git a/bindings/rust/libgpiod-sys/wrapper.h b/bindings/rust/libgpiod-sys/wrapper.h new file mode 100644 index 000000000000..7bc1158b7d90 --- /dev/null +++ b/bindings/rust/libgpiod-sys/wrapper.h @@ -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.
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.
On 27-07-22, 13:17, Kent Gibson wrote:
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.
By "all builds" I meant build/tests/fmt/clippy etc of this module, not others.
Ahh.
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.
#[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))]
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:
diff --git a/bindings/rust/libgpiod-sys/build.rs b/bindings/rust/libgpiod-sys/build.rs index 147daaf6b1da..96f832134431 100644 --- a/bindings/rust/libgpiod-sys/build.rs +++ b/bindings/rust/libgpiod-sys/build.rs @@ -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"); diff --git a/bindings/rust/libgpiod-sys/wrapper.h b/bindings/rust/libgpiod-sys/wrapper.h deleted file mode 100644 index 32290711642a..000000000000 --- a/bindings/rust/libgpiod-sys/wrapper.h +++ /dev/null @@ -1 +0,0 @@ -#include "../../../include/gpiod.h"
On 27-07-22, 13:17, Kent Gibson wrote:
On Wed, Jul 27, 2022 at 10:21:58AM +0530, Viresh Kumar wrote:
On 27-07-22, 10:57, Kent Gibson wrote:
On Fri, Jul 08, 2022 at 05:04:54PM +0530, Viresh Kumar wrote:
+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",
- ];
- #[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.
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.
On Mon, Aug 01, 2022 at 05:41:06PM +0530, Viresh Kumar wrote:
On 27-07-22, 13:17, Kent Gibson wrote:
On Wed, Jul 27, 2022 at 10:21:58AM +0530, Viresh Kumar wrote:
On 27-07-22, 10:57, Kent Gibson wrote:
On Fri, Jul 08, 2022 at 05:04:54PM +0530, Viresh Kumar wrote:
+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",
- ];
- #[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.
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.
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.
On 01-08-22, 23:56, Kent Gibson wrote:
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.
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.
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.
On Tue, Aug 02, 2022 at 02:20:08PM +0530, Viresh Kumar wrote:
On 01-08-22, 23:56, Kent Gibson wrote:
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.
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.
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.
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.
-- viresh
This adds a copy of pre generated bindings and adds the suggested way of updating those in README.
Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- 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
diff --git a/bindings/rust/libgpiod-sys/README.md b/bindings/rust/libgpiod-sys/README.md new file mode 100644 index 000000000000..ea037d6d7803 --- /dev/null +++ b/bindings/rust/libgpiod-sys/README.md @@ -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` diff --git a/bindings/rust/libgpiod-sys/src/bindings.rs b/bindings/rust/libgpiod-sys/src/bindings.rs new file mode 100644 index 000000000000..930eb894f66f --- /dev/null +++ b/bindings/rust/libgpiod-sys/src/bindings.rs @@ -0,0 +1,1920 @@ +/* automatically generated by rust-bindgen 0.59.2 */ + +pub const _STRING_H: u32 = 1; +pub const _FEATURES_H: u32 = 1; +pub const _DEFAULT_SOURCE: u32 = 1; +pub const __GLIBC_USE_ISOC2X: u32 = 0; +pub const __USE_ISOC11: u32 = 1; +pub const __USE_ISOC99: u32 = 1; +pub const __USE_ISOC95: u32 = 1; +pub const __USE_POSIX_IMPLICITLY: u32 = 1; +pub const _POSIX_SOURCE: u32 = 1; +pub const _POSIX_C_SOURCE: u32 = 200809; +pub const __USE_POSIX: u32 = 1; +pub const __USE_POSIX2: u32 = 1; +pub const __USE_POSIX199309: u32 = 1; +pub const __USE_POSIX199506: u32 = 1; +pub const __USE_XOPEN2K: u32 = 1; +pub const __USE_XOPEN2K8: u32 = 1; +pub const _ATFILE_SOURCE: u32 = 1; +pub const __USE_MISC: u32 = 1; +pub const __USE_ATFILE: u32 = 1; +pub const __USE_FORTIFY_LEVEL: u32 = 0; +pub const __GLIBC_USE_DEPRECATED_GETS: u32 = 0; +pub const __GLIBC_USE_DEPRECATED_SCANF: u32 = 0; +pub const _STDC_PREDEF_H: u32 = 1; +pub const __STDC_IEC_559__: u32 = 1; +pub const __STDC_IEC_559_COMPLEX__: u32 = 1; +pub const __STDC_ISO_10646__: u32 = 201706; +pub const __GNU_LIBRARY__: u32 = 6; +pub const __GLIBC__: u32 = 2; +pub const __GLIBC_MINOR__: u32 = 31; +pub const _SYS_CDEFS_H: u32 = 1; +pub const __glibc_c99_flexarr_available: u32 = 1; +pub const __WORDSIZE: u32 = 64; +pub const __WORDSIZE_TIME64_COMPAT32: u32 = 1; +pub const __SYSCALL_WORDSIZE: u32 = 64; +pub const __LONG_DOUBLE_USES_FLOAT128: u32 = 0; +pub const __HAVE_GENERIC_SELECTION: u32 = 1; +pub const __GLIBC_USE_LIB_EXT2: u32 = 0; +pub const __GLIBC_USE_IEC_60559_BFP_EXT: u32 = 0; +pub const __GLIBC_USE_IEC_60559_BFP_EXT_C2X: u32 = 0; +pub const __GLIBC_USE_IEC_60559_FUNCS_EXT: u32 = 0; +pub const __GLIBC_USE_IEC_60559_FUNCS_EXT_C2X: u32 = 0; +pub const __GLIBC_USE_IEC_60559_TYPES_EXT: u32 = 0; +pub const _BITS_TYPES_LOCALE_T_H: u32 = 1; +pub const _BITS_TYPES___LOCALE_T_H: u32 = 1; +pub const _STRINGS_H: u32 = 1; +pub const true_: u32 = 1; +pub const false_: u32 = 0; +pub const __bool_true_false_are_defined: u32 = 1; +pub const _STDINT_H: u32 = 1; +pub const _BITS_TYPES_H: u32 = 1; +pub const __TIMESIZE: u32 = 64; +pub const _BITS_TYPESIZES_H: u32 = 1; +pub const __OFF_T_MATCHES_OFF64_T: u32 = 1; +pub const __INO_T_MATCHES_INO64_T: u32 = 1; +pub const __RLIM_T_MATCHES_RLIM64_T: u32 = 1; +pub const __STATFS_MATCHES_STATFS64: u32 = 1; +pub const __FD_SETSIZE: u32 = 1024; +pub const _BITS_TIME64_H: u32 = 1; +pub const _BITS_WCHAR_H: u32 = 1; +pub const _BITS_STDINT_INTN_H: u32 = 1; +pub const _BITS_STDINT_UINTN_H: u32 = 1; +pub const INT8_MIN: i32 = -128; +pub const INT16_MIN: i32 = -32768; +pub const INT32_MIN: i32 = -2147483648; +pub const INT8_MAX: u32 = 127; +pub const INT16_MAX: u32 = 32767; +pub const INT32_MAX: u32 = 2147483647; +pub const UINT8_MAX: u32 = 255; +pub const UINT16_MAX: u32 = 65535; +pub const UINT32_MAX: u32 = 4294967295; +pub const INT_LEAST8_MIN: i32 = -128; +pub const INT_LEAST16_MIN: i32 = -32768; +pub const INT_LEAST32_MIN: i32 = -2147483648; +pub const INT_LEAST8_MAX: u32 = 127; +pub const INT_LEAST16_MAX: u32 = 32767; +pub const INT_LEAST32_MAX: u32 = 2147483647; +pub const UINT_LEAST8_MAX: u32 = 255; +pub const UINT_LEAST16_MAX: u32 = 65535; +pub const UINT_LEAST32_MAX: u32 = 4294967295; +pub const INT_FAST8_MIN: i32 = -128; +pub const INT_FAST16_MIN: i64 = -9223372036854775808; +pub const INT_FAST32_MIN: i64 = -9223372036854775808; +pub const INT_FAST8_MAX: u32 = 127; +pub const INT_FAST16_MAX: u64 = 9223372036854775807; +pub const INT_FAST32_MAX: u64 = 9223372036854775807; +pub const UINT_FAST8_MAX: u32 = 255; +pub const UINT_FAST16_MAX: i32 = -1; +pub const UINT_FAST32_MAX: i32 = -1; +pub const INTPTR_MIN: i64 = -9223372036854775808; +pub const INTPTR_MAX: u64 = 9223372036854775807; +pub const UINTPTR_MAX: i32 = -1; +pub const PTRDIFF_MIN: i64 = -9223372036854775808; +pub const PTRDIFF_MAX: u64 = 9223372036854775807; +pub const SIG_ATOMIC_MIN: i32 = -2147483648; +pub const SIG_ATOMIC_MAX: u32 = 2147483647; +pub const SIZE_MAX: i32 = -1; +pub const WINT_MIN: u32 = 0; +pub const WINT_MAX: u32 = 4294967295; +pub type size_t = ::std::os::raw::c_ulong; +extern "C" { + pub fn memcpy( + __dest: *mut ::std::os::raw::c_void, + __src: *const ::std::os::raw::c_void, + __n: ::std::os::raw::c_ulong, + ) -> *mut ::std::os::raw::c_void; +} +extern "C" { + pub fn memmove( + __dest: *mut ::std::os::raw::c_void, + __src: *const ::std::os::raw::c_void, + __n: ::std::os::raw::c_ulong, + ) -> *mut ::std::os::raw::c_void; +} +extern "C" { + pub fn memccpy( + __dest: *mut ::std::os::raw::c_void, + __src: *const ::std::os::raw::c_void, + __c: ::std::os::raw::c_int, + __n: ::std::os::raw::c_ulong, + ) -> *mut ::std::os::raw::c_void; +} +extern "C" { + pub fn memset( + __s: *mut ::std::os::raw::c_void, + __c: ::std::os::raw::c_int, + __n: ::std::os::raw::c_ulong, + ) -> *mut ::std::os::raw::c_void; +} +extern "C" { + pub fn memcmp( + __s1: *const ::std::os::raw::c_void, + __s2: *const ::std::os::raw::c_void, + __n: ::std::os::raw::c_ulong, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn memchr( + __s: *const ::std::os::raw::c_void, + __c: ::std::os::raw::c_int, + __n: ::std::os::raw::c_ulong, + ) -> *mut ::std::os::raw::c_void; +} +extern "C" { + pub fn strcpy( + __dest: *mut ::std::os::raw::c_char, + __src: *const ::std::os::raw::c_char, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn strncpy( + __dest: *mut ::std::os::raw::c_char, + __src: *const ::std::os::raw::c_char, + __n: ::std::os::raw::c_ulong, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn strcat( + __dest: *mut ::std::os::raw::c_char, + __src: *const ::std::os::raw::c_char, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn strncat( + __dest: *mut ::std::os::raw::c_char, + __src: *const ::std::os::raw::c_char, + __n: ::std::os::raw::c_ulong, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn strcmp( + __s1: *const ::std::os::raw::c_char, + __s2: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn strncmp( + __s1: *const ::std::os::raw::c_char, + __s2: *const ::std::os::raw::c_char, + __n: ::std::os::raw::c_ulong, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn strcoll( + __s1: *const ::std::os::raw::c_char, + __s2: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn strxfrm( + __dest: *mut ::std::os::raw::c_char, + __src: *const ::std::os::raw::c_char, + __n: ::std::os::raw::c_ulong, + ) -> ::std::os::raw::c_ulong; +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct __locale_struct { + pub __locales: [*mut __locale_data; 13usize], + pub __ctype_b: *const ::std::os::raw::c_ushort, + pub __ctype_tolower: *const ::std::os::raw::c_int, + pub __ctype_toupper: *const ::std::os::raw::c_int, + pub __names: [*const ::std::os::raw::c_char; 13usize], +} +#[test] +fn bindgen_test_layout___locale_struct() { + assert_eq!( + ::std::mem::size_of::<__locale_struct>(), + 232usize, + concat!("Size of: ", stringify!(__locale_struct)) + ); + assert_eq!( + ::std::mem::align_of::<__locale_struct>(), + 8usize, + concat!("Alignment of ", stringify!(__locale_struct)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__locale_struct>())).__locales as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(__locale_struct), + "::", + stringify!(__locales) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__locale_struct>())).__ctype_b as *const _ as usize }, + 104usize, + concat!( + "Offset of field: ", + stringify!(__locale_struct), + "::", + stringify!(__ctype_b) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__locale_struct>())).__ctype_tolower as *const _ as usize }, + 112usize, + concat!( + "Offset of field: ", + stringify!(__locale_struct), + "::", + stringify!(__ctype_tolower) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__locale_struct>())).__ctype_toupper as *const _ as usize }, + 120usize, + concat!( + "Offset of field: ", + stringify!(__locale_struct), + "::", + stringify!(__ctype_toupper) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__locale_struct>())).__names as *const _ as usize }, + 128usize, + concat!( + "Offset of field: ", + stringify!(__locale_struct), + "::", + stringify!(__names) + ) + ); +} +pub type __locale_t = *mut __locale_struct; +pub type locale_t = __locale_t; +extern "C" { + pub fn strcoll_l( + __s1: *const ::std::os::raw::c_char, + __s2: *const ::std::os::raw::c_char, + __l: locale_t, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn strxfrm_l( + __dest: *mut ::std::os::raw::c_char, + __src: *const ::std::os::raw::c_char, + __n: size_t, + __l: locale_t, + ) -> size_t; +} +extern "C" { + pub fn strdup(__s: *const ::std::os::raw::c_char) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn strndup( + __string: *const ::std::os::raw::c_char, + __n: ::std::os::raw::c_ulong, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn strchr( + __s: *const ::std::os::raw::c_char, + __c: ::std::os::raw::c_int, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn strrchr( + __s: *const ::std::os::raw::c_char, + __c: ::std::os::raw::c_int, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn strcspn( + __s: *const ::std::os::raw::c_char, + __reject: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_ulong; +} +extern "C" { + pub fn strspn( + __s: *const ::std::os::raw::c_char, + __accept: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_ulong; +} +extern "C" { + pub fn strpbrk( + __s: *const ::std::os::raw::c_char, + __accept: *const ::std::os::raw::c_char, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn strstr( + __haystack: *const ::std::os::raw::c_char, + __needle: *const ::std::os::raw::c_char, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn strtok( + __s: *mut ::std::os::raw::c_char, + __delim: *const ::std::os::raw::c_char, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn __strtok_r( + __s: *mut ::std::os::raw::c_char, + __delim: *const ::std::os::raw::c_char, + __save_ptr: *mut *mut ::std::os::raw::c_char, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn strtok_r( + __s: *mut ::std::os::raw::c_char, + __delim: *const ::std::os::raw::c_char, + __save_ptr: *mut *mut ::std::os::raw::c_char, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn strlen(__s: *const ::std::os::raw::c_char) -> ::std::os::raw::c_ulong; +} +extern "C" { + pub fn strnlen(__string: *const ::std::os::raw::c_char, __maxlen: size_t) -> size_t; +} +extern "C" { + pub fn strerror(__errnum: ::std::os::raw::c_int) -> *mut ::std::os::raw::c_char; +} +extern "C" { + #[link_name = "\u{1}__xpg_strerror_r"] + pub fn strerror_r( + __errnum: ::std::os::raw::c_int, + __buf: *mut ::std::os::raw::c_char, + __buflen: size_t, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn strerror_l( + __errnum: ::std::os::raw::c_int, + __l: locale_t, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn bcmp( + __s1: *const ::std::os::raw::c_void, + __s2: *const ::std::os::raw::c_void, + __n: ::std::os::raw::c_ulong, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn bcopy( + __src: *const ::std::os::raw::c_void, + __dest: *mut ::std::os::raw::c_void, + __n: size_t, + ); +} +extern "C" { + pub fn bzero(__s: *mut ::std::os::raw::c_void, __n: ::std::os::raw::c_ulong); +} +extern "C" { + pub fn index( + __s: *const ::std::os::raw::c_char, + __c: ::std::os::raw::c_int, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn rindex( + __s: *const ::std::os::raw::c_char, + __c: ::std::os::raw::c_int, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn ffs(__i: ::std::os::raw::c_int) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn ffsl(__l: ::std::os::raw::c_long) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn ffsll(__ll: ::std::os::raw::c_longlong) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn strcasecmp( + __s1: *const ::std::os::raw::c_char, + __s2: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn strncasecmp( + __s1: *const ::std::os::raw::c_char, + __s2: *const ::std::os::raw::c_char, + __n: ::std::os::raw::c_ulong, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn strcasecmp_l( + __s1: *const ::std::os::raw::c_char, + __s2: *const ::std::os::raw::c_char, + __loc: locale_t, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn strncasecmp_l( + __s1: *const ::std::os::raw::c_char, + __s2: *const ::std::os::raw::c_char, + __n: size_t, + __loc: locale_t, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn explicit_bzero(__s: *mut ::std::os::raw::c_void, __n: size_t); +} +extern "C" { + pub fn strsep( + __stringp: *mut *mut ::std::os::raw::c_char, + __delim: *const ::std::os::raw::c_char, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn strsignal(__sig: ::std::os::raw::c_int) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn __stpcpy( + __dest: *mut ::std::os::raw::c_char, + __src: *const ::std::os::raw::c_char, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn stpcpy( + __dest: *mut ::std::os::raw::c_char, + __src: *const ::std::os::raw::c_char, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn __stpncpy( + __dest: *mut ::std::os::raw::c_char, + __src: *const ::std::os::raw::c_char, + __n: size_t, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn stpncpy( + __dest: *mut ::std::os::raw::c_char, + __src: *const ::std::os::raw::c_char, + __n: ::std::os::raw::c_ulong, + ) -> *mut ::std::os::raw::c_char; +} +pub type wchar_t = ::std::os::raw::c_int; +#[repr(C)] +#[repr(align(16))] +#[derive(Debug, Copy, Clone)] +pub struct max_align_t { + pub __clang_max_align_nonce1: ::std::os::raw::c_longlong, + pub __bindgen_padding_0: u64, + pub __clang_max_align_nonce2: u128, +} +#[test] +fn bindgen_test_layout_max_align_t() { + assert_eq!( + ::std::mem::size_of::<max_align_t>(), + 32usize, + concat!("Size of: ", stringify!(max_align_t)) + ); + assert_eq!( + ::std::mem::align_of::<max_align_t>(), + 16usize, + concat!("Alignment of ", stringify!(max_align_t)) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<max_align_t>())).__clang_max_align_nonce1 as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(max_align_t), + "::", + stringify!(__clang_max_align_nonce1) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<max_align_t>())).__clang_max_align_nonce2 as *const _ as usize + }, + 16usize, + concat!( + "Offset of field: ", + stringify!(max_align_t), + "::", + stringify!(__clang_max_align_nonce2) + ) + ); +} +pub type __u_char = ::std::os::raw::c_uchar; +pub type __u_short = ::std::os::raw::c_ushort; +pub type __u_int = ::std::os::raw::c_uint; +pub type __u_long = ::std::os::raw::c_ulong; +pub type __int8_t = ::std::os::raw::c_schar; +pub type __uint8_t = ::std::os::raw::c_uchar; +pub type __int16_t = ::std::os::raw::c_short; +pub type __uint16_t = ::std::os::raw::c_ushort; +pub type __int32_t = ::std::os::raw::c_int; +pub type __uint32_t = ::std::os::raw::c_uint; +pub type __int64_t = ::std::os::raw::c_long; +pub type __uint64_t = ::std::os::raw::c_ulong; +pub type __int_least8_t = __int8_t; +pub type __uint_least8_t = __uint8_t; +pub type __int_least16_t = __int16_t; +pub type __uint_least16_t = __uint16_t; +pub type __int_least32_t = __int32_t; +pub type __uint_least32_t = __uint32_t; +pub type __int_least64_t = __int64_t; +pub type __uint_least64_t = __uint64_t; +pub type __quad_t = ::std::os::raw::c_long; +pub type __u_quad_t = ::std::os::raw::c_ulong; +pub type __intmax_t = ::std::os::raw::c_long; +pub type __uintmax_t = ::std::os::raw::c_ulong; +pub type __dev_t = ::std::os::raw::c_ulong; +pub type __uid_t = ::std::os::raw::c_uint; +pub type __gid_t = ::std::os::raw::c_uint; +pub type __ino_t = ::std::os::raw::c_ulong; +pub type __ino64_t = ::std::os::raw::c_ulong; +pub type __mode_t = ::std::os::raw::c_uint; +pub type __nlink_t = ::std::os::raw::c_ulong; +pub type __off_t = ::std::os::raw::c_long; +pub type __off64_t = ::std::os::raw::c_long; +pub type __pid_t = ::std::os::raw::c_int; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct __fsid_t { + pub __val: [::std::os::raw::c_int; 2usize], +} +#[test] +fn bindgen_test_layout___fsid_t() { + assert_eq!( + ::std::mem::size_of::<__fsid_t>(), + 8usize, + concat!("Size of: ", stringify!(__fsid_t)) + ); + assert_eq!( + ::std::mem::align_of::<__fsid_t>(), + 4usize, + concat!("Alignment of ", stringify!(__fsid_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__fsid_t>())).__val as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(__fsid_t), + "::", + stringify!(__val) + ) + ); +} +pub type __clock_t = ::std::os::raw::c_long; +pub type __rlim_t = ::std::os::raw::c_ulong; +pub type __rlim64_t = ::std::os::raw::c_ulong; +pub type __id_t = ::std::os::raw::c_uint; +pub type __time_t = ::std::os::raw::c_long; +pub type __useconds_t = ::std::os::raw::c_uint; +pub type __suseconds_t = ::std::os::raw::c_long; +pub type __daddr_t = ::std::os::raw::c_int; +pub type __key_t = ::std::os::raw::c_int; +pub type __clockid_t = ::std::os::raw::c_int; +pub type __timer_t = *mut ::std::os::raw::c_void; +pub type __blksize_t = ::std::os::raw::c_long; +pub type __blkcnt_t = ::std::os::raw::c_long; +pub type __blkcnt64_t = ::std::os::raw::c_long; +pub type __fsblkcnt_t = ::std::os::raw::c_ulong; +pub type __fsblkcnt64_t = ::std::os::raw::c_ulong; +pub type __fsfilcnt_t = ::std::os::raw::c_ulong; +pub type __fsfilcnt64_t = ::std::os::raw::c_ulong; +pub type __fsword_t = ::std::os::raw::c_long; +pub type __ssize_t = ::std::os::raw::c_long; +pub type __syscall_slong_t = ::std::os::raw::c_long; +pub type __syscall_ulong_t = ::std::os::raw::c_ulong; +pub type __loff_t = __off64_t; +pub type __caddr_t = *mut ::std::os::raw::c_char; +pub type __intptr_t = ::std::os::raw::c_long; +pub type __socklen_t = ::std::os::raw::c_uint; +pub type __sig_atomic_t = ::std::os::raw::c_int; +pub type int_least8_t = __int_least8_t; +pub type int_least16_t = __int_least16_t; +pub type int_least32_t = __int_least32_t; +pub type int_least64_t = __int_least64_t; +pub type uint_least8_t = __uint_least8_t; +pub type uint_least16_t = __uint_least16_t; +pub type uint_least32_t = __uint_least32_t; +pub type uint_least64_t = __uint_least64_t; +pub type int_fast8_t = ::std::os::raw::c_schar; +pub type int_fast16_t = ::std::os::raw::c_long; +pub type int_fast32_t = ::std::os::raw::c_long; +pub type int_fast64_t = ::std::os::raw::c_long; +pub type uint_fast8_t = ::std::os::raw::c_uchar; +pub type uint_fast16_t = ::std::os::raw::c_ulong; +pub type uint_fast32_t = ::std::os::raw::c_ulong; +pub type uint_fast64_t = ::std::os::raw::c_ulong; +pub type intmax_t = __intmax_t; +pub type uintmax_t = __uintmax_t; +#[doc = " @mainpage libgpiod public API"] +#[doc = ""] +#[doc = " This is the complete documentation of the public API made available to"] +#[doc = " users of libgpiod."] +#[doc = ""] +#[doc = " <p>The API is logically split into several parts such as: GPIO chip & line"] +#[doc = " operators, GPIO events handling etc."] +#[doc = ""] +#[doc = " <p>General note on error handling: all functions exported by libgpiod that"] +#[doc = " can fail, set errno to one of the error values defined in errno.h upon"] +#[doc = " failure. The way of notifying the caller that an error occurred varies"] +#[doc = " between functions, but in general a function that returns an int, returns -1"] +#[doc = " on error, while a function returning a pointer indicates an error condition"] +#[doc = " by returning a NULL pointer. It's not practical to list all possible error"] +#[doc = " codes for every function as they propagate errors from the underlying libc"] +#[doc = " functions."] +#[doc = ""] +#[doc = " <p>In general libgpiod functions are not NULL-aware and it's expected that"] +#[doc = " users pass valid pointers to objects as arguments. An exception to this rule"] +#[doc = " are the functions that free/close/release resources - which work when passed"] +#[doc = " a NULL-pointer as argument. Other exceptions are documented."] +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct gpiod_chip { + _unused: [u8; 0], +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct gpiod_chip_info { + _unused: [u8; 0], +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct gpiod_line_info { + _unused: [u8; 0], +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct gpiod_line_config { + _unused: [u8; 0], +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct gpiod_request_config { + _unused: [u8; 0], +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct gpiod_line_request { + _unused: [u8; 0], +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct gpiod_info_event { + _unused: [u8; 0], +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct gpiod_edge_event { + _unused: [u8; 0], +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct gpiod_edge_event_buffer { + _unused: [u8; 0], +} +extern "C" { + #[doc = " @brief Open a chip by path."] + #[doc = " @param path Path to the gpiochip device file."] + #[doc = " @return GPIO chip request or NULL if an error occurred."] + pub fn gpiod_chip_open(path: *const ::std::os::raw::c_char) -> *mut gpiod_chip; +} +extern "C" { + #[doc = " @brief Close the chip and release all associated resources."] + #[doc = " @param chip Chip to close."] + pub fn gpiod_chip_close(chip: *mut gpiod_chip); +} +extern "C" { + #[doc = " @brief Get information about the chip."] + #[doc = " @param chip GPIO chip object."] + #[doc = " @return New GPIO chip info object or NULL if an error occurred. The returned"] + #[doc = " object must be freed by the caller using ::gpiod_chip_info_free."] + pub fn gpiod_chip_get_info(chip: *mut gpiod_chip) -> *mut gpiod_chip_info; +} +extern "C" { + #[doc = " @brief Get the path used to open the chip."] + #[doc = " @param chip GPIO chip object."] + #[doc = " @return Path to the file passed as argument to ::gpiod_chip_open."] + pub fn gpiod_chip_get_path(chip: *mut gpiod_chip) -> *const ::std::os::raw::c_char; +} +extern "C" { + #[doc = " @brief Get a snapshot of information about a line."] + #[doc = " @param chip GPIO chip object."] + #[doc = " @param offset The offset of the GPIO line."] + #[doc = " @return New GPIO line info object or NULL if an error occurred. The returned"] + #[doc = "\t object must be freed by the caller using ::gpiod_line_info_free."] + pub fn gpiod_chip_get_line_info( + chip: *mut gpiod_chip, + offset: ::std::os::raw::c_uint, + ) -> *mut gpiod_line_info; +} +extern "C" { + #[doc = " @brief Get a snapshot of the status of a line and start watching it for"] + #[doc = "\t future changes."] + #[doc = " @param chip GPIO chip object."] + #[doc = " @param offset The offset of the GPIO line."] + #[doc = " @return New GPIO line info object or NULL if an error occurred. The returned"] + #[doc = "\t object must be freed by the caller using ::gpiod_line_info_free."] + #[doc = " @note Line status does not include the line value. To monitor the line"] + #[doc = "\t value the line must be requested as an input with edge detection set."] + pub fn gpiod_chip_watch_line_info( + chip: *mut gpiod_chip, + offset: ::std::os::raw::c_uint, + ) -> *mut gpiod_line_info; +} +extern "C" { + #[doc = " @brief Stop watching a line for status changes."] + #[doc = " @param chip GPIO chip object."] + #[doc = " @param offset The offset of the line to stop watching."] + #[doc = " @return 0 on success, -1 on failure."] + pub fn gpiod_chip_unwatch_line_info( + chip: *mut gpiod_chip, + offset: ::std::os::raw::c_uint, + ) -> ::std::os::raw::c_int; +} +extern "C" { + #[doc = " @brief Get the file descriptor associated with the chip."] + #[doc = " @param chip GPIO chip object."] + #[doc = " @return File descriptor number for the chip."] + #[doc = "\t This function never fails."] + #[doc = "\t The returned file descriptor must not be closed by the caller."] + #[doc = "\t Call ::gpiod_chip_close to close the file descriptor."] + pub fn gpiod_chip_get_fd(chip: *mut gpiod_chip) -> ::std::os::raw::c_int; +} +extern "C" { + #[doc = " @brief Wait for line status change events on any of the watched lines"] + #[doc = "\t on the chip."] + #[doc = " @param chip GPIO chip object."] + #[doc = " @param timeout_ns Wait time limit in nanoseconds. If set to 0, the function"] + #[doc = "\t\t returns immediatelly. If set to a negative number, the"] + #[doc = "\t\t function blocks indefinitely until an event becomes"] + #[doc = "\t\t available."] + #[doc = " @return 0 if wait timed out, -1 if an error occurred, 1 if an event is"] + #[doc = "\t pending."] + pub fn gpiod_chip_wait_info_event( + chip: *mut gpiod_chip, + timeout_ns: i64, + ) -> ::std::os::raw::c_int; +} +extern "C" { + #[doc = " @brief Read a single line status change event from the chip."] + #[doc = " @param chip GPIO chip object."] + #[doc = " @return Newly read watch event object or NULL on error. The event must be"] + #[doc = "\t freed by the caller using ::gpiod_info_event_free."] + #[doc = " @note If no events are pending, this function will block."] + pub fn gpiod_chip_read_info_event(chip: *mut gpiod_chip) -> *mut gpiod_info_event; +} +extern "C" { + #[doc = " @brief Map a line's name to its offset within the chip."] + #[doc = " @param chip GPIO chip object."] + #[doc = " @param name Name of the GPIO line to map."] + #[doc = " @return Offset of the line within the chip or -1 on error."] + #[doc = " @note If a line with given name is not exposed by the chip, the function"] + #[doc = " sets errno to ENOENT."] + pub fn gpiod_chip_get_line_offset_from_name( + chip: *mut gpiod_chip, + name: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + #[doc = " @brief Request a set of lines for exclusive usage."] + #[doc = " @param chip GPIO chip object."] + #[doc = " @param req_cfg Request config object."] + #[doc = " @param line_cfg Line config object."] + #[doc = " @return New line request object or NULL if an error occurred. The request"] + #[doc = "\t must be released by the caller using ::gpiod_line_request_release."] + #[doc = " @note Line configuration overrides for lines that are not requested are"] + #[doc = "\t silently ignored."] + pub fn gpiod_chip_request_lines( + chip: *mut gpiod_chip, + req_cfg: *mut gpiod_request_config, + line_cfg: *mut gpiod_line_config, + ) -> *mut gpiod_line_request; +} +extern "C" { + #[doc = " @brief Free a chip info object and release all associated resources."] + #[doc = " @param info GPIO chip info object to free."] + pub fn gpiod_chip_info_free(info: *mut gpiod_chip_info); +} +extern "C" { + #[doc = " @brief Get the name of the chip as represented in the kernel."] + #[doc = " @param info GPIO chip info object."] + #[doc = " @return Pointer to a human-readable string containing the chip name."] + pub fn gpiod_chip_info_get_name(info: *mut gpiod_chip_info) -> *const ::std::os::raw::c_char; +} +extern "C" { + #[doc = " @brief Get the label of the chip as represented in the kernel."] + #[doc = " @param info GPIO chip info object."] + #[doc = " @return Pointer to a human-readable string containing the chip label."] + pub fn gpiod_chip_info_get_label(info: *mut gpiod_chip_info) -> *const ::std::os::raw::c_char; +} +extern "C" { + #[doc = " @brief Get the number of lines exposed by the chip."] + #[doc = " @param info GPIO chip info object."] + #[doc = " @return Number of GPIO lines."] + pub fn gpiod_chip_info_get_num_lines(info: *mut gpiod_chip_info) -> size_t; +} +pub const GPIOD_LINE_VALUE_INACTIVE: ::std::os::raw::c_uint = 0; +pub const GPIOD_LINE_VALUE_ACTIVE: ::std::os::raw::c_uint = 1; +#[doc = " @brief Logical line state."] +pub type _bindgen_ty_1 = ::std::os::raw::c_uint; +pub const GPIOD_LINE_DIRECTION_AS_IS: ::std::os::raw::c_uint = 1; +pub const GPIOD_LINE_DIRECTION_INPUT: ::std::os::raw::c_uint = 2; +pub const GPIOD_LINE_DIRECTION_OUTPUT: ::std::os::raw::c_uint = 3; +#[doc = " @brief Direction settings."] +pub type _bindgen_ty_2 = ::std::os::raw::c_uint; +pub const GPIOD_LINE_EDGE_NONE: ::std::os::raw::c_uint = 1; +pub const GPIOD_LINE_EDGE_RISING: ::std::os::raw::c_uint = 2; +pub const GPIOD_LINE_EDGE_FALLING: ::std::os::raw::c_uint = 3; +pub const GPIOD_LINE_EDGE_BOTH: ::std::os::raw::c_uint = 4; +#[doc = " @brief Edge detection settings."] +pub type _bindgen_ty_3 = ::std::os::raw::c_uint; +pub const GPIOD_LINE_BIAS_AS_IS: ::std::os::raw::c_uint = 1; +pub const GPIOD_LINE_BIAS_UNKNOWN: ::std::os::raw::c_uint = 2; +pub const GPIOD_LINE_BIAS_DISABLED: ::std::os::raw::c_uint = 3; +pub const GPIOD_LINE_BIAS_PULL_UP: ::std::os::raw::c_uint = 4; +pub const GPIOD_LINE_BIAS_PULL_DOWN: ::std::os::raw::c_uint = 5; +#[doc = " @brief Internal bias settings."] +pub type _bindgen_ty_4 = ::std::os::raw::c_uint; +pub const GPIOD_LINE_DRIVE_PUSH_PULL: ::std::os::raw::c_uint = 1; +pub const GPIOD_LINE_DRIVE_OPEN_DRAIN: ::std::os::raw::c_uint = 2; +pub const GPIOD_LINE_DRIVE_OPEN_SOURCE: ::std::os::raw::c_uint = 3; +#[doc = " @brief Drive settings."] +pub type _bindgen_ty_5 = ::std::os::raw::c_uint; +pub const GPIOD_LINE_EVENT_CLOCK_MONOTONIC: ::std::os::raw::c_uint = 1; +pub const GPIOD_LINE_EVENT_CLOCK_REALTIME: ::std::os::raw::c_uint = 2; +#[doc = " @brief Event clock settings."] +pub type _bindgen_ty_6 = ::std::os::raw::c_uint; +extern "C" { + #[doc = " @brief Free a line info object and release all associated resources."] + #[doc = " @param info GPIO line info object to free."] + pub fn gpiod_line_info_free(info: *mut gpiod_line_info); +} +extern "C" { + #[doc = " @brief Copy a line info object."] + #[doc = " @param info Line info to copy."] + #[doc = " @return Copy of the line info or NULL on error. The returned object must"] + #[doc = "\t be freed by the caller using :gpiod_line_info_free."] + pub fn gpiod_line_info_copy(info: *mut gpiod_line_info) -> *mut gpiod_line_info; +} +extern "C" { + #[doc = " @brief Get the offset of the line."] + #[doc = " @param info GPIO line info object."] + #[doc = " @return Offset of the line within the parent chip."] + #[doc = ""] + #[doc = " The offset uniquely identifies the line on the chip."] + #[doc = " The combination of the chip and offset uniquely identifies the line within"] + #[doc = " the system."] + pub fn gpiod_line_info_get_offset(info: *mut gpiod_line_info) -> ::std::os::raw::c_uint; +} +extern "C" { + #[doc = " @brief Get the name of the line."] + #[doc = " @param info GPIO line info object."] + #[doc = " @return Name of the GPIO line as it is represented in the kernel."] + #[doc = "\t This function returns a pointer to a null-terminated string"] + #[doc = "\t or NULL if the line is unnamed."] + pub fn gpiod_line_info_get_name(info: *mut gpiod_line_info) -> *const ::std::os::raw::c_char; +} +extern "C" { + #[doc = " @brief Check if the line is in use."] + #[doc = " @param info GPIO line object."] + #[doc = " @return True if the line is in use, false otherwise."] + #[doc = ""] + #[doc = " The exact reason a line is busy cannot be determined from user space."] + #[doc = " It may have been requested by another process or hogged by the kernel."] + #[doc = " It only matters that the line is used and can't be requested until"] + #[doc = " released by the existing consumer."] + pub fn gpiod_line_info_is_used(info: *mut gpiod_line_info) -> bool; +} +extern "C" { + #[doc = " @brief Get the name of the consumer of the line."] + #[doc = " @param info GPIO line info object."] + #[doc = " @return Name of the GPIO consumer as it is represented in the kernel."] + #[doc = "\t This function returns a pointer to a null-terminated string"] + #[doc = "\t or NULL if the consumer name is not set."] + pub fn gpiod_line_info_get_consumer( + info: *mut gpiod_line_info, + ) -> *const ::std::os::raw::c_char; +} +extern "C" { + #[doc = " @brief Get the direction setting of the line."] + #[doc = " @param info GPIO line info object."] + #[doc = " @return Returns ::GPIOD_LINE_DIRECTION_INPUT or"] + #[doc = "\t ::GPIOD_LINE_DIRECTION_OUTPUT."] + pub fn gpiod_line_info_get_direction(info: *mut gpiod_line_info) -> ::std::os::raw::c_int; +} +extern "C" { + #[doc = " @brief Get the edge detection setting of the line."] + #[doc = " @param info GPIO line info object."] + #[doc = " @return Returns ::GPIOD_LINE_EDGE_NONE, ::GPIOD_LINE_EDGE_RISING,"] + #[doc = "\t ::GPIOD_LINE_EDGE_FALLING or ::GPIOD_LINE_EDGE_BOTH."] + pub fn gpiod_line_info_get_edge_detection(info: *mut gpiod_line_info) -> ::std::os::raw::c_int; +} +extern "C" { + #[doc = " @brief Get the bias setting of the line."] + #[doc = " @param info GPIO line object."] + #[doc = " @return Returns ::GPIOD_LINE_BIAS_PULL_UP, ::GPIOD_LINE_BIAS_PULL_DOWN,"] + #[doc = "\t ::GPIOD_LINE_BIAS_DISABLED or ::GPIOD_LINE_BIAS_UNKNOWN."] + pub fn gpiod_line_info_get_bias(info: *mut gpiod_line_info) -> ::std::os::raw::c_int; +} +extern "C" { + #[doc = " @brief Get the drive setting of the line."] + #[doc = " @param info GPIO line info object."] + #[doc = " @return Returns ::GPIOD_LINE_DRIVE_PUSH_PULL, ::GPIOD_LINE_DRIVE_OPEN_DRAIN"] + #[doc = "\t or ::GPIOD_LINE_DRIVE_OPEN_SOURCE."] + pub fn gpiod_line_info_get_drive(info: *mut gpiod_line_info) -> ::std::os::raw::c_int; +} +extern "C" { + #[doc = " @brief Check if the logical value of the line is inverted compared to the"] + #[doc = "\t physical."] + #[doc = " @param info GPIO line object."] + #[doc = " @return True if the line is "active-low", false otherwise."] + pub fn gpiod_line_info_is_active_low(info: *mut gpiod_line_info) -> bool; +} +extern "C" { + #[doc = " @brief Check if the line is debounced (either by hardware or by the kernel"] + #[doc = "\t software debouncer)."] + #[doc = " @param info GPIO line info object."] + #[doc = " @return True if the line is debounced, false otherwise."] + pub fn gpiod_line_info_is_debounced(info: *mut gpiod_line_info) -> bool; +} +extern "C" { + #[doc = " @brief Get the debounce period of the line, in microseconds."] + #[doc = " @param info GPIO line info object."] + #[doc = " @return Debounce period in microseconds."] + #[doc = "\t 0 if the line is not debounced."] + pub fn gpiod_line_info_get_debounce_period_us( + info: *mut gpiod_line_info, + ) -> ::std::os::raw::c_ulong; +} +extern "C" { + #[doc = " @brief Get the event clock setting used for edge event timestamps for the"] + #[doc = "\t line."] + #[doc = " @param info GPIO line info object."] + #[doc = " @return Returns ::GPIOD_LINE_EVENT_CLOCK_MONOTONIC or"] + #[doc = "\t ::GPIOD_LINE_EVENT_CLOCK_REALTIME."] + pub fn gpiod_line_info_get_event_clock(info: *mut gpiod_line_info) -> ::std::os::raw::c_int; +} +pub const GPIOD_INFO_EVENT_LINE_REQUESTED: ::std::os::raw::c_uint = 1; +pub const GPIOD_INFO_EVENT_LINE_RELEASED: ::std::os::raw::c_uint = 2; +pub const GPIOD_INFO_EVENT_LINE_CONFIG_CHANGED: ::std::os::raw::c_uint = 3; +#[doc = " @brief Line status change event types."] +pub type _bindgen_ty_7 = ::std::os::raw::c_uint; +extern "C" { + #[doc = " @brief Free the info event object and release all associated resources."] + #[doc = " @param event Info event to free."] + pub fn gpiod_info_event_free(event: *mut gpiod_info_event); +} +extern "C" { + #[doc = " @brief Get the event type of the status change event."] + #[doc = " @param event Line status watch event."] + #[doc = " @return One of ::GPIOD_INFO_EVENT_LINE_REQUESTED,"] + #[doc = "\t ::GPIOD_INFO_EVENT_LINE_RELEASED or"] + #[doc = "\t ::GPIOD_INFO_EVENT_LINE_CONFIG_CHANGED."] + pub fn gpiod_info_event_get_event_type(event: *mut gpiod_info_event) -> ::std::os::raw::c_int; +} +extern "C" { + #[doc = " @brief Get the timestamp of the event."] + #[doc = " @param event Line status watch event."] + #[doc = " @return Timestamp in nanoseconds, read from the monotonic clock."] + pub fn gpiod_info_event_get_timestamp_ns(event: *mut gpiod_info_event) -> u64; +} +extern "C" { + #[doc = " @brief Get the snapshot of line-info associated with the event."] + #[doc = " @param event Line info event object."] + #[doc = " @return Returns a pointer to the line-info object associated with the event"] + #[doc = "\t whose lifetime is tied to the event object. It must not be freed by"] + #[doc = "\t the caller."] + pub fn gpiod_info_event_get_line_info(event: *mut gpiod_info_event) -> *mut gpiod_line_info; +} +extern "C" { + #[doc = " @brief Create a new line config object."] + #[doc = " @return New line config object or NULL on error."] + pub fn gpiod_line_config_new() -> *mut gpiod_line_config; +} +extern "C" { + #[doc = " @brief Free the line config object and release all associated resources."] + #[doc = " @param config Line config object to free."] + pub fn gpiod_line_config_free(config: *mut gpiod_line_config); +} +extern "C" { + #[doc = " @brief Reset the line config object."] + #[doc = " @param config Line config object to free."] + #[doc = ""] + #[doc = " Resets the entire configuration stored in the object. This is useful if"] + #[doc = " the user wants to reuse the object without reallocating it."] + pub fn gpiod_line_config_reset(config: *mut gpiod_line_config); +} +extern "C" { + #[doc = " @brief Set the default line direction."] + #[doc = " @param config Line config object."] + #[doc = " @param direction New direction."] + pub fn gpiod_line_config_set_direction_default( + config: *mut gpiod_line_config, + direction: ::std::os::raw::c_int, + ); +} +extern "C" { + #[doc = " @brief Set the direction override for a line."] + #[doc = " @param config Line config object."] + #[doc = " @param direction New direction."] + #[doc = " @param offset The offset of the line for which to set the override."] + pub fn gpiod_line_config_set_direction_override( + config: *mut gpiod_line_config, + direction: ::std::os::raw::c_int, + offset: ::std::os::raw::c_uint, + ); +} +extern "C" { + #[doc = " @brief Clear the direction override for a line."] + #[doc = " @param config Line config object."] + #[doc = " @param offset The offset of the line for which to clear the override."] + #[doc = " @note Does nothing if no override is set for the line."] + pub fn gpiod_line_config_clear_direction_override( + config: *mut gpiod_line_config, + offset: ::std::os::raw::c_uint, + ); +} +extern "C" { + #[doc = " @brief Check if the direction is overridden for a line."] + #[doc = " @param config Line config object."] + #[doc = " @param offset The offset of the line to check for the override."] + #[doc = " @return True if direction is overridden on the line, false otherwise."] + pub fn gpiod_line_config_direction_is_overridden( + config: *mut gpiod_line_config, + offset: ::std::os::raw::c_uint, + ) -> bool; +} +extern "C" { + #[doc = " @brief Get the default direction setting."] + #[doc = " @param config Line config object."] + #[doc = " @return Direction setting used for any non-overridden line."] + pub fn gpiod_line_config_get_direction_default( + config: *mut gpiod_line_config, + ) -> ::std::os::raw::c_int; +} +extern "C" { + #[doc = " @brief Get the direction setting for a line."] + #[doc = " @param config Line config object."] + #[doc = " @param offset The offset of the line for which to read the direction."] + #[doc = " @return Direction setting for the line if the config object were used"] + #[doc = "\t in a request."] + pub fn gpiod_line_config_get_direction_offset( + config: *mut gpiod_line_config, + offset: ::std::os::raw::c_uint, + ) -> ::std::os::raw::c_int; +} +extern "C" { + #[doc = " @brief Set the default edge event detection."] + #[doc = " @param config Line config object."] + #[doc = " @param edge Type of edge events to detect."] + pub fn gpiod_line_config_set_edge_detection_default( + config: *mut gpiod_line_config, + edge: ::std::os::raw::c_int, + ); +} +extern "C" { + #[doc = " @brief Set the edge detection override for a line."] + #[doc = " @param config Line config object."] + #[doc = " @param edge Type of edge events to detect."] + #[doc = " @param offset The offset of the line for which to set the override."] + pub fn gpiod_line_config_set_edge_detection_override( + config: *mut gpiod_line_config, + edge: ::std::os::raw::c_int, + offset: ::std::os::raw::c_uint, + ); +} +extern "C" { + #[doc = " @brief Clear the edge detection override for a line."] + #[doc = " @param config Line config object."] + #[doc = " @param offset The offset of the line for which to clear the override."] + #[doc = " @note Does nothing if no override is set for the line."] + pub fn gpiod_line_config_clear_edge_detection_override( + config: *mut gpiod_line_config, + offset: ::std::os::raw::c_uint, + ); +} +extern "C" { + #[doc = " @brief Check if the edge detection setting is overridden for a line."] + #[doc = " @param config Line config object."] + #[doc = " @param offset The offset of the line to check for the override."] + #[doc = " @return True if edge detection is overridden for the line, false otherwise."] + pub fn gpiod_line_config_edge_detection_is_overridden( + config: *mut gpiod_line_config, + offset: ::std::os::raw::c_uint, + ) -> bool; +} +extern "C" { + #[doc = " @brief Get the default edge detection setting."] + #[doc = " @param config Line config object."] + #[doc = " @return Edge detection setting used for any non-overridden line."] + pub fn gpiod_line_config_get_edge_detection_default( + config: *mut gpiod_line_config, + ) -> ::std::os::raw::c_int; +} +extern "C" { + #[doc = " @brief Get the edge event detection setting for a line."] + #[doc = " @param config Line config object."] + #[doc = " @param offset The offset of the line for which to read the edge event detection"] + #[doc = "\t\t setting."] + #[doc = " @return Edge event detection setting for the line if the config object"] + #[doc = "\t were used in a request."] + pub fn gpiod_line_config_get_edge_detection_offset( + config: *mut gpiod_line_config, + offset: ::std::os::raw::c_uint, + ) -> ::std::os::raw::c_int; +} +extern "C" { + #[doc = " @brief Set the default bias setting."] + #[doc = " @param config Line config object."] + #[doc = " @param bias New bias."] + pub fn gpiod_line_config_set_bias_default( + config: *mut gpiod_line_config, + bias: ::std::os::raw::c_int, + ); +} +extern "C" { + #[doc = " @brief Set the bias override for a line."] + #[doc = " @param config Line config object."] + #[doc = " @param bias New bias setting."] + #[doc = " @param offset The offset of the line for which to set the override."] + pub fn gpiod_line_config_set_bias_override( + config: *mut gpiod_line_config, + bias: ::std::os::raw::c_int, + offset: ::std::os::raw::c_uint, + ); +} +extern "C" { + #[doc = " @brief Clear the bias override for a line."] + #[doc = " @param config Line config object."] + #[doc = " @param offset The offset of the line for which to clear the override."] + #[doc = " @note Does nothing if no override is set for the line."] + pub fn gpiod_line_config_clear_bias_override( + config: *mut gpiod_line_config, + offset: ::std::os::raw::c_uint, + ); +} +extern "C" { + #[doc = " @brief Check if the bias setting is overridden for a line."] + #[doc = " @param config Line config object."] + #[doc = " @param offset The offset of the line to check for the override."] + #[doc = " @return True if bias is overridden for the line, false otherwise."] + pub fn gpiod_line_config_bias_is_overridden( + config: *mut gpiod_line_config, + offset: ::std::os::raw::c_uint, + ) -> bool; +} +extern "C" { + #[doc = " @brief Get the default bias setting."] + #[doc = " @param config Line config object."] + #[doc = " @return Bias setting used for any non-overridden line."] + pub fn gpiod_line_config_get_bias_default( + config: *mut gpiod_line_config, + ) -> ::std::os::raw::c_int; +} +extern "C" { + #[doc = " @brief Get the bias setting for a line."] + #[doc = " @param config Line config object."] + #[doc = " @param offset The offset of the line for which to read the bias setting."] + #[doc = " @return Bias setting used for the line if the config object were used"] + #[doc = "\t in a request."] + pub fn gpiod_line_config_get_bias_offset( + config: *mut gpiod_line_config, + offset: ::std::os::raw::c_uint, + ) -> ::std::os::raw::c_int; +} +extern "C" { + #[doc = " @brief Set the default drive setting."] + #[doc = " @param config Line config object."] + #[doc = " @param drive New drive."] + pub fn gpiod_line_config_set_drive_default( + config: *mut gpiod_line_config, + drive: ::std::os::raw::c_int, + ); +} +extern "C" { + #[doc = " @brief Set the drive override for a line."] + #[doc = " @param config Line config object."] + #[doc = " @param drive New drive setting."] + #[doc = " @param offset The offset of the line for which to set the override."] + pub fn gpiod_line_config_set_drive_override( + config: *mut gpiod_line_config, + drive: ::std::os::raw::c_int, + offset: ::std::os::raw::c_uint, + ); +} +extern "C" { + #[doc = " @brief Clear the drive override for a line."] + #[doc = " @param config Line config object."] + #[doc = " @param offset The offset of the line for which to clear the override."] + #[doc = " @note Does nothing if no override is set for the line."] + pub fn gpiod_line_config_clear_drive_override( + config: *mut gpiod_line_config, + offset: ::std::os::raw::c_uint, + ); +} +extern "C" { + #[doc = " @brief Check if the drive setting is overridden for a line."] + #[doc = " @param config Line config object."] + #[doc = " @param offset The offset of the line to check for the override."] + #[doc = " @return True if drive is overridden for the line, false otherwise."] + pub fn gpiod_line_config_drive_is_overridden( + config: *mut gpiod_line_config, + offset: ::std::os::raw::c_uint, + ) -> bool; +} +extern "C" { + #[doc = " @brief Get the default drive setting."] + #[doc = " @param config Line config object."] + #[doc = " @return Drive setting for any non-overridden line."] + pub fn gpiod_line_config_get_drive_default( + config: *mut gpiod_line_config, + ) -> ::std::os::raw::c_int; +} +extern "C" { + #[doc = " @brief Get the drive setting for a line."] + #[doc = " @param config Line config object."] + #[doc = " @param offset The offset of the line for which to read the drive setting."] + #[doc = " @return Drive setting for the line if the config object were used in a"] + #[doc = "\t request."] + pub fn gpiod_line_config_get_drive_offset( + config: *mut gpiod_line_config, + offset: ::std::os::raw::c_uint, + ) -> ::std::os::raw::c_int; +} +extern "C" { + #[doc = " @brief Set the default active-low setting."] + #[doc = " @param config Line config object."] + #[doc = " @param active_low New active-low setting."] + pub fn gpiod_line_config_set_active_low_default( + config: *mut gpiod_line_config, + active_low: bool, + ); +} +extern "C" { + #[doc = " @brief Override the active-low setting for a line."] + #[doc = " @param config Line config object."] + #[doc = " @param active_low New active-low setting."] + #[doc = " @param offset The offset of the line for which to set the override."] + pub fn gpiod_line_config_set_active_low_override( + config: *mut gpiod_line_config, + active_low: bool, + offset: ::std::os::raw::c_uint, + ); +} +extern "C" { + #[doc = " @brief Clear the active-low override for a line."] + #[doc = " @param config Line config object."] + #[doc = " @param offset The offset of the line for which to clear the override."] + #[doc = " @note Does nothing if no override is set for the line."] + pub fn gpiod_line_config_clear_active_low_override( + config: *mut gpiod_line_config, + offset: ::std::os::raw::c_uint, + ); +} +extern "C" { + #[doc = " @brief Check if the active-low setting is overridden for a line."] + #[doc = " @param config Line config object."] + #[doc = " @param offset The offset of the line to check for the override."] + #[doc = " @return True if active-low is overridden for the line, false otherwise."] + pub fn gpiod_line_config_active_low_is_overridden( + config: *mut gpiod_line_config, + offset: ::std::os::raw::c_uint, + ) -> bool; +} +extern "C" { + #[doc = " @brief Check if active-low is the default setting."] + #[doc = " @param config Line config object."] + #[doc = " @return Active-low setting for any non-overridden line."] + pub fn gpiod_line_config_get_active_low_default(config: *mut gpiod_line_config) -> bool; +} +extern "C" { + #[doc = " @brief Check if a line is configured as active-low."] + #[doc = " @param config Line config object."] + #[doc = " @param offset The offset of the line for which to read the active-low setting."] + #[doc = " @return Active-low setting for the line if the config object were used in"] + #[doc = "\t a request."] + pub fn gpiod_line_config_get_active_low_offset( + config: *mut gpiod_line_config, + offset: ::std::os::raw::c_uint, + ) -> bool; +} +extern "C" { + #[doc = " @brief Set the default debounce period."] + #[doc = " @param config Line config object."] + #[doc = " @param period New debounce period in microseconds. Disables debouncing if 0."] + #[doc = " @note Debouncing is only useful on input lines with edge detection."] + #[doc = "\t Its purpose is to filter spurious events due to noise during the"] + #[doc = "\t edge transition. It has no effect on normal get or set operations."] + pub fn gpiod_line_config_set_debounce_period_us_default( + config: *mut gpiod_line_config, + period: ::std::os::raw::c_ulong, + ); +} +extern "C" { + #[doc = " @brief Override the debounce period setting for a line."] + #[doc = " @param config Line config object."] + #[doc = " @param period New debounce period in microseconds."] + #[doc = " @param offset The offset of the line for which to set the override."] + pub fn gpiod_line_config_set_debounce_period_us_override( + config: *mut gpiod_line_config, + period: ::std::os::raw::c_ulong, + offset: ::std::os::raw::c_uint, + ); +} +extern "C" { + #[doc = " @brief Clear the debounce period override for a line."] + #[doc = " @param config Line config object."] + #[doc = " @param offset The offset of the line for which to clear the override."] + #[doc = " @note Does nothing if no override is set for the line."] + pub fn gpiod_line_config_clear_debounce_period_us_override( + config: *mut gpiod_line_config, + offset: ::std::os::raw::c_uint, + ); +} +extern "C" { + #[doc = " @brief Check if the debounce period setting is overridden for a line."] + #[doc = " @param config Line config object."] + #[doc = " @param offset The offset of the line to check for the override."] + #[doc = " @return True if debounce period is overridden for the line, false"] + #[doc = "\t otherwise."] + pub fn gpiod_line_config_debounce_period_us_is_overridden( + config: *mut gpiod_line_config, + offset: ::std::os::raw::c_uint, + ) -> bool; +} +extern "C" { + #[doc = " @brief Get the default debounce period."] + #[doc = " @param config Line config object."] + #[doc = " @return Debounce period for any non-overridden line."] + #[doc = "\t Measured in microseconds."] + #[doc = "\t 0 if debouncing is disabled."] + pub fn gpiod_line_config_get_debounce_period_us_default( + config: *mut gpiod_line_config, + ) -> ::std::os::raw::c_ulong; +} +extern "C" { + #[doc = " @brief Get the debounce period for a line."] + #[doc = " @param config Line config object."] + #[doc = " @param offset The offset of the line for which to read the debounce period."] + #[doc = " @return Debounce period for the line if the config object were used in a"] + #[doc = "\t request."] + #[doc = "\t Measured in microseconds."] + #[doc = "\t 0 if debouncing is disabled."] + pub fn gpiod_line_config_get_debounce_period_us_offset( + config: *mut gpiod_line_config, + offset: ::std::os::raw::c_uint, + ) -> ::std::os::raw::c_ulong; +} +extern "C" { + #[doc = " @brief Set the default event timestamp clock."] + #[doc = " @param config Line config object."] + #[doc = " @param clock New clock to use."] + pub fn gpiod_line_config_set_event_clock_default( + config: *mut gpiod_line_config, + clock: ::std::os::raw::c_int, + ); +} +extern "C" { + #[doc = " @brief Override the event clock setting for a line."] + #[doc = " @param config Line config object."] + #[doc = " @param clock New event clock to use."] + #[doc = " @param offset The offset of the line for which to set the override."] + pub fn gpiod_line_config_set_event_clock_override( + config: *mut gpiod_line_config, + clock: ::std::os::raw::c_int, + offset: ::std::os::raw::c_uint, + ); +} +extern "C" { + #[doc = " @brief Clear the event clock override for a line."] + #[doc = " @param config Line config object."] + #[doc = " @param offset The offset of the line for which to clear the override."] + #[doc = " @note Does nothing if no override is set for the line."] + pub fn gpiod_line_config_clear_event_clock_override( + config: *mut gpiod_line_config, + offset: ::std::os::raw::c_uint, + ); +} +extern "C" { + #[doc = " @brief Check if the event clock setting is overridden for a line."] + #[doc = " @param config Line config object."] + #[doc = " @param offset The offset of the line to check for the override."] + #[doc = " @return True if event clock period is overridden for the line, false"] + #[doc = "\t otherwise."] + pub fn gpiod_line_config_event_clock_is_overridden( + config: *mut gpiod_line_config, + offset: ::std::os::raw::c_uint, + ) -> bool; +} +extern "C" { + #[doc = " @brief Get the default event clock setting."] + #[doc = " @param config Line config object."] + #[doc = " @return Event clock setting for any non-overridden line."] + pub fn gpiod_line_config_get_event_clock_default( + config: *mut gpiod_line_config, + ) -> ::std::os::raw::c_int; +} +extern "C" { + #[doc = " @brief Get the event clock setting for a line."] + #[doc = " @param config Line config object."] + #[doc = " @param offset The offset of the line for which to read the event clock setting."] + #[doc = " @return Event clock setting for the line if the config object were used in a"] + #[doc = "\t request."] + pub fn gpiod_line_config_get_event_clock_offset( + config: *mut gpiod_line_config, + offset: ::std::os::raw::c_uint, + ) -> ::std::os::raw::c_int; +} +extern "C" { + #[doc = " @brief Set the default output value."] + #[doc = " @param config Line config object."] + #[doc = " @param value New value."] + #[doc = ""] + #[doc = " The default output value applies to all non-overridden output lines."] + #[doc = " It does not apply to input lines or overridden lines."] + pub fn gpiod_line_config_set_output_value_default( + config: *mut gpiod_line_config, + value: ::std::os::raw::c_int, + ); +} +extern "C" { + #[doc = " @brief Override the output value for a line."] + #[doc = " @param config Line config object."] + #[doc = " @param offset The offset of the line for which to override the output value."] + #[doc = " @param value Output value to set."] + pub fn gpiod_line_config_set_output_value_override( + config: *mut gpiod_line_config, + value: ::std::os::raw::c_int, + offset: ::std::os::raw::c_uint, + ); +} +extern "C" { + #[doc = " @brief Override the output values for multiple lines."] + #[doc = " @param config Line config object."] + #[doc = " @param num_values Number of lines for which to override values."] + #[doc = " @param offsets Array of offsets identifying the lines for which to override"] + #[doc = "\t\t values, containing \p num_values entries."] + #[doc = " @param values Array of output values corresponding to the lines identified in"] + #[doc = "\t\t \p offsets, also containing \p num_values entries."] + pub fn gpiod_line_config_set_output_values( + config: *mut gpiod_line_config, + num_values: size_t, + offsets: *const ::std::os::raw::c_uint, + values: *const ::std::os::raw::c_int, + ); +} +extern "C" { + #[doc = " @brief Clear the output value override for a line."] + #[doc = " @param config Line config object."] + #[doc = " @param offset The offset of the line for which to clear the override."] + #[doc = " @note Does nothing if no override is set for the line."] + pub fn gpiod_line_config_clear_output_value_override( + config: *mut gpiod_line_config, + offset: ::std::os::raw::c_uint, + ); +} +extern "C" { + #[doc = " @brief Check if the output value is overridden for a line."] + #[doc = " @param config Line config object."] + #[doc = " @param offset The offset of the line to check for the override."] + #[doc = " @return True if output value is overridden for the line, false otherwise."] + pub fn gpiod_line_config_output_value_is_overridden( + config: *mut gpiod_line_config, + offset: ::std::os::raw::c_uint, + ) -> bool; +} +extern "C" { + #[doc = " @brief Get the default output value."] + #[doc = " @param config Line config object."] + #[doc = " @return Output value for any non-overridden line."] + pub fn gpiod_line_config_get_output_value_default( + config: *mut gpiod_line_config, + ) -> ::std::os::raw::c_int; +} +extern "C" { + #[doc = " @brief Get the configured output value for a line."] + #[doc = " @param config Line config object."] + #[doc = " @param offset Line offset for which to read the value."] + #[doc = " @return Output value for the line if the config object were used in a"] + #[doc = "\t request."] + pub fn gpiod_line_config_get_output_value_offset( + config: *mut gpiod_line_config, + offset: ::std::os::raw::c_uint, + ) -> ::std::os::raw::c_int; +} +pub const GPIOD_LINE_CONFIG_PROP_DIRECTION: ::std::os::raw::c_uint = 1; +pub const GPIOD_LINE_CONFIG_PROP_EDGE_DETECTION: ::std::os::raw::c_uint = 2; +pub const GPIOD_LINE_CONFIG_PROP_BIAS: ::std::os::raw::c_uint = 3; +pub const GPIOD_LINE_CONFIG_PROP_DRIVE: ::std::os::raw::c_uint = 4; +pub const GPIOD_LINE_CONFIG_PROP_ACTIVE_LOW: ::std::os::raw::c_uint = 5; +pub const GPIOD_LINE_CONFIG_PROP_DEBOUNCE_PERIOD_US: ::std::os::raw::c_uint = 6; +#[doc = " Debounce period."] +pub const GPIOD_LINE_CONFIG_PROP_EVENT_CLOCK: ::std::os::raw::c_uint = 7; +pub const GPIOD_LINE_CONFIG_PROP_OUTPUT_VALUE: ::std::os::raw::c_uint = 8; +#[doc = " @brief List of properties that can be stored in a line_config object."] +#[doc = ""] +#[doc = " Used when retrieving the overrides."] +pub type _bindgen_ty_8 = ::std::os::raw::c_uint; +extern "C" { + #[doc = " @brief Get the total number of overridden settings stored in the line config"] + #[doc = "\t object."] + #[doc = " @param config Line config object."] + #[doc = " @return Number of individual overridden settings."] + pub fn gpiod_line_config_get_num_overrides(config: *mut gpiod_line_config) -> size_t; +} +extern "C" { + #[doc = " @brief Get the list of overridden offsets and the corresponding types of"] + #[doc = "\t overridden settings."] + #[doc = " @param config Line config object."] + #[doc = " @param offsets Array to store the overidden offsets. Must be sized to hold"] + #[doc = "\t\t the number of unsigned integers returned by"] + #[doc = "\t\t ::gpiod_line_config_get_num_overrides."] + #[doc = " @param props Array to store the types of overridden settings. Must be sized"] + #[doc = "\t\tto hold the number of integers returned by"] + #[doc = "\t\t::gpiod_line_config_get_num_overrides."] + #[doc = ""] + #[doc = " The overridden (offset, prop) pairs are stored in the \p offsets and"] + #[doc = " \p props arrays, with the pairs having the same index."] + pub fn gpiod_line_config_get_overrides( + config: *mut gpiod_line_config, + offsets: *mut ::std::os::raw::c_uint, + props: *mut ::std::os::raw::c_int, + ); +} +extern "C" { + #[doc = " @brief Create a new request config object."] + #[doc = " @return New request config object or NULL on error."] + pub fn gpiod_request_config_new() -> *mut gpiod_request_config; +} +extern "C" { + #[doc = " @brief Free the request config object and release all associated resources."] + #[doc = " @param config Line config object."] + pub fn gpiod_request_config_free(config: *mut gpiod_request_config); +} +extern "C" { + #[doc = " @brief Set the consumer name for the request."] + #[doc = " @param config Request config object."] + #[doc = " @param consumer Consumer name."] + #[doc = " @note If the consumer string is too long, it will be truncated to the max"] + #[doc = " accepted length."] + pub fn gpiod_request_config_set_consumer( + config: *mut gpiod_request_config, + consumer: *const ::std::os::raw::c_char, + ); +} +extern "C" { + #[doc = " @brief Get the consumer name configured in the request config."] + #[doc = " @param config Request config object."] + #[doc = " @return Consumer name stored in the request config."] + pub fn gpiod_request_config_get_consumer( + config: *mut gpiod_request_config, + ) -> *const ::std::os::raw::c_char; +} +extern "C" { + #[doc = " @brief Set the offsets of the lines to be requested."] + #[doc = " @param config Request config object."] + #[doc = " @param num_offsets Number of offsets to set."] + #[doc = " @param offsets Array of offsets, containing \p num_offsets entries."] + #[doc = " @note If too many offsets were specified, the offsets above the limit"] + #[doc = " accepted by the kernel (64 lines) are silently dropped."] + pub fn gpiod_request_config_set_offsets( + config: *mut gpiod_request_config, + num_offsets: size_t, + offsets: *const ::std::os::raw::c_uint, + ); +} +extern "C" { + #[doc = " @brief Get the number of offsets configured in this request config."] + #[doc = " @param config Request config object."] + #[doc = " @return Number of line offsets in this request config."] + pub fn gpiod_request_config_get_num_offsets(config: *mut gpiod_request_config) -> size_t; +} +extern "C" { + #[doc = " @brief Get the offsets of lines in the request config."] + #[doc = " @param config Request config object."] + #[doc = " @param offsets Array to store offsets. Must be sized to hold the number of"] + #[doc = "\t\t lines returned by ::gpiod_request_config_get_num_offsets."] + pub fn gpiod_request_config_get_offsets( + config: *mut gpiod_request_config, + offsets: *mut ::std::os::raw::c_uint, + ); +} +extern "C" { + #[doc = " @brief Set the size of the kernel event buffer for the request."] + #[doc = " @param config Request config object."] + #[doc = " @param event_buffer_size New event buffer size."] + #[doc = " @note The kernel may adjust the value if it's too high. If set to 0, the"] + #[doc = " default value will be used."] + #[doc = " @note The kernel buffer is distinct from and independent of the user space"] + #[doc = "\t buffer (::gpiod_edge_event_buffer_new)."] + pub fn gpiod_request_config_set_event_buffer_size( + config: *mut gpiod_request_config, + event_buffer_size: size_t, + ); +} +extern "C" { + #[doc = " @brief Get the edge event buffer size for the request config."] + #[doc = " @param config Request config object."] + #[doc = " @return Edge event buffer size setting from the request config."] + pub fn gpiod_request_config_get_event_buffer_size(config: *mut gpiod_request_config) -> size_t; +} +extern "C" { + #[doc = " @brief Release the requested lines and free all associated resources."] + #[doc = " @param request Line request object to release."] + pub fn gpiod_line_request_release(request: *mut gpiod_line_request); +} +extern "C" { + #[doc = " @brief Get the number of lines in the request."] + #[doc = " @param request Line request object."] + #[doc = " @return Number of requested lines."] + pub fn gpiod_line_request_get_num_lines(request: *mut gpiod_line_request) -> size_t; +} +extern "C" { + #[doc = " @brief Get the offsets of the lines in the request."] + #[doc = " @param request Line request object."] + #[doc = " @param offsets Array to store offsets. Must be sized to hold the number of"] + #[doc = "\t\t lines returned by ::gpiod_line_request_get_num_lines."] + pub fn gpiod_line_request_get_offsets( + request: *mut gpiod_line_request, + offsets: *mut ::std::os::raw::c_uint, + ); +} +extern "C" { + #[doc = " @brief Get the value of a single requested line."] + #[doc = " @param request Line request object."] + #[doc = " @param offset The offset of the line of which the value should be read."] + #[doc = " @return Returns 1 or 0 on success and -1 on error."] + pub fn gpiod_line_request_get_value( + request: *mut gpiod_line_request, + offset: ::std::os::raw::c_uint, + ) -> ::std::os::raw::c_int; +} +extern "C" { + #[doc = " @brief Get the values of a subset of requested lines."] + #[doc = " @param request GPIO line request."] + #[doc = " @param num_values Number of lines for which to read values."] + #[doc = " @param offsets Array of offsets identifying the subset of requested lines"] + #[doc = "\t\t from which to read values."] + #[doc = " @param values Array in which the values will be stored. Must be sized"] + #[doc = "\t\t to hold \p num_values entries. Each value is associated with the"] + #[doc = "\t\t line identified by the corresponding entry in \p offsets."] + #[doc = " @return 0 on success, -1 on failure."] + pub fn gpiod_line_request_get_values_subset( + request: *mut gpiod_line_request, + num_values: size_t, + offsets: *const ::std::os::raw::c_uint, + values: *mut ::std::os::raw::c_int, + ) -> ::std::os::raw::c_int; +} +extern "C" { + #[doc = " @brief Get the values of all requested lines."] + #[doc = " @param request GPIO line request."] + #[doc = " @param values Array in which the values will be stored. Must be sized to"] + #[doc = "\t\t hold the number of lines returned by"] + #[doc = "\t\t ::gpiod_line_request_get_num_lines."] + #[doc = "\t\t Each value is associated with the line identified by the"] + #[doc = "\t\t corresponding entry in the offset array returned by"] + #[doc = "\t\t ::gpiod_line_request_get_offsets."] + #[doc = " @return 0 on success, -1 on failure."] + pub fn gpiod_line_request_get_values( + request: *mut gpiod_line_request, + values: *mut ::std::os::raw::c_int, + ) -> ::std::os::raw::c_int; +} +extern "C" { + #[doc = " @brief Set the value of a single requested line."] + #[doc = " @param request Line request object."] + #[doc = " @param offset The offset of the line for which the value should be set."] + #[doc = " @param value Value to set."] + pub fn gpiod_line_request_set_value( + request: *mut gpiod_line_request, + offset: ::std::os::raw::c_uint, + value: ::std::os::raw::c_int, + ) -> ::std::os::raw::c_int; +} +extern "C" { + #[doc = " @brief Set the values of a subset of requested lines."] + #[doc = " @param request GPIO line request."] + #[doc = " @param num_values Number of lines for which to set values."] + #[doc = " @param offsets Array of offsets, containing the number of entries specified"] + #[doc = "\t\t by \p num_values, identifying the requested lines for"] + #[doc = "\t\t which to set values."] + #[doc = " @param values Array of values to set, containing the number of entries"] + #[doc = "\t\t specified by \p num_values. Each value is associated with the"] + #[doc = "\t\t line identified by the corresponding entry in \p offsets."] + #[doc = " @return 0 on success, -1 on failure."] + pub fn gpiod_line_request_set_values_subset( + request: *mut gpiod_line_request, + num_values: size_t, + offsets: *const ::std::os::raw::c_uint, + values: *const ::std::os::raw::c_int, + ) -> ::std::os::raw::c_int; +} +extern "C" { + #[doc = " @brief Set the values of all lines associated with a request."] + #[doc = " @param request GPIO line request."] + #[doc = " @param values Array containing the values to set. Must be sized to"] + #[doc = "\t\t contain the number of lines returned by"] + #[doc = "\t\t ::gpiod_line_request_get_num_lines."] + #[doc = "\t\t Each value is associated with the line identified by the"] + #[doc = "\t\t corresponding entry in the offset array returned by"] + #[doc = "\t\t ::gpiod_line_request_get_offsets."] + pub fn gpiod_line_request_set_values( + request: *mut gpiod_line_request, + values: *const ::std::os::raw::c_int, + ) -> ::std::os::raw::c_int; +} +extern "C" { + #[doc = " @brief Update the configuration of lines associated with a line request."] + #[doc = " @param request GPIO line request."] + #[doc = " @param config New line config to apply."] + #[doc = " @return 0 on success, -1 on failure."] + #[doc = " @note The new line configuration completely replaces the old."] + #[doc = " @note Any requested lines without overrides are configured to the requested"] + #[doc = "\t defaults."] + #[doc = " @note Any configured overrides for lines that have not been requested"] + #[doc = "\t are silently ignored."] + pub fn gpiod_line_request_reconfigure_lines( + request: *mut gpiod_line_request, + config: *mut gpiod_line_config, + ) -> ::std::os::raw::c_int; +} +extern "C" { + #[doc = " @brief Get the file descriptor associated with a line request."] + #[doc = " @param request GPIO line request."] + #[doc = " @return The file descriptor associated with the request."] + #[doc = "\t This function never fails."] + #[doc = "\t The returned file descriptor must not be closed by the caller."] + #[doc = "\t Call ::gpiod_line_request_release to close the file."] + pub fn gpiod_line_request_get_fd(request: *mut gpiod_line_request) -> ::std::os::raw::c_int; +} +extern "C" { + #[doc = " @brief Wait for edge events on any of the requested lines."] + #[doc = " @param request GPIO line request."] + #[doc = " @param timeout_ns Wait time limit in nanoseconds. If set to 0, the function"] + #[doc = "\t\t returns immediatelly. If set to a negative number, the"] + #[doc = "\t\t function blocks indefinitely until an event becomes"] + #[doc = "\t\t available."] + #[doc = " @return 0 if wait timed out, -1 if an error occurred, 1 if an event is"] + #[doc = "\t pending."] + #[doc = "q"] + #[doc = " Lines must have edge detection set for edge events to be emitted."] + #[doc = " By default edge detection is disabled."] + pub fn gpiod_line_request_wait_edge_event( + request: *mut gpiod_line_request, + timeout_ns: i64, + ) -> ::std::os::raw::c_int; +} +extern "C" { + #[doc = " @brief Read a number of edge events from a line request."] + #[doc = " @param request GPIO line request."] + #[doc = " @param buffer Edge event buffer, sized to hold at least \p max_events."] + #[doc = " @param max_events Maximum number of events to read."] + #[doc = " @return On success returns the number of events read from the file"] + #[doc = "\t descriptor, on failure return -1."] + #[doc = " @note This function will block if no event was queued for the line request."] + #[doc = " @note Any exising events in the buffer are overwritten. This is not an"] + #[doc = " append operation."] + pub fn gpiod_line_request_read_edge_event( + request: *mut gpiod_line_request, + buffer: *mut gpiod_edge_event_buffer, + max_events: size_t, + ) -> ::std::os::raw::c_int; +} +pub const GPIOD_EDGE_EVENT_RISING_EDGE: ::std::os::raw::c_uint = 1; +pub const GPIOD_EDGE_EVENT_FALLING_EDGE: ::std::os::raw::c_uint = 2; +#[doc = " @brief Event types."] +pub type _bindgen_ty_9 = ::std::os::raw::c_uint; +extern "C" { + #[doc = " @brief Free the edge event object."] + #[doc = " @param event Edge event object to free."] + pub fn gpiod_edge_event_free(event: *mut gpiod_edge_event); +} +extern "C" { + #[doc = " @brief Copy the edge event object."] + #[doc = " @param event Edge event to copy."] + #[doc = " @return Copy of the edge event or NULL on error. The returned object must"] + #[doc = "\t be freed by the caller using ::gpiod_edge_event_free."] + pub fn gpiod_edge_event_copy(event: *mut gpiod_edge_event) -> *mut gpiod_edge_event; +} +extern "C" { + #[doc = " @brief Get the event type."] + #[doc = " @param event GPIO edge event."] + #[doc = " @return The event type (::GPIOD_EDGE_EVENT_RISING_EDGE or"] + #[doc = "\t ::GPIOD_EDGE_EVENT_FALLING_EDGE)."] + pub fn gpiod_edge_event_get_event_type(event: *mut gpiod_edge_event) -> ::std::os::raw::c_int; +} +extern "C" { + #[doc = " @brief Get the timestamp of the event."] + #[doc = " @param event GPIO edge event."] + #[doc = " @return Timestamp in nanoseconds."] + #[doc = " @note The source clock for the timestamp depends on the event_clock"] + #[doc = "\t setting for the line."] + pub fn gpiod_edge_event_get_timestamp_ns(event: *mut gpiod_edge_event) -> u64; +} +extern "C" { + #[doc = " @brief Get the offset of the line which triggered the event."] + #[doc = " @param event GPIO edge event."] + #[doc = " @return Line offset."] + pub fn gpiod_edge_event_get_line_offset(event: *mut gpiod_edge_event) + -> ::std::os::raw::c_uint; +} +extern "C" { + #[doc = " @brief Get the global sequence number of the event."] + #[doc = " @param event GPIO edge event."] + #[doc = " @return Sequence number of the event in the series of events for all lines"] + #[doc = "\t in the associated line request."] + pub fn gpiod_edge_event_get_global_seqno( + event: *mut gpiod_edge_event, + ) -> ::std::os::raw::c_ulong; +} +extern "C" { + #[doc = " @brief Get the event sequence number specific to the line."] + #[doc = " @param event GPIO edge event."] + #[doc = " @return Sequence number of the event in the series of events only for this"] + #[doc = "\t line within the lifetime of the associated line request."] + pub fn gpiod_edge_event_get_line_seqno(event: *mut gpiod_edge_event) + -> ::std::os::raw::c_ulong; +} +extern "C" { + #[doc = " @brief Create a new edge event buffer."] + #[doc = " @param capacity Number of events the buffer can store (min = 1, max = 1024)."] + #[doc = " @return New edge event buffer or NULL on error."] + #[doc = " @note If capacity equals 0, it will be set to a default value of 64. If"] + #[doc = "\t capacity is larger than 1024, it will be limited to 1024."] + #[doc = " @note The user space buffer is independent of the kernel buffer"] + #[doc = "\t (::gpiod_request_config_set_event_buffer_size). As the user space"] + #[doc = "\t buffer is filled from the kernel buffer, there is no benefit making"] + #[doc = "\t the user space buffer larger than the kernel buffer."] + #[doc = "\t The default kernel buffer size for each request is 16*num_lines."] + pub fn gpiod_edge_event_buffer_new(capacity: size_t) -> *mut gpiod_edge_event_buffer; +} +extern "C" { + #[doc = " @brief Get the capacity (the max number of events that can be stored) of"] + #[doc = "\t the event buffer."] + #[doc = " @param buffer Edge event buffer."] + #[doc = " @return The capacity of the buffer."] + pub fn gpiod_edge_event_buffer_get_capacity(buffer: *mut gpiod_edge_event_buffer) -> size_t; +} +extern "C" { + #[doc = " @brief Free the edge event buffer and release all associated resources."] + #[doc = " @param buffer Edge event buffer to free."] + pub fn gpiod_edge_event_buffer_free(buffer: *mut gpiod_edge_event_buffer); +} +extern "C" { + #[doc = " @brief Get an event stored in the buffer."] + #[doc = " @param buffer Edge event buffer."] + #[doc = " @param index Index of the event in the buffer."] + #[doc = " @return Pointer to an event stored in the buffer. The lifetime of the"] + #[doc = "\t event is tied to the buffer object. Users must not free the event"] + #[doc = "\t returned by this function."] + pub fn gpiod_edge_event_buffer_get_event( + buffer: *mut gpiod_edge_event_buffer, + index: ::std::os::raw::c_ulong, + ) -> *mut gpiod_edge_event; +} +extern "C" { + #[doc = " @brief Get the number of events a buffer has stored."] + #[doc = " @param buffer Edge event buffer."] + #[doc = " @return Number of events stored in the buffer."] + pub fn gpiod_edge_event_buffer_get_num_events(buffer: *mut gpiod_edge_event_buffer) -> size_t; +} +extern "C" { + #[doc = " @brief Check if the file pointed to by path is a GPIO chip character device."] + #[doc = " @param path Path to check."] + #[doc = " @return True if the file exists and is either a GPIO chip character device"] + #[doc = "\t or a symbolic link to one."] + pub fn gpiod_is_gpiochip_device(path: *const ::std::os::raw::c_char) -> bool; +} +extern "C" { + #[doc = " @brief Get the API version of the library as a human-readable string."] + #[doc = " @return Human-readable string containing the library version."] + pub fn gpiod_version_string() -> *const ::std::os::raw::c_char; +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct __locale_data { + pub _address: u8, +}
On Fri, Jul 08, 2022 at 05:04:55PM +0530, Viresh Kumar wrote:
This adds a copy of pre generated bindings and adds the suggested way of updating those in README.
Signed-off-by: Viresh Kumar viresh.kumar@linaro.org
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
diff --git a/bindings/rust/libgpiod-sys/README.md b/bindings/rust/libgpiod-sys/README.md new file mode 100644 index 000000000000..ea037d6d7803 --- /dev/null +++ b/bindings/rust/libgpiod-sys/README.md @@ -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
+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?
diff --git a/bindings/rust/libgpiod-sys/src/bindings.rs b/bindings/rust/libgpiod-sys/src/bindings.rs new file mode 100644 index 000000000000..930eb894f66f --- /dev/null +++ b/bindings/rust/libgpiod-sys/src/bindings.rs @@ -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.
On 27-07-22, 10:57, Kent Gibson wrote:
On Fri, Jul 08, 2022 at 05:04:55PM +0530, Viresh Kumar wrote:
+## Updating bindings +1. Clone the source from
+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/.
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.
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"] }
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.
diff --git a/bindings/rust/libgpiod-sys/src/bindings.rs b/bindings/rust/libgpiod-sys/src/bindings.rs new file mode 100644 index 000000000000..930eb894f66f --- /dev/null +++ b/bindings/rust/libgpiod-sys/src/bindings.rs @@ -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].
Having said that, I can confirm that
bindgen -o src/bindings.rs wrapper.h
with the appropriate bindgen, regenerates the pre-generated file.
On Wed, Jul 27, 2022 at 10:45:29AM +0530, Viresh Kumar wrote:
On 27-07-22, 10:57, Kent Gibson wrote:
On Fri, Jul 08, 2022 at 05:04:55PM +0530, Viresh Kumar wrote:
+## Updating bindings +1. Clone the source from
+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/.
But the instructions don't mention any of that, and the implication is that src/bindings.rs will be regenerated.
Cheers, Kent.
On 27-07-22, 13:31, Kent Gibson wrote:
On Wed, Jul 27, 2022 at 10:45:29AM +0530, Viresh Kumar wrote:
On 27-07-22, 10:57, Kent Gibson wrote:
On Fri, Jul 08, 2022 at 05:04:55PM +0530, Viresh Kumar wrote:
+## Updating bindings +1. Clone the source from
+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/.
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 ?
diff --git a/bindings/rust/libgpiod-sys/README.md b/bindings/rust/libgpiod-sys/README.md index ea037d6d7803..7d4583519e87 100644 --- a/bindings/rust/libgpiod-sys/README.md +++ b/bindings/rust/libgpiod-sys/README.md @@ -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`
On Wed, Jul 27, 2022 at 11:30:24AM +0530, Viresh Kumar wrote:
On 27-07-22, 13:31, Kent Gibson wrote:
On Wed, Jul 27, 2022 at 10:45:29AM +0530, Viresh Kumar wrote:
On 27-07-22, 10:57, Kent Gibson wrote:
On Fri, Jul 08, 2022 at 05:04:55PM +0530, Viresh Kumar wrote:
+## Updating bindings +1. Clone the source from
+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/.
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 ?
diff --git a/bindings/rust/libgpiod-sys/README.md b/bindings/rust/libgpiod-sys/README.md index ea037d6d7803..7d4583519e87 100644 --- a/bindings/rust/libgpiod-sys/README.md +++ b/bindings/rust/libgpiod-sys/README.md @@ -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.
Add support to generate gpiosim bindings.
Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- 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
diff --git a/bindings/rust/libgpiod-sys/Cargo.toml b/bindings/rust/libgpiod-sys/Cargo.toml index 77f82719d269..73b6761d16dd 100644 --- a/bindings/rust/libgpiod-sys/Cargo.toml +++ b/bindings/rust/libgpiod-sys/Cargo.toml @@ -9,6 +9,7 @@ edition = "2018"
[features] generate = [ "bindgen" ] +gpiosim = [ "generate", "bindgen" ]
[build-dependencies] bindgen = { version = "0.59.1", optional = true } diff --git a/bindings/rust/libgpiod-sys/build.rs b/bindings/rust/libgpiod-sys/build.rs index bbcd30f79d23..147daaf6b1da 100644 --- a/bindings/rust/libgpiod-sys/build.rs +++ b/bindings/rust/libgpiod-sys/build.rs @@ -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")] diff --git a/bindings/rust/libgpiod-sys/gpiosim_wrapper.h b/bindings/rust/libgpiod-sys/gpiosim_wrapper.h new file mode 100644 index 000000000000..47dc12a87917 --- /dev/null +++ b/bindings/rust/libgpiod-sys/gpiosim_wrapper.h @@ -0,0 +1 @@ +#include "../../../tests/gpiosim/gpiosim.h"
On Fri, Jul 08, 2022 at 05:04:56PM +0530, Viresh Kumar wrote:
Add support to generate gpiosim bindings.
Signed-off-by: Viresh Kumar viresh.kumar@linaro.org
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
diff --git a/bindings/rust/libgpiod-sys/Cargo.toml b/bindings/rust/libgpiod-sys/Cargo.toml index 77f82719d269..73b6761d16dd 100644 --- a/bindings/rust/libgpiod-sys/Cargo.toml +++ b/bindings/rust/libgpiod-sys/Cargo.toml @@ -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?).
[build-dependencies] bindgen = { version = "0.59.1", optional = true } diff --git a/bindings/rust/libgpiod-sys/build.rs b/bindings/rust/libgpiod-sys/build.rs index bbcd30f79d23..147daaf6b1da 100644 --- a/bindings/rust/libgpiod-sys/build.rs +++ b/bindings/rust/libgpiod-sys/build.rs @@ -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")] diff --git a/bindings/rust/libgpiod-sys/gpiosim_wrapper.h b/bindings/rust/libgpiod-sys/gpiosim_wrapper.h new file mode 100644 index 000000000000..47dc12a87917 --- /dev/null +++ b/bindings/rust/libgpiod-sys/gpiosim_wrapper.h @@ -0,0 +1 @@
+#include "../../../tests/gpiosim/gpiosim.h"
Why bother with this wrapper - just bindgen that header directly?
Cheers, Kent.
On 27-07-22, 10:57, Kent Gibson wrote:
On Fri, Jul 08, 2022 at 05:04:56PM +0530, Viresh Kumar wrote:
Add support to generate gpiosim bindings.
Signed-off-by: Viresh Kumar viresh.kumar@linaro.org
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
diff --git a/bindings/rust/libgpiod-sys/Cargo.toml b/bindings/rust/libgpiod-sys/Cargo.toml index 77f82719d269..73b6761d16dd 100644 --- a/bindings/rust/libgpiod-sys/Cargo.toml +++ b/bindings/rust/libgpiod-sys/Cargo.toml @@ -9,6 +9,7 @@ edition = "2018" [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.
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.
diff --git a/bindings/rust/libgpiod-sys/gpiosim_wrapper.h b/bindings/rust/libgpiod-sys/gpiosim_wrapper.h new file mode 100644 index 000000000000..47dc12a87917 --- /dev/null +++ b/bindings/rust/libgpiod-sys/gpiosim_wrapper.h @@ -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.
Add rust wrapper crate, around the libpiod-sys crate added earlier, to provide a convenient interface for the users.
Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- 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
diff --git a/bindings/rust/Cargo.toml b/bindings/rust/Cargo.toml new file mode 100644 index 000000000000..d5d81486fa2f --- /dev/null +++ b/bindings/rust/Cargo.toml @@ -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" diff --git a/bindings/rust/src/chip.rs b/bindings/rust/src/chip.rs new file mode 100644 index 000000000000..50b5d6102f96 --- /dev/null +++ b/bindings/rust/src/chip.rs @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org + +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. + fn drop(&mut self) { + unsafe { bindings::gpiod_chip_close(self.chip) } + } +} + +pub struct Chip { + ichip: Arc<ChipInternal>, + info: ChipInfo, +} + +impl Chip { + /// Find a chip by path. + pub fn open(path: &str) -> Result<Self> { + let ichip = Arc::new(ChipInternal::open(path)?); + 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() + } + + /// 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> { + // 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()) }; + + // 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()) }; + + 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<()> { + let ret = unsafe { + bindings::gpiod_chip_wait_info_event(self.ichip.chip(), timeout.as_nanos() as i64) + }; + + match ret { + -1 => Err(Error::OperationFailed( + "Gpio Chip info-event-wait", + IoError::last(), + )), + 0 => Err(Error::OperationTimedOut), + _ => Ok(()), + } + } + + /// 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> { + // 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) + } +} diff --git a/bindings/rust/src/chip_info.rs b/bindings/rust/src/chip_info.rs new file mode 100644 index 000000000000..950368b54c6f --- /dev/null +++ b/bindings/rust/src/chip_info.rs @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org + +use std::sync::Arc; +use std::{slice, str}; + +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, +} + +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(), + )); + } + + 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) }; + + // 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) } + } +} diff --git a/bindings/rust/src/edge_event.rs b/bindings/rust/src/edge_event.rs new file mode 100644 index 000000000000..89bda58709d2 --- /dev/null +++ b/bindings/rust/src/edge_event.rs @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org + +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 { + 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, + ) -> 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 { + ibuffer: None, + event, + }) + } else { + Ok(Self { + ibuffer: Some(ibuffer.clone()), + event, + }) + } + } + + /// 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) }; + } + } +} diff --git a/bindings/rust/src/event_buffer.rs b/bindings/rust/src/event_buffer.rs new file mode 100644 index 000000000000..bb568a80e09c --- /dev/null +++ b/bindings/rust/src/event_buffer.rs @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org + +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 +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> { + 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)?), + }) + } + + /// 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 } + } + + /// 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) + } + + /// Get the number of events the buffers stores. + pub fn get_num_events(&self) -> u32 { + unsafe { bindings::gpiod_edge_event_buffer_get_num_events(self.buffer()) as u32 } + } +} diff --git a/bindings/rust/src/info_event.rs b/bindings/rust/src/info_event.rs new file mode 100644 index 000000000000..ffba23f350be --- /dev/null +++ b/bindings/rust/src/info_event.rs @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org + +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) } + } +} diff --git a/bindings/rust/src/lib.rs b/bindings/rust/src/lib.rs new file mode 100644 index 000000000000..2f2ac515d353 --- /dev/null +++ b/bindings/rust/src/lib.rs @@ -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 viresh.kumar@linaro.org + +//! 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; +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; + +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::*; + +use std::os::raw::c_char; +use std::{slice, str}; + +use thiserror::Error as ThisError; +use vmm_sys_util::errno::Error as IoError; + +/// 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}")] + NameNotFound(&'static str), + #[error("Invalid String: {0:?}")] + InvalidString(str::Utf8Error), + #[error("Invalid {0} value: {1}")] + InvalidValue(&'static str, u32), + #[error("Operation {0} Failed: {1}")] + OperationFailed(&'static str, IoError), + #[error("Operation Timed-out")] + OperationTimedOut, +} + +/// 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, + /// 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, +} + +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 { + /// 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, +} + +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 { + 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 { + /// 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, + } + } +} + +/// 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, +} + +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)), + } + } +} + +#[derive(Copy, Clone)] +/// Edge event types. +pub enum LineEdgeEvent { + /// Rising edge event. + Rising, + /// Falling edge event. + Falling, +} + +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. +/// +/// 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) } +} + +/// 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) +} diff --git a/bindings/rust/src/line_config.rs b/bindings/rust/src/line_config.rs new file mode 100644 index 000000000000..0fc8d0b736a3 --- /dev/null +++ b/bindings/rust/src/line_config.rs @@ -0,0 +1,493 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org + +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 +/// 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). +/// +/// 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) } + } + + /// 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. + 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> { + 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) } + } + + /// 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) } + } +} diff --git a/bindings/rust/src/line_info.rs b/bindings/rust/src/line_info.rs new file mode 100644 index 000000000000..70b6bd6a84bb --- /dev/null +++ b/bindings/rust/src/line_info.rs @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org + +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 +/// 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, +} + +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(), + )); + } + + Ok(Self { + info, + ichip: if watch { Some(ichip) } else { None }, + free: watch, + }) + } + + /// 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; + } + } + + /// 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) } + } + + /// 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 { + 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, + }) + } +} + +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(); + unsafe { bindings::gpiod_line_info_free(self.info) } + } + } +} diff --git a/bindings/rust/src/line_request.rs b/bindings/rust/src/line_request.rs new file mode 100644 index 000000000000..bb338e72671d --- /dev/null +++ b/bindings/rust/src/line_request.rs @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org + +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) + } + } + + /// 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), + )); + } + + 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(()) + } + } + + /// 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), + )); + } + + 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) + }; + + match ret { + -1 => Err(Error::OperationFailed( + "Gpio LineRequest edge-event-wait", + IoError::last(), + )), + 0 => Err(Error::OperationTimedOut), + _ => Ok(()), + } + } + + /// 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> { + 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) } + } +} diff --git a/bindings/rust/src/request_config.rs b/bindings/rust/src/request_config.rs new file mode 100644 index 000000000000..3e667d28cc1f --- /dev/null +++ b/bindings/rust/src/request_config.rs @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org + +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 + } + + /// 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) + } + } + + /// 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 } + } +} + +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) } + } +}
On Fri, Jul 08, 2022 at 05:04:57PM +0530, Viresh Kumar wrote:
Add rust wrapper crate, around the libpiod-sys crate added earlier, to provide a convenient interface for the users.
Signed-off-by: Viresh Kumar viresh.kumar@linaro.org
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
diff --git a/bindings/rust/Cargo.toml b/bindings/rust/Cargo.toml new file mode 100644 index 000000000000..d5d81486fa2f --- /dev/null +++ b/bindings/rust/Cargo.toml @@ -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).
+# 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" diff --git a/bindings/rust/src/chip.rs b/bindings/rust/src/chip.rs new file mode 100644 index 000000000000..50b5d6102f96 --- /dev/null +++ b/bindings/rust/src/chip.rs @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org
+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.
- 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)
+impl Chip {
- /// Find a chip by path.
- pub fn open(path: &str) -> Result<Self> {
let ichip = Arc::new(ChipInternal::open(path)?);
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
- /// 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.
// 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.
// 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()) };
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)
let ret = unsafe {
bindings::gpiod_chip_wait_info_event(self.ichip.chip(), timeout.as_nanos() as i64)
};
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?
- /// 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.
// 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)
- }
+} diff --git a/bindings/rust/src/chip_info.rs b/bindings/rust/src/chip_info.rs new file mode 100644 index 000000000000..950368b54c6f --- /dev/null +++ b/bindings/rust/src/chip_info.rs @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org
+use std::sync::Arc; +use std::{slice, str};
+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
+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(),
));
}
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.
// 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) }
- }
+} diff --git a/bindings/rust/src/edge_event.rs b/bindings/rust/src/edge_event.rs new file mode 100644 index 000000000000..89bda58709d2 --- /dev/null +++ b/bindings/rust/src/edge_event.rs @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org
+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 {
- 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.
- ) -> 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 {
ibuffer: None,
event,
})
} else {
Ok(Self {
ibuffer: Some(ibuffer.clone()),
event,
})
}
- }
This is both a new() and a clone(). Split them out.
- /// 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) };
}
- }
+} diff --git a/bindings/rust/src/event_buffer.rs b/bindings/rust/src/event_buffer.rs new file mode 100644 index 000000000000..bb568a80e09c --- /dev/null +++ b/bindings/rust/src/event_buffer.rs @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org
+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 +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.
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)?),
})
- }
- /// 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().
- /// 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.
- /// 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.
- pub fn get_num_events(&self) -> u32 {
unsafe { bindings::gpiod_edge_event_buffer_get_num_events(self.buffer()) as u32 }
- }
+} diff --git a/bindings/rust/src/info_event.rs b/bindings/rust/src/info_event.rs new file mode 100644 index 000000000000..ffba23f350be --- /dev/null +++ b/bindings/rust/src/info_event.rs @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org
+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) }
- }
+} diff --git a/bindings/rust/src/lib.rs b/bindings/rust/src/lib.rs new file mode 100644 index 000000000000..2f2ac515d353 --- /dev/null +++ b/bindings/rust/src/lib.rs @@ -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 viresh.kumar@linaro.org
+//! 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; +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"?
+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::*;
+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.
+/// 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.
- 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?
- #[error("Invalid String: {0:?}")]
- InvalidString(str::Utf8Error),
Specifically the string is not UTF8, so "InvalidString" is a bit vague.
- #[error("Invalid {0} value: {1}")]
- InvalidValue(&'static str, u32),
And this is an InvalidEnumValue.
- #[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.
- #[error("Operation Timed-out")]
- OperationTimedOut,
No indication which operation?
Scratch that - this error should be removed. Refer to comments where used below.
+}
+/// 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,
- /// 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.
+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 {
- /// 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.
+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 {
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)
- /// 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,
}
- }
+}
+/// 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.
+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)),
}
- }
+}
+#[derive(Copy, Clone)] +/// Edge event types. +pub enum LineEdgeEvent {
- /// Rising edge event.
- Rising,
- /// Falling edge event.
- Falling,
+}
Similarly LineEdge or LineEdgeKind.
+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. +/// +/// 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.
+/// 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)
+} diff --git a/bindings/rust/src/line_config.rs b/bindings/rust/src/line_config.rs new file mode 100644 index 000000000000..0fc8d0b736a3 --- /dev/null +++ b/bindings/rust/src/line_config.rs @@ -0,0 +1,493 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org
+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 +/// 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.
+/// 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) }
- }
- /// 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
- 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> {
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.
- /// 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) }
- }
+} diff --git a/bindings/rust/src/line_info.rs b/bindings/rust/src/line_info.rs new file mode 100644 index 000000000000..70b6bd6a84bb --- /dev/null +++ b/bindings/rust/src/line_info.rs @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org
+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 +/// 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.
+}
+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(),
));
}
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.
- /// 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.
- /// 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) }
- }
- /// 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.
- 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,
})
- }
+}
+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???
unsafe { bindings::gpiod_line_info_free(self.info) }
}
- }
+} diff --git a/bindings/rust/src/line_request.rs b/bindings/rust/src/line_request.rs new file mode 100644 index 000000000000..bb338e72671d --- /dev/null +++ b/bindings/rust/src/line_request.rs @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org
+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)
}
- }
- /// 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().
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(())
}
- }
- /// 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.
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)
};
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.
- /// 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).
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) }
- }
+} diff --git a/bindings/rust/src/request_config.rs b/bindings/rust/src/request_config.rs new file mode 100644 index 000000000000..3e667d28cc1f --- /dev/null +++ b/bindings/rust/src/request_config.rs @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org
+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
- }
- /// 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).
- /// 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.
+}
+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) }
- }
+}
2.31.1.272.g89b43f80a514
Cheers, Kent.
On 27-07-22, 10:57, Kent Gibson wrote:
On Fri, Jul 08, 2022 at 05:04:57PM +0530, Viresh Kumar wrote:
- /// 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.
:)
// 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.
Add a null check to be sure.
But I am fine with this as well.
- /// 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() ?
let ret = unsafe {
bindings::gpiod_chip_wait_info_event(self.ichip.chip(), timeout.as_nanos() as i64)
};
diff --git a/bindings/rust/src/chip_info.rs b/bindings/rust/src/chip_info.rs +/// 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
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.
diff --git a/bindings/rust/src/lib.rs b/bindings/rust/src/lib.rs +/// 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.
And if it returns an IoError it must be an IoOperation? Else if the IoError is just an errno then call it that.
+}
diff --git a/bindings/rust/src/line_info.rs b/bindings/rust/src/line_info.rs +/// 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.
+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.
- /// 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.
I think there were some issues with my understanding of the whole watch thing. Is there any existing example of gpiowatch somewhere ?
+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.
+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.
- /// 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.
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> ?
Same applies to set_values_subset().
That would be fine here though.
- /// 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).
- /// 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.
On Wed, Jul 27, 2022 at 02:37:01PM +0530, Viresh Kumar wrote:
On 27-07-22, 10:57, Kent Gibson wrote:
On Fri, Jul 08, 2022 at 05:04:57PM +0530, Viresh Kumar wrote:
- /// 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.
:)
// 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.
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.
- /// 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() ?
let ret = unsafe {
bindings::gpiod_chip_wait_info_event(self.ichip.chip(), timeout.as_nanos() as i64)
};
diff --git a/bindings/rust/src/chip_info.rs b/bindings/rust/src/chip_info.rs +/// 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
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.
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).
diff --git a/bindings/rust/src/lib.rs b/bindings/rust/src/lib.rs +/// 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.
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).
And if it returns an IoError it must be an IoOperation? Else if the IoError is just an errno then call it that.
+}
diff --git a/bindings/rust/src/line_info.rs b/bindings/rust/src/line_info.rs +/// 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.
+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.
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.
- /// 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.
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/20220708120626.89844-5-warthog618@gmail.c...
+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.
+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.
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.
- /// 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.
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.
Same applies to set_values_subset().
That would be fine here though.
- /// 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).
- /// 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 ?
Not add verbatim. Maybe work them in. Probably should look at revising the C API comments and then just mirror that.
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.
On Wed, Jul 27, 2022 at 12:08 PM Kent Gibson warthog618@gmail.com wrote:
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
On Wed, Jul 27, 2022 at 01:06:28PM +0200, Miguel Ojeda wrote:
On Wed, Jul 27, 2022 at 12:08 PM Kent Gibson warthog618@gmail.com wrote:
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.
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.
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.
On Wed, Jul 27, 2022 at 2:40 PM Kent Gibson warthog618@gmail.com wrote:
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.
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.
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
On Wed, Jul 27, 2022 at 03:02:10PM +0200, Miguel Ojeda wrote:
On Wed, Jul 27, 2022 at 2:40 PM Kent Gibson warthog618@gmail.com wrote:
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.
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.
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.
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.
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
On 28-07-22, 11:11, Kent Gibson wrote:
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.
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 :)
On Wed, Jul 27, 2022 at 06:08:09PM +0800, Kent Gibson wrote:
- /// 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 ?
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.
On 27-07-22, 18:08, Kent Gibson wrote:
On Wed, Jul 27, 2022 at 02:37:01PM +0530, Viresh Kumar wrote:
On 27-07-22, 10:57, Kent Gibson wrote:
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::*;
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).
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.
On Mon, Aug 01, 2022 at 05:35:06PM +0530, Viresh Kumar wrote:
On 27-07-22, 18:08, Kent Gibson wrote:
On Wed, Jul 27, 2022 at 02:37:01PM +0530, Viresh Kumar wrote:
On 27-07-22, 10:57, Kent Gibson wrote:
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::*;
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).
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.
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.
On Mon, Aug 1, 2022 at 3:20 PM Kent Gibson warthog618@gmail.com wrote:
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)
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
On 27-07-22, 10:57, Kent Gibson wrote:
On Fri, Jul 08, 2022 at 05:04:57PM +0530, Viresh Kumar wrote:
- /// 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.
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()?
- pub fn get_num_events(&self) -> u32 {
unsafe { bindings::gpiod_edge_event_buffer_get_num_events(self.buffer()) as u32 }
- }
+}
On Thu, Jul 28, 2022 at 02:22:24PM +0530, Viresh Kumar wrote:
On 27-07-22, 10:57, Kent Gibson wrote:
On Fri, Jul 08, 2022 at 05:04:57PM +0530, Viresh Kumar wrote:
- /// 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.
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.
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.EdgeEventBu...
Add examples for the usage of the rust bindings, quite similar to the ones in cxx bindings.
Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- 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
diff --git a/bindings/rust/examples/gpiodetect.rs b/bindings/rust/examples/gpiodetect.rs new file mode 100644 index 000000000000..82307e4eecea --- /dev/null +++ b/bindings/rust/examples/gpiodetect.rs @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org +// +// 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 + ); + } + } +} diff --git a/bindings/rust/examples/gpiofind.rs b/bindings/rust/examples/gpiofind.rs new file mode 100644 index 000000000000..bbbd7a87ece8 --- /dev/null +++ b/bindings/rust/examples/gpiofind.rs @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org +// +// 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(); + 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) { + 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]); +} diff --git a/bindings/rust/examples/gpioget.rs b/bindings/rust/examples/gpioget.rs new file mode 100644 index 000000000000..c3bc35fcfdb6 --- /dev/null +++ b/bindings/rust/examples/gpioget.rs @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org +// +// 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); +} diff --git a/bindings/rust/examples/gpioinfo.rs b/bindings/rust/examples/gpioinfo.rs new file mode 100644 index 000000000000..bd30d9096ce8 --- /dev/null +++ b/bindings/rust/examples/gpioinfo.rs @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org +// +// 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); + } +} diff --git a/bindings/rust/examples/gpiomon.rs b/bindings/rust/examples/gpiomon.rs new file mode 100644 index 000000000000..872907b386f3 --- /dev/null +++ b/bindings/rust/examples/gpiomon.rs @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org +// +// 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(); + + 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, + 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() + ); + } + } +} diff --git a/bindings/rust/examples/gpioset.rs b/bindings/rust/examples/gpioset.rs new file mode 100644 index 000000000000..ef70e8edbaae --- /dev/null +++ b/bindings/rust/examples/gpioset.rs @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org +// +// 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); + 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(); +}
On Fri, Jul 08, 2022 at 05:04:58PM +0530, Viresh Kumar wrote:
Add examples for the usage of the rust bindings, quite similar to the ones in cxx bindings.
Signed-off-by: Viresh Kumar viresh.kumar@linaro.org
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
diff --git a/bindings/rust/examples/gpiodetect.rs b/bindings/rust/examples/gpiodetect.rs new file mode 100644 index 000000000000..82307e4eecea --- /dev/null +++ b/bindings/rust/examples/gpiodetect.rs @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org +// +// 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() {
use .flatten() to have the iterator unwrap the entry so it is actually an entry, not a Result.
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: AsRefstd::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.
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?
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.
diff --git a/bindings/rust/examples/gpiofind.rs b/bindings/rust/examples/gpiofind.rs new file mode 100644 index 000000000000..bbbd7a87ece8 --- /dev/null +++ b/bindings/rust/examples/gpiofind.rs @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org +// +// 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();
- 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?
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]);
+} diff --git a/bindings/rust/examples/gpioget.rs b/bindings/rust/examples/gpioget.rs new file mode 100644 index 000000000000..c3bc35fcfdb6 --- /dev/null +++ b/bindings/rust/examples/gpioget.rs @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org +// +// 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);
+} diff --git a/bindings/rust/examples/gpioinfo.rs b/bindings/rust/examples/gpioinfo.rs new file mode 100644 index 000000000000..bd30d9096ce8 --- /dev/null +++ b/bindings/rust/examples/gpioinfo.rs @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org +// +// 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);
- }
+} diff --git a/bindings/rust/examples/gpiomon.rs b/bindings/rust/examples/gpiomon.rs new file mode 100644 index 000000000000..872907b386f3 --- /dev/null +++ b/bindings/rust/examples/gpiomon.rs @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org +// +// 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();
- 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?
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()
);
}
- }
+} diff --git a/bindings/rust/examples/gpioset.rs b/bindings/rust/examples/gpioset.rs new file mode 100644 index 000000000000..ef70e8edbaae --- /dev/null +++ b/bindings/rust/examples/gpioset.rs @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org +// +// 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);
- 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?
+}
2.31.1.272.g89b43f80a514
And, as mentioned elsewhere, add a gpiowatch example.
Cheers, Kent.
On 27-07-22, 10:58, Kent Gibson wrote:
On Fri, Jul 08, 2022 at 05:04:58PM +0530, Viresh Kumar wrote:
diff --git a/bindings/rust/examples/gpioset.rs b/bindings/rust/examples/gpioset.rs
- 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 ?
And, as mentioned elsewhere, add a gpiowatch example.
Will do, sure. Any existing example of the same will help though.
On Wed, Jul 27, 2022 at 02:53:19PM +0530, Viresh Kumar wrote:
On 27-07-22, 10:58, Kent Gibson wrote:
On Fri, Jul 08, 2022 at 05:04:58PM +0530, Viresh Kumar wrote:
diff --git a/bindings/rust/examples/gpioset.rs b/bindings/rust/examples/gpioset.rs
- 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 ?
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?
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/20220708120626.89844-5-warthog618@gmail.c...
On 27-07-22, 17:59, Kent Gibson wrote:
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.
On Wed, Jul 27, 2022 at 03:36:33PM +0530, Viresh Kumar wrote:
On 27-07-22, 17:59, Kent Gibson wrote:
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.
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.
These are required for tests, which will be added by a later commit.
Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- 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(-)
diff --git a/bindings/rust/src/chip.rs b/bindings/rust/src/chip.rs index 50b5d6102f96..ecff4b003cd9 100644 --- a/bindings/rust/src/chip.rs +++ b/bindings/rust/src/chip.rs @@ -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> { diff --git a/bindings/rust/src/chip_info.rs b/bindings/rust/src/chip_info.rs index 950368b54c6f..7188f91a92a6 100644 --- a/bindings/rust/src/chip_info.rs +++ b/bindings/rust/src/chip_info.rs @@ -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, } diff --git a/bindings/rust/src/lib.rs b/bindings/rust/src/lib.rs index 2f2ac515d353..63b0b82281b7 100644 --- a/bindings/rust/src/lib.rs +++ b/bindings/rust/src/lib.rs @@ -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. diff --git a/bindings/rust/src/line_info.rs b/bindings/rust/src/line_info.rs index 70b6bd6a84bb..426bd16aa616 100644 --- a/bindings/rust/src/line_info.rs +++ b/bindings/rust/src/line_info.rs @@ -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>>,
On Fri, Jul 08, 2022 at 05:04:59PM +0530, Viresh Kumar wrote:
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
Signed-off-by: Viresh Kumar viresh.kumar@linaro.org
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(-)
diff --git a/bindings/rust/src/chip.rs b/bindings/rust/src/chip.rs index 50b5d6102f96..ecff4b003cd9 100644 --- a/bindings/rust/src/chip.rs +++ b/bindings/rust/src/chip.rs @@ -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> { diff --git a/bindings/rust/src/chip_info.rs b/bindings/rust/src/chip_info.rs index 950368b54c6f..7188f91a92a6 100644 --- a/bindings/rust/src/chip_info.rs +++ b/bindings/rust/src/chip_info.rs @@ -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, } diff --git a/bindings/rust/src/lib.rs b/bindings/rust/src/lib.rs index 2f2ac515d353..63b0b82281b7 100644 --- a/bindings/rust/src/lib.rs +++ b/bindings/rust/src/lib.rs @@ -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. diff --git a/bindings/rust/src/line_info.rs b/bindings/rust/src/line_info.rs index 70b6bd6a84bb..426bd16aa616 100644 --- a/bindings/rust/src/line_info.rs +++ b/bindings/rust/src/line_info.rs @@ -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>>, -- 2.31.1.272.g89b43f80a514
On 27-07-22, 10:58, Kent Gibson wrote:
On Fri, Jul 08, 2022 at 05:04:59PM +0530, Viresh Kumar wrote:
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.
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
Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- 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
diff --git a/bindings/rust/Cargo.toml b/bindings/rust/Cargo.toml index d5d81486fa2f..e2d6d5bb91b6 100644 --- a/bindings/rust/Cargo.toml +++ b/bindings/rust/Cargo.toml @@ -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"] } diff --git a/bindings/rust/src/line_request.rs b/bindings/rust/src/line_request.rs index bb338e72671d..c1dbbb397e73 100644 --- a/bindings/rust/src/line_request.rs +++ b/bindings/rust/src/line_request.rs @@ -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, } diff --git a/bindings/rust/tests/chip.rs b/bindings/rust/tests/chip.rs new file mode 100644 index 000000000000..4e64e9c7e291 --- /dev/null +++ b/bindings/rust/tests/chip.rs @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org + +mod common; + +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 { + use super::*; + + #[test] + fn nonexistent_file_failure() { + assert_eq!( + Chip::open("/dev/nonexistent").unwrap_err(), + ChipError::OperationFailed("Gpio Chip open", IoError::new(ENOENT)) + ); + } + + #[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() { + 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(); + } + } + + 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()); + assert_eq!(chip.get_path().unwrap(), sim.dev_path()); + assert_eq!(chip.get_num_lines(), NGPIO as u32); + chip.get_fd().unwrap(); + } + + #[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); + + // Failure + assert_eq!( + chip.find_line("nonexistent").unwrap_err(), + ChipError::OperationFailed("Gpio Chip find-line", IoError::new(ENOENT)) + ); + } + } +} diff --git a/bindings/rust/tests/common/config.rs b/bindings/rust/tests/common/config.rs new file mode 100644 index 000000000000..3abd9a8c4c8b --- /dev/null +++ b/bindings/rust/tests/common/config.rs @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org + +use std::sync::Arc; + +use crate::common::*; + +use libgpiod::{Bias, Chip, Direction, Edge, LineConfig, LineRequest, RequestConfig, Result}; + +//#[derive(Debug)] +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); + } + + if let Some(consumer) = consumer { + self.rconfig.set_consumer(consumer); + } + } + + pub(crate) fn rconfig(&self, offsets: Option<&[u32]>) { + self.rconfig_consumer(offsets, None); + } + + 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; + } +} diff --git a/bindings/rust/tests/common/mod.rs b/bindings/rust/tests/common/mod.rs new file mode 100644 index 000000000000..2dc37986396b --- /dev/null +++ b/bindings/rust/tests/common/mod.rs @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org + +#[allow(dead_code)] +mod sim; + +#[allow(unused_imports)] +pub(crate) use sim::*; + +#[allow(dead_code)] +mod config; + +#[allow(unused_imports)] +pub(crate) use config::*; diff --git a/bindings/rust/tests/common/sim.rs b/bindings/rust/tests/common/sim.rs new file mode 100644 index 000000000000..cd5ec66c3da5 --- /dev/null +++ b/bindings/rust/tests/common/sim.rs @@ -0,0 +1,306 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org + +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() + } +} diff --git a/bindings/rust/tests/edge_event.rs b/bindings/rust/tests/edge_event.rs new file mode 100644 index 000000000000..1b05b225aab7 --- /dev/null +++ b/bindings/rust/tests/edge_event.rs @@ -0,0 +1,389 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org + +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); + } + + #[test] + fn max_capacity() { + assert_eq!(EdgeEventBuffer::new(1024 * 2).unwrap().get_capacity(), 1024); + } + } + + mod failure { + use super::*; + + #[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(); + + // No events available + assert_eq!( + config + .request() + .wait_edge_event(Duration::from_millis(100)) + .unwrap_err(), + ChipError::OperationTimedOut + ); + } + + #[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(), + ChipError::OperationFailed("Gpio LineRequest request-lines", IoError::new(EINVAL)) + ); + } + } + + 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); + } + } +} diff --git a/bindings/rust/tests/info_event.rs b/bindings/rust/tests/info_event.rs new file mode 100644 index 000000000000..96d8385deadf --- /dev/null +++ b/bindings/rust/tests/info_event.rs @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org + +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); + + request.reconfigure_lines(&lconfig2).unwrap(); + + sleep(Duration::from_millis(10)); + }); + } + + 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); + } + } +} diff --git a/bindings/rust/tests/line_config.rs b/bindings/rust/tests/line_config.rs new file mode 100644 index 000000000000..82879324a7f0 --- /dev/null +++ b/bindings/rust/tests/line_config.rs @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org + +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(), + 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); + } + } + + 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); + } + } + } +} diff --git a/bindings/rust/tests/line_info.rs b/bindings/rust/tests/line_info.rs new file mode 100644 index 000000000000..f6f8f592cc85 --- /dev/null +++ b/bindings/rust/tests/line_info.rs @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org + +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"); + 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)); + } + } +} diff --git a/bindings/rust/tests/line_request.rs b/bindings/rust/tests/line_request.rs new file mode 100644 index 000000000000..361ee6318d2e --- /dev/null +++ b/bindings/rust/tests/line_request.rs @@ -0,0 +1,234 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org + +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]; + 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); + } + } +} diff --git a/bindings/rust/tests/request_config.rs b/bindings/rust/tests/request_config.rs new file mode 100644 index 000000000000..e914ca8ec887 --- /dev/null +++ b/bindings/rust/tests/request_config.rs @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org + +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); + } + } +}
On Fri, Jul 08, 2022 at 05:05:00PM +0530, Viresh Kumar wrote:
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.
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
Signed-off-by: Viresh Kumar viresh.kumar@linaro.org
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
diff --git a/bindings/rust/Cargo.toml b/bindings/rust/Cargo.toml index d5d81486fa2f..e2d6d5bb91b6 100644 --- a/bindings/rust/Cargo.toml +++ b/bindings/rust/Cargo.toml @@ -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"] } diff --git a/bindings/rust/src/line_request.rs b/bindings/rust/src/line_request.rs index bb338e72671d..c1dbbb397e73 100644 --- a/bindings/rust/src/line_request.rs +++ b/bindings/rust/src/line_request.rs @@ -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.
diff --git a/bindings/rust/tests/chip.rs b/bindings/rust/tests/chip.rs new file mode 100644 index 000000000000..4e64e9c7e291 --- /dev/null +++ b/bindings/rust/tests/chip.rs @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org
+mod common;
+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"?
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.
#[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() {
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.
}
- }
- 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());
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?
#[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);
// 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?
- }
+} diff --git a/bindings/rust/tests/common/config.rs b/bindings/rust/tests/common/config.rs new file mode 100644 index 000000000000..3abd9a8c4c8b --- /dev/null +++ b/bindings/rust/tests/common/config.rs @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org
+use std::sync::Arc;
+use crate::common::*;
+use libgpiod::{Bias, Chip, Direction, Edge, LineConfig, LineRequest, RequestConfig, Result};
+//#[derive(Debug)]
Uncomment or remove.
+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);
}
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.
- 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;
- }
+} diff --git a/bindings/rust/tests/common/mod.rs b/bindings/rust/tests/common/mod.rs new file mode 100644 index 000000000000..2dc37986396b --- /dev/null +++ b/bindings/rust/tests/common/mod.rs @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org
+#[allow(dead_code)] +mod sim;
+#[allow(unused_imports)] +pub(crate) use sim::*;
This allow seems unnecessary.
+#[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??
diff --git a/bindings/rust/tests/common/sim.rs b/bindings/rust/tests/common/sim.rs new file mode 100644 index 000000000000..cd5ec66c3da5 --- /dev/null +++ b/bindings/rust/tests/common/sim.rs @@ -0,0 +1,306 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org
+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()
- }
+} diff --git a/bindings/rust/tests/edge_event.rs b/bindings/rust/tests/edge_event.rs new file mode 100644 index 000000000000..1b05b225aab7 --- /dev/null +++ b/bindings/rust/tests/edge_event.rs @@ -0,0 +1,389 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org
+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);
}
#[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.
#[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();
// 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.
#[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(),
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?
- 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);
}
- }
+} diff --git a/bindings/rust/tests/info_event.rs b/bindings/rust/tests/info_event.rs new file mode 100644 index 000000000000..96d8385deadf --- /dev/null +++ b/bindings/rust/tests/info_event.rs @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org
+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);
request.reconfigure_lines(&lconfig2).unwrap();
sleep(Duration::from_millis(10));
});
- }
Benefit of the background thread?
- 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);
}
- }
+} diff --git a/bindings/rust/tests/line_config.rs b/bindings/rust/tests/line_config.rs new file mode 100644 index 000000000000..82879324a7f0 --- /dev/null +++ b/bindings/rust/tests/line_config.rs @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org
+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(),
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.
- 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);
}
}
- }
+} diff --git a/bindings/rust/tests/line_info.rs b/bindings/rust/tests/line_info.rs new file mode 100644 index 000000000000..f6f8f592cc85 --- /dev/null +++ b/bindings/rust/tests/line_info.rs @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org
+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");
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.
+} diff --git a/bindings/rust/tests/line_request.rs b/bindings/rust/tests/line_request.rs new file mode 100644 index 000000000000..361ee6318d2e --- /dev/null +++ b/bindings/rust/tests/line_request.rs @@ -0,0 +1,234 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org
+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];
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)
+} diff --git a/bindings/rust/tests/request_config.rs b/bindings/rust/tests/request_config.rs new file mode 100644 index 000000000000..e914ca8ec887 --- /dev/null +++ b/bindings/rust/tests/request_config.rs @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org
+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);
}
- }
+}
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_compar...
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_compar...
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_compar...
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_compar...
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_compar...
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_compar...
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_compar...
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_compar...
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_compar...
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_compar...
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_compar...
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_compar...
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_compar...
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_compar...
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_compar...
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_compar...
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_compar...
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_compar...
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_compar...
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_compar...
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_compar...
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_compar...
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_compar...
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_compar...
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_compar...
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_compar...
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_compar...
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.
On 27-07-22, 10:58, Kent Gibson wrote:
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.
diff --git a/bindings/rust/tests/chip.rs b/bindings/rust/tests/chip.rs
- 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());
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 :)
#[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);
// 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().
diff --git a/bindings/rust/tests/edge_event.rs b/bindings/rust/tests/edge_event.rs
#[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();
// 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.
Copied it from cxx again, I just tried to add similar tests in similar files.
TEST_CASE("edge_event wait timeout", "[edge-event]")
#[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(),
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]")
diff --git a/bindings/rust/tests/info_event.rs b/bindings/rust/tests/info_event.rs new file mode 100644 index 000000000000..96d8385deadf --- /dev/null +++ b/bindings/rust/tests/info_event.rs @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org
+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);
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.
- 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);
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 ?
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.
On Wed, Jul 27, 2022 at 03:29:55PM +0530, Viresh Kumar wrote:
On 27-07-22, 10:58, Kent Gibson wrote:
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.
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.
diff --git a/bindings/rust/tests/chip.rs b/bindings/rust/tests/chip.rs
- 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());
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 :(
I was trying to work out your naming scheme. Couldn't see one - sorry.
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.
#[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);
// 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 :)
Fair enough.
Sure I can name this find_line().
At least until you get around to renaming find_line to line_offset_from_name ;-).
diff --git a/bindings/rust/tests/edge_event.rs b/bindings/rust/tests/edge_event.rs
#[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();
// 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.
Copied it from cxx again, I just tried to add similar tests in similar files.
TEST_CASE("edge_event wait timeout", "[edge-event]")
#[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(),
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]")
diff --git a/bindings/rust/tests/info_event.rs b/bindings/rust/tests/info_event.rs new file mode 100644 index 000000000000..96d8385deadf --- /dev/null +++ b/bindings/rust/tests/info_event.rs @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org
+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);
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.
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.
- 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);
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 ?
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.
Turns out not. Surprised me too.
Cheers, Kent.
On 27-07-22, 10:58, Kent Gibson wrote:
diff --git a/bindings/rust/tests/line_request.rs b/bindings/rust/tests/line_request.rs
#[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();
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.
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().
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:
git@github.com:vireshk/libgpiod.git master
On Mon, Aug 01, 2022 at 05:24:02PM +0530, Viresh Kumar wrote:
On 27-07-22, 10:58, Kent Gibson wrote:
diff --git a/bindings/rust/tests/line_request.rs b/bindings/rust/tests/line_request.rs
#[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();
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.
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.
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().
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.
I have pushed my current changes here, if it helps:
git@github.com:vireshk/libgpiod.git master
-- viresh
On 01-08-22, 20:38, Kent Gibson wrote:
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.
On Tue, Aug 02, 2022 at 11:14:34AM +0530, Viresh Kumar wrote:
On 01-08-22, 20:38, Kent Gibson wrote:
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 ?
Right.
Cheers, Kent.
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.
-- viresh
Lets make build rust bindings as well.
Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- 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
diff --git a/README b/README index 814a0f161fd2..68b5d69f9b66 100644 --- a/README +++ b/README @@ -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 -------
diff --git a/TODO b/TODO index 8bb4d8f3ad56..cf4fd7b4a962 100644 --- a/TODO +++ b/TODO @@ -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
diff --git a/bindings/Makefile.am b/bindings/Makefile.am index 8f8c762f254f..004ae23dbc58 100644 --- a/bindings/Makefile.am +++ b/bindings/Makefile.am @@ -14,3 +14,9 @@ if WITH_BINDINGS_PYTHON SUBDIRS += python
endif + +if WITH_BINDINGS_RUST + +SUBDIRS += rust + +endif diff --git a/bindings/rust/Makefile.am b/bindings/rust/Makefile.am new file mode 100644 index 000000000000..79a52bc691ae --- /dev/null +++ b/bindings/rust/Makefile.am @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# SPDX-FileCopyrightText: 2022 Viresh Kumar viresh.kumar@linaro.org + +command = cargo build --release --lib + +if WITH_TESTS +command += --tests +endif + +if WITH_EXAMPLES +command += --examples +endif + +all: + $(command) + +clean: + cargo clean diff --git a/configure.ac b/configure.ac index ab03673589e9..8458f734a606 100644 --- a/configure.ac +++ b/configure.ac @@ -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
On Fri, Jul 08, 2022 at 05:05:01PM +0530, Viresh Kumar wrote:
Lets make build rust bindings as well.
Signed-off-by: Viresh Kumar viresh.kumar@linaro.org
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
diff --git a/README b/README index 814a0f161fd2..68b5d69f9b66 100644 --- a/README +++ b/README @@ -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
diff --git a/TODO b/TODO index 8bb4d8f3ad56..cf4fd7b4a962 100644 --- a/TODO +++ b/TODO @@ -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
diff --git a/bindings/Makefile.am b/bindings/Makefile.am index 8f8c762f254f..004ae23dbc58 100644 --- a/bindings/Makefile.am +++ b/bindings/Makefile.am @@ -14,3 +14,9 @@ if WITH_BINDINGS_PYTHON SUBDIRS += python endif
+if WITH_BINDINGS_RUST
+SUBDIRS += rust
+endif diff --git a/bindings/rust/Makefile.am b/bindings/rust/Makefile.am new file mode 100644 index 000000000000..79a52bc691ae --- /dev/null +++ b/bindings/rust/Makefile.am @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# SPDX-FileCopyrightText: 2022 Viresh Kumar viresh.kumar@linaro.org
+command = cargo build --release --lib
+if WITH_TESTS +command += --tests +endif
+if WITH_EXAMPLES +command += --examples +endif
+all:
- $(command)
+clean:
- cargo clean
diff --git a/configure.ac b/configure.ac index ab03673589e9..8458f734a606 100644 --- a/configure.ac +++ b/configure.ac @@ -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
man/Makefile])bindings/rust/Makefile
AC_OUTPUT -- 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
On 27-07-22, 10:59, Kent Gibson wrote:
Wouldn't build for me on a Debian bullseye VM. Apparently bindgen requires clang to find the system headers [1][2],
Right.
and there is no dep check or warning about that.
Ahh, I need to add that somewhere then.
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.
Anyway, for reference this was the build error without clang installed:
Did it work for you after clang was 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
On Wed, Jul 27, 2022 at 11:48:24AM +0530, Viresh Kumar wrote:
On 27-07-22, 10:59, Kent Gibson wrote:
Wouldn't build for me on a Debian bullseye VM. Apparently bindgen requires clang to find the system headers [1][2],
Right.
and there is no dep check or warning about that.
Ahh, I need to add that somewhere then.
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.
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?
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.
... 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
-- viresh
On 27-07-22, 14:25, Kent Gibson wrote:
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.
On Wed, Jul 27, 2022 at 12:05:27PM +0530, Viresh Kumar wrote:
On 27-07-22, 14:25, Kent Gibson wrote:
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.
On 27-07-22, 14:45, Kent Gibson wrote:
On Wed, Jul 27, 2022 at 12:05:27PM +0530, Viresh Kumar wrote:
On 27-07-22, 14:25, Kent Gibson wrote:
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.
On Fri, Jul 8, 2022 at 1:35 PM Viresh Kumar viresh.kumar@linaro.org wrote:
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
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
Hi Bartosz,
On Fri, Jul 15, 2022 at 9:08 PM Bartosz Golaszewski brgl@bgdev.pl wrote:
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
On Fri, Jul 15, 2022 at 9:17 PM Miguel Ojeda miguel.ojeda.sandonis@gmail.com wrote:
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.
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
On Fri, Jul 15, 2022 at 9:27 PM Miguel Ojeda miguel.ojeda.sandonis@gmail.com wrote:
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
On Sat, Jul 16, 2022 at 11:43 AM Miguel Ojeda miguel.ojeda.sandonis@gmail.com wrote:
On Fri, Jul 15, 2022 at 9:27 PM Miguel Ojeda miguel.ojeda.sandonis@gmail.com wrote:
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
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
On Sat, Jul 16, 2022 at 12:43:58PM +0200, Bartosz Golaszewski wrote:
On Sat, Jul 16, 2022 at 11:43 AM Miguel Ojeda miguel.ojeda.sandonis@gmail.com wrote:
On Fri, Jul 15, 2022 at 9:27 PM Miguel Ojeda miguel.ojeda.sandonis@gmail.com wrote:
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
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.
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.
On Sat, Jul 16, 2022 at 12:44 PM Bartosz Golaszewski brgl@bgdev.pl wrote:
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
stratos-dev@op-lists.linaro.org