Hello,
Here is another version of the rust bindings, based of the master branch.
Pushed here:
https://github.com/vireshk/libgpiod v10
V9->V10: - Removed auto-built bindings.rs files. - Updated license and copyright information for all files. - Renamed gpiosim crate as gpiosim-sys. - Cargo specific .gitnore changes moved to bindings/rust/.gitignore. - Fixed few SAFETY comments. - Added Reviewed-by tag from Kent. Since the changes were all minor, I haven't dropped them.
V8->V9: - Merged the last patch (supporting Events) with the other patches. - Events implementation is simplified and made efficient. nth() is also implemented for the iterator. - Unnecessary comment removed from Cargo.toml files. - Updated categories in libgpiod's Cargo.toml. - Updated gpio_events example to show cloned events live past another read_edge_events(). - Implement AsRawFd for Chip. - Other minor changes.
V7->V8: - Several updates to cargo.toml files, like license, version, etc. - Removed Sync support for chip and gpiosim. - Implemented, in a separate patch, iterator support for Events. - Fixed missing SAFETY comments. - Fixed build for 32 bit systems. - Use errno::Errno. - Removed Clone derive for many structures, that store raw pointers. - line setting helpers return the object back, so another helper can be called directly on them. Also made all helpers public and used the same in tests and example for single configurations. - Enums for gpiosim constants. - New examples to demonstrate parallelism and event handling. - Separated out HTE tests and marked as #[ignore] now. - Updated commit subjects. - Other minor changes.
V6->V7: - Don't let buffer read new events if the earlier events are still referenced. - BufferIntenal is gone now, to make the above work. - Update example and tests too for the same.
V5->V6: - Updates according to the new line-settings interface. - New file, line_settings.rs. - Renamed 'enum Setting' as 'SettingVal' to avoid conflicting names, as we also have 'struct Settings' now. - Support for HTE clock type. - Implement 'Eq' for public structure/enums (reported by build). - Remove 'SettingKindMap' and 'SettingMap' as they aren't required anymore. - Updated tests based on new interface.
V4->V5: - Arrange as workspace with crates for libgpiod-sys, libgpiod, gpiosim. - Use static libgpiod and libgpiosim libraries instead of rebuilding again. - Arrange in modules instead of flattened approach. - New enums like Setting and SettingKind and new types based on them SettingMap and SettingKindMap. - New property independent helpers for line_config, like set_prop_default(). - Improved tests/examples, new example for gpiowatch. - Add pre-built bindings for gpiosim too. - Many other changes.
V3->V4: - Rebased on top of new changes, and made changes accordingly. - Added rust integration tests with gpiosim. - Found a kernel bug with tests, sent a patch for that to LKML.
V2->V3: - Remove naming redundancy, users just need to do this now use libgpiod:{Chip, Direction, LineConfig} now (Bartosz); - Fix lifetime issues between event-buffer and edge-event modules, the event buffer is released after the last edge-event reference is dropped (Bartosz). - Allow edge-event to be copied, and freed later (Bartosz). - Add two separate rust crates, sys and wrapper (Gerard). - Null-terminate the strings passed to libgpiod (Wedson). - Drop unnecessary checks to validate string returned from chip:name/label/path. - Fix SAFETY comments (Wedson). - Drop unnecessary clone() instances (Bartosz).
V1->V2: - Added examples (I tested everything except gpiomon.rs, didn't have right hardware/mock device to test). - Build rust bindings as part of Make, update documentation.
Thanks.
-- Viresh
Viresh Kumar (6): bindings: rust: Add libgpiod-sys rust crate bindings: rust: Add libgpiod crate bindings: rust: Add gpiosim-sys crate bindings: rust: Add examples to libgpiod crate bindings: rust: Add tests for libgpiod crate bindings: rust: Integrate building of bindings with make
README | 8 +- TODO | 8 - bindings/Makefile.am | 6 + bindings/rust/.gitignore | 4 + bindings/rust/Cargo.toml | 11 + bindings/rust/Makefile.am | 19 + bindings/rust/gpiosim-sys/Cargo.toml | 23 + bindings/rust/gpiosim-sys/README.md | 14 + bindings/rust/gpiosim-sys/build.rs | 43 ++ bindings/rust/gpiosim-sys/src/lib.rs | 74 +++ bindings/rust/gpiosim-sys/src/sim.rs | 330 ++++++++++++ bindings/rust/libgpiod-sys/Cargo.toml | 21 + bindings/rust/libgpiod-sys/README.md | 14 + bindings/rust/libgpiod-sys/build.rs | 41 ++ bindings/rust/libgpiod-sys/src/lib.rs | 11 + bindings/rust/libgpiod/Cargo.toml | 25 + .../rust/libgpiod/examples/gpio_events.rs | 88 +++ .../examples/gpio_threaded_info_events.rs | 132 +++++ bindings/rust/libgpiod/examples/gpiodetect.rs | 30 ++ bindings/rust/libgpiod/examples/gpiofind.rs | 36 ++ bindings/rust/libgpiod/examples/gpioget.rs | 45 ++ bindings/rust/libgpiod/examples/gpioinfo.rs | 97 ++++ bindings/rust/libgpiod/examples/gpiomon.rs | 74 +++ bindings/rust/libgpiod/examples/gpioset.rs | 63 +++ bindings/rust/libgpiod/examples/gpiowatch.rs | 53 ++ bindings/rust/libgpiod/src/chip.rs | 309 +++++++++++ bindings/rust/libgpiod/src/edge_event.rs | 92 ++++ bindings/rust/libgpiod/src/event_buffer.rs | 168 ++++++ bindings/rust/libgpiod/src/info_event.rs | 68 +++ bindings/rust/libgpiod/src/lib.rs | 478 ++++++++++++++++ bindings/rust/libgpiod/src/line_config.rs | 134 +++++ bindings/rust/libgpiod/src/line_info.rs | 161 ++++++ bindings/rust/libgpiod/src/line_request.rs | 226 ++++++++ bindings/rust/libgpiod/src/line_settings.rs | 296 ++++++++++ bindings/rust/libgpiod/src/request_config.rs | 94 ++++ bindings/rust/libgpiod/tests/chip.rs | 97 ++++ bindings/rust/libgpiod/tests/common/config.rs | 142 +++++ bindings/rust/libgpiod/tests/common/mod.rs | 9 + bindings/rust/libgpiod/tests/edge_event.rs | 297 ++++++++++ bindings/rust/libgpiod/tests/info_event.rs | 166 ++++++ bindings/rust/libgpiod/tests/line_config.rs | 95 ++++ bindings/rust/libgpiod/tests/line_info.rs | 275 ++++++++++ bindings/rust/libgpiod/tests/line_request.rs | 509 ++++++++++++++++++ bindings/rust/libgpiod/tests/line_settings.rs | 203 +++++++ .../rust/libgpiod/tests/request_config.rs | 38 ++ configure.ac | 16 + 46 files changed, 5132 insertions(+), 11 deletions(-) create mode 100644 bindings/rust/.gitignore create mode 100644 bindings/rust/Cargo.toml create mode 100644 bindings/rust/Makefile.am create mode 100644 bindings/rust/gpiosim-sys/Cargo.toml create mode 100644 bindings/rust/gpiosim-sys/README.md create mode 100644 bindings/rust/gpiosim-sys/build.rs create mode 100644 bindings/rust/gpiosim-sys/src/lib.rs create mode 100644 bindings/rust/gpiosim-sys/src/sim.rs create mode 100644 bindings/rust/libgpiod-sys/Cargo.toml create mode 100644 bindings/rust/libgpiod-sys/README.md create mode 100644 bindings/rust/libgpiod-sys/build.rs create mode 100644 bindings/rust/libgpiod-sys/src/lib.rs create mode 100644 bindings/rust/libgpiod/Cargo.toml create mode 100644 bindings/rust/libgpiod/examples/gpio_events.rs create mode 100644 bindings/rust/libgpiod/examples/gpio_threaded_info_events.rs create mode 100644 bindings/rust/libgpiod/examples/gpiodetect.rs create mode 100644 bindings/rust/libgpiod/examples/gpiofind.rs create mode 100644 bindings/rust/libgpiod/examples/gpioget.rs create mode 100644 bindings/rust/libgpiod/examples/gpioinfo.rs create mode 100644 bindings/rust/libgpiod/examples/gpiomon.rs create mode 100644 bindings/rust/libgpiod/examples/gpioset.rs create mode 100644 bindings/rust/libgpiod/examples/gpiowatch.rs create mode 100644 bindings/rust/libgpiod/src/chip.rs create mode 100644 bindings/rust/libgpiod/src/edge_event.rs create mode 100644 bindings/rust/libgpiod/src/event_buffer.rs create mode 100644 bindings/rust/libgpiod/src/info_event.rs create mode 100644 bindings/rust/libgpiod/src/lib.rs create mode 100644 bindings/rust/libgpiod/src/line_config.rs create mode 100644 bindings/rust/libgpiod/src/line_info.rs create mode 100644 bindings/rust/libgpiod/src/line_request.rs create mode 100644 bindings/rust/libgpiod/src/line_settings.rs create mode 100644 bindings/rust/libgpiod/src/request_config.rs create mode 100644 bindings/rust/libgpiod/tests/chip.rs create mode 100644 bindings/rust/libgpiod/tests/common/config.rs create mode 100644 bindings/rust/libgpiod/tests/common/mod.rs create mode 100644 bindings/rust/libgpiod/tests/edge_event.rs create mode 100644 bindings/rust/libgpiod/tests/info_event.rs create mode 100644 bindings/rust/libgpiod/tests/line_config.rs create mode 100644 bindings/rust/libgpiod/tests/line_info.rs create mode 100644 bindings/rust/libgpiod/tests/line_request.rs create mode 100644 bindings/rust/libgpiod/tests/line_settings.rs create mode 100644 bindings/rust/libgpiod/tests/request_config.rs
This adds libgpiod-sys rust crate, which provides FFI (foreign function interface) bindings for libgpiod APIs.
Reviewed-by: Kent Gibson warthog618@gmail.com Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- bindings/rust/.gitignore | 4 +++ bindings/rust/Cargo.toml | 9 ++++++ bindings/rust/libgpiod-sys/Cargo.toml | 21 ++++++++++++++ bindings/rust/libgpiod-sys/README.md | 14 +++++++++ bindings/rust/libgpiod-sys/build.rs | 41 +++++++++++++++++++++++++++ bindings/rust/libgpiod-sys/src/lib.rs | 11 +++++++ 6 files changed, 100 insertions(+) create mode 100644 bindings/rust/.gitignore create mode 100644 bindings/rust/Cargo.toml create mode 100644 bindings/rust/libgpiod-sys/Cargo.toml create mode 100644 bindings/rust/libgpiod-sys/README.md create mode 100644 bindings/rust/libgpiod-sys/build.rs create mode 100644 bindings/rust/libgpiod-sys/src/lib.rs
diff --git a/bindings/rust/.gitignore b/bindings/rust/.gitignore new file mode 100644 index 000000000000..95054d9da312 --- /dev/null +++ b/bindings/rust/.gitignore @@ -0,0 +1,4 @@ +# Added by cargo + +target +Cargo.lock diff --git a/bindings/rust/Cargo.toml b/bindings/rust/Cargo.toml new file mode 100644 index 000000000000..e3e253a4cf5e --- /dev/null +++ b/bindings/rust/Cargo.toml @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: CC0-1.0 +# SPDX-FileCopyrightText: 2022 Linaro Ltd. +# SPDX-FileCopyrightTest: 2022 Viresh Kumar viresh.kumar@linaro.org + +[workspace] + +members = [ + "libgpiod-sys" +] diff --git a/bindings/rust/libgpiod-sys/Cargo.toml b/bindings/rust/libgpiod-sys/Cargo.toml new file mode 100644 index 000000000000..479184da3f7b --- /dev/null +++ b/bindings/rust/libgpiod-sys/Cargo.toml @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: CC0-1.0 +# SPDX-FileCopyrightText: 2022 Linaro Ltd. +# SPDX-FileCopyrightTest: 2022 Viresh Kumar viresh.kumar@linaro.org + +[package] +name = "libgpiod-sys" +version = "0.1.0" +authors = ["Viresh Kumar viresh.kumar@linaro.org"] +description = "libgpiod public header bindings" +repository = "https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git" +categories = ["external-ffi-bindings", "os::linux-apis"] +rust-version = "1.56" +keywords = ["libgpiod", "gpio"] +license = "Apache-2.0 OR BSD-3-Clause" +edition = "2021" + +[dependencies] + +[build-dependencies] +bindgen = "0.59.1" +cc = "1.0.46" diff --git a/bindings/rust/libgpiod-sys/README.md b/bindings/rust/libgpiod-sys/README.md new file mode 100644 index 000000000000..7e6e643ab2ab --- /dev/null +++ b/bindings/rust/libgpiod-sys/README.md @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: CC0-1.0 +# SPDX-FileCopyrightText: 2022 Linaro Ltd. +# SPDX-FileCopyrightTest: 2022 Viresh Kumar viresh.kumar@linaro.org + +# Generated libgpiod-sys Rust FFI bindings +Automatically generated Rust FFI bindings via + [bindgen](https://github.com/rust-lang/rust-bindgen). + +## License + +This project is licensed under either of + +- [Apache License](http://www.apache.org/licenses/LICENSE-2.0), Version 2.0 +- [BSD-3-Clause License](https://opensource.org/licenses/BSD-3-Clause) diff --git a/bindings/rust/libgpiod-sys/build.rs b/bindings/rust/libgpiod-sys/build.rs new file mode 100644 index 000000000000..274069eb9e9d --- /dev/null +++ b/bindings/rust/libgpiod-sys/build.rs @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar viresh.kumar@linaro.org + +extern crate bindgen; + +use std::env; +use std::path::PathBuf; + +fn generate_bindings() { + // Tell cargo to invalidate the built crate whenever following files change + println!("cargo:rerun-if-changed=../../../include/gpiod.h"); + + // The bindgen::Builder is the main entry point + // to bindgen, and lets you build up options for + // the resulting bindings. + let bindings = bindgen::Builder::default() + // The input header we would like to generate + // bindings for. + .header("../../../include/gpiod.h") + // Tell cargo to invalidate the built crate whenever any of the + // included header files changed. + .parse_callbacks(Box::new(bindgen::CargoCallbacks)) + // Finish the builder and generate the bindings. + .generate() + // Unwrap the Result and panic on failure. + .expect("Unable to generate bindings"); + + // Write the bindings to the $OUT_DIR/bindings.rs file. + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + bindings + .write_to_file(out_path.join("bindings.rs")) + .expect("Couldn't write bindings!"); +} + +fn main() { + generate_bindings(); + + println!("cargo:rustc-link-search=./../../lib/.libs/"); + println!("cargo:rustc-link-lib=static=gpiod"); +} diff --git a/bindings/rust/libgpiod-sys/src/lib.rs b/bindings/rust/libgpiod-sys/src/lib.rs new file mode 100644 index 000000000000..68526910c8ee --- /dev/null +++ b/bindings/rust/libgpiod-sys/src/lib.rs @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar viresh.kumar@linaro.org + +#[allow(non_camel_case_types, non_upper_case_globals)] +#[cfg_attr(test, allow(deref_nullptr, non_snake_case))] + +mod bindings_raw { + include!(concat!(env!("OUT_DIR"), "/bindings.rs")); +} +pub use bindings_raw::*;
Add rust wrapper crate, around the libpiod-sys crate added earlier, to provide a convenient interface for the users.
Reviewed-by: Kent Gibson warthog618@gmail.com Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- bindings/rust/Cargo.toml | 1 + bindings/rust/libgpiod/Cargo.toml | 22 + bindings/rust/libgpiod/src/chip.rs | 309 ++++++++++++ bindings/rust/libgpiod/src/edge_event.rs | 92 ++++ bindings/rust/libgpiod/src/event_buffer.rs | 168 +++++++ bindings/rust/libgpiod/src/info_event.rs | 68 +++ bindings/rust/libgpiod/src/lib.rs | 478 +++++++++++++++++++ bindings/rust/libgpiod/src/line_config.rs | 134 ++++++ bindings/rust/libgpiod/src/line_info.rs | 161 +++++++ bindings/rust/libgpiod/src/line_request.rs | 226 +++++++++ bindings/rust/libgpiod/src/line_settings.rs | 296 ++++++++++++ bindings/rust/libgpiod/src/request_config.rs | 94 ++++ 12 files changed, 2049 insertions(+) create mode 100644 bindings/rust/libgpiod/Cargo.toml create mode 100644 bindings/rust/libgpiod/src/chip.rs create mode 100644 bindings/rust/libgpiod/src/edge_event.rs create mode 100644 bindings/rust/libgpiod/src/event_buffer.rs create mode 100644 bindings/rust/libgpiod/src/info_event.rs create mode 100644 bindings/rust/libgpiod/src/lib.rs create mode 100644 bindings/rust/libgpiod/src/line_config.rs create mode 100644 bindings/rust/libgpiod/src/line_info.rs create mode 100644 bindings/rust/libgpiod/src/line_request.rs create mode 100644 bindings/rust/libgpiod/src/line_settings.rs create mode 100644 bindings/rust/libgpiod/src/request_config.rs
diff --git a/bindings/rust/Cargo.toml b/bindings/rust/Cargo.toml index e3e253a4cf5e..f4051387d581 100644 --- a/bindings/rust/Cargo.toml +++ b/bindings/rust/Cargo.toml @@ -5,5 +5,6 @@ [workspace]
members = [ + "libgpiod", "libgpiod-sys" ] diff --git a/bindings/rust/libgpiod/Cargo.toml b/bindings/rust/libgpiod/Cargo.toml new file mode 100644 index 000000000000..a38df1c0b10d --- /dev/null +++ b/bindings/rust/libgpiod/Cargo.toml @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: CC0-1.0 +# SPDX-FileCopyrightText: 2022 Linaro Ltd. +# SPDX-FileCopyrightTest: 2022 Viresh Kumar viresh.kumar@linaro.org + +[package] +name = "libgpiod" +version = "0.1.0" +authors = ["Viresh Kumar viresh.kumar@linaro.org"] +description = "libgpiod wrappers" +repository = "https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git" +categories = ["api-bindings", "hardware-support", "embedded", "os::linux-apis"] +rust-version = "1.56" +keywords = ["libgpiod", "gpio"] +license = "Apache-2.0 OR BSD-3-Clause" +edition = "2021" + +[dependencies] +errno = "0.2.8" +intmap = "2.0.0" +libc = "0.2.39" +libgpiod-sys = { path = "../libgpiod-sys" } +thiserror = "1.0" diff --git a/bindings/rust/libgpiod/src/chip.rs b/bindings/rust/libgpiod/src/chip.rs new file mode 100644 index 000000000000..91b4c947547b --- /dev/null +++ b/bindings/rust/libgpiod/src/chip.rs @@ -0,0 +1,309 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar viresh.kumar@linaro.org + +pub mod info { + /// GPIO chip info event related definitions. + pub use crate::info_event::*; +} + +use std::cmp::Ordering; +use std::ffi::{CStr, CString}; +use std::os::{raw::c_char, unix::prelude::AsRawFd}; +use std::path::Path; +use std::str; +use std::sync::Arc; +use std::time::Duration; + +use super::{ + gpiod, + line::{self, Offset}, + request, Error, OperationType, Result, +}; + +#[derive(Debug, Eq, PartialEq)] +struct Internal { + chip: *mut gpiod::gpiod_chip, +} + +impl Internal { + /// Find a chip by path. + fn open<P: AsRef<Path>>(path: &P) -> Result<Self> { + // Null-terminate the string + let path = path.as_ref().to_string_lossy() + "\0"; + + // SAFETY: The `gpiod_chip` returned by libgpiod is guaranteed to live as long + // as the `struct Internal`. + let chip = unsafe { gpiod::gpiod_chip_open(path.as_ptr() as *const c_char) }; + if chip.is_null() { + return Err(Error::OperationFailed( + OperationType::ChipOpen, + errno::errno(), + )); + } + + Ok(Self { chip }) + } +} + +impl Drop for Internal { + /// Close the chip and release all associated resources. + fn drop(&mut self) { + // SAFETY: `gpiod_chip` is guaranteed to be valid here. + unsafe { gpiod::gpiod_chip_close(self.chip) } + } +} + +/// 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. +#[derive(Debug, Eq, PartialEq)] +pub struct Chip { + ichip: Arc<Internal>, +} + +// SAFETY: Safe as `Internal` won't be freed until the `Chip` is dropped. +unsafe impl Send for Chip {} + +impl Chip { + /// Find a chip by path. + pub fn open<P: AsRef<Path>>(path: &P) -> Result<Self> { + let ichip = Arc::new(Internal::open(path)?); + + Ok(Self { ichip }) + } + + /// Get the chip name as represented in the kernel. + pub fn info(&self) -> Result<Info> { + Info::new(self.ichip.clone()) + } + + /// Get the path used to find the chip. + pub fn path(&self) -> Result<&str> { + // SAFETY: The string returned by libgpiod is guaranteed to live as long + // as the `struct Chip`. + let path = unsafe { gpiod::gpiod_chip_get_path(self.ichip.chip) }; + + // SAFETY: The string is guaranteed to be valid here by the C API. + unsafe { CStr::from_ptr(path) } + .to_str() + .map_err(Error::StringNotUtf8) + } + + /// Get a snapshot of information about the line. + pub fn line_info(&self, offset: Offset) -> Resultline::Info { + // SAFETY: The `gpiod_line_info` returned by libgpiod is guaranteed to live as long + // as the `struct Info`. + let info = unsafe { gpiod::gpiod_chip_get_line_info(self.ichip.chip, offset) }; + + if info.is_null() { + return Err(Error::OperationFailed( + OperationType::ChipGetLineInfo, + errno::errno(), + )); + } + + line::Info::new(info) + } + + /// Get the current snapshot of information about the line at given offset and start watching + /// it for future changes. + pub fn watch_line_info(&self, offset: Offset) -> Resultline::Info { + // SAFETY: `gpiod_line_info` is guaranteed to be valid here. + let info = unsafe { gpiod::gpiod_chip_watch_line_info(self.ichip.chip, offset) }; + + if info.is_null() { + return Err(Error::OperationFailed( + OperationType::ChipWatchLineInfo, + errno::errno(), + )); + } + + line::Info::new_watch(info) + } + + /// Stop watching a line + pub fn unwatch(&self, offset: Offset) { + // SAFETY: `gpiod_chip` is guaranteed to be valid here. + unsafe { + gpiod::gpiod_chip_unwatch_line_info(self.ichip.chip, offset); + } + } + + /// Wait for line status events on any of the watched lines on the chip. + pub fn wait_info_event(&self, timeout: Option<Duration>) -> Result<bool> { + let timeout = match timeout { + Some(x) => x.as_nanos() as i64, + // Block indefinitely + None => -1, + }; + + // SAFETY: `gpiod_chip` is guaranteed to be valid here. + let ret = unsafe { gpiod::gpiod_chip_wait_info_event(self.ichip.chip, timeout) }; + + match ret { + -1 => Err(Error::OperationFailed( + OperationType::ChipWaitInfoEvent, + errno::errno(), + )), + 0 => Ok(false), + _ => Ok(true), + } + } + + /// Read a single line status change event from the chip. If no events are + /// pending, this function will block. + pub fn read_info_event(&self) -> Resultinfo::Event { + // SAFETY: The `gpiod_info_event` returned by libgpiod is guaranteed to live as long + // as the `struct Event`. + let event = unsafe { gpiod::gpiod_chip_read_info_event(self.ichip.chip) }; + if event.is_null() { + return Err(Error::OperationFailed( + OperationType::ChipReadInfoEvent, + errno::errno(), + )); + } + + Ok(info::Event::new(event)) + } + + /// Map a GPIO line's name to its offset within the chip. + pub fn line_offset_from_name(&self, name: &str) -> Result<Offset> { + let name = CString::new(name).map_err(|_| Error::InvalidString)?; + + // SAFETY: `gpiod_chip` is guaranteed to be valid here. + let ret = unsafe { + gpiod::gpiod_chip_get_line_offset_from_name( + self.ichip.chip, + name.as_ptr() as *const c_char, + ) + }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::ChipGetLineOffsetFromName, + errno::errno(), + )) + } else { + Ok(ret as u32) + } + } + + /// Request a set of lines for exclusive usage. + pub fn request_lines( + &self, + rconfig: &request::Config, + lconfig: &line::Config, + ) -> Resultrequest::Request { + // SAFETY: The `gpiod_line_request` returned by libgpiod is guaranteed to live as long + // as the `struct Request`. + let request = unsafe { + gpiod::gpiod_chip_request_lines(self.ichip.chip, rconfig.config, lconfig.config) + }; + + if request.is_null() { + return Err(Error::OperationFailed( + OperationType::ChipRequestLines, + errno::errno(), + )); + } + + request::Request::new(request) + } +} + +impl AsRawFd for Chip { + /// 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. + fn as_raw_fd(&self) -> i32 { + // SAFETY: `gpiod_chip` is guaranteed to be valid here. + unsafe { gpiod::gpiod_chip_get_fd(self.ichip.chip) } + } +} + +/// GPIO chip Information +#[derive(Debug, Eq)] +pub struct Info { + info: *mut gpiod::gpiod_chip_info, +} + +impl Info { + /// Find a GPIO chip by path. + fn new(chip: Arc<Internal>) -> Result<Self> { + // SAFETY: `gpiod_chip` is guaranteed to be valid here. + let info = unsafe { gpiod::gpiod_chip_get_info(chip.chip) }; + if info.is_null() { + return Err(Error::OperationFailed( + OperationType::ChipGetInfo, + errno::errno(), + )); + } + + Ok(Self { info }) + } + + /// Get the GPIO chip name as represented in the kernel. + pub fn name(&self) -> Result<&str> { + // SAFETY: The string returned by libgpiod is guaranteed to live as long + // as the `struct Chip`. + let name = unsafe { gpiod::gpiod_chip_info_get_name(self.info) }; + + // SAFETY: The string is guaranteed to be valid here by the C API. + unsafe { CStr::from_ptr(name) } + .to_str() + .map_err(Error::StringNotUtf8) + } + + /// Get the GPIO chip label as represented in the kernel. + pub fn label(&self) -> Result<&str> { + // SAFETY: The string returned by libgpiod is guaranteed to live as long + // as the `struct Chip`. + let label = unsafe { gpiod::gpiod_chip_info_get_label(self.info) }; + + // SAFETY: The string is guaranteed to be valid here by the C API. + unsafe { CStr::from_ptr(label) } + .to_str() + .map_err(Error::StringNotUtf8) + } + + /// Get the number of GPIO lines exposed by the chip. + pub fn num_lines(&self) -> usize { + // SAFETY: `gpiod_chip` is guaranteed to be valid here. + unsafe { gpiod::gpiod_chip_info_get_num_lines(self.info) as usize } + } +} + +impl PartialEq for Info { + fn eq(&self, other: &Self) -> bool { + self.name().unwrap().eq(other.name().unwrap()) + } +} + +impl PartialOrd for Info { + fn partial_cmp(&self, other: &Self) -> Option<Ordering> { + let name = match self.name() { + Ok(name) => name, + _ => return None, + }; + + let other_name = match other.name() { + Ok(name) => name, + _ => return None, + }; + + name.partial_cmp(other_name) + } +} + +impl Drop for Info { + /// Close the GPIO chip info and release all associated resources. + fn drop(&mut self) { + // SAFETY: `gpiod_chip` is guaranteed to be valid here. + unsafe { gpiod::gpiod_chip_info_free(self.info) } + } +} diff --git a/bindings/rust/libgpiod/src/edge_event.rs b/bindings/rust/libgpiod/src/edge_event.rs new file mode 100644 index 000000000000..d8404952b0b0 --- /dev/null +++ b/bindings/rust/libgpiod/src/edge_event.rs @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar viresh.kumar@linaro.org + +use std::time::Duration; + +use super::{ + gpiod, + line::{EdgeKind, Offset}, + Error, OperationType, Result, +}; + +/// Line edge events handling +/// +/// An edge event object contains information about a single line edge event. +/// It contains the event type, timestamp and the offset of the line on which +/// the event occurred as well as two sequence numbers (global for all lines +/// in the associated request and local for this line only). +/// +/// Edge events are stored into an edge-event buffer object to improve +/// performance and to limit the number of memory allocations when a large +/// number of events are being read. + +#[derive(Debug, Eq, PartialEq)] +pub struct Event(*mut gpiod::gpiod_edge_event); + +impl Event { + pub fn event_clone(event: &Event) -> Result<Event> { + // SAFETY: `gpiod_edge_event` is guaranteed to be valid here. + let event = unsafe { gpiod::gpiod_edge_event_copy(event.0) }; + if event.is_null() { + return Err(Error::OperationFailed( + OperationType::EdgeEventCopy, + errno::errno(), + )); + } + + Ok(Self(event)) + } + + /// Get the event type. + pub fn event_type(&self) -> Result<EdgeKind> { + // SAFETY: `gpiod_edge_event` is guaranteed to be valid here. + EdgeKind::new(unsafe { gpiod::gpiod_edge_event_get_event_type(self.0) } as u32) + } + + /// Get the timestamp of the event. + pub fn timestamp(&self) -> Duration { + // SAFETY: `gpiod_edge_event` is guaranteed to be valid here. + Duration::from_nanos(unsafe { gpiod::gpiod_edge_event_get_timestamp_ns(self.0) }) + } + + /// Get the offset of the line on which the event was triggered. + pub fn line_offset(&self) -> Offset { + // SAFETY: `gpiod_edge_event` is guaranteed to be valid here. + unsafe { gpiod::gpiod_edge_event_get_line_offset(self.0) } + } + + /// Get the global sequence number of the event. + /// + /// Returns sequence number of the event relative to all lines in the + /// associated line request. + pub fn global_seqno(&self) -> usize { + // SAFETY: `gpiod_edge_event` is guaranteed to be valid here. + unsafe { + gpiod::gpiod_edge_event_get_global_seqno(self.0) + .try_into() + .unwrap() + } + } + + /// Get the event sequence number specific to concerned line. + /// + /// Returns sequence number of the event relative to the line within the + /// lifetime of the associated line request. + pub fn line_seqno(&self) -> usize { + // SAFETY: `gpiod_edge_event` is guaranteed to be valid here. + unsafe { + gpiod::gpiod_edge_event_get_line_seqno(self.0) + .try_into() + .unwrap() + } + } +} + +impl Drop for Event { + /// Free the edge event. + fn drop(&mut self) { + // SAFETY: `gpiod_edge_event` is guaranteed to be valid here. + unsafe { gpiod::gpiod_edge_event_free(self.0) }; + } +} diff --git a/bindings/rust/libgpiod/src/event_buffer.rs b/bindings/rust/libgpiod/src/event_buffer.rs new file mode 100644 index 000000000000..b56be9a27dc0 --- /dev/null +++ b/bindings/rust/libgpiod/src/event_buffer.rs @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar viresh.kumar@linaro.org + +use std::os::raw::c_ulong; +use std::ptr; + +use super::{ + gpiod, + request::{Event, Request}, + Error, OperationType, Result, +}; + +/// Line edge events +/// +/// An iterator over the elements of type `Event`. + +pub struct Events<'a> { + buffer: &'a mut Buffer, + read_index: usize, + len: usize, +} + +impl<'a> Events<'a> { + pub fn new(buffer: &'a mut Buffer, len: usize) -> Self { + Self { + buffer, + read_index: 0, + len, + } + } + + /// Get the number of contained events in the snapshot, this doesn't change + /// on reading events from the iterator. + pub fn len(&self) -> usize { + self.len + } + + /// Check if buffer is empty. + pub fn is_empty(&self) -> bool { + self.len == 0 + } +} + +impl<'a> Iterator for Events<'a> { + type Item = Result<&'a Event>; + + fn nth(&mut self, n: usize) -> OptionSelf::Item { + if self.read_index + n >= self.len { + return None; + } + + self.read_index += n + 1; + Some(self.buffer.event(self.read_index - 1)) + } + + fn next(&mut self) -> OptionSelf::Item { + self.nth(0) + } +} + +/// Line edge events buffer +#[derive(Debug, Eq, PartialEq)] +pub struct Buffer { + pub(crate) buffer: *mut gpiod::gpiod_edge_event_buffer, + events: Vec<*mut gpiod::gpiod_edge_event>, +} + +impl Buffer { + /// Create a new edge event buffer. + /// + /// If capacity equals 0, it will be set to a default value of 64. If + /// capacity is larger than 1024, it will be limited to 1024. + pub fn new(capacity: usize) -> Result<Self> { + // SAFETY: The `gpiod_edge_event_buffer` returned by libgpiod is guaranteed to live as long + // as the `struct Buffer`. + let buffer = unsafe { gpiod::gpiod_edge_event_buffer_new(capacity as c_ulong) }; + if buffer.is_null() { + return Err(Error::OperationFailed( + OperationType::EdgeEventBufferNew, + errno::errno(), + )); + } + + // SAFETY: `gpiod_edge_event_buffer` is guaranteed to be valid here. + let capacity = unsafe { gpiod::gpiod_edge_event_buffer_get_capacity(buffer) as usize }; + + Ok(Self { + buffer, + events: vec![ptr::null_mut(); capacity], + }) + } + + /// Get the capacity of the event buffer. + pub fn capacity(&self) -> usize { + self.events.len() + } + + /// Get edge events from a line request. + /// + /// This function will block if no event was queued for the line. + pub fn read_edge_events(&mut self, request: &Request) -> Result<Events> { + for i in 0..self.events.len() { + self.events[i] = ptr::null_mut(); + } + + // SAFETY: `gpiod_line_request` is guaranteed to be valid here. + let ret = unsafe { + gpiod::gpiod_line_request_read_edge_event( + request.request, + self.buffer, + self.events.len().try_into().unwrap(), + ) + }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::LineRequestReadEdgeEvent, + errno::errno(), + )) + } else { + let ret = ret as usize; + + if ret > self.events.len() { + Err(Error::TooManyEvents(ret, self.events.len())) + } else { + Ok(Events::new(self, ret)) + } + } + } + + /// Read an event stored in the buffer. + fn event<'a>(&mut self, index: usize) -> Result<&'a Event> { + if self.events[index].is_null() { + // SAFETY: The `gpiod_edge_event` returned by libgpiod is guaranteed to live as long + // as the `struct Event`. + let event = unsafe { + gpiod::gpiod_edge_event_buffer_get_event(self.buffer, index.try_into().unwrap()) + }; + + if event.is_null() { + return Err(Error::OperationFailed( + OperationType::EdgeEventBufferGetEvent, + errno::errno(), + )); + } + + self.events[index] = event; + } + + // SAFETY: Safe as the underlying events object won't get freed until the time the returned + // reference is still used. + Ok(unsafe { + // This will not lead to `drop(event)`. + (self.events.as_ptr().add(index) as *const Event) + .as_ref() + .unwrap() + }) + } +} + +impl Drop for Buffer { + /// Free the edge event buffer and release all associated resources. + fn drop(&mut self) { + // SAFETY: `gpiod_edge_event_buffer` is guaranteed to be valid here. + unsafe { gpiod::gpiod_edge_event_buffer_free(self.buffer) }; + } +} diff --git a/bindings/rust/libgpiod/src/info_event.rs b/bindings/rust/libgpiod/src/info_event.rs new file mode 100644 index 000000000000..8bd558532095 --- /dev/null +++ b/bindings/rust/libgpiod/src/info_event.rs @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar viresh.kumar@linaro.org + +use std::time::Duration; + +use super::{ + gpiod, + line::{self, InfoChangeKind}, + Error, OperationType, 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. + +#[derive(Debug, Eq, PartialEq)] +pub struct Event { + pub(crate) event: *mut gpiod::gpiod_info_event, +} + +impl Event { + /// Get a single chip's line's status change event. + pub(crate) fn new(event: *mut gpiod::gpiod_info_event) -> Self { + Self { event } + } + + /// Get the event type of the status change event. + pub fn event_type(&self) -> Result<InfoChangeKind> { + // SAFETY: `gpiod_info_event` is guaranteed to be valid here. + InfoChangeKind::new(unsafe { gpiod::gpiod_info_event_get_event_type(self.event) } as u32) + } + + /// Get the timestamp of the event, read from the monotonic clock. + pub fn timestamp(&self) -> Duration { + // SAFETY: `gpiod_info_event` is guaranteed to be valid here. + Duration::from_nanos(unsafe { gpiod::gpiod_info_event_get_timestamp_ns(self.event) }) + } + + /// Get the line-info object associated with the event. + pub fn line_info(&self) -> Resultline::Info { + // SAFETY: `gpiod_line_info` is guaranteed to be valid here. + let info = unsafe { gpiod::gpiod_info_event_get_line_info(self.event) }; + + if info.is_null() { + return Err(Error::OperationFailed( + OperationType::InfoEventGetLineInfo, + errno::errno(), + )); + } + + line::Info::new_from_event(info) + } +} + +impl Drop for Event { + /// Free the info event object and release all associated resources. + fn drop(&mut self) { + // SAFETY: `gpiod_info_event` is guaranteed to be valid here. + unsafe { gpiod::gpiod_info_event_free(self.event) } + } +} diff --git a/bindings/rust/libgpiod/src/lib.rs b/bindings/rust/libgpiod/src/lib.rs new file mode 100644 index 000000000000..161de164dddd --- /dev/null +++ b/bindings/rust/libgpiod/src/lib.rs @@ -0,0 +1,478 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar viresh.kumar@linaro.org +// +// Rust wrappers for GPIOD APIs + +//! 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. + +use std::ffi::CStr; +use std::fs; +use std::os::raw::c_char; +use std::path::Path; +use std::time::Duration; +use std::{fmt, str}; + +use intmap::IntMap; +use thiserror::Error as ThisError; + +use libgpiod_sys as gpiod; + +/// Operation types, used with OperationFailed() Error. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum OperationType { + ChipOpen, + ChipWaitInfoEvent, + ChipGetLine, + ChipGetLineInfo, + ChipGetLineOffsetFromName, + ChipGetInfo, + ChipReadInfoEvent, + ChipRequestLines, + ChipWatchLineInfo, + EdgeEventBufferGetEvent, + EdgeEventCopy, + EdgeEventBufferNew, + InfoEventGetLineInfo, + LineConfigNew, + LineConfigAddSettings, + LineConfigGetOffsets, + LineConfigGetSettings, + LineRequestReconfigLines, + LineRequestGetVal, + LineRequestGetValSubset, + LineRequestSetVal, + LineRequestSetValSubset, + LineRequestReadEdgeEvent, + LineRequestWaitEdgeEvent, + LineSettingsNew, + LineSettingsCopy, + LineSettingsGetOutVal, + LineSettingsSetDirection, + LineSettingsSetEdgeDetection, + LineSettingsSetBias, + LineSettingsSetDrive, + LineSettingsSetActiveLow, + LineSettingsSetDebouncePeriod, + LineSettingsSetEventClock, + LineSettingsSetOutputValue, + RequestConfigNew, + RequestConfigGetConsumer, + SimBankGetVal, + SimBankNew, + SimBankSetLabel, + SimBankSetNumLines, + SimBankSetLineName, + SimBankSetPull, + SimBankHogLine, + SimCtxNew, + SimDevNew, + SimDevEnable, + SimDevDisable, +} + +impl fmt::Display for OperationType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +/// Result of libgpiod operations. +pub type Result<T> = std::result::Result<T, Error>; + +/// Error codes for libgpiod operations. +#[derive(Copy, Clone, Debug, Eq, PartialEq, ThisError)] +pub enum Error { + #[error("Failed to get {0}")] + NullString(&'static str), + #[error("String not utf8: {0:?}")] + StringNotUtf8(str::Utf8Error), + #[error("Invalid String")] + InvalidString, + #[error("Invalid enum {0} value: {1}")] + InvalidEnumValue(&'static str, u32), + #[error("Operation {0} Failed: {1}")] + OperationFailed(OperationType, errno::Errno), + #[error("Invalid Arguments")] + InvalidArguments, + #[error("Event count more than buffer capacity: {0} > {1}")] + TooManyEvents(usize, usize), + #[error("Std Io Error")] + IoError, +} + +mod info_event; + +/// GPIO chip related definitions. +pub mod chip; + +mod edge_event; +mod event_buffer; +mod line_request; +mod request_config; + +/// GPIO chip request related definitions. +pub mod request { + pub use crate::edge_event::*; + pub use crate::event_buffer::*; + pub use crate::line_request::*; + pub use crate::request_config::*; +} + +mod line_config; +mod line_info; +mod line_settings; + +/// GPIO chip line related definitions. +pub mod line { + pub use crate::line_config::*; + pub use crate::line_info::*; + pub use crate::line_settings::*; + + use super::*; + + /// Value settings. + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + pub enum Value { + /// Active + Active, + /// Inactive + InActive, + } + + /// Maps offset to Value. + pub type ValueMap = IntMap<Value>; + + impl Value { + pub fn new(val: i32) -> Result<Self> { + Ok(match val { + 0 => Value::InActive, + 1 => Value::Active, + _ => return Err(Error::InvalidEnumValue("Value", val as u32)), + }) + } + + pub(crate) fn value(&self) -> i32 { + match self { + Value::Active => 1, + Value::InActive => 0, + } + } + } + + /// Offset type. + pub type Offset = u32; + + /// Direction settings. + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + pub enum Direction { + /// Request the line(s), but don't change direction. + AsIs, + /// Direction is input - for reading the value of an externally driven GPIO line. + Input, + /// Direction is output - for driving the GPIO line. + Output, + } + + impl Direction { + pub(crate) fn new(dir: u32) -> Result<Self> { + Ok(match dir { + gpiod::GPIOD_LINE_DIRECTION_AS_IS => Direction::AsIs, + gpiod::GPIOD_LINE_DIRECTION_INPUT => Direction::Input, + gpiod::GPIOD_LINE_DIRECTION_OUTPUT => Direction::Output, + _ => return Err(Error::InvalidEnumValue("Direction", dir)), + }) + } + + pub(crate) fn gpiod_direction(&self) -> u32 { + match self { + Direction::AsIs => gpiod::GPIOD_LINE_DIRECTION_AS_IS, + Direction::Input => gpiod::GPIOD_LINE_DIRECTION_INPUT, + Direction::Output => gpiod::GPIOD_LINE_DIRECTION_OUTPUT, + } + } + } + + /// Internal bias settings. + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + pub enum Bias { + /// The internal bias is disabled. + Disabled, + /// The internal pull-up bias is enabled. + PullUp, + /// The internal pull-down bias is enabled. + PullDown, + } + + impl Bias { + pub(crate) fn new(bias: u32) -> Result<Option<Self>> { + Ok(match bias { + gpiod::GPIOD_LINE_BIAS_UNKNOWN => None, + gpiod::GPIOD_LINE_BIAS_AS_IS => None, + gpiod::GPIOD_LINE_BIAS_DISABLED => Some(Bias::Disabled), + gpiod::GPIOD_LINE_BIAS_PULL_UP => Some(Bias::PullUp), + gpiod::GPIOD_LINE_BIAS_PULL_DOWN => Some(Bias::PullDown), + _ => return Err(Error::InvalidEnumValue("Bias", bias)), + }) + } + + pub(crate) fn gpiod_bias(bias: Option<Bias>) -> u32 { + match bias { + None => gpiod::GPIOD_LINE_BIAS_AS_IS, + Some(bias) => match bias { + Bias::Disabled => gpiod::GPIOD_LINE_BIAS_DISABLED, + Bias::PullUp => gpiod::GPIOD_LINE_BIAS_PULL_UP, + Bias::PullDown => gpiod::GPIOD_LINE_BIAS_PULL_DOWN, + }, + } + } + } + + /// Drive settings. + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + pub enum Drive { + /// Drive setting is push-pull. + PushPull, + /// Line output is open-drain. + OpenDrain, + /// Line output is open-source. + OpenSource, + } + + impl Drive { + pub(crate) fn new(drive: u32) -> Result<Self> { + Ok(match drive { + gpiod::GPIOD_LINE_DRIVE_PUSH_PULL => Drive::PushPull, + gpiod::GPIOD_LINE_DRIVE_OPEN_DRAIN => Drive::OpenDrain, + gpiod::GPIOD_LINE_DRIVE_OPEN_SOURCE => Drive::OpenSource, + _ => return Err(Error::InvalidEnumValue("Drive", drive)), + }) + } + + pub(crate) fn gpiod_drive(&self) -> u32 { + match self { + Drive::PushPull => gpiod::GPIOD_LINE_DRIVE_PUSH_PULL, + Drive::OpenDrain => gpiod::GPIOD_LINE_DRIVE_OPEN_DRAIN, + Drive::OpenSource => gpiod::GPIOD_LINE_DRIVE_OPEN_SOURCE, + } + } + } + + /// Edge detection settings. + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + pub enum Edge { + /// Line detects rising edge events. + Rising, + /// Line detects falling edge events. + Falling, + /// Line detects both rising and falling edge events. + Both, + } + + impl Edge { + pub(crate) fn new(edge: u32) -> Result<Option<Self>> { + Ok(match edge { + gpiod::GPIOD_LINE_EDGE_NONE => None, + gpiod::GPIOD_LINE_EDGE_RISING => Some(Edge::Rising), + gpiod::GPIOD_LINE_EDGE_FALLING => Some(Edge::Falling), + gpiod::GPIOD_LINE_EDGE_BOTH => Some(Edge::Both), + _ => return Err(Error::InvalidEnumValue("Edge", edge)), + }) + } + + pub(crate) fn gpiod_edge(edge: Option<Edge>) -> u32 { + match edge { + None => gpiod::GPIOD_LINE_EDGE_NONE, + Some(edge) => match edge { + Edge::Rising => gpiod::GPIOD_LINE_EDGE_RISING, + Edge::Falling => gpiod::GPIOD_LINE_EDGE_FALLING, + Edge::Both => gpiod::GPIOD_LINE_EDGE_BOTH, + }, + } + } + } + + /// Line setting kind. + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + pub enum SettingKind { + /// Line direction. + Direction, + /// Bias. + Bias, + /// Drive. + Drive, + /// Edge detection. + EdgeDetection, + /// Active-low setting. + ActiveLow, + /// Debounce period. + DebouncePeriod, + /// Event clock type. + EventClock, + /// Output value. + OutputValue, + } + + /// Line settings. + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + pub enum SettingVal { + /// Line direction. + Direction(Direction), + /// Bias. + Bias(Option<Bias>), + /// Drive. + Drive(Drive), + /// Edge detection. + EdgeDetection(Option<Edge>), + /// Active-low setting. + ActiveLow(bool), + /// Debounce period. + DebouncePeriod(Duration), + /// Event clock type. + EventClock(EventClock), + /// Output value. + OutputValue(Value), + } + + impl fmt::Display for SettingVal { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } + } + + /// Event clock settings. + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + pub enum EventClock { + /// Line uses the monotonic clock for edge event timestamps. + Monotonic, + /// Line uses the realtime clock for edge event timestamps. + Realtime, + /// Line uses the hardware timestamp engine clock for edge event timestamps. + HTE, + } + + impl EventClock { + pub(crate) fn new(clock: u32) -> Result<Self> { + Ok(match clock { + gpiod::GPIOD_LINE_EVENT_CLOCK_MONOTONIC => EventClock::Monotonic, + gpiod::GPIOD_LINE_EVENT_CLOCK_REALTIME => EventClock::Realtime, + gpiod::GPIOD_LINE_EVENT_CLOCK_HTE => EventClock::HTE, + _ => return Err(Error::InvalidEnumValue("Eventclock", clock)), + }) + } + + pub(crate) fn gpiod_clock(&self) -> u32 { + match self { + EventClock::Monotonic => gpiod::GPIOD_LINE_EVENT_CLOCK_MONOTONIC, + EventClock::Realtime => gpiod::GPIOD_LINE_EVENT_CLOCK_REALTIME, + EventClock::HTE => gpiod::GPIOD_LINE_EVENT_CLOCK_HTE, + } + } + } + + /// Line status change event types. + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + pub enum InfoChangeKind { + /// Line has been requested. + LineRequested, + /// Previously requested line has been released. + LineReleased, + /// Line configuration has changed. + LineConfigChanged, + } + + impl InfoChangeKind { + pub(crate) fn new(kind: u32) -> Result<Self> { + Ok(match kind { + gpiod::GPIOD_INFO_EVENT_LINE_REQUESTED => InfoChangeKind::LineRequested, + gpiod::GPIOD_INFO_EVENT_LINE_RELEASED => InfoChangeKind::LineReleased, + gpiod::GPIOD_INFO_EVENT_LINE_CONFIG_CHANGED => InfoChangeKind::LineConfigChanged, + _ => return Err(Error::InvalidEnumValue("InfoChangeKind", kind)), + }) + } + } + + /// Edge event types. + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + pub enum EdgeKind { + /// Rising edge event. + Rising, + /// Falling edge event. + Falling, + } + + impl EdgeKind { + pub(crate) fn new(kind: u32) -> Result<Self> { + Ok(match kind { + gpiod::GPIOD_EDGE_EVENT_RISING_EDGE => EdgeKind::Rising, + gpiod::GPIOD_EDGE_EVENT_FALLING_EDGE => EdgeKind::Falling, + _ => return Err(Error::InvalidEnumValue("EdgeEvent", kind)), + }) + } + } +} + +/// Various libgpiod-related functions. + +/// Check if the file pointed to by path is a GPIO chip character device. +/// +/// Returns true if the file exists and is a GPIO chip character device or a +/// symbolic link to it. +pub fn is_gpiochip_device<P: AsRef<Path>>(path: &P) -> bool { + // Null-terminate the string + let path = path.as_ref().to_string_lossy() + "\0"; + + // SAFETY: libgpiod won't access the path reference once the call returns. + unsafe { gpiod::gpiod_is_gpiochip_device(path.as_ptr() as *const c_char) } +} + +/// GPIO devices. +/// +/// Returns a vector of unique available GPIO Chips. +/// +/// The chips are sorted in ascending order of the chip names. +pub fn gpiochip_devices<P: AsRef<Path>>(path: &P) -> Result<Vecchip::Chip> { + let mut devices = Vec::new(); + + for entry in fs::read_dir(path).map_err(|_| Error::IoError)?.flatten() { + let path = entry.path(); + + if is_gpiochip_device(&path) { + let chip = chip::Chip::open(&path)?; + let info = chip.info()?; + + devices.push((chip, info)); + } + } + + devices.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap()); + devices.dedup_by(|a, b| a.1.eq(&b.1)); + + Ok(devices.into_iter().map(|a| a.0).collect()) +} + +/// Get the API version of the libgpiod library as a human-readable string. +pub fn libgpiod_version() -> Result<&'static str> { + // SAFETY: The string returned by libgpiod is guaranteed to live forever. + let version = unsafe { gpiod::gpiod_version_string() }; + + if version.is_null() { + return Err(Error::NullString("GPIO library version")); + } + + // SAFETY: The string is guaranteed to be valid here by the C API. + unsafe { CStr::from_ptr(version) } + .to_str() + .map_err(Error::StringNotUtf8) +} + +/// Get the API version of the libgpiod crate as a human-readable string. +pub fn crate_version() -> &'static str { + env!("CARGO_PKG_VERSION") +} diff --git a/bindings/rust/libgpiod/src/line_config.rs b/bindings/rust/libgpiod/src/line_config.rs new file mode 100644 index 000000000000..19dc187e6cbd --- /dev/null +++ b/bindings/rust/libgpiod/src/line_config.rs @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar viresh.kumar@linaro.org + +use std::os::raw::{c_ulong, c_void}; +use std::slice; + +use super::{ + gpiod, + line::{Offset, Settings}, + Error, OperationType, 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 empty. Using it in a request will lead to an +/// error. In order for a line-config to become useful, it needs to be assigned +/// at least one offset-to-settings mapping by calling +/// ::gpiod_line_config_add_line_settings. +/// +/// When calling ::gpiod_chip_request_lines, the library will request all +/// offsets that were assigned settings in the order that they were assigned. + +#[derive(Debug, Eq, PartialEq)] +pub struct Config { + pub(crate) config: *mut gpiod::gpiod_line_config, +} + +impl Config { + /// Create a new line config object. + pub fn new() -> Result<Self> { + // SAFETY: The `gpiod_line_config` returned by libgpiod is guaranteed to live as long + // as the `struct Config`. + let config = unsafe { gpiod::gpiod_line_config_new() }; + + if config.is_null() { + return Err(Error::OperationFailed( + OperationType::LineConfigNew, + errno::errno(), + )); + } + + Ok(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) { + // SAFETY: `gpiod_line_config` is guaranteed to be valid here. + unsafe { gpiod::gpiod_line_config_reset(self.config) } + } + + /// Add line settings for a set of offsets. + pub fn add_line_settings(&self, offsets: &[Offset], settings: Settings) -> Result<()> { + // SAFETY: `gpiod_line_config` is guaranteed to be valid here. + let ret = unsafe { + gpiod::gpiod_line_config_add_line_settings( + self.config, + offsets.as_ptr(), + offsets.len() as c_ulong, + settings.settings, + ) + }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::LineConfigAddSettings, + errno::errno(), + )) + } else { + Ok(()) + } + } + + /// Get line settings for offset. + pub fn line_settings(&self, offset: Offset) -> Result<Settings> { + // SAFETY: `gpiod_line_config` is guaranteed to be valid here. + let settings = unsafe { gpiod::gpiod_line_config_get_line_settings(self.config, offset) }; + + if settings.is_null() { + return Err(Error::OperationFailed( + OperationType::LineConfigGetSettings, + errno::errno(), + )); + } + + Ok(Settings::new_with_settings(settings)) + } + + /// Get configured offsets. + pub fn offsets(&self) -> Result<Vec<Offset>> { + let mut num: u64 = 0; + let mut ptr: *mut Offset = std::ptr::null_mut(); + + // SAFETY: The `ptr` array returned by libgpiod is guaranteed to live as long + // as it is not explicitly freed with `free()`. + let ret = unsafe { + gpiod::gpiod_line_config_get_offsets( + self.config, + &mut num as *mut _ as *mut _, + &mut ptr, + ) + }; + + if ret == -1 { + return Err(Error::OperationFailed( + OperationType::LineConfigGetOffsets, + errno::errno(), + )); + } + + // SAFETY: The `ptr` array returned by libgpiod is guaranteed to live as long + // as it is not explicitly freed with `free()`. + let offsets = unsafe { slice::from_raw_parts(ptr as *const Offset, num as usize).to_vec() }; + + // SAFETY: The `ptr` array is guaranteed to be valid here. + unsafe { libc::free(ptr as *mut c_void) }; + + Ok(offsets) + } +} + +impl Drop for Config { + /// Free the line config object and release all associated resources. + fn drop(&mut self) { + // SAFETY: `gpiod_line_config` is guaranteed to be valid here. + unsafe { gpiod::gpiod_line_config_free(self.config) } + } +} diff --git a/bindings/rust/libgpiod/src/line_info.rs b/bindings/rust/libgpiod/src/line_info.rs new file mode 100644 index 000000000000..1784cde27e2f --- /dev/null +++ b/bindings/rust/libgpiod/src/line_info.rs @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar viresh.kumar@linaro.org + +use std::ffi::CStr; +use std::str; +use std::time::Duration; + +use super::{ + gpiod, + line::{Bias, Direction, Drive, Edge, EventClock, Offset}, + Error, 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. + +#[derive(Debug, Eq, PartialEq)] +pub struct Info { + info: *mut gpiod::gpiod_line_info, + contained: bool, +} + +impl Info { + fn new_internal(info: *mut gpiod::gpiod_line_info, contained: bool) -> Result<Self> { + Ok(Self { info, contained }) + } + + /// Get a snapshot of information about the line. + pub(crate) fn new(info: *mut gpiod::gpiod_line_info) -> Result<Self> { + Info::new_internal(info, false) + } + + /// Get a snapshot of information about the line and start watching it for changes. + pub(crate) fn new_watch(info: *mut gpiod::gpiod_line_info) -> Result<Self> { + Info::new_internal(info, false) + } + + /// Get the Line info object associated with an event. + pub(crate) fn new_from_event(info: *mut gpiod::gpiod_line_info) -> Result<Self> { + Info::new_internal(info, true) + } + + /// Get the offset of the line within the GPIO chip. + /// + /// The offset uniquely identifies the line on the chip. The combination of the chip and offset + /// uniquely identifies the line within the system. + + pub fn offset(&self) -> Offset { + // SAFETY: `gpiod_line_info` is guaranteed to be valid here. + unsafe { gpiod::gpiod_line_info_get_offset(self.info) } + } + + /// Get GPIO line's name. + pub fn name(&self) -> Result<&str> { + // SAFETY: The string returned by libgpiod is guaranteed to live as long + // as the `struct Info`. + let name = unsafe { gpiod::gpiod_line_info_get_name(self.info) }; + if name.is_null() { + return Err(Error::NullString("GPIO line's name")); + } + + // SAFETY: The string is guaranteed to be valid here by the C API. + unsafe { CStr::from_ptr(name) } + .to_str() + .map_err(Error::StringNotUtf8) + } + + /// Returns True if the line is in use, false otherwise. + /// + /// The user space can't know exactly why a line is busy. It may have been + /// requested by another process or hogged by the kernel. It only matters that + /// the line is used and we can't request it. + pub fn is_used(&self) -> bool { + // SAFETY: `gpiod_line_info` is guaranteed to be valid here. + unsafe { gpiod::gpiod_line_info_is_used(self.info) } + } + + /// Get the GPIO line's consumer name. + pub fn consumer(&self) -> Result<&str> { + // SAFETY: The string returned by libgpiod is guaranteed to live as long + // as the `struct Info`. + let name = unsafe { gpiod::gpiod_line_info_get_consumer(self.info) }; + if name.is_null() { + return Err(Error::NullString("GPIO line's consumer name")); + } + + // SAFETY: The string is guaranteed to be valid here by the C API. + unsafe { CStr::from_ptr(name) } + .to_str() + .map_err(Error::StringNotUtf8) + } + + /// Get the GPIO line's direction. + pub fn direction(&self) -> Result<Direction> { + // SAFETY: `gpiod_line_info` is guaranteed to be valid here. + Direction::new(unsafe { gpiod::gpiod_line_info_get_direction(self.info) } as u32) + } + + /// Returns true if the line is "active-low", false otherwise. + pub fn is_active_low(&self) -> bool { + // SAFETY: `gpiod_line_info` is guaranteed to be valid here. + unsafe { gpiod::gpiod_line_info_is_active_low(self.info) } + } + + /// Get the GPIO line's bias setting. + pub fn bias(&self) -> Result<Option<Bias>> { + // SAFETY: `gpiod_line_info` is guaranteed to be valid here. + Bias::new(unsafe { gpiod::gpiod_line_info_get_bias(self.info) } as u32) + } + + /// Get the GPIO line's drive setting. + pub fn drive(&self) -> Result<Drive> { + // SAFETY: `gpiod_line_info` is guaranteed to be valid here. + Drive::new(unsafe { gpiod::gpiod_line_info_get_drive(self.info) } as u32) + } + + /// Get the current edge detection setting of the line. + pub fn edge_detection(&self) -> Result<Option<Edge>> { + // SAFETY: `gpiod_line_info` is guaranteed to be valid here. + Edge::new(unsafe { gpiod::gpiod_line_info_get_edge_detection(self.info) } as u32) + } + + /// Get the current event clock setting used for edge event timestamps. + pub fn event_clock(&self) -> Result<EventClock> { + // SAFETY: `gpiod_line_info` is guaranteed to be valid here. + EventClock::new(unsafe { gpiod::gpiod_line_info_get_event_clock(self.info) } as u32) + } + + /// Returns true if the line is debounced (either by hardware or by the + /// kernel software debouncer), false otherwise. + pub fn is_debounced(&self) -> bool { + // SAFETY: `gpiod_line_info` is guaranteed to be valid here. + unsafe { gpiod::gpiod_line_info_is_debounced(self.info) } + } + + /// Get the debounce period of the line. + pub fn debounce_period(&self) -> Duration { + // SAFETY: `gpiod_line_info` is guaranteed to be valid here. + Duration::from_micros(unsafe { + gpiod::gpiod_line_info_get_debounce_period_us(self.info) as u64 + }) + } +} + +impl Drop for Info { + fn drop(&mut self) { + // We must not free the Line info object created from `struct chip::Event` by calling + // libgpiod API. + if !self.contained { + // SAFETY: `gpiod_line_info` is guaranteed to be valid here. + unsafe { gpiod::gpiod_line_info_free(self.info) } + } + } +} diff --git a/bindings/rust/libgpiod/src/line_request.rs b/bindings/rust/libgpiod/src/line_request.rs new file mode 100644 index 000000000000..3215ab816434 --- /dev/null +++ b/bindings/rust/libgpiod/src/line_request.rs @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar viresh.kumar@linaro.org + +use std::os::{raw::c_ulong, unix::prelude::AsRawFd}; +use std::time::Duration; + +use super::{ + gpiod, + line::{self, Offset, Value, ValueMap}, + request, Error, OperationType, Result, +}; + +/// Line request operations +/// +/// Allows interaction with a set of requested lines. +#[derive(Debug, Eq, PartialEq)] +pub struct Request { + pub(crate) request: *mut gpiod::gpiod_line_request, +} + +impl Request { + /// Request a set of lines for exclusive usage. + pub(crate) fn new(request: *mut gpiod::gpiod_line_request) -> Result<Self> { + Ok(Self { request }) + } + + /// Get the number of lines in the request. + pub fn num_lines(&self) -> usize { + // SAFETY: `gpiod_line_request` is guaranteed to be valid here. + unsafe { gpiod::gpiod_line_request_get_num_lines(self.request) as usize } + } + + /// Get the offsets of lines in the request. + pub fn offsets(&self) -> Vec<Offset> { + let mut offsets = vec![0; self.num_lines() as usize]; + + // SAFETY: `gpiod_line_request` is guaranteed to be valid here. + unsafe { gpiod::gpiod_line_request_get_offsets(self.request, offsets.as_mut_ptr()) }; + offsets + } + + /// Get the value (0 or 1) of a single line associated with the request. + pub fn value(&self, offset: Offset) -> Result<Value> { + // SAFETY: `gpiod_line_request` is guaranteed to be valid here. + let value = unsafe { gpiod::gpiod_line_request_get_value(self.request, offset) }; + + if value != 0 && value != 1 { + Err(Error::OperationFailed( + OperationType::LineRequestGetVal, + errno::errno(), + )) + } else { + Value::new(value) + } + } + + /// Get values of a subset of lines associated with the request. + pub fn values_subset(&self, offsets: &[Offset]) -> Result<ValueMap> { + let mut values = vec![0; offsets.len()]; + + // SAFETY: `gpiod_line_request` is guaranteed to be valid here. + let ret = unsafe { + gpiod::gpiod_line_request_get_values_subset( + self.request, + offsets.len() as c_ulong, + offsets.as_ptr(), + values.as_mut_ptr(), + ) + }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::LineRequestGetValSubset, + errno::errno(), + )) + } else { + let mut map = ValueMap::new(); + + for (i, val) in values.iter().enumerate() { + map.insert(offsets[i].into(), Value::new(*val)?); + } + + Ok(map) + } + } + + /// Get values of all lines associated with the request. + pub fn values(&self) -> Result<ValueMap> { + self.values_subset(&self.offsets()) + } + + /// Set the value of a single line associated with the request. + pub fn set_value(&self, offset: Offset, value: Value) -> Result<()> { + // SAFETY: `gpiod_line_request` is guaranteed to be valid here. + let ret = + unsafe { gpiod::gpiod_line_request_set_value(self.request, offset, value.value()) }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::LineRequestSetVal, + errno::errno(), + )) + } else { + Ok(()) + } + } + + /// Get values of a subset of lines associated with the request. + pub fn set_values_subset(&self, map: ValueMap) -> Result<()> { + let mut offsets = Vec::new(); + let mut values = Vec::new(); + + for (offset, value) in map { + offsets.push(offset as u32); + values.push(value.value()); + } + + // SAFETY: `gpiod_line_request` is guaranteed to be valid here. + let ret = unsafe { + gpiod::gpiod_line_request_set_values_subset( + self.request, + offsets.len() as c_ulong, + offsets.as_ptr(), + values.as_ptr(), + ) + }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::LineRequestSetValSubset, + errno::errno(), + )) + } else { + Ok(()) + } + } + + /// Get values of all lines associated with the request. + pub fn set_values(&self, values: &[Value]) -> Result<()> { + if values.len() != self.num_lines() as usize { + return Err(Error::InvalidArguments); + } + + let mut new_values = Vec::new(); + for value in values { + new_values.push(value.value()); + } + + // SAFETY: `gpiod_line_request` is guaranteed to be valid here. + let ret = + unsafe { gpiod::gpiod_line_request_set_values(self.request, new_values.as_ptr()) }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::LineRequestSetVal, + errno::errno(), + )) + } else { + Ok(()) + } + } + + /// Update the configuration of lines associated with the line request. + pub fn reconfigure_lines(&self, lconfig: &line::Config) -> Result<()> { + // SAFETY: `gpiod_line_request` is guaranteed to be valid here. + let ret = + unsafe { gpiod::gpiod_line_request_reconfigure_lines(self.request, lconfig.config) }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::LineRequestReconfigLines, + errno::errno(), + )) + } else { + Ok(()) + } + } + + /// Wait for edge events on any of the lines associated with the request. + pub fn wait_edge_event(&self, timeout: Option<Duration>) -> Result<bool> { + let timeout = match timeout { + Some(x) => x.as_nanos() as i64, + // Block indefinitely + None => -1, + }; + + // SAFETY: `gpiod_line_request` is guaranteed to be valid here. + let ret = unsafe { gpiod::gpiod_line_request_wait_edge_event(self.request, timeout) }; + + match ret { + -1 => Err(Error::OperationFailed( + OperationType::LineRequestWaitEdgeEvent, + errno::errno(), + )), + 0 => Ok(false), + _ => Ok(true), + } + } + + /// Get a number of edge events from a line request. + /// + /// This function will block if no event was queued for the line. + pub fn read_edge_events<'a>( + &'a self, + buffer: &'a mut request::Buffer, + ) -> Resultrequest::Events { + buffer.read_edge_events(self) + } +} + +impl AsRawFd for Request { + /// Get the file descriptor associated with the line request. + fn as_raw_fd(&self) -> i32 { + // SAFETY: `gpiod_line_request` is guaranteed to be valid here. + unsafe { gpiod::gpiod_line_request_get_fd(self.request) } + } +} + +impl Drop for Request { + /// Release the requested lines and free all associated resources. + fn drop(&mut self) { + // SAFETY: `gpiod_line_request` is guaranteed to be valid here. + unsafe { gpiod::gpiod_line_request_release(self.request) } + } +} diff --git a/bindings/rust/libgpiod/src/line_settings.rs b/bindings/rust/libgpiod/src/line_settings.rs new file mode 100644 index 000000000000..cedf7cabafcc --- /dev/null +++ b/bindings/rust/libgpiod/src/line_settings.rs @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar viresh.kumar@linaro.org + +use std::time::Duration; + +use super::{ + gpiod, + line::{Bias, Direction, Drive, Edge, EventClock, SettingKind, SettingVal, Value}, + Error, OperationType, Result, +}; + +/// Line settings objects. +/// +/// Line settings object contains a set of line properties that can be used +/// when requesting lines or reconfiguring an existing request. +/// +/// Mutators in general can only fail if the new property value is invalid. The +/// return values can be safely ignored - the object remains valid even after +/// a mutator fails and simply uses the sane default appropriate for given +/// property. + +#[derive(Debug, Eq, PartialEq)] +pub struct Settings { + pub(crate) settings: *mut gpiod::gpiod_line_settings, +} + +impl Settings { + /// Create a new line settings object. + pub fn new() -> Result<Self> { + // SAFETY: The `gpiod_line_settings` returned by libgpiod is guaranteed to live as long + // as the `struct Settings`. + let settings = unsafe { gpiod::gpiod_line_settings_new() }; + + if settings.is_null() { + return Err(Error::OperationFailed( + OperationType::LineSettingsNew, + errno::errno(), + )); + } + + Ok(Self { settings }) + } + + pub fn new_with_settings(settings: *mut gpiod::gpiod_line_settings) -> Self { + Self { settings } + } + + /// Resets the line settings object to its default values. + pub fn reset(&mut self) { + // SAFETY: `gpiod_line_settings` is guaranteed to be valid here. + unsafe { gpiod::gpiod_line_settings_reset(self.settings) } + } + + /// Makes copy of the settings object. + pub fn settings_clone(&self) -> Result<Self> { + // SAFETY: `gpiod_line_settings` is guaranteed to be valid here. + let settings = unsafe { gpiod::gpiod_line_settings_copy(self.settings) }; + if settings.is_null() { + return Err(Error::OperationFailed( + OperationType::LineSettingsCopy, + errno::errno(), + )); + } + + Ok(Self { settings }) + } + + /// Set line prop setting. + pub fn set_prop(&mut self, props: &[SettingVal]) -> Result<&mut Self> { + for property in props { + match property { + SettingVal::Direction(prop) => self.set_direction(*prop)?, + SettingVal::EdgeDetection(prop) => self.set_edge_detection(*prop)?, + SettingVal::Bias(prop) => self.set_bias(*prop)?, + SettingVal::Drive(prop) => self.set_drive(*prop)?, + SettingVal::ActiveLow(prop) => self.set_active_low(*prop), + SettingVal::DebouncePeriod(prop) => self.set_debounce_period(*prop), + SettingVal::EventClock(prop) => self.set_event_clock(*prop)?, + SettingVal::OutputValue(prop) => self.set_output_value(*prop)?, + }; + } + + Ok(self) + } + + /// Get the line prop setting. + pub fn prop(&self, property: SettingKind) -> Result<SettingVal> { + Ok(match property { + SettingKind::Direction => SettingVal::Direction(self.direction()?), + SettingKind::EdgeDetection => SettingVal::EdgeDetection(self.edge_detection()?), + SettingKind::Bias => SettingVal::Bias(self.bias()?), + SettingKind::Drive => SettingVal::Drive(self.drive()?), + SettingKind::ActiveLow => SettingVal::ActiveLow(self.active_low()), + SettingKind::DebouncePeriod => SettingVal::DebouncePeriod(self.debounce_period()?), + SettingKind::EventClock => SettingVal::EventClock(self.event_clock()?), + SettingKind::OutputValue => SettingVal::OutputValue(self.output_value()?), + }) + } + + /// Set the line direction. + pub fn set_direction(&mut self, direction: Direction) -> Result<&mut Self> { + // SAFETY: `gpiod_line_settings` is guaranteed to be valid here. + let ret = unsafe { + gpiod::gpiod_line_settings_set_direction( + self.settings, + direction.gpiod_direction() as i32, + ) + }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::LineSettingsSetDirection, + errno::errno(), + )) + } else { + Ok(self) + } + } + + /// Get the direction setting. + pub fn direction(&self) -> Result<Direction> { + // SAFETY: `gpiod_line_settings` is guaranteed to be valid here. + Direction::new(unsafe { gpiod::gpiod_line_settings_get_direction(self.settings) } as u32) + } + + /// Set the edge event detection setting. + pub fn set_edge_detection(&mut self, edge: Option<Edge>) -> Result<&mut Self> { + // SAFETY: `gpiod_line_settings` is guaranteed to be valid here. + let ret = unsafe { + gpiod::gpiod_line_settings_set_edge_detection( + self.settings, + Edge::gpiod_edge(edge) as i32, + ) + }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::LineSettingsSetEdgeDetection, + errno::errno(), + )) + } else { + Ok(self) + } + } + + /// Get the edge event detection setting. + pub fn edge_detection(&self) -> Result<Option<Edge>> { + // SAFETY: `gpiod_line_settings` is guaranteed to be valid here. + Edge::new(unsafe { gpiod::gpiod_line_settings_get_edge_detection(self.settings) } as u32) + } + + /// Set the bias setting. + pub fn set_bias(&mut self, bias: Option<Bias>) -> Result<&mut Self> { + // SAFETY: `gpiod_line_settings` is guaranteed to be valid here. + let ret = unsafe { + gpiod::gpiod_line_settings_set_bias(self.settings, Bias::gpiod_bias(bias) as i32) + }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::LineSettingsSetBias, + errno::errno(), + )) + } else { + Ok(self) + } + } + + /// Get the bias setting. + pub fn bias(&self) -> Result<Option<Bias>> { + // SAFETY: `gpiod_line_settings` is guaranteed to be valid here. + Bias::new(unsafe { gpiod::gpiod_line_settings_get_bias(self.settings) } as u32) + } + + /// Set the drive setting. + pub fn set_drive(&mut self, drive: Drive) -> Result<&mut Self> { + // SAFETY: `gpiod_line_settings` is guaranteed to be valid here. + let ret = unsafe { + gpiod::gpiod_line_settings_set_drive(self.settings, drive.gpiod_drive() as i32) + }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::LineSettingsSetDrive, + errno::errno(), + )) + } else { + Ok(self) + } + } + + /// Get the drive setting. + pub fn drive(&self) -> Result<Drive> { + // SAFETY: `gpiod_line_settings` is guaranteed to be valid here. + Drive::new(unsafe { gpiod::gpiod_line_settings_get_drive(self.settings) } as u32) + } + + /// Set active-low setting. + pub fn set_active_low(&mut self, active_low: bool) -> &mut Self { + // SAFETY: `gpiod_line_settings` is guaranteed to be valid here. + unsafe { + gpiod::gpiod_line_settings_set_active_low(self.settings, active_low); + } + self + } + + /// Check the active-low setting. + pub fn active_low(&self) -> bool { + // SAFETY: `gpiod_line_settings` is guaranteed to be valid here. + unsafe { gpiod::gpiod_line_settings_get_active_low(self.settings) } + } + + /// Set the debounce period setting. + pub fn set_debounce_period(&mut self, period: Duration) -> &mut Self { + // SAFETY: `gpiod_line_settings` is guaranteed to be valid here. + unsafe { + gpiod::gpiod_line_settings_set_debounce_period_us( + self.settings, + period.as_micros().try_into().unwrap(), + ); + } + + self + } + + /// Get the debounce period. + pub fn debounce_period(&self) -> Result<Duration> { + // SAFETY: `gpiod_line_settings` is guaranteed to be valid here. + Ok(Duration::from_micros(unsafe { + gpiod::gpiod_line_settings_get_debounce_period_us(self.settings) as u64 + })) + } + + /// Set the event clock setting. + pub fn set_event_clock(&mut self, clock: EventClock) -> Result<&mut Self> { + // SAFETY: `gpiod_line_settings` is guaranteed to be valid here. + let ret = unsafe { + gpiod::gpiod_line_settings_set_event_clock(self.settings, clock.gpiod_clock() as i32) + }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::LineSettingsSetEventClock, + errno::errno(), + )) + } else { + Ok(self) + } + } + + /// Get the event clock setting. + pub fn event_clock(&self) -> Result<EventClock> { + // SAFETY: `gpiod_line_settings` is guaranteed to be valid here. + EventClock::new(unsafe { gpiod::gpiod_line_settings_get_event_clock(self.settings) } as u32) + } + + /// Set the output value setting. + pub fn set_output_value(&mut self, value: Value) -> Result<&mut Self> { + // SAFETY: `gpiod_line_settings` is guaranteed to be valid here. + let ret = + unsafe { gpiod::gpiod_line_settings_set_output_value(self.settings, value.value()) }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::LineSettingsSetOutputValue, + errno::errno(), + )) + } else { + Ok(self) + } + } + + /// Get the output value, 0 or 1. + pub fn output_value(&self) -> Result<Value> { + // SAFETY: `gpiod_line_settings` is guaranteed to be valid here. + let value = unsafe { gpiod::gpiod_line_settings_get_output_value(self.settings) }; + + if value != 0 && value != 1 { + Err(Error::OperationFailed( + OperationType::LineSettingsGetOutVal, + errno::errno(), + )) + } else { + Value::new(value) + } + } +} + +impl Drop for Settings { + /// Free the line settings object and release all associated resources. + fn drop(&mut self) { + // SAFETY: `gpiod_line_settings` is guaranteed to be valid here. + unsafe { gpiod::gpiod_line_settings_free(self.settings) } + } +} diff --git a/bindings/rust/libgpiod/src/request_config.rs b/bindings/rust/libgpiod/src/request_config.rs new file mode 100644 index 000000000000..9d38548dd817 --- /dev/null +++ b/bindings/rust/libgpiod/src/request_config.rs @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar viresh.kumar@linaro.org + +use std::ffi::{CStr, CString}; +use std::os::raw::{c_char, c_ulong}; +use std::str; + +use super::{gpiod, Error, OperationType, 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. The mutators don't return error values. If the +/// values are invalid, in general they are silently adjusted to acceptable +/// ranges. + +#[derive(Debug, Eq, PartialEq)] +pub struct Config { + pub(crate) config: *mut gpiod::gpiod_request_config, +} + +impl Config { + /// Create a new request config object. + pub fn new() -> Result<Self> { + // SAFETY: The `gpiod_request_config` returned by libgpiod is guaranteed to live as long + // as the `struct Config`. + let config = unsafe { gpiod::gpiod_request_config_new() }; + if config.is_null() { + return Err(Error::OperationFailed( + OperationType::RequestConfigNew, + errno::errno(), + )); + } + + Ok(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) -> Result<()> { + let consumer = CString::new(consumer).map_err(|_| Error::InvalidString)?; + + // SAFETY: `gpiod_request_config` is guaranteed to be valid here. + unsafe { + gpiod::gpiod_request_config_set_consumer( + self.config, + consumer.as_ptr() as *const c_char, + ) + } + + Ok(()) + } + + /// Get the consumer name configured in the request config. + pub fn consumer(&self) -> Result<&str> { + // SAFETY: The string returned by libgpiod is guaranteed to live as long + // as the `struct Config`. + let consumer = unsafe { gpiod::gpiod_request_config_get_consumer(self.config) }; + if consumer.is_null() { + return Err(Error::OperationFailed( + OperationType::RequestConfigGetConsumer, + errno::errno(), + )); + } + + // SAFETY: The string is guaranteed to be valid here by the C API. + unsafe { CStr::from_ptr(consumer) } + .to_str() + .map_err(Error::StringNotUtf8) + } + + /// Set the size of the kernel event buffer for the request. + pub fn set_event_buffer_size(&self, size: usize) { + // SAFETY: `gpiod_request_config` is guaranteed to be valid here. + unsafe { gpiod::gpiod_request_config_set_event_buffer_size(self.config, size as c_ulong) } + } + + /// Get the edge event buffer size setting for the request config. + pub fn event_buffer_size(&self) -> usize { + // SAFETY: `gpiod_request_config` is guaranteed to be valid here. + unsafe { gpiod::gpiod_request_config_get_event_buffer_size(self.config) as usize } + } +} + +impl Drop for Config { + /// Free the request config object and release all associated resources. + fn drop(&mut self) { + // SAFETY: `gpiod_request_config` is guaranteed to be valid here. + unsafe { gpiod::gpiod_request_config_free(self.config) } + } +}
+/// Get the API version of the libgpiod library as a human-readable string. +pub fn libgpiod_version() -> Result<&'static str> {
- // SAFETY: The string returned by libgpiod is guaranteed to live forever.
- let version = unsafe { gpiod::gpiod_version_string() };
- if version.is_null() {
return Err(Error::NullString("GPIO library version"));
- }
- // SAFETY: The string is guaranteed to be valid here by the C API.
- unsafe { CStr::from_ptr(version) }
.to_str()
.map_err(Error::StringNotUtf8)
+}
This is not a blocker, I will apply this series to master later and we can add modifications on top of that, but I am now questioning the need for this function here and also the value of __version__ in Python bindings.
Previously the python bindings were built with autotools as part of the whole library. In v2 python now has a proper setup.py script and I intend to publish the bindings on pypi. It can now be built separately from the rest of the libgpiod code as long as the system satisfies the dependency for libgpiod. Example: I will split the yocto recipe for libgpiod into one for the core lib + tools + C++ bindings and another for python that will go to meta-python. The latter will depend on the libgpiod package but will be built in a separate sysroot.
In that case keeping the libgpiod API version as the Python's package __version__ (which made sense before when that code was closely integrated with libgpiod core) is no longer necessary. I'm thinking about setting __version__ to v2.0.0 (because we already had python bindings with v1.x.y versioning out there) but decoupling it from libgpiod's API version.
In your rust code all the crates already have their own versions that don't follow libgpiod's API's version. I think we should drop this function. What do you think?
Also: is there a standardized way for crates to inspect their version? As in: println!(crate.version()) or something?
Bart
On 21-11-22, 15:12, Bartosz Golaszewski wrote:
This is not a blocker, I will apply this series to master later and we can add modifications on top of that, but I am now questioning the need for this function here and also the value of __version__ in Python bindings.
Previously the python bindings were built with autotools as part of the whole library. In v2 python now has a proper setup.py script and I intend to publish the bindings on pypi. It can now be built separately from the rest of the libgpiod code as long as the system satisfies the dependency for libgpiod. Example: I will split the yocto recipe for libgpiod into one for the core lib + tools + C++ bindings and another for python that will go to meta-python. The latter will depend on the libgpiod package but will be built in a separate sysroot.
In that case keeping the libgpiod API version as the Python's package __version__ (which made sense before when that code was closely integrated with libgpiod core) is no longer necessary. I'm thinking about setting __version__ to v2.0.0 (because we already had python bindings with v1.x.y versioning out there) but decoupling it from libgpiod's API version.
In your rust code all the crates already have their own versions that don't follow libgpiod's API's version. I think we should drop this function. What do you think?
whatever you and Kent decide is fine with me :)
Also: is there a standardized way for crates to inspect their version? As in: println!(crate.version()) or something?
I think that would be "version!()" [1].
On Tue, Nov 22, 2022 at 10:08:31AM +0530, Viresh Kumar wrote:
On 21-11-22, 15:12, Bartosz Golaszewski wrote:
This is not a blocker, I will apply this series to master later and we can add modifications on top of that, but I am now questioning the need for this function here and also the value of __version__ in Python bindings.
Previously the python bindings were built with autotools as part of the whole library. In v2 python now has a proper setup.py script and I intend to publish the bindings on pypi. It can now be built separately from the rest of the libgpiod code as long as the system satisfies the dependency for libgpiod. Example: I will split the yocto recipe for libgpiod into one for the core lib + tools + C++ bindings and another for python that will go to meta-python. The latter will depend on the libgpiod package but will be built in a separate sysroot.
In that case keeping the libgpiod API version as the Python's package __version__ (which made sense before when that code was closely integrated with libgpiod core) is no longer necessary. I'm thinking about setting __version__ to v2.0.0 (because we already had python bindings with v1.x.y versioning out there) but decoupling it from libgpiod's API version.
In your rust code all the crates already have their own versions that don't follow libgpiod's API's version. I think we should drop this function. What do you think?
whatever you and Kent decide is fine with me :)
As previously mentioned, it makes sense to me for there to be functions that return both the binding version and the libgpiod version you are running against. Separately, as the two are distinct.
So keeping the wrapper around the libgpiod version method for the latter, and another function that returns the CARGO_PKG_VERSION for the binding crate.
Then, if the user is interested, they can report them in their help, version, log, startup banner or whatever works for them.
Also: is there a standardized way for crates to inspect their version? As in: println!(crate.version()) or something?
I think that would be "version!()" [1].
Which is a library that reads the envvars set by Cargo at compile time, so you could also just
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
and provide a function that returns that.
Cheers, Kent.
On 22-11-22, 13:09, Kent Gibson wrote:
Which is a library that reads the envvars set by Cargo at compile time, so you could also just
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
and provide a function that returns that.
Right, this is what we are doing currently:
/// Get the API version of the libgpiod crate as a human-readable string. pub fn crate_version() -> &'static str { env!("CARGO_PKG_VERSION") }
On Tue, Nov 22, 2022 at 10:45:41AM +0530, Viresh Kumar wrote:
On 22-11-22, 13:09, Kent Gibson wrote:
Which is a library that reads the envvars set by Cargo at compile time, so you could also just
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
and provide a function that returns that.
Right, this is what we are doing currently:
/// Get the API version of the libgpiod crate as a human-readable string. pub fn crate_version() -> &'static str { env!("CARGO_PKG_VERSION") }
Which is already the standard way, so not sure how the discussion took the path it did, but anyway...
It is safe to assume the libgpiod-sys version is tied to the libgpiod crate version?
Cheers, Kent.
This adds gpiosim-sys rust crate, which provides helpers to emulate GPIO chips.
Reviewed-by: Kent Gibson warthog618@gmail.com Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- bindings/rust/Cargo.toml | 1 + bindings/rust/gpiosim-sys/Cargo.toml | 23 ++ bindings/rust/gpiosim-sys/README.md | 14 ++ bindings/rust/gpiosim-sys/build.rs | 43 ++++ bindings/rust/gpiosim-sys/src/lib.rs | 74 ++++++ bindings/rust/gpiosim-sys/src/sim.rs | 330 +++++++++++++++++++++++++++ 6 files changed, 485 insertions(+) create mode 100644 bindings/rust/gpiosim-sys/Cargo.toml create mode 100644 bindings/rust/gpiosim-sys/README.md create mode 100644 bindings/rust/gpiosim-sys/build.rs create mode 100644 bindings/rust/gpiosim-sys/src/lib.rs create mode 100644 bindings/rust/gpiosim-sys/src/sim.rs
diff --git a/bindings/rust/Cargo.toml b/bindings/rust/Cargo.toml index f4051387d581..b4a28fd7129c 100644 --- a/bindings/rust/Cargo.toml +++ b/bindings/rust/Cargo.toml @@ -5,6 +5,7 @@ [workspace]
members = [ + "gpiosim-sys", "libgpiod", "libgpiod-sys" ] diff --git a/bindings/rust/gpiosim-sys/Cargo.toml b/bindings/rust/gpiosim-sys/Cargo.toml new file mode 100644 index 000000000000..c3571d2cf48e --- /dev/null +++ b/bindings/rust/gpiosim-sys/Cargo.toml @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: CC0-1.0 +# SPDX-FileCopyrightText: 2022 Linaro Ltd. +# SPDX-FileCopyrightTest: 2022 Viresh Kumar viresh.kumar@linaro.org + +[package] +name = "gpiosim-sys" +version = "0.1.0" +authors = ["Viresh Kumar viresh.kumar@linaro.org"] +description = "gpiosim header bindings" +repository = "https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git" +categories = ["external-ffi-bindings", "os::linux-apis"] +rust-version = "1.56" +keywords = ["libgpiod", "gpio", "gpiosim"] +license = "Apache-2.0 OR BSD-3-Clause" +edition = "2021" + +[dependencies] +errno = "0.2.8" +libgpiod = { path = "../libgpiod" } + +[build-dependencies] +bindgen = "0.59.1" +cc = "1.0.46" diff --git a/bindings/rust/gpiosim-sys/README.md b/bindings/rust/gpiosim-sys/README.md new file mode 100644 index 000000000000..686d522bf174 --- /dev/null +++ b/bindings/rust/gpiosim-sys/README.md @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: CC0-1.0 +# SPDX-FileCopyrightText: 2022 Linaro Ltd. +# SPDX-FileCopyrightTest: 2022 Viresh Kumar viresh.kumar@linaro.org + +# Generated gpiosim Rust FFI bindings +Automatically generated Rust FFI bindings via + [bindgen](https://github.com/rust-lang/rust-bindgen). + +## License + +This project is licensed under either of + +- [Apache License](http://www.apache.org/licenses/LICENSE-2.0), Version 2.0 +- [BSD-3-Clause License](https://opensource.org/licenses/BSD-3-Clause) diff --git a/bindings/rust/gpiosim-sys/build.rs b/bindings/rust/gpiosim-sys/build.rs new file mode 100644 index 000000000000..0651c6115322 --- /dev/null +++ b/bindings/rust/gpiosim-sys/build.rs @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar viresh.kumar@linaro.org + +extern crate bindgen; + +use std::env; +use std::path::PathBuf; + +fn generate_bindings() { + // Tell cargo to invalidate the built crate whenever following files change + println!("cargo:rerun-if-changed=../../../tests/gpiosim/gpiosim.h"); + + // The bindgen::Builder is the main entry point + // to bindgen, and lets you build up options for + // the resulting bindings. + let bindings = bindgen::Builder::default() + // The input header we would like to generate + // bindings for. + .header("../../../tests/gpiosim/gpiosim.h") + // Tell cargo to invalidate the built crate whenever any of the + // included header files changed. + .parse_callbacks(Box::new(bindgen::CargoCallbacks)) + // Finish the builder and generate the bindings. + .generate() + // Unwrap the Result and panic on failure. + .expect("Unable to generate bindings"); + + // Write the bindings to the $OUT_DIR/bindings.rs file. + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + bindings + .write_to_file(out_path.join("bindings.rs")) + .expect("Couldn't write bindings!"); +} + +fn main() { + generate_bindings(); + + println!("cargo:rustc-link-lib=kmod"); + println!("cargo:rustc-link-lib=mount"); + println!("cargo:rustc-link-search=./../../tests/gpiosim/.libs/"); + println!("cargo:rustc-link-lib=static=gpiosim"); +} diff --git a/bindings/rust/gpiosim-sys/src/lib.rs b/bindings/rust/gpiosim-sys/src/lib.rs new file mode 100644 index 000000000000..5391dbd62f44 --- /dev/null +++ b/bindings/rust/gpiosim-sys/src/lib.rs @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar viresh.kumar@linaro.org + +use libgpiod::{Error, Result}; + +#[allow(non_camel_case_types, non_upper_case_globals)] +#[cfg_attr(test, allow(deref_nullptr, non_snake_case))] +#[allow(dead_code)] +mod bindings_raw { + include!(concat!(env!("OUT_DIR"), "/bindings.rs")); +} +use bindings_raw::*; + +mod sim; +pub use sim::*; + +/// Value settings. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Value { + /// Active + Active, + /// Inactive + InActive, +} + +impl Value { + pub(crate) fn new(val: u32) -> Result<Self> { + match val { + GPIOSIM_VALUE_INACTIVE => Ok(Value::InActive), + GPIOSIM_VALUE_ACTIVE => Ok(Value::Active), + _ => Err(Error::InvalidEnumValue("Value", val as u32)), + } + } +} + +/// Direction settings. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Direction { + /// Direction is input - for reading the value of an externally driven GPIO line. + Input, + /// Direction is output - for driving the GPIO line, value is high. + OutputHigh, + /// Direction is output - for driving the GPIO line, value is low. + OutputLow, +} + +impl Direction { + fn val(self) -> i32 { + (match self { + Direction::Input => GPIOSIM_HOG_DIR_INPUT, + Direction::OutputHigh => GPIOSIM_HOG_DIR_OUTPUT_HIGH, + Direction::OutputLow => GPIOSIM_HOG_DIR_OUTPUT_LOW, + }) as i32 + } +} + +/// Internal pull settings. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Pull { + /// The internal pull-up is enabled. + Up, + /// The internal pull-down is enabled. + Down, +} + +impl Pull { + fn val(self) -> i32 { + (match self { + Pull::Up => GPIOSIM_PULL_UP, + Pull::Down => GPIOSIM_PULL_DOWN, + }) as i32 + } +} diff --git a/bindings/rust/gpiosim-sys/src/sim.rs b/bindings/rust/gpiosim-sys/src/sim.rs new file mode 100644 index 000000000000..4930fb6bff49 --- /dev/null +++ b/bindings/rust/gpiosim-sys/src/sim.rs @@ -0,0 +1,330 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar viresh.kumar@linaro.org + +use std::ffi::{CStr, CString}; +use std::os::raw::c_char; +use std::path::PathBuf; +use std::str; + +use libgpiod::{line::Offset, Error, OperationType, Result}; + +use crate::*; + +/// Sim Ctx +#[derive(Debug)] +struct SimCtx { + ctx: *mut gpiosim_ctx, +} + +// Safe as the pointer is guaranteed to be valid and the associated resource +// won't be freed until the object is dropped. +unsafe impl Send for SimCtx {} + +impl SimCtx { + fn new() -> Result<Self> { + // SAFETY: `gpiosim_ctx` returned by gpiosim is guaranteed to live + // as long as the `struct SimCtx`. + let ctx = unsafe { gpiosim_ctx_new() }; + if ctx.is_null() { + return Err(Error::OperationFailed( + OperationType::SimCtxNew, + errno::errno(), + )); + } + + Ok(Self { ctx }) + } +} + +impl Drop for SimCtx { + fn drop(&mut self) { + // SAFETY: `gpiosim_ctx` is guaranteed to be valid here. + unsafe { gpiosim_ctx_unref(self.ctx) } + } +} + +/// Sim Dev +#[derive(Debug)] +struct SimDev { + dev: *mut gpiosim_dev, +} + +// Safe as the pointer is guaranteed to be valid and the associated resource +// won't be freed until the object is dropped. +unsafe impl Send for SimDev {} + +impl SimDev { + fn new(ctx: &SimCtx) -> Result<Self> { + // SAFETY: `gpiosim_dev` returned by gpiosim is guaranteed to live + // as long as the `struct SimDev`. + let dev = unsafe { gpiosim_dev_new(ctx.ctx) }; + if dev.is_null() { + return Err(Error::OperationFailed( + OperationType::SimDevNew, + errno::errno(), + )); + } + + Ok(Self { dev }) + } + + fn enable(&self) -> Result<()> { + // SAFETY: `gpiosim_dev` is guaranteed to be valid here. + let ret = unsafe { gpiosim_dev_enable(self.dev) }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::SimDevEnable, + errno::errno(), + )) + } else { + Ok(()) + } + } + + fn disable(&self) -> Result<()> { + // SAFETY: `gpiosim_dev` is guaranteed to be valid here. + let ret = unsafe { gpiosim_dev_disable(self.dev) }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::SimDevDisable, + errno::errno(), + )) + } else { + Ok(()) + } + } +} + +impl Drop for SimDev { + fn drop(&mut self) { + // SAFETY: `gpiosim_dev` is guaranteed to be valid here. + unsafe { gpiosim_dev_unref(self.dev) } + } +} + +/// Sim Bank +#[derive(Debug)] +struct SimBank { + bank: *mut gpiosim_bank, +} + +// Safe as the pointer is guaranteed to be valid and the associated resource +// won't be freed until the object is dropped. +unsafe impl Send for SimBank {} + +impl SimBank { + fn new(dev: &SimDev) -> Result<Self> { + // SAFETY: `gpiosim_bank` returned by gpiosim is guaranteed to live + // as long as the `struct SimBank`. + let bank = unsafe { gpiosim_bank_new(dev.dev) }; + if bank.is_null() { + return Err(Error::OperationFailed( + OperationType::SimBankNew, + errno::errno(), + )); + } + + 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 { gpiosim_bank_get_chip_name(self.bank) }; + + // SAFETY: The string is guaranteed to be valid here. + unsafe { CStr::from_ptr(name) } + .to_str() + .map_err(Error::StringNotUtf8) + } + + fn dev_path(&self) -> Result<PathBuf> { + // SAFETY: The string returned by gpiosim is guaranteed to live as long + // as the `struct SimBank`. + let path = unsafe { gpiosim_bank_get_dev_path(self.bank) }; + + // SAFETY: The string is guaranteed to be valid here. + let path = unsafe { CStr::from_ptr(path) } + .to_str() + .map_err(Error::StringNotUtf8)?; + + Ok(PathBuf::from(path)) + } + + fn val(&self, offset: Offset) -> Result<Value> { + // SAFETY: `gpiosim_bank` is guaranteed to be valid here. + let ret = unsafe { gpiosim_bank_get_value(self.bank, offset) }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::SimBankGetVal, + errno::errno(), + )) + } else { + Value::new(ret as u32) + } + } + + fn set_label(&self, label: &str) -> Result<()> { + let label = CString::new(label).map_err(|_| Error::InvalidString)?; + + // SAFETY: `gpiosim_bank` is guaranteed to be valid here. + let ret = unsafe { gpiosim_bank_set_label(self.bank, label.as_ptr() as *const c_char) }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::SimBankSetLabel, + errno::errno(), + )) + } else { + Ok(()) + } + } + + fn set_num_lines(&self, num: usize) -> Result<()> { + // SAFETY: `gpiosim_bank` is guaranteed to be valid here. + let ret = unsafe { gpiosim_bank_set_num_lines(self.bank, num.try_into().unwrap()) }; + if ret == -1 { + Err(Error::OperationFailed( + OperationType::SimBankSetNumLines, + errno::errno(), + )) + } else { + Ok(()) + } + } + + fn set_line_name(&self, offset: Offset, name: &str) -> Result<()> { + let name = CString::new(name).map_err(|_| Error::InvalidString)?; + + // SAFETY: `gpiosim_bank` is guaranteed to be valid here. + let ret = unsafe { + gpiosim_bank_set_line_name(self.bank, offset, name.as_ptr() as *const c_char) + }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::SimBankSetLineName, + errno::errno(), + )) + } else { + Ok(()) + } + } + + fn set_pull(&self, offset: Offset, pull: Pull) -> Result<()> { + // SAFETY: `gpiosim_bank` is guaranteed to be valid here. + let ret = unsafe { gpiosim_bank_set_pull(self.bank, offset, pull.val()) }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::SimBankSetPull, + errno::errno(), + )) + } else { + Ok(()) + } + } + + fn hog_line(&self, offset: Offset, name: &str, dir: Direction) -> Result<()> { + let name = CString::new(name).map_err(|_| Error::InvalidString)?; + + // SAFETY: `gpiosim_bank` is guaranteed to be valid here. + let ret = unsafe { + gpiosim_bank_hog_line(self.bank, offset, name.as_ptr() as *const c_char, dir.val()) + }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::SimBankHogLine, + errno::errno(), + )) + } else { + Ok(()) + } + } +} + +impl Drop for SimBank { + fn drop(&mut self) { + // SAFETY: `gpiosim_bank` is guaranteed to be valid here. + unsafe { gpiosim_bank_unref(self.bank) } + } +} + +/// GPIO SIM +#[derive(Debug)] +pub struct Sim { + _ctx: SimCtx, + dev: SimDev, + bank: SimBank, +} + +impl Sim { + pub fn new(ngpio: Option<usize>, 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: ctx, + dev, + bank, + }) + } + + pub fn chip_name(&self) -> &str { + self.bank.chip_name().unwrap() + } + + pub fn dev_path(&self) -> PathBuf { + self.bank.dev_path().unwrap() + } + + pub fn val(&self, offset: Offset) -> Result<Value> { + self.bank.val(offset) + } + + pub fn set_label(&self, label: &str) -> Result<()> { + self.bank.set_label(label) + } + + pub fn set_num_lines(&self, num: usize) -> Result<()> { + self.bank.set_num_lines(num) + } + + pub fn set_line_name(&self, offset: Offset, name: &str) -> Result<()> { + self.bank.set_line_name(offset, name) + } + + pub fn set_pull(&self, offset: Offset, pull: Pull) -> Result<()> { + self.bank.set_pull(offset, pull) + } + + pub fn hog_line(&self, offset: Offset, name: &str, dir: Direction) -> Result<()> { + self.bank.hog_line(offset, name, dir) + } + + pub fn enable(&self) -> Result<()> { + self.dev.enable() + } + + pub fn disable(&self) -> Result<()> { + self.dev.disable() + } +}
Add examples for the usage of the rust bindings, quite similar to the ones in cxx bindings.
Reviewed-by: Kent Gibson warthog618@gmail.com Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- .../rust/libgpiod/examples/gpio_events.rs | 88 ++++++++++++ .../examples/gpio_threaded_info_events.rs | 132 ++++++++++++++++++ bindings/rust/libgpiod/examples/gpiodetect.rs | 30 ++++ bindings/rust/libgpiod/examples/gpiofind.rs | 36 +++++ bindings/rust/libgpiod/examples/gpioget.rs | 45 ++++++ bindings/rust/libgpiod/examples/gpioinfo.rs | 97 +++++++++++++ bindings/rust/libgpiod/examples/gpiomon.rs | 74 ++++++++++ bindings/rust/libgpiod/examples/gpioset.rs | 63 +++++++++ bindings/rust/libgpiod/examples/gpiowatch.rs | 53 +++++++ 9 files changed, 618 insertions(+) create mode 100644 bindings/rust/libgpiod/examples/gpio_events.rs create mode 100644 bindings/rust/libgpiod/examples/gpio_threaded_info_events.rs create mode 100644 bindings/rust/libgpiod/examples/gpiodetect.rs create mode 100644 bindings/rust/libgpiod/examples/gpiofind.rs create mode 100644 bindings/rust/libgpiod/examples/gpioget.rs create mode 100644 bindings/rust/libgpiod/examples/gpioinfo.rs create mode 100644 bindings/rust/libgpiod/examples/gpiomon.rs create mode 100644 bindings/rust/libgpiod/examples/gpioset.rs create mode 100644 bindings/rust/libgpiod/examples/gpiowatch.rs
diff --git a/bindings/rust/libgpiod/examples/gpio_events.rs b/bindings/rust/libgpiod/examples/gpio_events.rs new file mode 100644 index 000000000000..981005043d55 --- /dev/null +++ b/bindings/rust/libgpiod/examples/gpio_events.rs @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar viresh.kumar@linaro.org +// +// Simplified Rust implementation to show handling of events, when the buffer +// is read into multiple times. Based on gpiomon example. + +use std::env; + +use libgpiod::{ + chip::Chip, + line::{self, Edge, Offset}, + request, Error, Result, +}; + +fn usage(name: &str) { + println!("Usage: {} <chip> <offset0> ...", name); +} + +fn main() -> Result<()> { + let args: Vec<String> = env::args().collect(); + if args.len() < 3 { + usage(&args[0]); + return Err(Error::InvalidArguments); + } + + let mut lsettings = line::Settings::new()?; + let lconfig = line::Config::new()?; + let mut offsets = Vec::<Offset>::new(); + + for arg in &args[2..] { + let offset = arg.parse::<Offset>().map_err(|_| Error::InvalidArguments)?; + offsets.push(offset); + } + + lsettings.set_edge_detection(Some(Edge::Both))?; + lconfig.add_line_settings(&offsets, lsettings)?; + + let path = format!("/dev/gpiochip{}", args[1]); + let chip = Chip::open(&path)?; + + let rconfig = request::Config::new()?; + + let mut buffer = request::Buffer::new(1)?; + let request = chip.request_lines(&rconfig, &lconfig)?; + + loop { + match request.wait_edge_event(None) { + Err(x) => { + println!("{:?}", x); + return Err(Error::InvalidArguments); + } + + Ok(false) => { + // This shouldn't happen as the call is blocking. + panic!(); + } + Ok(true) => (), + } + + let mut events = request.read_edge_events(&mut buffer)?; + + // This can't be used across the next read_edge_events(). + let event = events.next().unwrap()?; + + // This will out live `event` and the next read_edge_events(). + let cloned_event = request::Event::event_clone(event)?; + + let events = request.read_edge_events(&mut buffer)?; + for event in events { + let event = event?; + println!( + "line: {} type: {:?}, time: {:?}", + event.line_offset(), + event.event_type(), + event.timestamp() + ); + } + + // `cloned_event` is still available to be used. + println!( + "line: {} type: {:?}, time: {:?}", + cloned_event.line_offset(), + cloned_event.event_type(), + cloned_event.timestamp() + ); + } +} diff --git a/bindings/rust/libgpiod/examples/gpio_threaded_info_events.rs b/bindings/rust/libgpiod/examples/gpio_threaded_info_events.rs new file mode 100644 index 000000000000..e17f0f08bcfa --- /dev/null +++ b/bindings/rust/libgpiod/examples/gpio_threaded_info_events.rs @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar viresh.kumar@linaro.org +// +// Simplified Rust implementation to show handling of info events, that are +// generated from another thread. + +use std::{ + env, + sync::{ + mpsc::{self, Receiver, Sender}, + Arc, Mutex, + }, + thread, + time::Duration, +}; + +use libgpiod::{ + chip::Chip, + line::{self, Direction, InfoChangeKind, Offset}, + request, Error, Result, +}; + +fn usage(name: &str) { + println!("Usage: {} <chip> <offset>", name); +} + +fn request_reconfigure_line( + chip: Arc<Mutex<Chip>>, + offset: Offset, + tx: Sender<()>, + rx: Receiver<()>, +) { + thread::spawn(move || { + let lconfig = line::Config::new().unwrap(); + let lsettings = line::Settings::new().unwrap(); + lconfig.add_line_settings(&[offset], lsettings).unwrap(); + let rconfig = request::Config::new().unwrap(); + + let request = chip + .lock() + .unwrap() + .request_lines(&rconfig, &lconfig) + .unwrap(); + + // Signal the parent to continue + tx.send(()).expect("Could not send signal on channel"); + + // Wait for parent to signal + rx.recv().expect("Could not receive from channel"); + + let lconfig = line::Config::new().unwrap(); + let mut lsettings = line::Settings::new().unwrap(); + lsettings.set_direction(Direction::Output).unwrap(); + lconfig.add_line_settings(&[offset], lsettings).unwrap(); + + request.reconfigure_lines(&lconfig).unwrap(); + + // Signal the parent to continue + tx.send(()).expect("Could not send signal on channel"); + + // Wait for parent to signal + rx.recv().expect("Could not receive from channel"); + }); +} + +fn main() -> Result<()> { + let args: Vec<String> = env::args().collect(); + if args.len() != 3 { + usage(&args[0]); + return Err(Error::InvalidArguments); + } + + let path = format!("/dev/gpiochip{}", args[1]); + let offset = args[2] + .parse::<Offset>() + .map_err(|_| Error::InvalidArguments)?; + + let chip = Arc::new(Mutex::new(Chip::open(&path)?)); + chip.lock().unwrap().watch_line_info(offset)?; + + // Thread synchronizing mechanism + let (tx_main, rx_thread) = mpsc::channel(); + let (tx_thread, rx_main) = mpsc::channel(); + + // Generate events + request_reconfigure_line(chip.clone(), offset, tx_thread, rx_thread); + + // Wait for thread to signal + rx_main.recv().expect("Could not receive from channel"); + + // Line requested event + assert!(chip + .lock() + .unwrap() + .wait_info_event(Some(Duration::from_secs(1)))?); + let event = chip.lock().unwrap().read_info_event()?; + assert_eq!(event.event_type()?, InfoChangeKind::LineRequested); + + // Signal the thread to continue + tx_main.send(()).expect("Could not send signal on channel"); + + // Wait for thread to signal + rx_main.recv().expect("Could not receive from channel"); + + // Line changed event + assert!(chip + .lock() + .unwrap() + .wait_info_event(Some(Duration::from_millis(10)))?); + let event = chip.lock().unwrap().read_info_event()?; + assert_eq!(event.event_type()?, InfoChangeKind::LineConfigChanged); + + // Signal the thread to continue + tx_main.send(()).expect("Could not send signal on channel"); + + // Line released event + assert!(chip + .lock() + .unwrap() + .wait_info_event(Some(Duration::from_millis(10)))?); + let event = chip.lock().unwrap().read_info_event()?; + assert_eq!(event.event_type().unwrap(), InfoChangeKind::LineReleased); + + // No events available + assert!(!chip + .lock() + .unwrap() + .wait_info_event(Some(Duration::from_millis(100)))?); + + Ok(()) +} diff --git a/bindings/rust/libgpiod/examples/gpiodetect.rs b/bindings/rust/libgpiod/examples/gpiodetect.rs new file mode 100644 index 000000000000..96581bc604c6 --- /dev/null +++ b/bindings/rust/libgpiod/examples/gpiodetect.rs @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar viresh.kumar@linaro.org +// +// Simplified Rust implementation of gpiodetect tool. + +use std::env; +use std::path::Path; + +use libgpiod::{self, Error, Result}; + +fn main() -> Result<()> { + let args: Vec<String> = env::args().collect(); + if args.len() > 1 { + println!("Usage: {}", args[0]); + return Err(Error::InvalidArguments); + } + + for chip in libgpiod::gpiochip_devices(&Path::new("/dev"))? { + let info = chip.info()?; + println!( + "{} [{}] ({})", + info.name()?, + info.label()?, + info.num_lines(), + ); + } + + Ok(()) +} diff --git a/bindings/rust/libgpiod/examples/gpiofind.rs b/bindings/rust/libgpiod/examples/gpiofind.rs new file mode 100644 index 000000000000..99fba30c1a3c --- /dev/null +++ b/bindings/rust/libgpiod/examples/gpiofind.rs @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar viresh.kumar@linaro.org +// +// Simplified Rust implementation of gpiofind tool. + +use std::env; +use std::path::Path; + +use libgpiod::{self, Error, Result}; + +fn main() -> Result<()> { + let args: Vec<String> = env::args().collect(); + if args.len() != 2 { + println!("Usage: {} <line-name>", args[0]); + return Err(Error::InvalidArguments); + } + + for chip in libgpiod::gpiochip_devices(&Path::new("/dev"))? { + let offset = chip.line_offset_from_name(&args[1]); + let info = chip.info()?; + + if offset.is_ok() { + println!( + "Line {} found: Chip: {}, offset: {}", + args[1], + info.name()?, + offset? + ); + return Ok(()); + } + } + + println!("Failed to find line: {}", args[1]); + Ok(()) +} diff --git a/bindings/rust/libgpiod/examples/gpioget.rs b/bindings/rust/libgpiod/examples/gpioget.rs new file mode 100644 index 000000000000..6e60833e243f --- /dev/null +++ b/bindings/rust/libgpiod/examples/gpioget.rs @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar viresh.kumar@linaro.org +// +// Simplified Rust implementation of gpioget tool. + +use std::env; + +use libgpiod::{ + chip::Chip, + line::{self, Direction, Offset}, + request, Error, Result, +}; + +fn main() -> Result<()> { + let args: Vec<String> = env::args().collect(); + if args.len() < 3 { + println!("Usage: {} <chip> <line_offset0> ...", args[0]); + return Err(Error::InvalidArguments); + } + + let mut lsettings = line::Settings::new()?; + let lconfig = line::Config::new()?; + let mut offsets = Vec::<Offset>::new(); + + for arg in &args[2..] { + let offset = arg.parse::<Offset>().map_err(|_| Error::InvalidArguments)?; + offsets.push(offset); + } + + lsettings.set_direction(Direction::Input)?; + lconfig.add_line_settings(&offsets, lsettings)?; + + let path = format!("/dev/gpiochip{}", args[1]); + let chip = Chip::open(&path)?; + + let rconfig = request::Config::new()?; + rconfig.set_consumer(&args[0])?; + + let request = chip.request_lines(&rconfig, &lconfig)?; + let map = request.values()?; + + println!("{:?}", map); + Ok(()) +} diff --git a/bindings/rust/libgpiod/examples/gpioinfo.rs b/bindings/rust/libgpiod/examples/gpioinfo.rs new file mode 100644 index 000000000000..02d8c9765ab3 --- /dev/null +++ b/bindings/rust/libgpiod/examples/gpioinfo.rs @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar viresh.kumar@linaro.org +// +// Simplified Rust implementation of gpioinfo tool. + +use std::env; +use std::path::Path; + +use libgpiod::{ + chip::Chip, + line::{Direction, Offset}, + Error, Result, +}; + +fn line_info(chip: &Chip, offset: Offset) -> Result<()> { + let info = chip.line_info(offset)?; + let off = info.offset(); + + let name = match info.name() { + Ok(name) => name, + _ => "unused", + }; + + let consumer = match info.consumer() { + Ok(name) => name, + _ => "unnamed", + }; + + let low = if info.is_active_low() { + "active-low" + } else { + "active-high" + }; + + let dir = match info.direction()? { + 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 + ); + + Ok(()) +} + +fn chip_info(chip: &Chip) -> Result<()> { + let info = chip.info()?; + let ngpio = info.num_lines(); + + println!("GPIO Chip name: {}", info.name()?); + println!("\tlabel: {}", info.label()?); + println!("\tpath: {}", chip.path()?); + println!("\tngpio: {}\n", ngpio); + + println!("\tLine information:"); + + for offset in 0..ngpio { + line_info(chip, offset as Offset)?; + } + println!("\n"); + + Ok(()) +} + +fn main() -> Result<()> { + let args: Vec<String> = env::args().collect(); + if args.len() > 2 { + println!("Usage: {}", args[0]); + return Err(Error::InvalidArguments); + } + + if args.len() == 1 { + for chip in libgpiod::gpiochip_devices(&Path::new("/dev"))? { + chip_info(&chip)?; + } + } else { + let index = args[1] + .parse::<u32>() + .map_err(|_| Error::InvalidArguments)?; + let path = format!("/dev/gpiochip{}", index); + if libgpiod::is_gpiochip_device(&path) { + let chip = Chip::open(&path)?; + + chip_info(&chip)?; + } + } + + Ok(()) +} diff --git a/bindings/rust/libgpiod/examples/gpiomon.rs b/bindings/rust/libgpiod/examples/gpiomon.rs new file mode 100644 index 000000000000..c38652c1deff --- /dev/null +++ b/bindings/rust/libgpiod/examples/gpiomon.rs @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar viresh.kumar@linaro.org +// +// Simplified Rust implementation of the gpiomon tool. + +use std::env; + +use libgpiod::{ + chip::Chip, + line::{self, Edge, EdgeKind, Offset}, + request, Error, Result, +}; + +fn usage(name: &str) { + println!("Usage: {} <chip> <offset0> ...", name); +} + +fn main() -> Result<()> { + let args: Vec<String> = env::args().collect(); + if args.len() < 3 { + usage(&args[0]); + return Err(Error::InvalidArguments); + } + + let mut lsettings = line::Settings::new()?; + let lconfig = line::Config::new()?; + let mut offsets = Vec::<Offset>::new(); + + for arg in &args[2..] { + let offset = arg.parse::<Offset>().map_err(|_| Error::InvalidArguments)?; + offsets.push(offset); + } + + lsettings.set_edge_detection(Some(Edge::Both))?; + lconfig.add_line_settings(&offsets, lsettings)?; + + let path = format!("/dev/gpiochip{}", args[1]); + let chip = Chip::open(&path)?; + + let rconfig = request::Config::new()?; + + let mut buffer = request::Buffer::new(1)?; + let request = chip.request_lines(&rconfig, &lconfig)?; + + loop { + match request.wait_edge_event(None) { + Err(x) => { + println!("{:?}", x); + return Err(Error::InvalidArguments); + } + + Ok(false) => { + // This shouldn't happen as the call is blocking. + panic!(); + } + Ok(true) => (), + } + + let events = request.read_edge_events(&mut buffer)?; + for event in events { + let event = event?; + println!( + "line: {} type: {}, time: {:?}", + event.line_offset(), + match event.event_type()? { + EdgeKind::Rising => "Rising", + EdgeKind::Falling => "Falling", + }, + event.timestamp() + ); + } + } +} diff --git a/bindings/rust/libgpiod/examples/gpioset.rs b/bindings/rust/libgpiod/examples/gpioset.rs new file mode 100644 index 000000000000..875a3ad72391 --- /dev/null +++ b/bindings/rust/libgpiod/examples/gpioset.rs @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar viresh.kumar@linaro.org +// +// Simplified Rust implementation of the gpioset tool. + +use std::env; +use std::io::{stdin, Read}; + +use libgpiod::{ + chip::Chip, + line::{self, Direction, Offset, SettingVal, Value}, + request, Error, Result, +}; + +fn usage(name: &str) { + println!("Usage: {} <chip> <line_offset0>=<value0> ...", name); +} + +fn main() -> Result<()> { + let args: Vec<String> = env::args().collect(); + if args.len() < 3 { + usage(&args[0]); + return Err(Error::InvalidArguments); + } + + let lconfig = line::Config::new()?; + + for arg in &args[2..] { + let pair: Vec<&str> = arg.split('=').collect(); + if pair.len() != 2 { + usage(&args[0]); + return Err(Error::InvalidArguments); + } + + let offset = pair[0] + .parse::<Offset>() + .map_err(|_| Error::InvalidArguments)?; + let value = pair[1] + .parse::<i32>() + .map_err(|_| Error::InvalidArguments)?; + + let mut lsettings = line::Settings::new()?; + lsettings.set_prop(&[ + SettingVal::Direction(Direction::Output), + SettingVal::OutputValue(Value::new(value)?), + ])?; + lconfig.add_line_settings(&[offset], lsettings)?; + } + + let path = format!("/dev/gpiochip{}", args[1]); + let chip = Chip::open(&path)?; + + let rconfig = request::Config::new()?; + rconfig.set_consumer(&args[0])?; + + chip.request_lines(&rconfig, &lconfig)?; + + // Wait for keypress, let user verify line status. + stdin().read_exact(&mut [0u8]).unwrap(); + + Ok(()) +} diff --git a/bindings/rust/libgpiod/examples/gpiowatch.rs b/bindings/rust/libgpiod/examples/gpiowatch.rs new file mode 100644 index 000000000000..a356fd589721 --- /dev/null +++ b/bindings/rust/libgpiod/examples/gpiowatch.rs @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar viresh.kumar@linaro.org +// +// Simplified Rust implementation of the gpiowatch tool. + +use std::env; + +use libgpiod::{chip::Chip, line::Offset, Error, Result}; + +fn usage(name: &str) { + println!("Usage: {} <chip> <offset0> ...", name); +} + +fn main() -> Result<()> { + let args: Vec<String> = env::args().collect(); + if args.len() < 2 { + usage(&args[0]); + return Err(Error::InvalidArguments); + } + + let path = format!("/dev/gpiochip{}", args[1]); + let offset = args[2] + .parse::<Offset>() + .map_err(|_| Error::InvalidArguments)?; + + let chip = Chip::open(&path)?; + let _info = chip.watch_line_info(offset)?; + + match chip.wait_info_event(None) { + Err(x) => { + println!("{:?}", x); + return Err(Error::InvalidArguments); + } + + Ok(false) => { + // This shouldn't happen as the call is blocking. + panic!(); + } + Ok(true) => (), + } + + let event = chip.read_info_event()?; + println!( + "line: {} type: {:?}, time: {:?}", + offset, + event.event_type()?, + event.timestamp() + ); + + chip.unwatch(offset); + Ok(()) +}
Add tests for the rust bindings, quite similar to the ones in cxx bindings.
Reviewed-by: Kent Gibson warthog618@gmail.com Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- bindings/rust/libgpiod/Cargo.toml | 3 + bindings/rust/libgpiod/tests/chip.rs | 97 ++++ bindings/rust/libgpiod/tests/common/config.rs | 142 +++++ bindings/rust/libgpiod/tests/common/mod.rs | 9 + bindings/rust/libgpiod/tests/edge_event.rs | 297 ++++++++++ bindings/rust/libgpiod/tests/info_event.rs | 166 ++++++ bindings/rust/libgpiod/tests/line_config.rs | 95 ++++ bindings/rust/libgpiod/tests/line_info.rs | 275 ++++++++++ bindings/rust/libgpiod/tests/line_request.rs | 509 ++++++++++++++++++ bindings/rust/libgpiod/tests/line_settings.rs | 203 +++++++ .../rust/libgpiod/tests/request_config.rs | 38 ++ 11 files changed, 1834 insertions(+) create mode 100644 bindings/rust/libgpiod/tests/chip.rs create mode 100644 bindings/rust/libgpiod/tests/common/config.rs create mode 100644 bindings/rust/libgpiod/tests/common/mod.rs create mode 100644 bindings/rust/libgpiod/tests/edge_event.rs create mode 100644 bindings/rust/libgpiod/tests/info_event.rs create mode 100644 bindings/rust/libgpiod/tests/line_config.rs create mode 100644 bindings/rust/libgpiod/tests/line_info.rs create mode 100644 bindings/rust/libgpiod/tests/line_request.rs create mode 100644 bindings/rust/libgpiod/tests/line_settings.rs create mode 100644 bindings/rust/libgpiod/tests/request_config.rs
diff --git a/bindings/rust/libgpiod/Cargo.toml b/bindings/rust/libgpiod/Cargo.toml index a38df1c0b10d..655e7a2ce107 100644 --- a/bindings/rust/libgpiod/Cargo.toml +++ b/bindings/rust/libgpiod/Cargo.toml @@ -20,3 +20,6 @@ intmap = "2.0.0" libc = "0.2.39" libgpiod-sys = { path = "../libgpiod-sys" } thiserror = "1.0" + +[dev-dependencies] +gpiosim-sys = { path = "../gpiosim-sys" } diff --git a/bindings/rust/libgpiod/tests/chip.rs b/bindings/rust/libgpiod/tests/chip.rs new file mode 100644 index 000000000000..702af93e5f2c --- /dev/null +++ b/bindings/rust/libgpiod/tests/chip.rs @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar viresh.kumar@linaro.org + +mod common; + +mod chip { + use libc::{ENODEV, ENOENT, ENOTTY}; + use std::path::PathBuf; + + use gpiosim_sys::Sim; + use libgpiod::{chip::Chip, Error as ChipError, OperationType}; + + mod open { + use super::*; + + #[test] + fn nonexistent_file() { + assert_eq!( + Chip::open(&PathBuf::from("/dev/nonexistent")).unwrap_err(), + ChipError::OperationFailed(OperationType::ChipOpen, errno::Errno(ENOENT)) + ); + } + + #[test] + fn no_dev_file() { + assert_eq!( + Chip::open(&PathBuf::from("/tmp")).unwrap_err(), + ChipError::OperationFailed(OperationType::ChipOpen, errno::Errno(ENOTTY)) + ); + } + + #[test] + fn non_gpio_char_dev_file() { + assert_eq!( + Chip::open(&PathBuf::from("/dev/null")).unwrap_err(), + ChipError::OperationFailed(OperationType::ChipOpen, errno::Errno(ENODEV)) + ); + } + + #[test] + fn gpiosim_file() { + let sim = Sim::new(None, None, true).unwrap(); + assert!(Chip::open(&sim.dev_path()).is_ok()); + } + } + + mod verify { + use super::*; + const NGPIO: usize = 16; + const LABEL: &str = "foobar"; + + #[test] + fn basic_helpers() { + let sim = Sim::new(Some(NGPIO), Some(LABEL), true).unwrap(); + let chip = Chip::open(&sim.dev_path()).unwrap(); + let info = chip.info().unwrap(); + + assert_eq!(info.label().unwrap(), LABEL); + assert_eq!(info.name().unwrap(), sim.chip_name()); + assert_eq!(chip.path().unwrap(), sim.dev_path().to_str().unwrap()); + assert_eq!(info.num_lines(), NGPIO as usize); + } + + #[test] + fn find_line() { + 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.line_offset_from_name("zero").unwrap(), 0); + assert_eq!(chip.line_offset_from_name("two").unwrap(), 2); + assert_eq!(chip.line_offset_from_name("three").unwrap(), 3); + assert_eq!(chip.line_offset_from_name("five").unwrap(), 5); + + // Success with duplicate names, should return first entry + assert_eq!(chip.line_offset_from_name("ten").unwrap(), 10); + + // Failure + assert_eq!( + chip.line_offset_from_name("nonexistent").unwrap_err(), + ChipError::OperationFailed( + OperationType::ChipGetLineOffsetFromName, + errno::Errno(ENOENT), + ) + ); + } + } +} diff --git a/bindings/rust/libgpiod/tests/common/config.rs b/bindings/rust/libgpiod/tests/common/config.rs new file mode 100644 index 000000000000..842a70a9d70e --- /dev/null +++ b/bindings/rust/libgpiod/tests/common/config.rs @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar viresh.kumar@linaro.org + +use std::sync::{Arc, Mutex}; +use std::time::Duration; + +use gpiosim_sys::{Pull, Sim, Value as SimValue}; +use libgpiod::{ + chip::Chip, + line::{self, Bias, Direction, Drive, Edge, EventClock, Offset, SettingVal, Value}, + request, Result, +}; + +pub(crate) struct TestConfig { + sim: Arc<Mutex<Sim>>, + chip: Option<Chip>, + request: Optionrequest::Request, + rconfig: request::Config, + lconfig: line::Config, + lsettings: Optionline::Settings, +} + +impl TestConfig { + pub(crate) fn new(ngpio: usize) -> Result<Self> { + Ok(Self { + sim: Arc::new(Mutex::new(Sim::new(Some(ngpio), None, true)?)), + chip: None, + request: None, + rconfig: request::Config::new().unwrap(), + lconfig: line::Config::new().unwrap(), + lsettings: Some(line::Settings::new().unwrap()), + }) + } + + pub(crate) fn set_pull(&self, offsets: &[Offset], pulls: &[Pull]) { + for i in 0..pulls.len() { + self.sim + .lock() + .unwrap() + .set_pull(offsets[i], pulls[i]) + .unwrap(); + } + } + + pub(crate) fn rconfig_set_consumer(&self, consumer: &str) { + self.rconfig.set_consumer(consumer).unwrap(); + } + + pub(crate) fn lconfig_val(&mut self, dir: Option<Direction>, val: Option<Value>) { + let mut settings = Vec::new(); + + if let Some(dir) = dir { + settings.push(SettingVal::Direction(dir)); + } + + if let Some(val) = val { + settings.push(SettingVal::OutputValue(val)); + } + + if !settings.is_empty() { + self.lsettings().set_prop(&settings).unwrap(); + } + } + + pub(crate) fn lconfig_bias(&mut self, dir: Direction, bias: Option<Bias>) { + let settings = vec![SettingVal::Direction(dir), SettingVal::Bias(bias)]; + self.lsettings().set_prop(&settings).unwrap(); + } + + pub(crate) fn lconfig_clock(&mut self, clock: EventClock) { + let settings = vec![SettingVal::EventClock(clock)]; + self.lsettings().set_prop(&settings).unwrap(); + } + + pub(crate) fn lconfig_debounce(&mut self, duration: Duration) { + let settings = vec![ + SettingVal::Direction(Direction::Input), + SettingVal::DebouncePeriod(duration), + ]; + self.lsettings().set_prop(&settings).unwrap(); + } + + pub(crate) fn lconfig_drive(&mut self, dir: Direction, drive: Drive) { + let settings = vec![SettingVal::Direction(dir), SettingVal::Drive(drive)]; + self.lsettings().set_prop(&settings).unwrap(); + } + + pub(crate) fn lconfig_edge(&mut self, dir: Option<Direction>, edge: Option<Edge>) { + let mut settings = Vec::new(); + + if let Some(dir) = dir { + settings.push(SettingVal::Direction(dir)); + } + + settings.push(SettingVal::EdgeDetection(edge)); + self.lsettings().set_prop(&settings).unwrap(); + } + + pub(crate) fn lconfig_add_settings(&mut self, offsets: &[Offset]) { + self.lconfig + .add_line_settings(offsets, self.lsettings.take().unwrap()) + .unwrap() + } + + pub(crate) fn request_lines(&mut self) -> Result<()> { + let chip = Chip::open(&self.sim.lock().unwrap().dev_path())?; + + self.request = Some(chip.request_lines(&self.rconfig, &self.lconfig)?); + self.chip = Some(chip); + + Ok(()) + } + + pub(crate) fn sim(&self) -> Arc<Mutex<Sim>> { + self.sim.clone() + } + + pub(crate) fn sim_val(&self, offset: Offset) -> Result<SimValue> { + self.sim.lock().unwrap().val(offset) + } + + pub(crate) fn chip(&self) -> &Chip { + self.chip.as_ref().unwrap() + } + + pub(crate) fn lsettings(&mut self) -> &mut line::Settings { + self.lsettings.as_mut().unwrap() + } + + pub(crate) fn request(&self) -> &request::Request { + 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/libgpiod/tests/common/mod.rs b/bindings/rust/libgpiod/tests/common/mod.rs new file mode 100644 index 000000000000..5f725c61f905 --- /dev/null +++ b/bindings/rust/libgpiod/tests/common/mod.rs @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar viresh.kumar@linaro.org + +#[allow(dead_code)] +mod config; + +#[allow(unused_imports)] +pub(crate) use config::*; diff --git a/bindings/rust/libgpiod/tests/edge_event.rs b/bindings/rust/libgpiod/tests/edge_event.rs new file mode 100644 index 000000000000..571e574fa15e --- /dev/null +++ b/bindings/rust/libgpiod/tests/edge_event.rs @@ -0,0 +1,297 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar viresh.kumar@linaro.org + +mod common; + +mod edge_event { + use std::time::Duration; + + use crate::common::*; + use gpiosim_sys::{Pull, Sim}; + use libgpiod::{ + line::{Edge, EdgeKind, Offset}, + request, + }; + + const NGPIO: usize = 8; + + mod buffer_capacity { + use super::*; + + #[test] + fn default_capacity() { + assert_eq!(request::Buffer::new(0).unwrap().capacity(), 64); + } + + #[test] + fn user_defined_capacity() { + assert_eq!(request::Buffer::new(123).unwrap().capacity(), 123); + } + + #[test] + fn max_capacity() { + assert_eq!(request::Buffer::new(1024 * 2).unwrap().capacity(), 1024); + } + } + + mod trigger { + use super::*; + use std::{ + sync::{Arc, Mutex}, + thread, + }; + + // Helpers to generate events + fn trigger_falling_and_rising_edge(sim: Arc<Mutex<Sim>>, offset: Offset) { + thread::spawn(move || { + thread::sleep(Duration::from_millis(30)); + sim.lock().unwrap().set_pull(offset, Pull::Up).unwrap(); + + thread::sleep(Duration::from_millis(30)); + sim.lock().unwrap().set_pull(offset, Pull::Down).unwrap(); + }); + } + + fn trigger_rising_edge_events_on_two_offsets(sim: Arc<Mutex<Sim>>, offset: [Offset; 2]) { + thread::spawn(move || { + thread::sleep(Duration::from_millis(30)); + sim.lock().unwrap().set_pull(offset[0], Pull::Up).unwrap(); + + thread::sleep(Duration::from_millis(30)); + sim.lock().unwrap().set_pull(offset[1], Pull::Up).unwrap(); + }); + } + + fn trigger_multiple_events(sim: Arc<Mutex<Sim>>, offset: Offset) { + sim.lock().unwrap().set_pull(offset, Pull::Up).unwrap(); + thread::sleep(Duration::from_millis(10)); + + sim.lock().unwrap().set_pull(offset, Pull::Down).unwrap(); + thread::sleep(Duration::from_millis(10)); + + sim.lock().unwrap().set_pull(offset, Pull::Up).unwrap(); + thread::sleep(Duration::from_millis(10)); + } + + #[test] + fn both_edges() { + const GPIO: Offset = 2; + let mut buf = request::Buffer::new(0).unwrap(); + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_edge(None, Some(Edge::Both)); + config.lconfig_add_settings(&[GPIO]); + config.request_lines().unwrap(); + + // Generate events + trigger_falling_and_rising_edge(config.sim(), GPIO); + + // Rising event + assert!(config + .request() + .wait_edge_event(Some(Duration::from_secs(1))) + .unwrap()); + + let mut events = config.request().read_edge_events(&mut buf).unwrap(); + assert_eq!(events.len(), 1); + + let event = events.next().unwrap().unwrap(); + let ts_rising = event.timestamp(); + assert_eq!(event.event_type().unwrap(), EdgeKind::Rising); + assert_eq!(event.line_offset(), GPIO); + + // Falling event + assert!(config + .request() + .wait_edge_event(Some(Duration::from_secs(1))) + .unwrap()); + + let mut events = config.request().read_edge_events(&mut buf).unwrap(); + assert_eq!(events.len(), 1); + + let event = events.next().unwrap().unwrap(); + let ts_falling = event.timestamp(); + assert_eq!(event.event_type().unwrap(), EdgeKind::Falling); + assert_eq!(event.line_offset(), GPIO); + + // No events available + assert!(!config + .request() + .wait_edge_event(Some(Duration::from_millis(100))) + .unwrap()); + + assert!(ts_falling > ts_rising); + } + + #[test] + fn rising_edge() { + const GPIO: Offset = 6; + let mut buf = request::Buffer::new(0).unwrap(); + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_edge(None, Some(Edge::Rising)); + config.lconfig_add_settings(&[GPIO]); + config.request_lines().unwrap(); + + // Generate events + trigger_falling_and_rising_edge(config.sim(), GPIO); + + // Rising event + assert!(config + .request() + .wait_edge_event(Some(Duration::from_secs(1))) + .unwrap()); + + let mut events = config.request().read_edge_events(&mut buf).unwrap(); + assert_eq!(events.len(), 1); + + let event = events.next().unwrap().unwrap(); + assert_eq!(event.event_type().unwrap(), EdgeKind::Rising); + assert_eq!(event.line_offset(), GPIO); + + // No events available + assert!(!config + .request() + .wait_edge_event(Some(Duration::from_millis(100))) + .unwrap()); + } + + #[test] + fn falling_edge() { + const GPIO: Offset = 7; + let mut buf = request::Buffer::new(0).unwrap(); + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_edge(None, Some(Edge::Falling)); + config.lconfig_add_settings(&[GPIO]); + config.request_lines().unwrap(); + + // Generate events + trigger_falling_and_rising_edge(config.sim(), GPIO); + + // Falling event + assert!(config + .request() + .wait_edge_event(Some(Duration::from_secs(1))) + .unwrap()); + + let mut events = config.request().read_edge_events(&mut buf).unwrap(); + assert_eq!(events.len(), 1); + + let event = events.next().unwrap().unwrap(); + assert_eq!(event.event_type().unwrap(), EdgeKind::Falling); + assert_eq!(event.line_offset(), GPIO); + + // No events available + assert!(!config + .request() + .wait_edge_event(Some(Duration::from_millis(100))) + .unwrap()); + } + + #[test] + fn edge_sequence() { + const GPIO: [u32; 2] = [0, 1]; + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_edge(None, Some(Edge::Both)); + config.lconfig_add_settings(&GPIO); + config.request_lines().unwrap(); + + // Generate events + trigger_rising_edge_events_on_two_offsets(config.sim(), GPIO); + + // Rising event GPIO 0 + let mut buf = request::Buffer::new(0).unwrap(); + assert!(config + .request() + .wait_edge_event(Some(Duration::from_secs(1))) + .unwrap()); + + let mut events = config.request().read_edge_events(&mut buf).unwrap(); + assert_eq!(events.len(), 1); + + let event = events.next().unwrap().unwrap(); + assert_eq!(event.event_type().unwrap(), EdgeKind::Rising); + assert_eq!(event.line_offset(), GPIO[0]); + assert_eq!(event.global_seqno(), 1); + assert_eq!(event.line_seqno(), 1); + + // Rising event GPIO 1 + assert!(config + .request() + .wait_edge_event(Some(Duration::from_secs(1))) + .unwrap()); + + let mut events = config.request().read_edge_events(&mut buf).unwrap(); + assert_eq!(events.len(), 1); + + let event = events.next().unwrap().unwrap(); + assert_eq!(event.event_type().unwrap(), EdgeKind::Rising); + assert_eq!(event.line_offset(), GPIO[1]); + assert_eq!(event.global_seqno(), 2); + assert_eq!(event.line_seqno(), 1); + + // No events available + assert!(!config + .request() + .wait_edge_event(Some(Duration::from_millis(100))) + .unwrap()); + } + + #[test] + fn multiple_events() { + const GPIO: Offset = 1; + let mut buf = request::Buffer::new(0).unwrap(); + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_edge(None, Some(Edge::Both)); + config.lconfig_add_settings(&[GPIO]); + config.request_lines().unwrap(); + + // Generate events + trigger_multiple_events(config.sim(), GPIO); + + // Read multiple events + assert!(config + .request() + .wait_edge_event(Some(Duration::from_secs(1))) + .unwrap()); + + let events = config.request().read_edge_events(&mut buf).unwrap(); + assert_eq!(events.len(), 3); + + let mut global_seqno = 1; + let mut line_seqno = 1; + + // Verify sequence number of events + for event in events { + let event = event.unwrap(); + assert_eq!(event.line_offset(), GPIO); + assert_eq!(event.global_seqno(), global_seqno); + assert_eq!(event.line_seqno(), line_seqno); + + global_seqno += 1; + line_seqno += 1; + } + } + + #[test] + fn over_capacity() { + const GPIO: Offset = 2; + let mut buf = request::Buffer::new(2).unwrap(); + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_edge(None, Some(Edge::Both)); + config.lconfig_add_settings(&[GPIO]); + config.request_lines().unwrap(); + + // Generate events + trigger_multiple_events(config.sim(), GPIO); + + // Read multiple events + assert!(config + .request() + .wait_edge_event(Some(Duration::from_secs(1))) + .unwrap()); + + let events = config.request().read_edge_events(&mut buf).unwrap(); + assert_eq!(events.len(), 2); + } + } +} diff --git a/bindings/rust/libgpiod/tests/info_event.rs b/bindings/rust/libgpiod/tests/info_event.rs new file mode 100644 index 000000000000..bfa0058e313e --- /dev/null +++ b/bindings/rust/libgpiod/tests/info_event.rs @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar viresh.kumar@linaro.org + +mod common; + +mod info_event { + use libc::EINVAL; + use std::{ + sync::{ + mpsc::{self, Receiver, Sender}, + Arc, Mutex, + }, + thread, + time::Duration, + }; + + use gpiosim_sys::Sim; + use libgpiod::{ + chip::Chip, + line::{self, Direction, InfoChangeKind, Offset}, + request, Error as ChipError, OperationType, + }; + + fn request_reconfigure_line(chip: Arc<Mutex<Chip>>, tx: Sender<()>, rx: Receiver<()>) { + thread::spawn(move || { + let lconfig1 = line::Config::new().unwrap(); + let lsettings = line::Settings::new().unwrap(); + lconfig1.add_line_settings(&[7], lsettings).unwrap(); + let rconfig = request::Config::new().unwrap(); + + let request = chip + .lock() + .unwrap() + .request_lines(&rconfig, &lconfig1) + .unwrap(); + + // Signal the parent to continue + tx.send(()).expect("Could not send signal on channel"); + + // Wait for parent to signal + rx.recv().expect("Could not receive from channel"); + + let lconfig2 = line::Config::new().unwrap(); + let mut lsettings = line::Settings::new().unwrap(); + lsettings.set_direction(Direction::Output).unwrap(); + lconfig2.add_line_settings(&[7], lsettings).unwrap(); + + request.reconfigure_lines(&lconfig2).unwrap(); + + // Signal the parent to continue + tx.send(()).expect("Could not send signal on channel"); + + // Wait for parent to signal + rx.recv().expect("Could not receive from channel"); + }); + } + + mod watch { + use super::*; + const NGPIO: usize = 8; + const GPIO: Offset = 7; + + #[test] + fn line_info() { + 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(OperationType::ChipWatchLineInfo, errno::Errno(EINVAL)) + ); + + let info = chip.watch_line_info(GPIO).unwrap(); + assert_eq!(info.offset(), GPIO); + + // No events available + assert!(!chip + .wait_info_event(Some(Duration::from_millis(100))) + .unwrap()); + } + + #[test] + fn reconfigure() { + let sim = Sim::new(Some(NGPIO), None, true).unwrap(); + let chip = Arc::new(Mutex::new(Chip::open(&sim.dev_path()).unwrap())); + let info = chip.lock().unwrap().watch_line_info(GPIO).unwrap(); + + assert_eq!(info.direction().unwrap(), Direction::Input); + + // Thread synchronizing mechanism + let (tx_main, rx_thread) = mpsc::channel(); + let (tx_thread, rx_main) = mpsc::channel(); + + // Generate events + request_reconfigure_line(chip.clone(), tx_thread, rx_thread); + + // Wait for thread to signal + rx_main.recv().expect("Could not receive from channel"); + + // Line requested event + assert!(chip + .lock() + .unwrap() + .wait_info_event(Some(Duration::from_secs(1))) + .unwrap()); + let event = chip.lock().unwrap().read_info_event().unwrap(); + let ts_req = event.timestamp(); + + assert_eq!(event.event_type().unwrap(), InfoChangeKind::LineRequested); + assert_eq!( + event.line_info().unwrap().direction().unwrap(), + Direction::Input + ); + + // Signal the thread to continue + tx_main.send(()).expect("Could not send signal on channel"); + + // Wait for thread to signal + rx_main.recv().expect("Could not receive from channel"); + + // Line changed event + assert!(chip + .lock() + .unwrap() + .wait_info_event(Some(Duration::from_secs(1))) + .unwrap()); + let event = chip.lock().unwrap().read_info_event().unwrap(); + let ts_rec = event.timestamp(); + + assert_eq!( + event.event_type().unwrap(), + InfoChangeKind::LineConfigChanged + ); + assert_eq!( + event.line_info().unwrap().direction().unwrap(), + Direction::Output + ); + + // Signal the thread to continue + tx_main.send(()).expect("Could not send signal on channel"); + + // Line released event + assert!(chip + .lock() + .unwrap() + .wait_info_event(Some(Duration::from_secs(1))) + .unwrap()); + let event = chip.lock().unwrap().read_info_event().unwrap(); + let ts_rel = event.timestamp(); + + assert_eq!(event.event_type().unwrap(), InfoChangeKind::LineReleased); + + // No events available + assert!(!chip + .lock() + .unwrap() + .wait_info_event(Some(Duration::from_millis(100))) + .unwrap()); + + // Check timestamps are really monotonic. + assert!(ts_rel > ts_rec); + assert!(ts_rec > ts_req); + } + } +} diff --git a/bindings/rust/libgpiod/tests/line_config.rs b/bindings/rust/libgpiod/tests/line_config.rs new file mode 100644 index 000000000000..bebf106f3f8b --- /dev/null +++ b/bindings/rust/libgpiod/tests/line_config.rs @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar viresh.kumar@linaro.org + +mod common; + +mod line_config { + use libgpiod::line::{ + self, Bias, Direction, Drive, Edge, EventClock, SettingKind, SettingVal, Value, + }; + + #[test] + fn settings() { + let mut lsettings1 = line::Settings::new().unwrap(); + lsettings1 + .set_direction(Direction::Input) + .unwrap() + .set_edge_detection(Some(Edge::Both)) + .unwrap() + .set_bias(Some(Bias::PullDown)) + .unwrap() + .set_drive(Drive::PushPull) + .unwrap(); + + let mut lsettings2 = line::Settings::new().unwrap(); + lsettings2 + .set_direction(Direction::Output) + .unwrap() + .set_active_low(true) + .set_event_clock(EventClock::Realtime) + .unwrap() + .set_output_value(Value::Active) + .unwrap(); + + // Add settings for multiple lines + let lconfig = line::Config::new().unwrap(); + lconfig.add_line_settings(&[0, 1, 2], lsettings1).unwrap(); + lconfig.add_line_settings(&[4, 5], lsettings2).unwrap(); + + // Retrieve settings + let lsettings = lconfig.line_settings(1).unwrap(); + assert_eq!( + lsettings.prop(SettingKind::Direction).unwrap(), + SettingVal::Direction(Direction::Input) + ); + assert_eq!( + lsettings.prop(SettingKind::EdgeDetection).unwrap(), + SettingVal::EdgeDetection(Some(Edge::Both)) + ); + assert_eq!( + lsettings.prop(SettingKind::Bias).unwrap(), + SettingVal::Bias(Some(Bias::PullDown)) + ); + assert_eq!( + lsettings.prop(SettingKind::Drive).unwrap(), + SettingVal::Drive(Drive::PushPull) + ); + + let lsettings = lconfig.line_settings(5).unwrap(); + assert_eq!( + lsettings.prop(SettingKind::Direction).unwrap(), + SettingVal::Direction(Direction::Output) + ); + assert_eq!( + lsettings.prop(SettingKind::ActiveLow).unwrap(), + SettingVal::ActiveLow(true) + ); + assert_eq!( + lsettings.prop(SettingKind::EventClock).unwrap(), + SettingVal::EventClock(EventClock::Realtime) + ); + assert_eq!( + lsettings.prop(SettingKind::OutputValue).unwrap(), + SettingVal::OutputValue(Value::Active) + ); + } + + #[test] + fn offsets() { + let mut lsettings1 = line::Settings::new().unwrap(); + lsettings1.set_direction(Direction::Input).unwrap(); + + let mut lsettings2 = line::Settings::new().unwrap(); + lsettings2.set_event_clock(EventClock::Realtime).unwrap(); + + // Add settings for multiple lines + let lconfig = line::Config::new().unwrap(); + lconfig.add_line_settings(&[0, 1, 2], lsettings1).unwrap(); + lconfig.add_line_settings(&[4, 5], lsettings2).unwrap(); + + // Verify offsets + let offsets = lconfig.offsets().unwrap(); + assert_eq!(offsets, [0, 1, 2, 4, 5]); + } +} diff --git a/bindings/rust/libgpiod/tests/line_info.rs b/bindings/rust/libgpiod/tests/line_info.rs new file mode 100644 index 000000000000..9e4928b49682 --- /dev/null +++ b/bindings/rust/libgpiod/tests/line_info.rs @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar viresh.kumar@linaro.org + +mod common; + +mod line_info { + use libc::EINVAL; + use std::time::Duration; + + use crate::common::*; + use gpiosim_sys::{Direction as SimDirection, Sim}; + use libgpiod::{ + chip::Chip, + line::{Bias, Direction, Drive, Edge, EventClock}, + Error as ChipError, OperationType, + }; + + const NGPIO: usize = 8; + + mod properties { + use super::*; + + #[test] + fn default() { + let sim = Sim::new(Some(NGPIO), None, false).unwrap(); + sim.set_line_name(4, "four").unwrap(); + sim.hog_line(4, "hog4", SimDirection::OutputLow).unwrap(); + sim.enable().unwrap(); + + let chip = Chip::open(&sim.dev_path()).unwrap(); + + let info4 = chip.line_info(4).unwrap(); + assert_eq!(info4.offset(), 4); + assert_eq!(info4.name().unwrap(), "four"); + assert!(info4.is_used()); + assert_eq!(info4.consumer().unwrap(), "hog4"); + assert_eq!(info4.direction().unwrap(), Direction::Output); + assert!(!info4.is_active_low()); + assert_eq!(info4.bias().unwrap(), None); + assert_eq!(info4.drive().unwrap(), Drive::PushPull); + assert_eq!(info4.edge_detection().unwrap(), None); + assert_eq!(info4.event_clock().unwrap(), EventClock::Monotonic); + assert!(!info4.is_debounced()); + assert_eq!(info4.debounce_period(), Duration::from_millis(0)); + + assert_eq!( + chip.line_info(NGPIO as u32).unwrap_err(), + ChipError::OperationFailed(OperationType::ChipGetLineInfo, errno::Errno(EINVAL)) + ); + } + + #[test] + fn name_and_offset() { + let sim = Sim::new(Some(NGPIO), None, false).unwrap(); + + // Line 0 has no name + for i in 1..NGPIO { + sim.set_line_name(i as u32, &i.to_string()).unwrap(); + } + sim.enable().unwrap(); + + let chip = Chip::open(&sim.dev_path()).unwrap(); + let info = chip.line_info(0).unwrap(); + + assert_eq!(info.offset(), 0); + assert_eq!( + info.name().unwrap_err(), + ChipError::NullString("GPIO line's name") + ); + + for i in 1..NGPIO { + let info = chip.line_info(i as u32).unwrap(); + + assert_eq!(info.offset(), i as u32); + assert_eq!(info.name().unwrap(), &i.to_string()); + } + } + + #[test] + fn is_used() { + let sim = Sim::new(Some(NGPIO), None, false).unwrap(); + sim.hog_line(0, "hog", SimDirection::OutputHigh).unwrap(); + sim.enable().unwrap(); + + let chip = Chip::open(&sim.dev_path()).unwrap(); + + let info = chip.line_info(0).unwrap(); + assert!(info.is_used()); + + let info = chip.line_info(1).unwrap(); + assert!(!info.is_used()); + } + + #[test] + fn consumer() { + let sim = Sim::new(Some(NGPIO), None, false).unwrap(); + sim.hog_line(0, "hog", SimDirection::OutputHigh).unwrap(); + sim.enable().unwrap(); + + let chip = Chip::open(&sim.dev_path()).unwrap(); + + let info = chip.line_info(0).unwrap(); + assert_eq!(info.consumer().unwrap(), "hog"); + + let info = chip.line_info(1).unwrap(); + assert_eq!( + info.consumer().unwrap_err(), + ChipError::NullString("GPIO line's consumer name") + ); + } + + #[test] + fn direction() { + let sim = Sim::new(Some(NGPIO), None, false).unwrap(); + sim.hog_line(0, "hog", SimDirection::Input).unwrap(); + sim.hog_line(1, "hog", SimDirection::OutputHigh).unwrap(); + sim.hog_line(2, "hog", SimDirection::OutputLow).unwrap(); + sim.enable().unwrap(); + + let chip = Chip::open(&sim.dev_path()).unwrap(); + + let info = chip.line_info(0).unwrap(); + assert_eq!(info.direction().unwrap(), Direction::Input); + + let info = chip.line_info(1).unwrap(); + assert_eq!(info.direction().unwrap(), Direction::Output); + + let info = chip.line_info(2).unwrap(); + assert_eq!(info.direction().unwrap(), Direction::Output); + } + + #[test] + fn bias() { + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.bias().unwrap(), None); + + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_bias(Direction::Input, Some(Bias::PullUp)); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.bias().unwrap(), Some(Bias::PullUp)); + + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_bias(Direction::Input, Some(Bias::PullDown)); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.bias().unwrap(), Some(Bias::PullDown)); + + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_bias(Direction::Input, Some(Bias::Disabled)); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.bias().unwrap(), Some(Bias::Disabled)); + } + + #[test] + fn drive() { + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.drive().unwrap(), Drive::PushPull); + + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_drive(Direction::Input, Drive::PushPull); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.drive().unwrap(), Drive::PushPull); + + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_drive(Direction::Output, Drive::OpenDrain); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.drive().unwrap(), Drive::OpenDrain); + + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_drive(Direction::Output, Drive::OpenSource); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.drive().unwrap(), Drive::OpenSource); + } + + #[test] + fn edge() { + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.edge_detection().unwrap(), None); + + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_edge(Some(Direction::Input), Some(Edge::Both)); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.edge_detection().unwrap(), Some(Edge::Both)); + + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_edge(Some(Direction::Input), Some(Edge::Rising)); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.edge_detection().unwrap(), Some(Edge::Rising)); + + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_edge(Some(Direction::Input), Some(Edge::Falling)); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.edge_detection().unwrap(), Some(Edge::Falling)); + } + + #[test] + fn event_clock() { + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.event_clock().unwrap(), EventClock::Monotonic); + + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_clock(EventClock::Monotonic); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.event_clock().unwrap(), EventClock::Monotonic); + + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_clock(EventClock::Realtime); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.event_clock().unwrap(), EventClock::Realtime); + } + + #[test] + #[ignore] + fn event_clock_hte() { + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_clock(EventClock::HTE); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.event_clock().unwrap(), EventClock::HTE); + } + + #[test] + fn debounce() { + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert!(!info.is_debounced()); + assert_eq!(info.debounce_period(), Duration::from_millis(0)); + + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_debounce(Duration::from_millis(100)); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert!(info.is_debounced()); + assert_eq!(info.debounce_period(), Duration::from_millis(100)); + } + } +} diff --git a/bindings/rust/libgpiod/tests/line_request.rs b/bindings/rust/libgpiod/tests/line_request.rs new file mode 100644 index 000000000000..286cd6ca6722 --- /dev/null +++ b/bindings/rust/libgpiod/tests/line_request.rs @@ -0,0 +1,509 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar viresh.kumar@linaro.org + +mod common; + +mod line_request { + use libc::{E2BIG, EINVAL}; + use std::time::Duration; + + use crate::common::*; + use gpiosim_sys::{Pull, Value as SimValue}; + use libgpiod::{ + line::{ + self, Bias, Direction, Drive, Edge, EventClock, Offset, SettingVal, Value, ValueMap, + }, + Error as ChipError, OperationType, + }; + + const NGPIO: usize = 8; + + mod invalid_arguments { + use super::*; + + #[test] + fn no_offsets() { + let mut config = TestConfig::new(NGPIO).unwrap(); + + assert_eq!( + config.request_lines().unwrap_err(), + ChipError::OperationFailed(OperationType::ChipRequestLines, errno::Errno(EINVAL)) + ); + } + + #[test] + fn out_of_bound_offsets() { + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_add_settings(&[2, 0, 8, 4]); + + assert_eq!( + config.request_lines().unwrap_err(), + ChipError::OperationFailed(OperationType::ChipRequestLines, errno::Errno(EINVAL)) + ); + } + + #[test] + fn dir_out_edge_failure() { + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_edge(Some(Direction::Output), Some(Edge::Both)); + config.lconfig_add_settings(&[0]); + + assert_eq!( + config.request_lines().unwrap_err(), + ChipError::OperationFailed(OperationType::ChipRequestLines, errno::Errno(EINVAL)) + ); + } + } + + mod verify { + use super::*; + + #[test] + fn custom_consumer() { + const GPIO: Offset = 2; + const CONSUMER: &str = "foobar"; + let mut config = TestConfig::new(NGPIO).unwrap(); + config.rconfig_set_consumer(CONSUMER); + config.lconfig_add_settings(&[GPIO]); + config.request_lines().unwrap(); + + let info = config.chip().line_info(GPIO).unwrap(); + + assert!(info.is_used()); + assert_eq!(info.consumer().unwrap(), CONSUMER); + } + + #[test] + fn empty_consumer() { + const GPIO: Offset = 2; + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_add_settings(&[GPIO]); + config.request_lines().unwrap(); + + let info = config.chip().line_info(GPIO).unwrap(); + + assert!(info.is_used()); + assert_eq!(info.consumer().unwrap(), "?"); + } + + #[test] + fn read_values() { + let offsets = [7, 1, 0, 6, 2]; + let pulls = [Pull::Up, Pull::Up, Pull::Down, Pull::Up, Pull::Down]; + let mut config = TestConfig::new(NGPIO).unwrap(); + config.set_pull(&offsets, &pulls); + config.lconfig_val(Some(Direction::Input), None); + config.lconfig_add_settings(&offsets); + config.request_lines().unwrap(); + + let request = config.request(); + + // Single values read properly + assert_eq!(request.value(7).unwrap(), Value::Active); + + // Values read properly + let map = request.values().unwrap(); + for i in 0..offsets.len() { + assert_eq!( + *map.get(offsets[i].into()).unwrap(), + match pulls[i] { + Pull::Down => Value::InActive, + _ => Value::Active, + } + ); + } + + // Subset of values read properly + let map = request.values_subset(&[2, 0, 6]).unwrap(); + assert_eq!(*map.get(2).unwrap(), Value::InActive); + assert_eq!(*map.get(0).unwrap(), Value::InActive); + assert_eq!(*map.get(6).unwrap(), Value::Active); + + // Value read properly after reconfigure + let mut lsettings = line::Settings::new().unwrap(); + lsettings.set_active_low(true); + let lconfig = line::Config::new().unwrap(); + lconfig.add_line_settings(&offsets, lsettings).unwrap(); + request.reconfigure_lines(&lconfig).unwrap(); + assert_eq!(request.value(7).unwrap(), Value::InActive); + } + + #[test] + fn set_output_values() { + let offsets = [0, 1, 3, 4]; + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_val(Some(Direction::Output), Some(Value::Active)); + config.lconfig_add_settings(&offsets); + config.request_lines().unwrap(); + + assert_eq!(config.sim_val(0).unwrap(), SimValue::Active); + assert_eq!(config.sim_val(1).unwrap(), SimValue::Active); + assert_eq!(config.sim_val(3).unwrap(), SimValue::Active); + assert_eq!(config.sim_val(4).unwrap(), SimValue::Active); + + // Default + assert_eq!(config.sim_val(2).unwrap(), SimValue::InActive); + } + + #[test] + fn update_output_values() { + let offsets = [0, 1, 3, 4]; + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_val(Some(Direction::Output), Some(Value::InActive)); + config.lconfig_add_settings(&offsets); + config.request_lines().unwrap(); + let request = config.request(); + + // Set single value + request.set_value(1, Value::Active).unwrap(); + assert_eq!(config.sim_val(0).unwrap(), SimValue::InActive); + assert_eq!(config.sim_val(1).unwrap(), SimValue::Active); + assert_eq!(config.sim_val(3).unwrap(), SimValue::InActive); + assert_eq!(config.sim_val(4).unwrap(), SimValue::InActive); + request.set_value(1, Value::InActive).unwrap(); + assert_eq!(config.sim_val(1).unwrap(), SimValue::InActive); + + // Set values of subset + let mut map = ValueMap::new(); + map.insert(4, Value::Active); + map.insert(3, Value::Active); + request.set_values_subset(map).unwrap(); + assert_eq!(config.sim_val(0).unwrap(), SimValue::InActive); + assert_eq!(config.sim_val(1).unwrap(), SimValue::InActive); + assert_eq!(config.sim_val(3).unwrap(), SimValue::Active); + assert_eq!(config.sim_val(4).unwrap(), SimValue::Active); + + let mut map = ValueMap::new(); + map.insert(4, Value::InActive); + map.insert(3, Value::InActive); + request.set_values_subset(map).unwrap(); + assert_eq!(config.sim_val(3).unwrap(), SimValue::InActive); + assert_eq!(config.sim_val(4).unwrap(), SimValue::InActive); + + // Set all values + request + .set_values(&[ + Value::Active, + Value::InActive, + Value::Active, + Value::InActive, + ]) + .unwrap(); + assert_eq!(config.sim_val(0).unwrap(), SimValue::Active); + assert_eq!(config.sim_val(1).unwrap(), SimValue::InActive); + assert_eq!(config.sim_val(3).unwrap(), SimValue::Active); + assert_eq!(config.sim_val(4).unwrap(), SimValue::InActive); + request + .set_values(&[ + Value::InActive, + Value::InActive, + Value::InActive, + Value::InActive, + ]) + .unwrap(); + assert_eq!(config.sim_val(0).unwrap(), SimValue::InActive); + assert_eq!(config.sim_val(1).unwrap(), SimValue::InActive); + assert_eq!(config.sim_val(3).unwrap(), SimValue::InActive); + assert_eq!(config.sim_val(4).unwrap(), SimValue::InActive); + } + + #[test] + fn set_bias() { + let offsets = [3]; + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_bias(Direction::Input, Some(Bias::PullUp)); + config.lconfig_add_settings(&offsets); + config.request_lines().unwrap(); + config.request(); + + // Set single value + assert_eq!(config.sim_val(3).unwrap(), SimValue::Active); + } + + #[test] + fn no_events() { + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_edge(None, Some(Edge::Both)); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + + // No events available + assert!(!config + .request() + .wait_edge_event(Some(Duration::from_millis(100))) + .unwrap()); + } + } + + mod reconfigure { + use super::*; + + #[test] + fn e2big() { + let offsets = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + let mut config = TestConfig::new(16).unwrap(); + config.lconfig_add_settings(&offsets); + config.request_lines().unwrap(); + + let request = config.request(); + + // Reconfigure + let mut lsettings = line::Settings::new().unwrap(); + lsettings.set_direction(Direction::Input).unwrap(); + let lconfig = line::Config::new().unwrap(); + + // The uAPI config has only 10 attribute slots, this should pass. + for offset in offsets { + lsettings.set_debounce_period(Duration::from_millis((100 + offset).into())); + lconfig + .add_line_settings(&[offset as Offset], lsettings.settings_clone().unwrap()) + .unwrap(); + } + + assert!(request.reconfigure_lines(&lconfig).is_ok()); + + // The uAPI config has only 10 attribute slots, and this is the 11th entry. + // This should fail with E2BIG. + lsettings.set_debounce_period(Duration::from_millis(100 + 11)); + lconfig.add_line_settings(&[11], lsettings).unwrap(); + + assert_eq!( + request.reconfigure_lines(&lconfig).unwrap_err(), + ChipError::OperationFailed( + OperationType::LineRequestReconfigLines, + errno::Errno(E2BIG), + ) + ); + } + + #[test] + fn bias() { + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.bias().unwrap(), None); + + let request = config.request(); + + // Reconfigure + let lconfig = line::Config::new().unwrap(); + let mut lsettings = line::Settings::new().unwrap(); + lsettings + .set_prop(&[ + SettingVal::Direction(Direction::Input), + SettingVal::Bias(Some(Bias::PullUp)), + ]) + .unwrap(); + lconfig.add_line_settings(&[0], lsettings).unwrap(); + request.reconfigure_lines(&lconfig).unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.bias().unwrap(), Some(Bias::PullUp)); + + let lconfig = line::Config::new().unwrap(); + let mut lsettings = line::Settings::new().unwrap(); + lsettings + .set_prop(&[ + SettingVal::Direction(Direction::Input), + SettingVal::Bias(Some(Bias::PullDown)), + ]) + .unwrap(); + lconfig.add_line_settings(&[0], lsettings).unwrap(); + request.reconfigure_lines(&lconfig).unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.bias().unwrap(), Some(Bias::PullDown)); + + let lconfig = line::Config::new().unwrap(); + let mut lsettings = line::Settings::new().unwrap(); + lsettings + .set_prop(&[ + SettingVal::Direction(Direction::Input), + SettingVal::Bias(Some(Bias::Disabled)), + ]) + .unwrap(); + lconfig.add_line_settings(&[0], lsettings).unwrap(); + request.reconfigure_lines(&lconfig).unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.bias().unwrap(), Some(Bias::Disabled)); + } + + #[test] + fn drive() { + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.drive().unwrap(), Drive::PushPull); + + let request = config.request(); + + // Reconfigure + let lconfig = line::Config::new().unwrap(); + let mut lsettings = line::Settings::new().unwrap(); + lsettings + .set_prop(&[ + SettingVal::Direction(Direction::Input), + SettingVal::Drive(Drive::PushPull), + ]) + .unwrap(); + lconfig.add_line_settings(&[0], lsettings).unwrap(); + request.reconfigure_lines(&lconfig).unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.drive().unwrap(), Drive::PushPull); + + let lconfig = line::Config::new().unwrap(); + let mut lsettings = line::Settings::new().unwrap(); + lsettings + .set_prop(&[ + SettingVal::Direction(Direction::Output), + SettingVal::Drive(Drive::OpenDrain), + ]) + .unwrap(); + lconfig.add_line_settings(&[0], lsettings).unwrap(); + request.reconfigure_lines(&lconfig).unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.drive().unwrap(), Drive::OpenDrain); + + let lconfig = line::Config::new().unwrap(); + let mut lsettings = line::Settings::new().unwrap(); + lsettings + .set_prop(&[ + SettingVal::Direction(Direction::Output), + SettingVal::Drive(Drive::OpenSource), + ]) + .unwrap(); + lconfig.add_line_settings(&[0], lsettings).unwrap(); + request.reconfigure_lines(&lconfig).unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.drive().unwrap(), Drive::OpenSource); + } + + #[test] + fn edge() { + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.edge_detection().unwrap(), None); + + let request = config.request(); + + // Reconfigure + let lconfig = line::Config::new().unwrap(); + let mut lsettings = line::Settings::new().unwrap(); + lsettings + .set_prop(&[ + SettingVal::Direction(Direction::Input), + SettingVal::EdgeDetection(Some(Edge::Both)), + ]) + .unwrap(); + lconfig.add_line_settings(&[0], lsettings).unwrap(); + request.reconfigure_lines(&lconfig).unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.edge_detection().unwrap(), Some(Edge::Both)); + + let lconfig = line::Config::new().unwrap(); + let mut lsettings = line::Settings::new().unwrap(); + lsettings + .set_prop(&[ + SettingVal::Direction(Direction::Input), + SettingVal::EdgeDetection(Some(Edge::Rising)), + ]) + .unwrap(); + lconfig.add_line_settings(&[0], lsettings).unwrap(); + request.reconfigure_lines(&lconfig).unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.edge_detection().unwrap(), Some(Edge::Rising)); + + let lconfig = line::Config::new().unwrap(); + let mut lsettings = line::Settings::new().unwrap(); + lsettings + .set_prop(&[ + SettingVal::Direction(Direction::Input), + SettingVal::EdgeDetection(Some(Edge::Falling)), + ]) + .unwrap(); + lconfig.add_line_settings(&[0], lsettings).unwrap(); + request.reconfigure_lines(&lconfig).unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.edge_detection().unwrap(), Some(Edge::Falling)); + } + + #[test] + fn event_clock() { + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.event_clock().unwrap(), EventClock::Monotonic); + + let request = config.request(); + + // Reconfigure + let lconfig = line::Config::new().unwrap(); + let mut lsettings = line::Settings::new().unwrap(); + lsettings.set_event_clock(EventClock::Monotonic).unwrap(); + lconfig.add_line_settings(&[0], lsettings).unwrap(); + request.reconfigure_lines(&lconfig).unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.event_clock().unwrap(), EventClock::Monotonic); + + let lconfig = line::Config::new().unwrap(); + let mut lsettings = line::Settings::new().unwrap(); + lsettings.set_event_clock(EventClock::Realtime).unwrap(); + lconfig.add_line_settings(&[0], lsettings).unwrap(); + request.reconfigure_lines(&lconfig).unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.event_clock().unwrap(), EventClock::Realtime); + } + + #[test] + #[ignore] + fn event_clock_hte() { + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.event_clock().unwrap(), EventClock::Monotonic); + + let request = config.request(); + + // Reconfigure + let lconfig = line::Config::new().unwrap(); + let mut lsettings = line::Settings::new().unwrap(); + lsettings.set_event_clock(EventClock::HTE).unwrap(); + lconfig.add_line_settings(&[0], lsettings).unwrap(); + request.reconfigure_lines(&lconfig).unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.event_clock().unwrap(), EventClock::HTE); + } + + #[test] + fn debounce() { + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert!(!info.is_debounced()); + assert_eq!(info.debounce_period(), Duration::from_millis(0)); + + let request = config.request(); + + // Reconfigure + let lconfig = line::Config::new().unwrap(); + let mut lsettings = line::Settings::new().unwrap(); + lsettings + .set_prop(&[ + SettingVal::Direction(Direction::Input), + SettingVal::DebouncePeriod(Duration::from_millis(100)), + ]) + .unwrap(); + lconfig.add_line_settings(&[0], lsettings).unwrap(); + request.reconfigure_lines(&lconfig).unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert!(info.is_debounced()); + assert_eq!(info.debounce_period(), Duration::from_millis(100)); + } + } +} diff --git a/bindings/rust/libgpiod/tests/line_settings.rs b/bindings/rust/libgpiod/tests/line_settings.rs new file mode 100644 index 000000000000..de2148ee6018 --- /dev/null +++ b/bindings/rust/libgpiod/tests/line_settings.rs @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar viresh.kumar@linaro.org + +mod common; + +mod line_settings { + use std::time::Duration; + + use libgpiod::line::{ + self, Bias, Direction, Drive, Edge, EventClock, SettingKind, SettingVal, Value, + }; + + #[test] + fn direction() { + let mut lsettings = line::Settings::new().unwrap(); + assert_eq!( + lsettings.prop(SettingKind::Direction).unwrap(), + SettingVal::Direction(Direction::AsIs) + ); + + lsettings.set_direction(Direction::Input).unwrap(); + assert_eq!( + lsettings.prop(SettingKind::Direction).unwrap(), + SettingVal::Direction(Direction::Input) + ); + + lsettings.set_direction(Direction::Output).unwrap(); + assert_eq!( + lsettings.prop(SettingKind::Direction).unwrap(), + SettingVal::Direction(Direction::Output) + ); + } + + #[test] + fn edge_detection() { + let mut lsettings = line::Settings::new().unwrap(); + assert_eq!( + lsettings.prop(SettingKind::EdgeDetection).unwrap(), + SettingVal::EdgeDetection(None) + ); + + lsettings.set_edge_detection(Some(Edge::Both)).unwrap(); + assert_eq!( + lsettings.prop(SettingKind::EdgeDetection).unwrap(), + SettingVal::EdgeDetection(Some(Edge::Both)) + ); + + lsettings.set_edge_detection(Some(Edge::Rising)).unwrap(); + assert_eq!( + lsettings.prop(SettingKind::EdgeDetection).unwrap(), + SettingVal::EdgeDetection(Some(Edge::Rising)) + ); + + lsettings.set_edge_detection(Some(Edge::Falling)).unwrap(); + assert_eq!( + lsettings.prop(SettingKind::EdgeDetection).unwrap(), + SettingVal::EdgeDetection(Some(Edge::Falling)) + ); + } + + #[test] + fn bias() { + let mut lsettings = line::Settings::new().unwrap(); + assert_eq!( + lsettings.prop(SettingKind::Bias).unwrap(), + SettingVal::Bias(None) + ); + + lsettings.set_bias(Some(Bias::PullDown)).unwrap(); + assert_eq!( + lsettings.prop(SettingKind::Bias).unwrap(), + SettingVal::Bias(Some(Bias::PullDown)) + ); + + lsettings.set_bias(Some(Bias::PullUp)).unwrap(); + assert_eq!( + lsettings.prop(SettingKind::Bias).unwrap(), + SettingVal::Bias(Some(Bias::PullUp)) + ); + } + + #[test] + fn drive() { + let mut lsettings = line::Settings::new().unwrap(); + assert_eq!( + lsettings.prop(SettingKind::Drive).unwrap(), + SettingVal::Drive(Drive::PushPull) + ); + + lsettings.set_drive(Drive::PushPull).unwrap(); + assert_eq!( + lsettings.prop(SettingKind::Drive).unwrap(), + SettingVal::Drive(Drive::PushPull) + ); + + lsettings.set_drive(Drive::OpenDrain).unwrap(); + assert_eq!( + lsettings.prop(SettingKind::Drive).unwrap(), + SettingVal::Drive(Drive::OpenDrain) + ); + + lsettings.set_drive(Drive::OpenSource).unwrap(); + assert_eq!( + lsettings.prop(SettingKind::Drive).unwrap(), + SettingVal::Drive(Drive::OpenSource) + ); + } + + #[test] + fn active_low() { + let mut lsettings = line::Settings::new().unwrap(); + assert_eq!( + lsettings.prop(SettingKind::ActiveLow).unwrap(), + SettingVal::ActiveLow(false) + ); + + lsettings.set_active_low(true); + assert_eq!( + lsettings.prop(SettingKind::ActiveLow).unwrap(), + SettingVal::ActiveLow(true) + ); + + lsettings.set_active_low(false); + assert_eq!( + lsettings.prop(SettingKind::ActiveLow).unwrap(), + SettingVal::ActiveLow(false) + ); + } + + #[test] + fn debounce_period() { + let mut lsettings = line::Settings::new().unwrap(); + assert_eq!( + lsettings.prop(SettingKind::DebouncePeriod).unwrap(), + SettingVal::DebouncePeriod(Duration::from_millis(0)) + ); + + lsettings.set_debounce_period(Duration::from_millis(5)); + assert_eq!( + lsettings.prop(SettingKind::DebouncePeriod).unwrap(), + SettingVal::DebouncePeriod(Duration::from_millis(5)) + ); + } + + #[test] + fn event_clock() { + let mut lsettings = line::Settings::new().unwrap(); + assert_eq!( + lsettings.prop(SettingKind::EventClock).unwrap(), + SettingVal::EventClock(EventClock::Monotonic) + ); + + lsettings.set_event_clock(EventClock::Realtime).unwrap(); + assert_eq!( + lsettings.prop(SettingKind::EventClock).unwrap(), + SettingVal::EventClock(EventClock::Realtime) + ); + + lsettings.set_event_clock(EventClock::Monotonic).unwrap(); + assert_eq!( + lsettings.prop(SettingKind::EventClock).unwrap(), + SettingVal::EventClock(EventClock::Monotonic) + ); + } + + #[test] + #[ignore] + fn event_clock_hte() { + let mut lsettings = line::Settings::new().unwrap(); + assert_eq!( + lsettings.prop(SettingKind::EventClock).unwrap(), + SettingVal::EventClock(EventClock::Monotonic) + ); + + lsettings.set_event_clock(EventClock::HTE).unwrap(); + assert_eq!( + lsettings.prop(SettingKind::EventClock).unwrap(), + SettingVal::EventClock(EventClock::HTE) + ); + } + + #[test] + fn output_value() { + let mut lsettings = line::Settings::new().unwrap(); + assert_eq!( + lsettings.prop(SettingKind::OutputValue).unwrap(), + SettingVal::OutputValue(Value::InActive) + ); + + lsettings.set_output_value(Value::Active).unwrap(); + assert_eq!( + lsettings.prop(SettingKind::OutputValue).unwrap(), + SettingVal::OutputValue(Value::Active) + ); + + lsettings.set_output_value(Value::InActive).unwrap(); + assert_eq!( + lsettings.prop(SettingKind::OutputValue).unwrap(), + SettingVal::OutputValue(Value::InActive) + ); + } +} diff --git a/bindings/rust/libgpiod/tests/request_config.rs b/bindings/rust/libgpiod/tests/request_config.rs new file mode 100644 index 000000000000..8c676384b7c3 --- /dev/null +++ b/bindings/rust/libgpiod/tests/request_config.rs @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar viresh.kumar@linaro.org + +mod common; + +mod request_config { + use libgpiod::{request, Error as ChipError, OperationType}; + + mod verify { + use super::*; + + #[test] + fn default() { + let rconfig = request::Config::new().unwrap(); + + assert_eq!(rconfig.event_buffer_size(), 0); + assert_eq!( + rconfig.consumer().unwrap_err(), + ChipError::OperationFailed( + OperationType::RequestConfigGetConsumer, + errno::Errno(0), + ) + ); + } + + #[test] + fn initialized() { + const CONSUMER: &str = "foobar"; + let rconfig = request::Config::new().unwrap(); + rconfig.set_consumer(CONSUMER).unwrap(); + rconfig.set_event_buffer_size(64); + + assert_eq!(rconfig.event_buffer_size(), 64); + assert_eq!(rconfig.consumer().unwrap(), CONSUMER); + } + } +}
Lets make build rust bindings as well.
Reviewed-by: Kent Gibson warthog618@gmail.com Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- README | 8 +++++--- TODO | 8 -------- bindings/Makefile.am | 6 ++++++ bindings/rust/Makefile.am | 19 +++++++++++++++++++ configure.ac | 16 ++++++++++++++++ 5 files changed, 46 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..a0d0772b01cb --- /dev/null +++ b/bindings/rust/Makefile.am @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# SPDX-FileCopyrightText: 2022 Linaro Ltd. +# SPDX-FileCopyrightTest: 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 048b2ac7af8f..4a2cdb68b8b0 100644 --- a/configure.ac +++ b/configure.ac @@ -212,6 +212,21 @@ then [AC_SUBST(PYTHON_LIBS, [`$PYTHON-config --libs`])]) fi
+AC_ARG_ENABLE([bindings-rust], + [AS_HELP_STRING([--enable-bindings-rust],[enable rust bindings [default=no]])], + [if test "x$enableval" = xyes; then with_bindings_rust=true; fi], + [with_bindings_rust=false]) +AM_CONDITIONAL([WITH_BINDINGS_RUST], [test "x$with_bindings_rust" = xtrue]) + +if test "x$with_bindings_rust" = xtrue +then + AC_CHECK_PROG([has_cargo], [cargo], [true], [false]) + if test "x$has_cargo" = xfalse + then + AC_MSG_ERROR([cargo not found - needed for rust bindings]) + fi +fi + AC_CHECK_PROG([has_doxygen], [doxygen], [true], [false]) AM_CONDITIONAL([HAS_DOXYGEN], [test "x$has_doxygen" = xtrue]) if test "x$has_doxygen" = xfalse @@ -249,6 +264,7 @@ AC_CONFIG_FILES([Makefile bindings/python/examples/Makefile bindings/python/tests/Makefile bindings/python/tests/gpiosim/Makefile + bindings/rust/Makefile man/Makefile])
AC_OUTPUT
On Fri, Nov 18, 2022 at 11:45 AM Viresh Kumar viresh.kumar@linaro.org wrote:
Here is another version of the rust bindings, based of the master branch.
This stuff is great for the GPIO and Rust communities. Also, looks finished. Here is a cheerful: Acked-by: Linus Walleij linus.walleij@linaro.org
Yours, Linus Walleij
On Mon, Nov 21, 2022 at 2:46 PM Linus Walleij linus.walleij@linaro.org wrote:
On Fri, Nov 18, 2022 at 11:45 AM Viresh Kumar viresh.kumar@linaro.org wrote:
Here is another version of the rust bindings, based of the master branch.
This stuff is great for the GPIO and Rust communities. Also, looks finished. Here is a cheerful: Acked-by: Linus Walleij linus.walleij@linaro.org
Yours, Linus Walleij
Agreed. Series applied. Great work and thank you Viresh for the perseverance!
Bart
On 21-11-22, 21:22, Bartosz Golaszewski wrote:
On Mon, Nov 21, 2022 at 2:46 PM Linus Walleij linus.walleij@linaro.org wrote:
This stuff is great for the GPIO and Rust communities. Also, looks finished. Here is a cheerful: Acked-by: Linus Walleij linus.walleij@linaro.org
Thanks Linus.
Agreed. Series applied. Great work and thank you Viresh for the perseverance!
Thanks Bartosz.
Just for fun, I went back to see when I started doing this work. I shared my work with everyone for the first time on 31 Aug 2021. Almost 15 months :)
A special thanks to Miguel and Kent, who were very kind to allocate time to provide reviews for this work.
stratos-dev@op-lists.linaro.org