Hi Bartosz/Miguel,
Here is another attempt to add rust wrappers for libgpiod, hopefully it would look much better this time.
V1->V2 - Propagate proper errors returned by the kernel to the user. Improved error handling overall.
- Improved Enum support, with their own helpers to reduce redundant code.
- Improved names for methods of various structures, they are updated to match the underlying libgpiod helpers.
- SAFETY comments added to methods that return strings.
- Based off next/post-libgpiod-2.0 branch.
-- Viresh
Viresh Kumar (2): libgpiod: Generate rust FFI bindings libgpiod: Add rust wrappers
.gitignore | 7 + Cargo.toml | 14 + build.rs | 60 ++ src/bindings.h | 3 + src/bindings.rs | 16 + src/lib.rs | 1474 +++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 1574 insertions(+) create mode 100644 Cargo.toml create mode 100644 build.rs create mode 100644 src/bindings.h create mode 100644 src/bindings.rs create mode 100644 src/lib.rs
Build FFI bindings for the libgpiod helpers.
Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- .gitignore | 7 ++++++ Cargo.toml | 12 ++++++++++ build.rs | 60 +++++++++++++++++++++++++++++++++++++++++++++++++ src/bindings.h | 3 +++ src/bindings.rs | 16 +++++++++++++ src/lib.rs | 0 6 files changed, 98 insertions(+) create mode 100644 Cargo.toml create mode 100644 build.rs create mode 100644 src/bindings.h create mode 100644 src/bindings.rs create mode 100644 src/lib.rs
diff --git a/.gitignore b/.gitignore index 2d7cc7fc0758..82a9d1ea2879 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,10 @@ libtool *-libtool m4/ stamp-h1 + + +# Added by cargo + +/target +Cargo.lock +bindings_gen.rs diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 000000000000..1a43063a9d64 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "libgpiod" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +[build-dependencies] +bindgen = "0.59.1" +cc = "1.0.46" diff --git a/build.rs b/build.rs new file mode 100644 index 000000000000..ee29798312d1 --- /dev/null +++ b/build.rs @@ -0,0 +1,60 @@ +extern crate bindgen; + +use std::path::PathBuf; + +fn generate_bindings() { + // Tell cargo to invalidate the built crate whenever the wrapper changes + println!("cargo:rerun-if-changed=include/gpiod.h"); + println!("cargo:rerun-if-changed=src/bindings.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("src/bindings.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("src/bindings_gen.rs"); + bindings + .write_to_file(out_path) + .expect("Couldn't write bindings!"); +} + +fn build_gpiod() { + // Tell Cargo that if the given file changes, to rerun this build script. + println!("cargo:rerun-if-changed=lib/"); + + let files = vec![ + "lib/chip.c", + "lib/edge-event.c", + "lib/info-event.c", + "lib/internal.c", + "lib/line-config.c", + "lib/line-info.c", + "lib/line-request.c", + "lib/misc.c", + "lib/request-config.c", + ]; + + // Use the `cc` crate to build a C file and statically link it. + cc::Build::new() + .files(files) + .define("_GNU_SOURCE", None) + .define("GPIOD_VERSION_STR", ""libgpio-rust"") + .include("include") + .compile("gpiod"); +} + +fn main() { + generate_bindings(); + build_gpiod(); +} diff --git a/src/bindings.h b/src/bindings.h new file mode 100644 index 000000000000..734943a920e2 --- /dev/null +++ b/src/bindings.h @@ -0,0 +1,3 @@ +#include <string.h> +#include "../include/gpiod.h" + diff --git a/src/bindings.rs b/src/bindings.rs new file mode 100644 index 000000000000..3da778daa796 --- /dev/null +++ b/src/bindings.rs @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0 + +#[allow( + clippy::all, + deref_nullptr, + dead_code, + non_camel_case_types, + non_upper_case_globals, + non_snake_case, + improper_ctypes +)] + +mod bindings_raw { + include!("bindings_gen.rs"); +} +pub use bindings_raw::*; diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 000000000000..e69de29bb2d1
Add rust wrappers around FFI bindings to provide a convenient interface for the users.
Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- Cargo.toml | 2 + src/lib.rs | 1474 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1476 insertions(+)
diff --git a/Cargo.toml b/Cargo.toml index 1a43063a9d64..caad83738b3d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,8 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] +thiserror = "1.0" +vmm-sys-util = "=0.9.0"
[build-dependencies] bindgen = "0.59.1" diff --git a/src/lib.rs b/src/lib.rs index e69de29bb2d1..7109a58cc102 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -0,0 +1,1474 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Rust wrappers for GPIOD APIs +// +// Copyright 2021 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org + +//! libgpiod public API +//! +//! This is the complete documentation of the public Rust API made available to +//! users of libgpiod. +//! +//! The API is logically split into several parts such as: GPIO chip & line +//! operators, GPIO events handling etc. + +mod bindings; + +use std::convert::TryFrom; +use std::os::raw::c_char; +use std::sync::Arc; +use std::time::Duration; +use std::{slice, str}; +use vmm_sys_util::errno::Error as IoError; + +use thiserror::Error as ThisError; + +/// Result of libgpiod operations +pub type Result<T> = std::result::Result<T, Error>; + +/// Error codes for libgpiod operations +#[derive(Debug, ThisError)] +pub enum Error { + #[error("Failed to find {0}")] + NameNotFound(&'static str), + #[error("Invalid String: {0:?}")] + InvalidString(str::Utf8Error), + #[error("Invalid {0} value: {1}")] + InvalidValue(&'static str, u32), + #[error("Operation Failed: {0}")] + OperationFailed(IoError), + #[error("Operation Timed-out")] + OperationTimedOut, +} + +/// Direction settings. +pub enum Direction { + /// Request the line(s), but didn't change current direction. + AsIs, + /// Direction is input - we're reading the state of a GPIO line. + Input, + /// Direction is output - we're driving the GPIO line. + Output, +} + +impl Direction { + fn new(dir: u32) -> Result<Self> { + match dir { + bindings::GPIOD_LINE_DIRECTION_AS_IS => Ok(Direction::AsIs), + bindings::GPIOD_LINE_DIRECTION_INPUT => Ok(Direction::Input), + bindings::GPIOD_LINE_DIRECTION_OUTPUT => Ok(Direction::Output), + _ => Err(Error::InvalidValue("direction", dir)), + } + } + + fn gpiod_direction(&self) -> u32 { + match self { + Direction::AsIs => bindings::GPIOD_LINE_DIRECTION_AS_IS, + Direction::Input => bindings::GPIOD_LINE_DIRECTION_INPUT, + Direction::Output => bindings::GPIOD_LINE_DIRECTION_OUTPUT, + } + } +} + +/// Internal bias settings. +pub enum Bias { + /// Don't change the bias setting when applying line config. + AsIs, + /// The internal bias state is unknown. + Unknown, + /// The internal bias is disabled. + Disabled, + /// The internal pull-up bias is enabled. + PullUp, + /// The internal pull-down bias is enabled. + PullDown, +} + +impl Bias { + fn new(bias: u32) -> Result<Self> { + match bias { + bindings::GPIOD_LINE_BIAS_AS_IS => Ok(Bias::AsIs), + bindings::GPIOD_LINE_BIAS_UNKNOWN => Ok(Bias::Unknown), + bindings::GPIOD_LINE_BIAS_DISABLED => Ok(Bias::Disabled), + bindings::GPIOD_LINE_BIAS_PULL_UP => Ok(Bias::PullUp), + bindings::GPIOD_LINE_BIAS_PULL_DOWN => Ok(Bias::PullDown), + _ => Err(Error::InvalidValue("bias", bias)), + } + } + + fn gpiod_bias(&self) -> u32 { + match self { + Bias::AsIs => bindings::GPIOD_LINE_BIAS_AS_IS, + Bias::Unknown => bindings::GPIOD_LINE_BIAS_UNKNOWN, + Bias::Disabled => bindings::GPIOD_LINE_BIAS_DISABLED, + Bias::PullUp => bindings::GPIOD_LINE_BIAS_PULL_UP, + Bias::PullDown => bindings::GPIOD_LINE_BIAS_PULL_DOWN, + } + } +} + +/// Drive settings. +pub enum Drive { + /// Drive setting is push-pull. + PushPull, + /// Line output is open-drain. + OpenDrain, + /// Line output is open-source. + OpenSource, +} + +impl Drive { + fn new(drive: u32) -> Result<Self> { + match drive { + bindings::GPIOD_LINE_DRIVE_PUSH_PULL => Ok(Drive::PushPull), + bindings::GPIOD_LINE_DRIVE_OPEN_DRAIN => Ok(Drive::OpenDrain), + bindings::GPIOD_LINE_DRIVE_OPEN_SOURCE => Ok(Drive::OpenSource), + _ => Err(Error::InvalidValue("drive", drive)), + } + } + + fn gpiod_drive(&self) -> u32 { + match self { + Drive::PushPull => bindings::GPIOD_LINE_DRIVE_PUSH_PULL, + Drive::OpenDrain => bindings::GPIOD_LINE_DRIVE_OPEN_DRAIN, + Drive::OpenSource => bindings::GPIOD_LINE_DRIVE_OPEN_SOURCE, + } + } +} + +/// Edge detection settings. +pub enum Edge { + /// Line edge detection is disabled. + None, + /// Line detects rising edge events. + Rising, + /// Line detect falling edge events. + Falling, + /// Line detects both rising and falling edge events. + Both, +} + +impl Edge { + fn new(edge: u32) -> Result<Self> { + match edge { + bindings::GPIOD_LINE_EDGE_NONE => Ok(Edge::None), + bindings::GPIOD_LINE_EDGE_RISING => Ok(Edge::Rising), + bindings::GPIOD_LINE_EDGE_FALLING => Ok(Edge::Falling), + bindings::GPIOD_LINE_EDGE_BOTH => Ok(Edge::Both), + _ => Err(Error::InvalidValue("edge", edge)), + } + } + + fn gpiod_edge(&self) -> u32 { + match self { + Edge::None => bindings::GPIOD_LINE_EDGE_NONE, + Edge::Rising => bindings::GPIOD_LINE_EDGE_RISING, + Edge::Falling => bindings::GPIOD_LINE_EDGE_FALLING, + Edge::Both => bindings::GPIOD_LINE_EDGE_BOTH, + } + } +} + +/// Event clock settings. +pub enum EventClock { + /// Line uses the monotonic clock for edge event timestamps. + Monotonic, + /// Line uses the realtime clock for edge event timestamps. + Realtime, +} + +impl EventClock { + fn new(clock: u32) -> Result<Self> { + match clock { + bindings::GPIOD_LINE_EVENT_CLOCK_MONOTONIC => Ok(EventClock::Monotonic), + bindings::GPIOD_LINE_EVENT_CLOCK_REALTIME => Ok(EventClock::Realtime), + _ => Err(Error::InvalidValue("event clock", clock)), + } + } + + fn gpiod_clock(&self) -> u32 { + match self { + EventClock::Monotonic => bindings::GPIOD_LINE_EVENT_CLOCK_MONOTONIC, + EventClock::Realtime => bindings::GPIOD_LINE_EVENT_CLOCK_REALTIME, + } + } +} + +/// Line status change event types. +pub enum Event { + /// Line has been requested. + LineRequested, + /// Previously requested line has been released. + LineReleased, + /// Line configuration has changed. + LineConfigChanged, +} + +impl Event { + fn new(event: u32) -> Result<Self> { + match event { + bindings::GPIOD_INFO_EVENT_LINE_REQUESTED => Ok(Event::LineRequested), + bindings::GPIOD_INFO_EVENT_LINE_RELEASED => Ok(Event::LineReleased), + bindings::GPIOD_INFO_EVENT_LINE_CONFIG_CHANGED => Ok(Event::LineConfigChanged), + _ => Err(Error::InvalidValue("event", event)), + } + } +} + +/// Edge event types. +pub enum EdgeEvent { + /// Rising edge event. + Rising, + /// Falling edge event. + Falling, +} + +impl EdgeEvent { + fn new(event: u32) -> Result<Self> { + match event { + bindings::GPIOD_EDGE_EVENT_RISING_EDGE => Ok(EdgeEvent::Rising), + bindings::GPIOD_EDGE_EVENT_FALLING_EDGE => Ok(EdgeEvent::Falling), + _ => Err(Error::InvalidValue("edge event", event)), + } + } +} + +/// 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. +struct GpiodChipInternal { + chip: *mut bindings::gpiod_chip, +} + +impl GpiodChipInternal { + /// Find a GPIO chip by path. + pub fn open(path: String) -> Result<Self> { + let chip = unsafe { bindings::gpiod_chip_open(path.as_ptr() as *const c_char) }; + if chip.is_null() { + return Err(Error::OperationFailed(IoError::last())); + } + + Ok(Self { chip }) + } + + /// Private helper, Returns gpiod_chip + fn chip(&self) -> *mut bindings::gpiod_chip { + self.chip + } +} + +impl Drop for GpiodChipInternal { + /// Close the GPIO chip and release all associated resources. + fn drop(&mut self) { + unsafe { bindings::gpiod_chip_close(self.chip) } + } +} + +pub struct GpiodChip { + ichip: Arc<GpiodChipInternal>, +} + +impl GpiodChip { + /// Find a GPIO chip by path. + pub fn open(path: String) -> Result<Self> { + Ok(Self { + ichip: Arc::new(GpiodChipInternal::open(path)?), + }) + } + + /// Get the GPIO chip name as represented in the kernel. + /// + /// SAFETY: + /// + /// The string returned by libgpiod is guaranteed to live as long as the `struct GpiodChip`. + pub fn get_name(&self) -> Result<&str> { + let name = unsafe { bindings::gpiod_chip_get_name(self.ichip.chip()) }; + if name.is_null() { + return Err(Error::NameNotFound("GPIO chip name")); + } + + unsafe { + str::from_utf8(slice::from_raw_parts( + name as *const u8, + bindings::strlen(name) as usize, + )) + .map_err(Error::InvalidString) + } + } + + /// Get the GPIO chip label as represented in the kernel. + /// + /// SAFETY: + /// + /// The string returned by libgpiod is guaranteed to live as long as the `struct GpiodChip`. + pub fn get_label(&self) -> Result<&str> { + let label = unsafe { bindings::gpiod_chip_get_label(self.ichip.chip()) }; + if label.is_null() { + return Err(Error::NameNotFound("GPIO chip label")); + } + + unsafe { + str::from_utf8(slice::from_raw_parts( + label as *const u8, + bindings::strlen(label) as usize, + )) + .map_err(Error::InvalidString) + } + } + + /// Get the path used to find this GPIO chip. + /// + /// SAFETY: + /// + /// The string returned by libgpiod is guaranteed to live as long as the `struct GpiodChip`. + pub fn get_path(&self) -> Result<&str> { + let path = unsafe { bindings::gpiod_chip_get_path(self.ichip.chip()) }; + if path.is_null() { + return Err(Error::NameNotFound("GPIO chip path")); + } + + unsafe { + str::from_utf8(slice::from_raw_parts( + path as *const u8, + bindings::strlen(path) as usize, + )) + .map_err(Error::InvalidString) + } + } + + /// Get the number of GPIO lines exposed by this chip. + pub fn get_num_lines(&self) -> u32 { + unsafe { bindings::gpiod_chip_get_num_lines(self.ichip.chip()) } + } + + /// Get the current snapshot of information about the line at given offset. + pub fn line_info(&self, offset: u32) -> Result<GpiodLineInfo> { + GpiodLineInfo::new(self.ichip.clone(), offset, false) + } + + /// Get the current snapshot of information about the line at given offset + /// and optionally start watching it for future changes. + pub fn watch_line_info(&self, offset: u32) -> Result<GpiodLineInfo> { + GpiodLineInfo::new(self.ichip.clone(), offset, true) + } + + /// Get the file descriptor associated with this chip. + /// + /// The returned file descriptor must not be closed by the caller, else other methods for the + /// `struct GpiodChip` may fail. + pub fn get_fd(&self) -> Result<u32> { + let fd = unsafe { bindings::gpiod_chip_get_fd(self.ichip.chip()) }; + + if fd < 0 { + Err(Error::OperationFailed(IoError::last())) + } else { + Ok(fd as u32) + } + } + + /// Wait for line status events on any of the watched lines exposed by this + /// chip. + pub fn info_event_wait(&self, timeout: Duration) -> Result<()> { + let ret = unsafe { + bindings::gpiod_chip_info_event_wait(self.ichip.chip(), timeout.as_nanos() as u64) + }; + + match ret { + -1 => Err(Error::OperationFailed(IoError::last())), + 0 => Err(Error::OperationTimedOut), + _ => Ok(()), + } + } + + /// Read a single line status change event from this chip. If no events are + /// pending, this function will block. + pub fn info_event_read(&self) -> Result<GpiodInfoEvent> { + GpiodInfoEvent::new(self.ichip.clone()) + } + + /// Map a GPIO line's name to its offset within the chip. + pub fn find_line(&self, name: String) -> Result<u32> { + let ret = unsafe { + bindings::gpiod_chip_find_line(self.ichip.chip(), name.as_ptr() as *const c_char) + }; + + if ret == -1 { + Err(Error::OperationFailed(IoError::last())) + } else { + Ok(ret as u32) + } + } + + /// Request a set of lines for exclusive usage. + pub fn request_lines( + &self, + rconfig: &GpiodRequestConfig, + lconfig: &GpiodLineConfig, + ) -> Result<GpiodLineRequest> { + GpiodLineRequest::new(self.ichip.clone(), rconfig, lconfig) + } +} + +/// Line info +/// +/// Exposes functions for retrieving kernel information about both requested and +/// free lines. Line info object contains an immutable snapshot of the line's +/// state at the time when it was created. +pub struct GpiodLineInfo { + info: *mut bindings::gpiod_line_info, + ichip: Option<Arc<GpiodChipInternal>>, + free: bool, +} + +impl GpiodLineInfo { + /// Get the current snapshot of information about the line at given offset + /// and optionally start watching it for changes. + fn new(ichip: Arc<GpiodChipInternal>, offset: u32, watch: bool) -> Result<Self> { + let info = if watch { + unsafe { bindings::gpiod_chip_watch_line_info(ichip.chip(), offset) } + } else { + unsafe { bindings::gpiod_chip_get_line_info(ichip.chip(), offset) } + }; + + if info.is_null() { + return Err(Error::OperationFailed(IoError::last())); + } + + Ok(Self { + info, + ichip: if watch { Some(ichip) } else { None }, + free: watch, + }) + } + + /// Stop watching the line + pub fn unwatch(&mut self) { + if let Some(ichip) = &self.ichip { + unsafe { + bindings::gpiod_chip_unwatch_line_info(ichip.chip(), self.get_offset()); + } + self.ichip = None; + } + } + + /// Get the offset of the line within the GPIO chip. + pub fn get_offset(&mut self) -> u32 { + unsafe { bindings::gpiod_line_info_get_offset(self.info) } + } + + /// Get GPIO line's name. + /// + /// SAFETY: + /// + /// The string returned by libgpiod is guaranteed to live as long as the `struct GpiodLineInfo`. + pub fn get_name(&mut self) -> Result<&str> { + let name = unsafe { bindings::gpiod_line_info_get_name(self.info) }; + if name.is_null() { + return Err(Error::NameNotFound("GPIO line's name")); + } + + unsafe { + str::from_utf8(slice::from_raw_parts( + name as *const u8, + bindings::strlen(name) as usize, + )) + .map_err(Error::InvalidString) + } + } + + /// Returns True if the line is in use, false otherwise. + /// + /// The user space can't know exactly why a line is busy. It may have been + /// requested by another process or hogged by the kernel. It only matters that + /// the line is used and we can't request it. + pub fn is_used(&mut self) -> bool { + unsafe { bindings::gpiod_line_info_is_used(self.info) } + } + + /// Get the GPIO line's consumer name. + /// + /// SAFETY: + /// + /// The string returned by libgpiod is guaranteed to live as long as the `struct GpiodLineInfo`. + pub fn get_consumer(&self) -> Result<&str> { + let name = unsafe { bindings::gpiod_line_info_get_consumer(self.info) }; + if name.is_null() { + return Err(Error::NameNotFound("GPIO line's consumer name")); + } + + unsafe { + str::from_utf8(slice::from_raw_parts( + name as *const u8, + bindings::strlen(name) as usize, + )) + .map_err(Error::InvalidString) + } + } + + /// Get the GPIO line's direction. + pub fn get_direction(&self) -> Result<Direction> { + Direction::new(unsafe { bindings::gpiod_line_info_get_direction(self.info) } as u32) + } + + /// Returns true if this line is "active-low", false otherwise. + pub fn is_active_low(&mut self) -> bool { + unsafe { bindings::gpiod_line_info_is_active_low(self.info) } + } + + /// Get the GPIO line's bias setting. + pub fn get_bias(&self) -> Result<Bias> { + Bias::new(unsafe { bindings::gpiod_line_info_get_bias(self.info) } as u32) + } + + /// Get the GPIO line's drive setting. + pub fn get_drive(&self) -> Result<Drive> { + Drive::new(unsafe { bindings::gpiod_line_info_get_drive(self.info) } as u32) + } + + /// Get the current edge detection setting of this line. + pub fn get_edge_detection(&self) -> Result<Edge> { + Edge::new(unsafe { bindings::gpiod_line_info_get_edge_detection(self.info) } as u32) + } + + /// Get the current event clock setting used for edge event timestamps. + pub fn get_event_clock(&self) -> Result<EventClock> { + EventClock::new(unsafe { bindings::gpiod_line_info_get_event_clock(self.info) } as u32) + } + + /// Returns true if the line is debounced (either by hardware or by the + /// kernel software debouncer), false otherwise. + pub fn is_debounced(&mut self) -> bool { + unsafe { bindings::gpiod_line_info_is_debounced(self.info) } + } + + /// Get the current debounce period. + pub fn get_debounce_period_us(&mut self) -> Duration { + Duration::from_micros(unsafe { + bindings::gpiod_line_info_get_debounce_period_us(self.info) + }) + } +} + +impl TryFrom<&GpiodInfoEvent> for GpiodLineInfo { + type Error = Error; + + /// Get the Line info object associated with a event. + fn try_from(event: &GpiodInfoEvent) -> Result<Self> { + let info = unsafe { bindings::gpiod_info_event_get_line_info(event.event) }; + if info.is_null() { + return Err(Error::OperationFailed(IoError::last())); + } + + Ok(Self { + info, + ichip: None, + free: false, + }) + } +} + +impl Drop for GpiodLineInfo { + fn drop(&mut self) { + // We must not free the Line info object created from `struct GpiodInfoEvent` by calling + // libgpiod API. + if self.free { + self.unwatch(); + unsafe { bindings::gpiod_line_info_free(self.info) } + } + } +} + +/// Line configuration objects. +/// +/// The line-config object stores the configuration for lines that can be used +/// in two cases: when making a line request and when reconfiguring a set of +/// already requested lines. The mutators for the line request don't return +/// errors. If the set of options is too complex to be translated into kernel +/// uAPI structures - the error will be returned at the time of the request or +/// reconfiguration. If an invalid value was passed to any of the getters - the +/// default value will be silently used instead. Each option can be set +/// globally, for a single line offset or for multiple line offsets. +pub struct GpiodLineConfig { + config: *mut bindings::gpiod_line_config, +} + +impl GpiodLineConfig { + /// Create a new line config object. + pub fn new() -> Result<Self> { + let config = unsafe { bindings::gpiod_line_config_new() }; + + if config.is_null() { + return Err(Error::OperationFailed(IoError::last())); + } + + Ok(Self { config }) + } + + /// Private helper, Returns gpiod_line_config + fn config(&self) -> *mut bindings::gpiod_line_config { + self.config + } + + /// Resets the entire configuration stored in this object. This is useful if + /// the user wants to reuse the object without reallocating it. + pub fn reset(&mut self) { + unsafe { bindings::gpiod_line_config_reset(self.config) } + } + + /// Set the direction for all lines. + pub fn set_direction(&mut self, direction: Direction) { + unsafe { + bindings::gpiod_line_config_set_direction( + self.config, + direction.gpiod_direction() as i32, + ) + } + } + + /// Set the direction for a single line at given offset. + pub fn set_direction_offset(&mut self, direction: Direction, offset: u32) { + unsafe { + bindings::gpiod_line_config_set_direction_offset( + self.config, + direction.gpiod_direction() as i32, + offset, + ) + } + } + + /// Set the direction for a subset of lines. + pub fn set_direction_subset(&mut self, direction: Direction, mut offsets: Vec<u32>) { + unsafe { + bindings::gpiod_line_config_set_direction_subset( + self.config, + direction.gpiod_direction() as i32, + offsets.len() as u32, + offsets.as_mut_ptr(), + ) + } + } + + /// Get the direction of a given line. + /// + /// If an offset is used for which no config was provided, the function will + /// return the global default value. + pub fn get_direction(&mut self, offset: u32) -> Result<Direction> { + Direction::new( + unsafe { bindings::gpiod_line_config_get_direction(self.config, offset) } as u32, + ) + } + + /// Set the edge event detection for all lines. + pub fn set_edge_detection(&mut self, edge: Edge) { + unsafe { + bindings::gpiod_line_config_set_edge_detection(self.config, edge.gpiod_edge() as i32) + } + } + + /// Set the edge event detection for a single line at given offset. + pub fn set_edge_detection_offset(&mut self, edge: Edge, offset: u32) { + unsafe { + bindings::gpiod_line_config_set_edge_detection_offset( + self.config, + edge.gpiod_edge() as i32, + offset, + ) + } + } + + /// Set the edge event detection for a subset of lines. + pub fn set_edge_detection_subset(&mut self, edge: Edge, mut offsets: Vec<u32>) { + unsafe { + bindings::gpiod_line_config_set_edge_detection_subset( + self.config, + edge.gpiod_edge() as i32, + offsets.len() as u32, + offsets.as_mut_ptr(), + ) + } + } + + /// Get the edge event detection setting for a given line. + /// + /// Returns edge event detection setting that would have been used for given + /// offset if the config object was used in a request at the time of the + /// call. If an offset is used for which no config was provided, the + /// function will return the global default value. + pub fn get_edge_detection(&mut self, offset: u32) -> Result<Edge> { + Edge::new( + unsafe { bindings::gpiod_line_config_get_edge_detection(self.config, offset) } as u32, + ) + } + + /// Set the bias of all lines. + pub fn set_bias(&mut self, bias: Bias) { + unsafe { bindings::gpiod_line_config_set_bias(self.config, bias.gpiod_bias() as i32) } + } + + /// Set the bias for a single line at given offset. + pub fn set_bias_offset(&mut self, bias: Bias, offset: u32) { + unsafe { + bindings::gpiod_line_config_set_bias_offset( + self.config, + bias.gpiod_bias() as i32, + offset, + ) + } + } + + /// Set the bias for a subset of lines. + pub fn set_bias_subset(&mut self, bias: Bias, mut offsets: Vec<u32>) { + unsafe { + bindings::gpiod_line_config_set_bias_subset( + self.config, + bias.gpiod_bias() as i32, + offsets.len() as u32, + offsets.as_mut_ptr(), + ) + } + } + + /// Get the bias setting for a given line. + /// + /// Returns Bias setting that would have been used for given offset if the + /// config object was used in a request at the time of the call. If an + /// offset is used for which no config was provided, the function will + /// return the global default value. + pub fn get_bias(&mut self, offset: u32) -> Result<Bias> { + Bias::new(unsafe { bindings::gpiod_line_config_get_bias(self.config, offset) } as u32) + } + + /// Set the drive of all lines. + pub fn set_drive(&mut self, drive: Drive) { + unsafe { bindings::gpiod_line_config_set_drive(self.config, drive.gpiod_drive() as i32) } + } + + /// Set the drive for a single line at given offset. + pub fn set_drive_offset(&mut self, drive: Drive, offset: u32) { + unsafe { + bindings::gpiod_line_config_set_drive_offset( + self.config, + drive.gpiod_drive() as i32, + offset, + ) + } + } + + /// Set the drive for a subset of lines. + pub fn set_drive_subset(&mut self, drive: Drive, mut offsets: Vec<u32>) { + unsafe { + bindings::gpiod_line_config_set_drive_subset( + self.config, + drive.gpiod_drive() as i32, + offsets.len() as u32, + offsets.as_mut_ptr(), + ) + } + } + + /// Get the drive setting for a given line. + /// + /// Returns drive setting that would have been used for given offset if the + /// config object was used in a request at the time of the call. If an + /// offset is used for which no config was provided, the function will + /// return the global default value. + pub fn get_drive(&mut self, offset: u32) -> Result<Drive> { + Drive::new(unsafe { bindings::gpiod_line_config_get_drive(self.config, offset) } as u32) + } + + /// Set all lines as active-low. + pub fn set_active_low(&mut self) { + unsafe { bindings::gpiod_line_config_set_active_low(self.config) } + } + + /// Set a single line as active-low. + pub fn set_active_low_offset(&mut self, offset: u32) { + unsafe { bindings::gpiod_line_config_set_active_low_offset(self.config, offset) } + } + + /// Set a subset of lines as active-low. + pub fn set_active_low_subset(&mut self, mut offsets: Vec<u32>) { + unsafe { + bindings::gpiod_line_config_set_active_low_subset( + self.config, + offsets.len() as u32, + offsets.as_mut_ptr(), + ) + } + } + + /// Check if the line at given offset was configured as active-low. + /// + /// Returns active-low setting that would have been used for given offset if + /// the config object was used in a request at the time of the call. If an + /// offset is used for which no config was provided, the function will + /// return the global default value. + pub fn is_active_low(&mut self, offset: u32) -> bool { + unsafe { bindings::gpiod_line_config_is_active_low(self.config, offset) } + } + + /// Set all lines as active-high. + pub fn set_active_high(&mut self) { + unsafe { bindings::gpiod_line_config_set_active_high(self.config) } + } + + /// Set a single line as active-high. + pub fn set_active_high_offset(&mut self, offset: u32) { + unsafe { bindings::gpiod_line_config_set_active_high_offset(self.config, offset) } + } + + /// Set a subset of lines as active-high. + pub fn set_active_high_subset(&mut self, mut offsets: Vec<u32>) { + unsafe { + bindings::gpiod_line_config_set_active_high_subset( + self.config, + offsets.len() as u32, + offsets.as_mut_ptr(), + ) + } + } + + /// Set the debounce period for all lines, disables debouncing if 0. + pub fn set_debounce_period_us(&mut self, period: Duration) { + unsafe { + bindings::gpiod_line_config_set_debounce_period_us( + self.config, + period.as_micros() as u64, + ) + } + } + + /// Set the debounce period for a single line at given offset, disables + /// debouncing if 0. + pub fn set_debounce_period_us_offset(&mut self, period: Duration, offset: u32) { + unsafe { + bindings::gpiod_line_config_set_debounce_period_us_offset( + self.config, + period.as_micros() as u64, + offset, + ) + } + } + + /// Set the debounce period for a subset of lines, disables debouncing if + /// `period` is 0. + pub fn set_debounce_period_us_subset(&mut self, period: Duration, mut offsets: Vec<u32>) { + unsafe { + bindings::gpiod_line_config_set_debounce_period_us_subset( + self.config, + period.as_micros() as u64, + offsets.len() as u32, + offsets.as_mut_ptr(), + ) + } + } + + /// Get the debounce period for a given line. + /// + /// Returns debounce period that would have been used for given offset if + /// the config object was used in a request at the time of the call. If an + /// offset is used for which no config was provided, the function will + /// return the global default value. + pub fn get_debounce_period_us(&mut self, offset: u32) -> Result<Duration> { + Ok(Duration::from_micros(unsafe { + bindings::gpiod_line_config_get_debounce_us_period(self.config, offset) + })) + } + + /// Set the event clock for all lines. + pub fn set_event_clock(&mut self, clock: EventClock) { + unsafe { + bindings::gpiod_line_config_set_event_clock(self.config, clock.gpiod_clock() as i32) + } + } + + /// Set the event clock for a single line at given offset. + pub fn set_event_clock_offset(&mut self, clock: EventClock, offset: u32) { + unsafe { + bindings::gpiod_line_config_set_event_clock_offset( + self.config, + clock.gpiod_clock() as i32, + offset, + ) + } + } + + /// Set the event clock for a subset of lines. + pub fn set_event_clock_subset(&mut self, clock: EventClock, mut offsets: Vec<u32>) { + unsafe { + bindings::gpiod_line_config_set_event_clock_subset( + self.config, + clock.gpiod_clock() as i32, + offsets.len() as u32, + offsets.as_mut_ptr(), + ) + } + } + + /// Get the event clock setting for a given line. + /// + /// Returns event clock setting that would have been used for given offset + /// if the config object was used in a request at the time of the call. If + /// an offset is used for which no config was provided, the function will + /// return the global default value. + pub fn get_event_clock(&mut self, offset: u32) -> Result<EventClock> { + EventClock::new( + unsafe { bindings::gpiod_line_config_get_event_clock(self.config, offset) } as u32, + ) + } + + /// Set the output value for a single offset. + pub fn set_output_value(&mut self, offset: u32, value: u32) { + unsafe { bindings::gpiod_line_config_set_output_value(self.config, offset, value as i32) } + } + + /// Set the output values for a set of offsets. + pub fn set_output_values(&mut self, mut offsets: Vec<u32>, mut values: Vec<i32>) { + unsafe { + bindings::gpiod_line_config_set_output_values( + self.config, + values.len() as u32, + offsets.as_mut_ptr(), + values.as_mut_ptr(), + ) + } + } + + /// Get the number of lines for which the config object stores values. + pub fn num_output_values(&mut self) -> u32 { + unsafe { bindings::gpiod_line_config_num_output_values(self.config) } + } + + /// Get the output value configured for a given line, 0 or 1. + pub fn get_output_value(&mut self, offset: u32) -> Result<u32> { + let value = unsafe { bindings::gpiod_line_config_get_output_value(self.config, offset) }; + + if value != 0 && value != 1 { + Err(Error::OperationFailed(IoError::last())) + } else { + Ok(value as u32) + } + } + + /// Get the output value mapping (offset, value) at given index. + /// + /// This function together with `get_num_output()` allows to iterate over + /// all output value mappings currently held by this object. + pub fn get_output_value_index(&mut self, index: u32) -> Result<(u32, u32)> { + let mut offset: u32 = 0; + let mut value: i32 = 0; + + let ret = unsafe { + bindings::gpiod_line_config_get_output_value_index( + self.config, + index, + &mut offset, + &mut value, + ) + }; + + if ret == -1 { + Err(Error::OperationFailed(IoError::last())) + } else { + Ok((offset, !!(value as u32))) + } + } + + /// Get all output value mappings stored in this config object. + /// + /// Each offset in the offsets vector corresponds to the value in the values + /// array at the same index. + pub fn get_output_values(&mut self) -> Result<(Vec<u32>, Vec<i32>)> { + let count = self.num_output_values() as usize; + let mut offset: Vec<u32> = vec![0; count]; + let mut value: Vec<i32> = vec![0; count]; + + unsafe { + bindings::gpiod_line_config_get_output_values( + self.config, + offset.as_mut_ptr(), + value.as_mut_ptr(), + ) + }; + + Ok((offset, value)) + } +} + +impl Drop for GpiodLineConfig { + /// Free the line config object and release all associated resources. + fn drop(&mut self) { + unsafe { bindings::gpiod_line_config_free(self.config) } + } +} + +/// Request configuration objects +/// +/// Request config object is used to pass a set of options to the kernel at the +/// time of the line request. Similarly to the line-config - the mutators don't +/// return error values. If the values are invalid, in general they are silently +/// adjusted to acceptable ranges. +pub struct GpiodRequestConfig { + config: *mut bindings::gpiod_request_config, +} + +impl GpiodRequestConfig { + /// Create a new request config object. + pub fn new() -> Result<Self> { + let config = unsafe { bindings::gpiod_request_config_new() }; + if config.is_null() { + return Err(Error::OperationFailed(IoError::last())); + } + + Ok(Self { config }) + } + + /// Private helper, Returns gpiod_request_config + fn config(&self) -> *mut bindings::gpiod_request_config { + self.config + } + + /// Set the consumer string. + /// + /// If the consumer string is too long, it will be truncated to the max + /// accepted length. + pub fn set_consumer(&self, consumer: String) { + unsafe { + bindings::gpiod_request_config_set_consumer( + self.config, + consumer.as_ptr() as *const c_char, + ) + } + } + + /// Get the consumer string. + /// + /// SAFETY: + /// + /// The string returned by libgpiod is guaranteed to live as long as the `struct GpiodRequestConfig`. + pub fn get_consumer(&self) -> Result<&str> { + let consumer = unsafe { bindings::gpiod_request_config_get_consumer(self.config) }; + if consumer.is_null() { + return Err(Error::OperationFailed(IoError::last())); + } + + unsafe { + str::from_utf8(slice::from_raw_parts( + consumer as *const u8, + bindings::strlen(consumer) as usize, + )) + .map_err(Error::InvalidString) + } + } + + /// Set line offsets for this request. + /// + /// If too many offsets were specified, the offsets above the limit accepted + /// by the kernel (64 lines) are silently dropped. + pub fn set_offsets(&self, mut offsets: Vec<u32>) { + unsafe { + bindings::gpiod_request_config_set_offsets( + self.config, + offsets.len() as u32, + offsets.as_mut_ptr(), + ) + } + } + + /// Get the offsets of lines in this request config. + pub fn get_num_offsets(&self) -> Vec<u32> { + let num = unsafe { bindings::gpiod_request_config_get_num_offsets(self.config) }; + let mut offsets = vec![0, num]; + + unsafe { bindings::gpiod_request_config_get_offsets(self.config, offsets.as_mut_ptr()) }; + offsets + } + + /// Set the size of the kernel event buffer. + /// + /// The kernel may adjust the value if it's too high. If set to 0, the + /// default value will be used. + pub fn set_event_buffer_size(&self, size: u32) { + unsafe { bindings::gpiod_request_config_set_event_buffer_size(self.config, size) } + } + + /// Get the edge event buffer size from this request config. + pub fn get_event_buffer_size(&self) -> u32 { + unsafe { bindings::gpiod_request_config_get_event_buffer_size(self.config) } + } +} + +impl Drop for GpiodRequestConfig { + /// Free the request config object and release all associated resources. + fn drop(&mut self) { + unsafe { bindings::gpiod_request_config_free(self.config) } + } +} + +/// Line request operations +/// +/// Allows interaction with a set of requested lines. +pub struct GpiodLineRequest { + request: *mut bindings::gpiod_line_request, +} + +impl GpiodLineRequest { + /// Request a set of lines for exclusive usage. + fn new( + ichip: Arc<GpiodChipInternal>, + rconfig: &GpiodRequestConfig, + lconfig: &GpiodLineConfig, + ) -> Result<Self> { + let request = unsafe { + bindings::gpiod_chip_request_lines(ichip.chip(), rconfig.config(), lconfig.config()) + }; + + if request.is_null() { + return Err(Error::OperationFailed(IoError::last())); + } + + Ok(Self { request }) + } + + /// Get the number of lines in this request. + pub fn get_num_lines(&self) -> u32 { + unsafe { bindings::gpiod_line_request_get_num_lines(self.request) } + } + + /// Get the offsets of lines in this request. + pub fn get_offsets(&self) -> Vec<u32> { + let mut offsets = vec![0; self.get_num_lines() as usize]; + + unsafe { bindings::gpiod_line_request_get_offsets(self.request, offsets.as_mut_ptr()) }; + offsets + } + + /// Get values of all lines associated with this request. + pub fn get_values(&self, mut values: Vec<i32>) -> Result<()> { + let ret = + unsafe { bindings::gpiod_line_request_get_values(self.request, values.as_mut_ptr()) }; + + if ret == -1 { + Err(Error::OperationFailed(IoError::last())) + } else { + Ok(()) + } + } + + /// Get the value (0 or 1) of a single line associated with this request. + pub fn get_value(&self, offset: u32) -> Result<u32> { + let value = unsafe { bindings::gpiod_line_request_get_value(self.request, offset) }; + + if value != 0 && value != 1 { + Err(Error::OperationFailed(IoError::last())) + } else { + Ok(value as u32) + } + } + + /// Get values of a subset of lines associated with this request. + pub fn get_values_subset(&self, mut offsets: Vec<u32>, mut values: Vec<i32>) -> Result<()> { + let ret = unsafe { + bindings::gpiod_line_request_get_values_subset( + self.request, + offsets.len() as u32, + offsets.as_mut_ptr(), + values.as_mut_ptr(), + ) + }; + + if ret == -1 { + Err(Error::OperationFailed(IoError::last())) + } else { + Ok(()) + } + } + + /// Get values of all lines associated with this request. + pub fn set_values(&self, mut values: Vec<i32>) -> Result<()> { + let ret = + unsafe { bindings::gpiod_line_request_set_values(self.request, values.as_mut_ptr()) }; + + if ret == -1 { + Err(Error::OperationFailed(IoError::last())) + } else { + Ok(()) + } + } + + /// Set the value of a single line associated with this request. + pub fn set_value(&self, offset: u32, value: i32) -> Result<()> { + let ret = unsafe { bindings::gpiod_line_request_set_value(self.request, offset, !!value) }; + + if ret == -1 { + Err(Error::OperationFailed(IoError::last())) + } else { + Ok(()) + } + } + + /// Get values of a subset of lines associated with this request. + pub fn set_values_subset(&self, mut offsets: Vec<u32>, mut values: Vec<i32>) -> Result<()> { + let ret = unsafe { + bindings::gpiod_line_request_set_values_subset( + self.request, + offsets.len() as u32, + offsets.as_mut_ptr(), + values.as_mut_ptr(), + ) + }; + + if ret == -1 { + Err(Error::OperationFailed(IoError::last())) + } else { + Ok(()) + } + } + + /// Update the configuration of lines associated with this line request. + pub fn reconfigure_lines(&self, lconfig: GpiodLineConfig) -> Result<()> { + let ret = unsafe { + bindings::gpiod_line_request_reconfigure_lines(self.request, lconfig.config()) + }; + + if ret == -1 { + Err(Error::OperationFailed(IoError::last())) + } else { + Ok(()) + } + } + + /// Get the file descriptor associated with this line request. + pub fn get_fd(&self) -> u32 { + unsafe { bindings::gpiod_line_request_get_fd(self.request) as u32 } + } + + /// Wait for edge events on any of the lines associated with this request. + pub fn edge_event_wait(&self, timeout: Duration) -> Result<()> { + let ret = unsafe { + bindings::gpiod_line_request_edge_event_wait(self.request, timeout.as_nanos() as u64) + }; + + match ret { + -1 => Err(Error::OperationFailed(IoError::last())), + 0 => Err(Error::OperationTimedOut), + _ => Ok(()), + } + } + + /// Get a number of edge events from a line request. + /// + /// This function will block if no event was queued for this line. + pub fn edge_event_read(&self, buffer: GpiodEdgeEventBuffer, max_events: u32) -> Result<u32> { + let ret = unsafe { + bindings::gpiod_line_request_edge_event_read(self.request, buffer.buffer(), max_events) + }; + + if ret == -1 { + Err(Error::OperationFailed(IoError::last())) + } else { + Ok(ret as u32) + } + } +} + +impl Drop for GpiodLineRequest { + /// Release the requested lines and free all associated resources. + fn drop(&mut self) { + unsafe { bindings::gpiod_line_request_release(self.request) } + } +} + +/// Line status watch events +/// +/// Accessors for the info event objects allowing to monitor changes in GPIO +/// line state. +/// +/// Callers can be notified about changes in line's state using the interfaces +/// exposed by GPIO chips. Each info event contains information about the event +/// itself (timestamp, type) as well as a snapshot of line's state in the form +/// of a line-info object. + +pub struct GpiodInfoEvent { + event: *mut bindings::gpiod_info_event, +} + +impl GpiodInfoEvent { + /// Get a single chip's line's status change event. + fn new(ichip: Arc<GpiodChipInternal>) -> Result<Self> { + let event = unsafe { bindings::gpiod_chip_info_event_read(ichip.chip()) }; + if event.is_null() { + return Err(Error::OperationFailed(IoError::last())); + } + + Ok(Self { event }) + } + + /// Get the event type of this status change event. + pub fn get_event_type(&self) -> Result<Event> { + Event::new(unsafe { bindings::gpiod_info_event_get_event_type(self.event) } as u32) + } + + /// Get the timestamp of the event. + pub fn get_timestamp(&self) -> Duration { + Duration::from_nanos(unsafe { bindings::gpiod_info_event_get_timestamp(self.event) }) + } + + /// Get the line-info object associated with this event. + pub fn line_info(&self) -> Result<GpiodLineInfo> { + GpiodLineInfo::try_from(self) + } +} + +impl Drop for GpiodInfoEvent { + /// Free the info event object and release all associated resources. + fn drop(&mut self) { + unsafe { bindings::gpiod_info_event_free(self.event) } + } +} + +/// Line edge events handling +/// +/// An edge event object contains information about a single line event. It +/// contains the event type, timestamp and the offset of the line on which the +/// event occurred as well as two sequential numbers (global for all lines +/// associated with the parent chip and local for this line only). +/// +/// For performance and to limit the number of memory allocations when a lot of +/// events are being read, edge events are stored in an edge-event buffer object. + +pub struct GpiodEdgeEvent { + event: *mut bindings::gpiod_edge_event, +} + +impl GpiodEdgeEvent { + /// Get an event stored in the buffer. + pub fn new(buffer: &GpiodEdgeEventBuffer, index: u64) -> Result<Self> { + let event = unsafe { bindings::gpiod_edge_event_buffer_get_event(buffer.buffer(), index) }; + if event.is_null() { + return Err(Error::OperationFailed(IoError::last())); + } + + Ok(Self { event }) + } + + /// Get the event type. + pub fn get_event_type(&self) -> Result<EdgeEvent> { + EdgeEvent::new(unsafe { bindings::gpiod_edge_event_get_event_type(self.event) } as u32) + } + + /// Get the timestamp of the event. + pub fn get_timestamp(&self) -> Duration { + Duration::from_nanos(unsafe { bindings::gpiod_edge_event_get_timestamp(self.event) }) + } + + /// Get the offset of the line on which the event was triggered. + pub fn get_line_offset(&self) -> u32 { + unsafe { bindings::gpiod_edge_event_get_line_offset(self.event) } + } + + /// Get the global sequence number of this event. + /// + /// Returns sequence number of the event relative to all lines in the + /// associated line request. + pub fn get_global_seqno(&self) -> u64 { + unsafe { bindings::gpiod_edge_event_get_global_seqno(self.event) } + } + + /// Get the event sequence number specific to concerned line. + /// + /// Returns sequence number of the event relative to this line within the + /// lifetime of the associated line request. + pub fn get_line_seqno(&self) -> u64 { + unsafe { bindings::gpiod_edge_event_get_line_seqno(self.event) } + } +} + +impl Drop for GpiodEdgeEvent { + /// Free the edge event object and release all associated resources. + fn drop(&mut self) { + unsafe { bindings::gpiod_edge_event_free(self.event) } + } +} + +/// Line edge events buffer +pub struct GpiodEdgeEventBuffer { + buffer: *mut bindings::gpiod_edge_event_buffer, +} + +impl GpiodEdgeEventBuffer { + /// Create a new edge event buffer. + /// + /// If capacity equals 0, it will be set to a default value of 64. If + /// capacity is larger than 1024, it will be limited to 1024. + pub fn new(capacity: u32) -> Result<Self> { + let buffer = unsafe { bindings::gpiod_edge_event_buffer_new(capacity) }; + if buffer.is_null() { + return Err(Error::OperationFailed(IoError::last())); + } + + Ok(Self { buffer }) + } + + /// Private helper, Returns gpiod_edge_event_buffer + fn buffer(&self) -> *mut bindings::gpiod_edge_event_buffer { + self.buffer + } + + /// Get the capacity of the event buffer. + pub fn get_capacity(&self) -> u32 { + unsafe { bindings::gpiod_edge_event_buffer_get_capacity(self.buffer) } + } + + /// Read an event stored in the buffer. + pub fn get_event(&self, index: u64) -> Result<GpiodEdgeEvent> { + GpiodEdgeEvent::new(self, index) + } + + /// Get the number of events this buffers stores. + pub fn num_events(&self) -> u32 { + unsafe { bindings::gpiod_edge_event_buffer_num_events(self.buffer) } + } +} + +impl Drop for GpiodEdgeEventBuffer { + /// Free the edge event buffer and release all associated resources. + fn drop(&mut self) { + unsafe { bindings::gpiod_edge_event_buffer_free(self.buffer) }; + } +} + +/// Various libgpiod-related functions. + +/// Check if the file pointed to by path is a GPIO chip character device. +/// +/// Returns true if the file exists and is a GPIO chip character device or a +/// symbolic link to it. +pub fn gpiod_is_gpiochip_device(path: String) -> bool { + unsafe { bindings::gpiod_is_gpiochip_device(path.as_ptr() as *const c_char) } +} + +/// Get the API version of the library as a human-readable string. +/// +/// SAFETY: +/// +/// The string returned by libgpiod is guaranteed to live forever. +pub fn gpiod_version_string() -> Result<&'static str> { + let version = unsafe { bindings::gpiod_version_string() }; + + if version.is_null() { + return Err(Error::NameNotFound("GPIO library version")); + } + + unsafe { + str::from_utf8(slice::from_raw_parts( + version as *const u8, + bindings::strlen(version) as usize, + )) + .map_err(Error::InvalidString) + } +}
Hi Viresh,
On Wed, Nov 3, 2021 at 6:03 AM Viresh Kumar viresh.kumar@linaro.org wrote:
Here is another attempt to add rust wrappers for libgpiod, hopefully it would look much better this time.
From a quick look, it does look way better!
- Propagate proper errors returned by the kernel to the user. Improved error handling overall.
I see you are using `ThisError`, nice! :)
- Improved Enum support, with their own helpers to reduce redundant code.
The new enums look much cleaner (e.g. `Direction::`) -- thanks for using that recommendation.
- SAFETY comments added to methods that return strings.
Note that these should be in the body, as a normal comment right before `unsafe` blocks explaining why the block is sound, e.g.:
// SAFETY: `x` is dereferencable by the type invariants. unsafe { *x }
I think you are confusing them with the ones in the function documentation, which are a Markdown section (`#`) used in unsafe functions to explain the preconditions on the caller:
/// Does foo and bar /// /// # Safety /// /// `x` must be a multiple of 2. unsafe fn f(x: u32) { // ... }
Cheers, Miguel
On 08-11-21, 17:27, Miguel Ojeda wrote:
Note that these should be in the body, as a normal comment right before `unsafe` blocks explaining why the block is sound, e.g.:
// SAFETY: `x` is dereferencable by the type invariants. unsafe { *x }
I think you are confusing them with the ones in the function documentation, which are a Markdown section (`#`) used in unsafe functions to explain the preconditions on the caller:
/// Does foo and bar /// /// # Safety /// /// `x` must be a multiple of 2. unsafe fn f(x: u32) { // ... }
I get that now. Actually I tried to find documentation on the Safety comments and failed to find any. Is this undocumented tribal knowledge or is really suggested by Rust documentation which I missed to find ?
On Tue, Nov 9, 2021 at 5:17 AM Viresh Kumar viresh.kumar@linaro.org wrote:
I get that now. Actually I tried to find documentation on the Safety comments and failed to find any. Is this undocumented tribal knowledge or is really suggested by Rust documentation which I missed to find ?
It is a convention used by the standard library and other libraries, and Clippy has lints for both.
For instance, see `Option::unwrap_unchecked` which has both cases: https://doc.rust-lang.org/src/core/option.rs.html#780
Note that you can still find places without the `SAFETY` comment in the standard library, though.
Cheers, Miguel
On 09-11-21, 11:57, Miguel Ojeda wrote:
It is a convention used by the standard library and other libraries, and Clippy has lints for both.
For instance, see `Option::unwrap_unchecked` which has both cases: https://doc.rust-lang.org/src/core/option.rs.html#780
Note that you can still find places without the `SAFETY` comment in the standard library, though.
Thanks.
On 03-11-21, 10:32, Viresh Kumar wrote:
Hi Bartosz,
The API gpiod_line_request_reconfigure_lines() uses the GPIO_V2_LINE_GET_VALUES_IOCTL, shouldn't this be GPIO_V2_LINE_SET_CONFIG_IOCTL instead ?
On Fri, Nov 19, 2021 at 7:11 AM Viresh Kumar viresh.kumar@linaro.org wrote:
On 03-11-21, 10:32, Viresh Kumar wrote:
Hi Bartosz,
The API gpiod_line_request_reconfigure_lines() uses the GPIO_V2_LINE_GET_VALUES_IOCTL, shouldn't this be GPIO_V2_LINE_SET_CONFIG_IOCTL instead ?
-- viresh
Hey Viresh, I only now saw this email because this account is going away. I'm no longer with baylibre (as per the MAINTAINERS file). Why isn't this on the list?
Bart
Cc'in my current address too.
On Fri, Nov 19, 2021 at 3:03 PM Bartosz Golaszewski bgolaszewski@baylibre.com wrote:
On Fri, Nov 19, 2021 at 7:11 AM Viresh Kumar viresh.kumar@linaro.org wrote:
On 03-11-21, 10:32, Viresh Kumar wrote:
Hi Bartosz,
The API gpiod_line_request_reconfigure_lines() uses the GPIO_V2_LINE_GET_VALUES_IOCTL, shouldn't this be GPIO_V2_LINE_SET_CONFIG_IOCTL instead ?
-- viresh
Hey Viresh, I only now saw this email because this account is going away. I'm no longer with baylibre (as per the MAINTAINERS file). Why isn't this on the list?
Bart
On 19-11-21, 15:03, Bartosz Golaszewski wrote:
On Fri, Nov 19, 2021 at 7:11 AM Viresh Kumar viresh.kumar@linaro.org wrote:
On 03-11-21, 10:32, Viresh Kumar wrote:
Hi Bartosz,
The API gpiod_line_request_reconfigure_lines() uses the GPIO_V2_LINE_GET_VALUES_IOCTL, shouldn't this be GPIO_V2_LINE_SET_CONFIG_IOCTL instead ?
-- viresh
Hey Viresh, I only now saw this email because this account is going away. I'm no longer with baylibre (as per the MAINTAINERS file).
I surely missed that, I just followed the old thread :(
Why isn't this on the list?
I thought this is still WIP stuff, the v2 bindings, and so didn't go online with it.
stratos-dev@op-lists.linaro.org