Hi Bartosz,
This patch adds rust bindings for libgpiod v2.0, this is already partially tested with the virtio rust backend I am developing, which uses these to talk to the host kernel.
This is based of the next/post-libgpiod-2.0 branch.
I haven't added any mock test for this as of now and I am not sure how exactly am I required to add them. I did see what you mentioned in your patchset about mock-test vs gpio-sim stuff. Rust also have its own test-framework and I am not sure if that should be used instead or something else.
Since I am posting this publicly for the first time, it is still named as V1. I have not made significant changes to the code since last time, but just divided the same into multiple files.
-- Viresh
Viresh Kumar (2): libgpiod: Generate rust FFI bindings libgpiod: Add rust wrappers
.gitignore | 6 + bindings/rust/Cargo.toml | 14 + bindings/rust/build.rs | 60 ++++ bindings/rust/src/bindings.rs | 16 ++ bindings/rust/src/chip.rs | 197 +++++++++++++ bindings/rust/src/edge_event.rs | 78 +++++ bindings/rust/src/event_buffer.rs | 59 ++++ bindings/rust/src/info_event.rs | 70 +++++ bindings/rust/src/lib.rs | 268 +++++++++++++++++ bindings/rust/src/line_config.rs | 431 ++++++++++++++++++++++++++++ bindings/rust/src/line_info.rs | 186 ++++++++++++ bindings/rust/src/line_request.rs | 218 ++++++++++++++ bindings/rust/src/request_config.rs | 118 ++++++++ bindings/rust/wrapper.h | 2 + 14 files changed, 1723 insertions(+) create mode 100644 bindings/rust/Cargo.toml create mode 100644 bindings/rust/build.rs create mode 100644 bindings/rust/src/bindings.rs create mode 100644 bindings/rust/src/chip.rs create mode 100644 bindings/rust/src/edge_event.rs create mode 100644 bindings/rust/src/event_buffer.rs create mode 100644 bindings/rust/src/info_event.rs create mode 100644 bindings/rust/src/lib.rs create mode 100644 bindings/rust/src/line_config.rs create mode 100644 bindings/rust/src/line_info.rs create mode 100644 bindings/rust/src/line_request.rs create mode 100644 bindings/rust/src/request_config.rs create mode 100644 bindings/rust/wrapper.h
Build FFI bindings for the libgpiod helpers.
Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- .gitignore | 6 ++++ bindings/rust/Cargo.toml | 12 +++++++ bindings/rust/build.rs | 60 +++++++++++++++++++++++++++++++++++ bindings/rust/src/bindings.rs | 16 ++++++++++ bindings/rust/src/lib.rs | 0 bindings/rust/wrapper.h | 2 ++ 6 files changed, 96 insertions(+) create mode 100644 bindings/rust/Cargo.toml create mode 100644 bindings/rust/build.rs create mode 100644 bindings/rust/src/bindings.rs create mode 100644 bindings/rust/src/lib.rs create mode 100644 bindings/rust/wrapper.h
diff --git a/.gitignore b/.gitignore index 2d7cc7fc0758..8b82ada3bc80 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,9 @@ libtool *-libtool m4/ stamp-h1 + +# Added by cargo + +bindings/rust/target +bindings/rust/Cargo.lock +bindings/rust/bindings_gen.rs diff --git a/bindings/rust/Cargo.toml b/bindings/rust/Cargo.toml new file mode 100644 index 000000000000..62f3d52ddb0f --- /dev/null +++ b/bindings/rust/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "libgpiod" +version = "0.1.0" +edition = "2021" + +# 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/bindings/rust/build.rs b/bindings/rust/build.rs new file mode 100644 index 000000000000..cd776332bbb9 --- /dev/null +++ b/bindings/rust/build.rs @@ -0,0 +1,60 @@ +extern crate bindgen; + +use std::env; +use std::path::PathBuf; + +fn generate_bindings() { + // Tell cargo to invalidate the built crate whenever the wrapper changes + println!("cargo:rerun-if-changed=wrapper.h"); + + // The bindgen::Builder is the main entry point + // to bindgen, and lets you build up options for + // the resulting bindings. + let bindings = bindgen::Builder::default() + // The input header we would like to generate + // bindings for. + .header("wrapper.h") + // Tell cargo to invalidate the built crate whenever any of the + // included header files changed. + .parse_callbacks(Box::new(bindgen::CargoCallbacks)) + // Finish the builder and generate the bindings. + .generate() + // Unwrap the Result and panic on failure. + .expect("Unable to generate bindings"); + + // Write the bindings to the $OUT_DIR/bindings.rs file. + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + bindings + .write_to_file(out_path.join("bindings.rs")) + .expect("Couldn't write bindings!"); +} + +fn build_gpiod() { + // 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/bindings/rust/src/bindings.rs b/bindings/rust/src/bindings.rs new file mode 100644 index 000000000000..7d6caa7d9c11 --- /dev/null +++ b/bindings/rust/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!(concat!(env!("OUT_DIR"), "/bindings.rs")); +} +pub use bindings_raw::*; diff --git a/bindings/rust/src/lib.rs b/bindings/rust/src/lib.rs new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/bindings/rust/wrapper.h b/bindings/rust/wrapper.h new file mode 100644 index 000000000000..50dc5f4db406 --- /dev/null +++ b/bindings/rust/wrapper.h @@ -0,0 +1,2 @@ +#include <string.h> +#include "../../include/gpiod.h"
Add rust wrappers around FFI bindings to provide a convenient interface for the users.
Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- bindings/rust/Cargo.toml | 2 + bindings/rust/src/chip.rs | 197 +++++++++++++ bindings/rust/src/edge_event.rs | 78 +++++ bindings/rust/src/event_buffer.rs | 59 ++++ bindings/rust/src/info_event.rs | 70 +++++ bindings/rust/src/lib.rs | 268 +++++++++++++++++ bindings/rust/src/line_config.rs | 431 ++++++++++++++++++++++++++++ bindings/rust/src/line_info.rs | 186 ++++++++++++ bindings/rust/src/line_request.rs | 218 ++++++++++++++ bindings/rust/src/request_config.rs | 118 ++++++++ 10 files changed, 1627 insertions(+) create mode 100644 bindings/rust/src/chip.rs create mode 100644 bindings/rust/src/edge_event.rs create mode 100644 bindings/rust/src/event_buffer.rs create mode 100644 bindings/rust/src/info_event.rs create mode 100644 bindings/rust/src/line_config.rs create mode 100644 bindings/rust/src/line_info.rs create mode 100644 bindings/rust/src/line_request.rs create mode 100644 bindings/rust/src/request_config.rs
diff --git a/bindings/rust/Cargo.toml b/bindings/rust/Cargo.toml index 62f3d52ddb0f..d57dfd65ba7d 100644 --- a/bindings/rust/Cargo.toml +++ b/bindings/rust/Cargo.toml @@ -6,6 +6,8 @@ edition = "2021" # 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/bindings/rust/src/chip.rs b/bindings/rust/src/chip.rs new file mode 100644 index 000000000000..144f5ef9c570 --- /dev/null +++ b/bindings/rust/src/chip.rs @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2021 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org + +use std::os::raw::c_char; +use std::sync::Arc; +use std::time::Duration; +use std::{slice, str}; + +use vmm_sys_util::errno::Error as IoError; + +use super::{bindings, Error, Result}; +use crate::info_event::GpiodInfoEvent; +use crate::line_config::GpiodLineConfig; +use crate::line_info::GpiodLineInfo; +use crate::line_request::GpiodLineRequest; +use crate::request_config::GpiodRequestConfig; + +/// GPIO chip +/// +/// A GPIO chip object is associated with an open file descriptor to the GPIO +/// character device. It exposes basic information about the chip and allows +/// callers to retrieve information about each line, watch lines for state +/// changes and make line requests. +pub(crate) struct GpiodChipInternal { + chip: *mut bindings::gpiod_chip, +} + +impl GpiodChipInternal { + /// Find a GPIO chip by path. + pub(crate) fn open(path: &str) -> Result<Self> { + let chip = unsafe { bindings::gpiod_chip_open(path.as_ptr() as *const c_char) }; + if chip.is_null() { + return Err(Error::OperationFailed("GpiodChip open", IoError::last())); + } + + Ok(Self { chip }) + } + + /// Private helper, Returns gpiod_chip + pub(crate) fn chip(&self) -> *mut bindings::gpiod_chip { + self.chip + } +} + +impl Drop for 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: &str) -> Result<Self> { + Ok(Self { + ichip: Arc::new(GpiodChipInternal::open(path)?), + }) + } + + /// Get the GPIO chip name as represented in the kernel. + pub fn get_name(&self) -> Result<&str> { + // SAFETY: The string returned by libgpiod is guaranteed to live as long + // as the `struct GpiodChip`. + 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. + pub fn get_label(&self) -> Result<&str> { + // SAFETY: The string returned by libgpiod is guaranteed to live as long + // as the `struct GpiodChip`. + 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. + pub fn get_path(&self) -> Result<&str> { + // SAFETY: The string returned by libgpiod is guaranteed to live as long + // as the `struct GpiodChip`. + 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("GpiodChip get-fd", 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( + "GpiodChip info-event-wait", + 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: &str) -> 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( + "GpiodChip find-line", + 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) + } +} diff --git a/bindings/rust/src/edge_event.rs b/bindings/rust/src/edge_event.rs new file mode 100644 index 000000000000..5fe292ac69aa --- /dev/null +++ b/bindings/rust/src/edge_event.rs @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2021 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org + +use std::time::Duration; + +use vmm_sys_util::errno::Error as IoError; + +use super::{bindings, EdgeEvent, Error, Result}; +use crate::event_buffer::GpiodEdgeEventBuffer; + +/// 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( + "GpiodEdgeEvent buffer-get-event", + 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) } + } +} diff --git a/bindings/rust/src/event_buffer.rs b/bindings/rust/src/event_buffer.rs new file mode 100644 index 000000000000..fd702e91818d --- /dev/null +++ b/bindings/rust/src/event_buffer.rs @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2021 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org + +use vmm_sys_util::errno::Error as IoError; + +use super::{bindings, Error, Result}; +use crate::edge_event::GpiodEdgeEvent; + +/// 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( + "GpiodEdgeEventBuffer new", + IoError::last(), + )); + } + + Ok(Self { buffer }) + } + + /// Private helper, Returns gpiod_edge_event_buffer + pub(crate) fn buffer(&self) -> *mut bindings::gpiod_edge_event_buffer { + self.buffer + } + + /// 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) }; + } +} diff --git a/bindings/rust/src/info_event.rs b/bindings/rust/src/info_event.rs new file mode 100644 index 000000000000..e0f82b31548f --- /dev/null +++ b/bindings/rust/src/info_event.rs @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2021 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org + +use std::convert::TryFrom; +use std::sync::Arc; +use std::time::Duration; + +use vmm_sys_util::errno::Error as IoError; + +use super::chip::GpiodChipInternal; +use super::{bindings, Error, Event, Result}; +use crate::line_info::GpiodLineInfo; + +/// 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. + pub(crate) 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( + "GpiodInfoEvent event-read", + IoError::last(), + )); + } + + Ok(Self { event }) + } + + /// Private helper, Returns gpiod_info_event + pub(crate) fn event(&self) -> *mut bindings::gpiod_info_event { + self.event + } + + /// Get the event type of 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) } + } +} diff --git a/bindings/rust/src/lib.rs b/bindings/rust/src/lib.rs index e69de29bb2d1..cbdef64fdb8b 100644 --- a/bindings/rust/src/lib.rs +++ b/bindings/rust/src/lib.rs @@ -0,0 +1,268 @@ +// 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; +pub mod chip; +pub mod edge_event; +pub mod event_buffer; +pub mod info_event; +pub mod line_config; +pub mod line_info; +pub mod line_request; +pub mod request_config; + +use std::os::raw::c_char; +use std::{slice, str}; + +use thiserror::Error as ThisError; +use vmm_sys_util::errno::Error as IoError; + +/// Result of libgpiod operations +pub type Result<T> = std::result::Result<T, Error>; + +/// Error codes for libgpiod operations +#[derive(Copy, Clone, Debug, PartialEq, ThisError)] +pub enum Error { + #[error("Failed to find {0}")] + NameNotFound(&'static str), + #[error("Invalid String: {0:?}")] + InvalidString(str::Utf8Error), + #[error("Invalid {0} value: {1}")] + InvalidValue(&'static str, u32), + #[error("Operation {0} Failed: {1}")] + OperationFailed(&'static str, IoError), + #[error("Operation Timed-out")] + OperationTimedOut, +} + +/// Direction settings. +pub enum Direction { + /// Request the line(s), but 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)), + } + } +} + +/// Various libgpiod-related functions. + +/// Check if the file pointed to by path is a GPIO chip character device. +/// +/// Returns true if the file exists and is a GPIO chip character device or a +/// symbolic link to it. +pub fn gpiod_is_gpiochip_device(path: &str) -> bool { + unsafe { bindings::gpiod_is_gpiochip_device(path.as_ptr() as *const c_char) } +} + +/// Get the API version of the library as a human-readable string. +pub fn gpiod_version_string() -> Result<&'static str> { + // SAFETY: The string returned by libgpiod is guaranteed to live forever. + let version = unsafe { bindings::gpiod_version_string() }; + + if version.is_null() { + return Err(Error::NameNotFound("GPIO library version")); + } + + unsafe { + str::from_utf8(slice::from_raw_parts( + version as *const u8, + bindings::strlen(version) as usize, + )) + .map_err(Error::InvalidString) + } +} diff --git a/bindings/rust/src/line_config.rs b/bindings/rust/src/line_config.rs new file mode 100644 index 000000000000..c657902bd6f5 --- /dev/null +++ b/bindings/rust/src/line_config.rs @@ -0,0 +1,431 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2021 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org + +use std::time::Duration; + +use vmm_sys_util::errno::Error as IoError; + +use super::{bindings, Bias, Direction, Drive, Edge, Error, EventClock, Result}; + +/// 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( + "GpiodLineConfig new", + IoError::last(), + )); + } + + Ok(Self { config }) + } + + /// Private helper, Returns gpiod_line_config + pub(crate) fn config(&self) -> *mut bindings::gpiod_line_config { + self.config + } + + /// Resets the entire configuration stored in 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, offsets: &mut 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, offsets: &mut 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, offsets: &mut 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, offsets: &mut 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, offsets: &mut 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, offsets: &mut 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, offsets: &mut 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, offsets: &mut 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(), + ) + } + } + + /// 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, offsets: &mut Vec<u32>, values: &mut 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( + "GpiodLineConfig get-output-value", + 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( + "GpiodLineConfig get-output-value-index", + 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) } + } +} diff --git a/bindings/rust/src/line_info.rs b/bindings/rust/src/line_info.rs new file mode 100644 index 000000000000..8d8097de9a60 --- /dev/null +++ b/bindings/rust/src/line_info.rs @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2021 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org + +use std::convert::TryFrom; +use std::sync::Arc; +use std::time::Duration; +use std::{slice, str}; + +use vmm_sys_util::errno::Error as IoError; + +use super::chip::GpiodChipInternal; +use super::{bindings, Bias, Direction, Drive, Edge, Error, EventClock, Result}; +use crate::info_event::GpiodInfoEvent; + +/// 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. + pub(crate) 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( + "GpiodLineInfo line-info", + IoError::last(), + )); + } + + Ok(Self { + info, + ichip: if watch { Some(ichip) } else { None }, + free: watch, + }) + } + + /// Stop watching the line + pub fn unwatch(&mut self) { + if let Some(ichip) = &self.ichip { + unsafe { + bindings::gpiod_chip_unwatch_line_info(ichip.chip(), self.get_offset()); + } + self.ichip = None; + } + } + + /// Get the offset of the line within the GPIO chip. + pub fn get_offset(&mut self) -> u32 { + unsafe { bindings::gpiod_line_info_get_offset(self.info) } + } + + /// Get GPIO line's name. + pub fn get_name(&self) -> Result<&str> { + // SAFETY: The string returned by libgpiod is guaranteed to live as long + // as the `struct GpiodLineInfo`. + 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. + pub fn get_consumer(&self) -> Result<&str> { + // SAFETY: The string returned by libgpiod is guaranteed to live as long + // as the `struct GpiodLineInfo`. + 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( + "GpiodLineInfo try-from", + 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) } + } + } +} diff --git a/bindings/rust/src/line_request.rs b/bindings/rust/src/line_request.rs new file mode 100644 index 000000000000..547c00ce8278 --- /dev/null +++ b/bindings/rust/src/line_request.rs @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2021 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org + +use std::sync::Arc; +use std::time::Duration; + +use vmm_sys_util::errno::Error as IoError; + +use super::{bindings, chip::GpiodChipInternal, Error, Result}; +use crate::event_buffer::GpiodEdgeEventBuffer; +use crate::line_config::GpiodLineConfig; +use crate::request_config::GpiodRequestConfig; + +/// 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. + pub(crate) 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( + "GpiodLineRequest request-lines", + 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, values: &mut Vec<i32>) -> Result<()> { + let ret = + unsafe { bindings::gpiod_line_request_get_values(self.request, values.as_mut_ptr()) }; + + if ret == -1 { + Err(Error::OperationFailed( + "GpiodLineRequest get-values", + 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( + "GpiodLineRequest get-value", + IoError::last(), + )) + } else { + Ok(value as u32) + } + } + + /// Get values of a subset of lines associated with this request. + pub fn get_values_subset(&self, offsets: &mut Vec<u32>, values: &mut 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( + "GpiodLineRequest get-values-subset", + IoError::last(), + )) + } else { + Ok(()) + } + } + + /// Get values of all lines associated with this request. + pub fn set_values(&self, values: &mut Vec<i32>) -> Result<()> { + let ret = + unsafe { bindings::gpiod_line_request_set_values(self.request, values.as_mut_ptr()) }; + + if ret == -1 { + Err(Error::OperationFailed( + "GpiodLineRequest set-values", + 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( + "GpiodLineRequest set-value", + IoError::last(), + )) + } else { + Ok(()) + } + } + + /// Get values of a subset of lines associated with this request. + pub fn set_values_subset(&self, offsets: &mut Vec<u32>, values: &mut 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( + "GpiodLineRequest set-values-subset", + 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( + "GpiodLineRequest reconfigure-lines", + 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( + "GpiodLineRequest edge-event-wait", + IoError::last(), + )), + 0 => Err(Error::OperationTimedOut), + _ => Ok(()), + } + } + + /// Get a number of edge events from a line request. + /// + /// This function will block if no event was queued for 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( + "GpiodLineRequest edge-event-read", + 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) } + } +} diff --git a/bindings/rust/src/request_config.rs b/bindings/rust/src/request_config.rs new file mode 100644 index 000000000000..a2f1ad37c8ce --- /dev/null +++ b/bindings/rust/src/request_config.rs @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2021 Linaro Ltd. All Rights Reserved. +// Viresh Kumar viresh.kumar@linaro.org + +use std::os::raw::c_char; +use std::{slice, str}; + +use vmm_sys_util::errno::Error as IoError; + +use super::{bindings, Error, Result}; + +/// 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( + "GpiodRequestConfig new", + IoError::last(), + )); + } + + Ok(Self { config }) + } + + /// Private helper, Returns gpiod_request_config + pub(crate) fn config(&self) -> *mut bindings::gpiod_request_config { + self.config + } + + /// Set the consumer string. + /// + /// If the consumer string is too long, it will be truncated to the max + /// accepted length. + pub fn set_consumer(&self, consumer: &str) { + unsafe { + bindings::gpiod_request_config_set_consumer( + self.config, + consumer.as_ptr() as *const c_char, + ) + } + } + + /// Get the consumer string. + pub fn get_consumer(&self) -> Result<&str> { + // SAFETY: The string returned by libgpiod is guaranteed to live as long + // as the `struct GpiodRequestConfig`. + let consumer = unsafe { bindings::gpiod_request_config_get_consumer(self.config) }; + if consumer.is_null() { + return Err(Error::OperationFailed( + "GpiodRequestConfig get-consumer", + 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, offsets: &mut 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) } + } +}
On Mon, Nov 29, 2021 at 11:42 AM Viresh Kumar viresh.kumar@linaro.org wrote:
Hi Bartosz,
This patch adds rust bindings for libgpiod v2.0, this is already partially tested with the virtio rust backend I am developing, which uses these to talk to the host kernel.
This is based of the next/post-libgpiod-2.0 branch.
I haven't added any mock test for this as of now and I am not sure how exactly am I required to add them. I did see what you mentioned in your patchset about mock-test vs gpio-sim stuff. Rust also have its own test-framework and I am not sure if that should be used instead or something else.
Since I am posting this publicly for the first time, it is still named as V1. I have not made significant changes to the code since last time, but just divided the same into multiple files.
-- Viresh
Viresh Kumar (2): libgpiod: Generate rust FFI bindings libgpiod: Add rust wrappers
.gitignore | 6 + bindings/rust/Cargo.toml | 14 + bindings/rust/build.rs | 60 ++++ bindings/rust/src/bindings.rs | 16 ++ bindings/rust/src/chip.rs | 197 +++++++++++++ bindings/rust/src/edge_event.rs | 78 +++++ bindings/rust/src/event_buffer.rs | 59 ++++ bindings/rust/src/info_event.rs | 70 +++++ bindings/rust/src/lib.rs | 268 +++++++++++++++++ bindings/rust/src/line_config.rs | 431 ++++++++++++++++++++++++++++ bindings/rust/src/line_info.rs | 186 ++++++++++++ bindings/rust/src/line_request.rs | 218 ++++++++++++++ bindings/rust/src/request_config.rs | 118 ++++++++ bindings/rust/wrapper.h | 2 + 14 files changed, 1723 insertions(+) create mode 100644 bindings/rust/Cargo.toml create mode 100644 bindings/rust/build.rs create mode 100644 bindings/rust/src/bindings.rs create mode 100644 bindings/rust/src/chip.rs create mode 100644 bindings/rust/src/edge_event.rs create mode 100644 bindings/rust/src/event_buffer.rs create mode 100644 bindings/rust/src/info_event.rs create mode 100644 bindings/rust/src/lib.rs create mode 100644 bindings/rust/src/line_config.rs create mode 100644 bindings/rust/src/line_info.rs create mode 100644 bindings/rust/src/line_request.rs create mode 100644 bindings/rust/src/request_config.rs create mode 100644 bindings/rust/wrapper.h
-- 2.31.1.272.g89b43f80a514
Hi Viresh!
Thanks for the hard work on that. Before I even dig into the patches themselves, I'd like to clarify a couple things (because I'm still not sure we're on the same page) and the projected timeline for v2.
#1. The v2 API is obviously not stable yet and we're still reworking certain structures (like line_config's accessors). This means I can't merge the bindings just yet but I'm fine with taking them in for v2 given that the missing elements are added, which brings me to:
#2. If you look at the existing bindings, you'll notice they all have tests implemented. These tests use the combination of whatever testing framework was chosen for a given language and a wrapper around libgpio-mockup that allows to use the gpio-mockup kernel module to instantiate simulated GPIO devices. I'd like to see a comprehensive test suite for the rust bindings too before they get into the repo. Except that:
#3. The development of other parts of the project is currently blocked by the gpio-sim development in the kernel. I don't want to reuse gpio-mockup in v2 because it's simply badly designed. Currently the v11 of the gpio-sim series[1] (based on configfs & sysfs) is on the list and only once it lands in mainline, will we be able to advance the tests for the C, C++ and Python interfaces. This is when the rust tests should be developed too by following what other parts of libgpiod do: providing a wrapper around the future libgpiosim (a wrapper around gpio-sim configfs/sysfs interface that I'll implement once gpio-sim is in next) and then using whatever testing framework for rust is preferable.
#4. This one is something you could already add at this time: other bindings contain the examples directory. Inside there are simplified reimplementations of the gpio-tools. It would be very useful for me (as someone not very fluent in rust) to see how those bindings are used in practice. Could you please add it?
Thanks! Bart
On 01-12-21, 12:08, Bartosz Golaszewski wrote:
Thanks for the hard work on that. Before I even dig into the patches themselves, I'd like to clarify a couple things (because I'm still not sure we're on the same page) and the projected timeline for v2.
#1. The v2 API is obviously not stable yet and we're still reworking certain structures (like line_config's accessors). This means I can't merge the bindings just yet but I'm fine with taking them in for v2 given that the missing elements are added, which brings me to:
#2. If you look at the existing bindings, you'll notice they all have tests implemented. These tests use the combination of whatever testing framework was chosen for a given language and a wrapper around libgpio-mockup that allows to use the gpio-mockup kernel module to instantiate simulated GPIO devices. I'd like to see a comprehensive test suite for the rust bindings too before they get into the repo. Except that:
Yeah, I was able to get around that yesterday, while I was integrating rust's build with Make and understood what we are doing for cpp and python.
For now, I dropped the idea of implementing rust tests, and wait for you to go ahead with migrating the python/cpp ones to libgpiosim.
#3. The development of other parts of the project is currently blocked by the gpio-sim development in the kernel. I don't want to reuse gpio-mockup in v2 because it's simply badly designed. Currently the v11 of the gpio-sim series[1] (based on configfs & sysfs) is on the list
I went into details of that as well yesterday, looked nice.
and only once it lands in mainline, will we be able to advance the tests for the C, C++ and Python interfaces. This is when the rust tests should be developed too by following what other parts of libgpiod do: providing a wrapper around the future libgpiosim (a wrapper around gpio-sim configfs/sysfs interface that I'll implement once gpio-sim is in next) and then using whatever testing framework for rust is preferable.
Exactly.
#4. This one is something you could already add at this time: other bindings contain the examples directory. Inside there are simplified reimplementations of the gpio-tools. It would be very useful for me (as someone not very fluent in rust) to see how those bindings are used in practice. Could you please add it?
Oh yes, sure. I can do that.
stratos-dev@op-lists.linaro.org