From patchwork Fri Nov 18 10:44:38 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Viresh Kumar X-Patchwork-Id: 626467 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 1A5F0C4167B for ; Fri, 18 Nov 2022 10:46:06 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S241622AbiKRKqF (ORCPT ); Fri, 18 Nov 2022 05:46:05 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:35820 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S241847AbiKRKpm (ORCPT ); Fri, 18 Nov 2022 05:45:42 -0500 Received: from mail-pl1-x629.google.com (mail-pl1-x629.google.com [IPv6:2607:f8b0:4864:20::629]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id DAD8899EB7 for ; Fri, 18 Nov 2022 02:45:13 -0800 (PST) Received: by mail-pl1-x629.google.com with SMTP id b21so4207780plc.9 for ; Fri, 18 Nov 2022 02:45:13 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=byHabTvELlMk45euqQsA5LKhXM9HjAb2W8oGePuGo5k=; b=dPg6URxBtHB3zeTo9phsm45S1VF8c6e+WmamihEmRmAq+vBfRUqnzFMQZj43DUXQHc KzhTLAXaa4RkzVYc8hLk/n1opnXIO09yns43/AbADcNUHeGMTRcEj4TQmd4Donc7t5TQ HkrrAk+vrZDYPh4bpm0ckL2Skb7O5FSr7KdAZr9djJoYeJ4Pc7HJ3e5eSsLzrzVovm6w OMZgS9+d/XjFBXEKGa9MOmq8Gy+TeV9NwLPpI/362IXJzjtVdLio2C175f/PtVC4h2b4 EbSkJJ19TLTy8cD4iM9pWdqxJviPPL1BhgGeAmL/xkz3+8gWTD+uNUHBh87OQygeM7vf B9ZQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=byHabTvELlMk45euqQsA5LKhXM9HjAb2W8oGePuGo5k=; b=cXiIUPqUjfCYaY/YHv6iQv91J8MimrLjTwceEsZRBiiSJqVv83dX0lMdbpP9xMjXnQ MoMU0EOfXx+jhLZqYax0yb2ZALlawihZ1L9/8J5vGH51mi6Hram2eEmmc/b+dX3ZoB1y c1U+JEUN2SoFWz4U16KpOnldjkoy6xh15IiQoCBCCxneM1y2KsWpM/xf1aQiC86oOC5l RvORDzO8Cn3jyFPRMGnvxrj3TxCvUX1gvT929K4Vp8vsHVTkT4ZcCeGqY+xZ+DsOp3Lu /TJN8AG8WGGlfwrzntrKocYjdFc52oVOyn8ILD2FiEricC27Dj5MfZhubyPcRRhEO8JD +6vA== X-Gm-Message-State: ANoB5pmARKEOzl4G8qyA4ifFOjrCwa9wXyWyzMGfCeJJ6zkklOA3/dX3 Wn/faI1e4RwFu5hVkNtl7PM3gA== X-Google-Smtp-Source: AA0mqf4b/iUW4VG7KjScRHktJ9x5x3Qkv3q5FPPdVZRZVHmWy/gw2YJ+1cBDMLUS175dspWp8AsWdg== X-Received: by 2002:a17:90a:d984:b0:213:d1d5:d661 with SMTP id d4-20020a17090ad98400b00213d1d5d661mr13431799pjv.43.1668768312026; Fri, 18 Nov 2022 02:45:12 -0800 (PST) Received: from localhost ([122.172.85.60]) by smtp.gmail.com with ESMTPSA id y14-20020aa79aee000000b005609d3d3008sm2899925pfp.171.2022.11.18.02.45.10 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 18 Nov 2022 02:45:11 -0800 (PST) From: Viresh Kumar To: Linus Walleij , Bartosz Golaszewski , Kent Gibson Cc: Viresh Kumar , Vincent Guittot , linux-gpio@vger.kernel.org, Miguel Ojeda , Wedson Almeida Filho , =?utf-8?q?Alex_Benn=C3=A9e?= , stratos-dev@op-lists.linaro.org, Gerard Ryan , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_Roy_Baron?= , y86-dev Subject: [libgpiod][PATCH V10 2/6] bindings: rust: Add libgpiod crate Date: Fri, 18 Nov 2022 16:14:38 +0530 Message-Id: X-Mailer: git-send-email 2.31.1.272.g89b43f80a514 In-Reply-To: References: MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-gpio@vger.kernel.org Add rust wrapper crate, around the libpiod-sys crate added earlier, to provide a convenient interface for the users. Reviewed-by: Kent Gibson Signed-off-by: Viresh Kumar --- bindings/rust/Cargo.toml | 1 + bindings/rust/libgpiod/Cargo.toml | 22 + bindings/rust/libgpiod/src/chip.rs | 309 ++++++++++++ bindings/rust/libgpiod/src/edge_event.rs | 92 ++++ bindings/rust/libgpiod/src/event_buffer.rs | 168 +++++++ bindings/rust/libgpiod/src/info_event.rs | 68 +++ bindings/rust/libgpiod/src/lib.rs | 478 +++++++++++++++++++ bindings/rust/libgpiod/src/line_config.rs | 134 ++++++ bindings/rust/libgpiod/src/line_info.rs | 161 +++++++ bindings/rust/libgpiod/src/line_request.rs | 226 +++++++++ bindings/rust/libgpiod/src/line_settings.rs | 296 ++++++++++++ bindings/rust/libgpiod/src/request_config.rs | 94 ++++ 12 files changed, 2049 insertions(+) create mode 100644 bindings/rust/libgpiod/Cargo.toml create mode 100644 bindings/rust/libgpiod/src/chip.rs create mode 100644 bindings/rust/libgpiod/src/edge_event.rs create mode 100644 bindings/rust/libgpiod/src/event_buffer.rs create mode 100644 bindings/rust/libgpiod/src/info_event.rs create mode 100644 bindings/rust/libgpiod/src/lib.rs create mode 100644 bindings/rust/libgpiod/src/line_config.rs create mode 100644 bindings/rust/libgpiod/src/line_info.rs create mode 100644 bindings/rust/libgpiod/src/line_request.rs create mode 100644 bindings/rust/libgpiod/src/line_settings.rs create mode 100644 bindings/rust/libgpiod/src/request_config.rs diff --git a/bindings/rust/Cargo.toml b/bindings/rust/Cargo.toml index e3e253a4cf5e..f4051387d581 100644 --- a/bindings/rust/Cargo.toml +++ b/bindings/rust/Cargo.toml @@ -5,5 +5,6 @@ [workspace] members = [ + "libgpiod", "libgpiod-sys" ] diff --git a/bindings/rust/libgpiod/Cargo.toml b/bindings/rust/libgpiod/Cargo.toml new file mode 100644 index 000000000000..a38df1c0b10d --- /dev/null +++ b/bindings/rust/libgpiod/Cargo.toml @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: CC0-1.0 +# SPDX-FileCopyrightText: 2022 Linaro Ltd. +# SPDX-FileCopyrightTest: 2022 Viresh Kumar + +[package] +name = "libgpiod" +version = "0.1.0" +authors = ["Viresh Kumar "] +description = "libgpiod wrappers" +repository = "https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git" +categories = ["api-bindings", "hardware-support", "embedded", "os::linux-apis"] +rust-version = "1.56" +keywords = ["libgpiod", "gpio"] +license = "Apache-2.0 OR BSD-3-Clause" +edition = "2021" + +[dependencies] +errno = "0.2.8" +intmap = "2.0.0" +libc = "0.2.39" +libgpiod-sys = { path = "../libgpiod-sys" } +thiserror = "1.0" diff --git a/bindings/rust/libgpiod/src/chip.rs b/bindings/rust/libgpiod/src/chip.rs new file mode 100644 index 000000000000..91b4c947547b --- /dev/null +++ b/bindings/rust/libgpiod/src/chip.rs @@ -0,0 +1,309 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar + +pub mod info { + /// GPIO chip info event related definitions. + pub use crate::info_event::*; +} + +use std::cmp::Ordering; +use std::ffi::{CStr, CString}; +use std::os::{raw::c_char, unix::prelude::AsRawFd}; +use std::path::Path; +use std::str; +use std::sync::Arc; +use std::time::Duration; + +use super::{ + gpiod, + line::{self, Offset}, + request, Error, OperationType, Result, +}; + +#[derive(Debug, Eq, PartialEq)] +struct Internal { + chip: *mut gpiod::gpiod_chip, +} + +impl Internal { + /// Find a chip by path. + fn open>(path: &P) -> Result { + // Null-terminate the string + let path = path.as_ref().to_string_lossy() + "\0"; + + // SAFETY: The `gpiod_chip` returned by libgpiod is guaranteed to live as long + // as the `struct Internal`. + let chip = unsafe { gpiod::gpiod_chip_open(path.as_ptr() as *const c_char) }; + if chip.is_null() { + return Err(Error::OperationFailed( + OperationType::ChipOpen, + errno::errno(), + )); + } + + Ok(Self { chip }) + } +} + +impl Drop for Internal { + /// Close the chip and release all associated resources. + fn drop(&mut self) { + // SAFETY: `gpiod_chip` is guaranteed to be valid here. + unsafe { gpiod::gpiod_chip_close(self.chip) } + } +} + +/// GPIO chip +/// +/// A GPIO chip object is associated with an open file descriptor to the GPIO +/// character device. It exposes basic information about the chip and allows +/// callers to retrieve information about each line, watch lines for state +/// changes and make line requests. +#[derive(Debug, Eq, PartialEq)] +pub struct Chip { + ichip: Arc, +} + +// SAFETY: Safe as `Internal` won't be freed until the `Chip` is dropped. +unsafe impl Send for Chip {} + +impl Chip { + /// Find a chip by path. + pub fn open>(path: &P) -> Result { + let ichip = Arc::new(Internal::open(path)?); + + Ok(Self { ichip }) + } + + /// Get the chip name as represented in the kernel. + pub fn info(&self) -> Result { + Info::new(self.ichip.clone()) + } + + /// Get the path used to find the chip. + pub fn path(&self) -> Result<&str> { + // SAFETY: The string returned by libgpiod is guaranteed to live as long + // as the `struct Chip`. + let path = unsafe { gpiod::gpiod_chip_get_path(self.ichip.chip) }; + + // SAFETY: The string is guaranteed to be valid here by the C API. + unsafe { CStr::from_ptr(path) } + .to_str() + .map_err(Error::StringNotUtf8) + } + + /// Get a snapshot of information about the line. + pub fn line_info(&self, offset: Offset) -> Result { + // SAFETY: The `gpiod_line_info` returned by libgpiod is guaranteed to live as long + // as the `struct Info`. + let info = unsafe { gpiod::gpiod_chip_get_line_info(self.ichip.chip, offset) }; + + if info.is_null() { + return Err(Error::OperationFailed( + OperationType::ChipGetLineInfo, + errno::errno(), + )); + } + + line::Info::new(info) + } + + /// Get the current snapshot of information about the line at given offset and start watching + /// it for future changes. + pub fn watch_line_info(&self, offset: Offset) -> Result { + // SAFETY: `gpiod_line_info` is guaranteed to be valid here. + let info = unsafe { gpiod::gpiod_chip_watch_line_info(self.ichip.chip, offset) }; + + if info.is_null() { + return Err(Error::OperationFailed( + OperationType::ChipWatchLineInfo, + errno::errno(), + )); + } + + line::Info::new_watch(info) + } + + /// Stop watching a line + pub fn unwatch(&self, offset: Offset) { + // SAFETY: `gpiod_chip` is guaranteed to be valid here. + unsafe { + gpiod::gpiod_chip_unwatch_line_info(self.ichip.chip, offset); + } + } + + /// Wait for line status events on any of the watched lines on the chip. + pub fn wait_info_event(&self, timeout: Option) -> Result { + let timeout = match timeout { + Some(x) => x.as_nanos() as i64, + // Block indefinitely + None => -1, + }; + + // SAFETY: `gpiod_chip` is guaranteed to be valid here. + let ret = unsafe { gpiod::gpiod_chip_wait_info_event(self.ichip.chip, timeout) }; + + match ret { + -1 => Err(Error::OperationFailed( + OperationType::ChipWaitInfoEvent, + errno::errno(), + )), + 0 => Ok(false), + _ => Ok(true), + } + } + + /// Read a single line status change event from the chip. If no events are + /// pending, this function will block. + pub fn read_info_event(&self) -> Result { + // SAFETY: The `gpiod_info_event` returned by libgpiod is guaranteed to live as long + // as the `struct Event`. + let event = unsafe { gpiod::gpiod_chip_read_info_event(self.ichip.chip) }; + if event.is_null() { + return Err(Error::OperationFailed( + OperationType::ChipReadInfoEvent, + errno::errno(), + )); + } + + Ok(info::Event::new(event)) + } + + /// Map a GPIO line's name to its offset within the chip. + pub fn line_offset_from_name(&self, name: &str) -> Result { + let name = CString::new(name).map_err(|_| Error::InvalidString)?; + + // SAFETY: `gpiod_chip` is guaranteed to be valid here. + let ret = unsafe { + gpiod::gpiod_chip_get_line_offset_from_name( + self.ichip.chip, + name.as_ptr() as *const c_char, + ) + }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::ChipGetLineOffsetFromName, + errno::errno(), + )) + } else { + Ok(ret as u32) + } + } + + /// Request a set of lines for exclusive usage. + pub fn request_lines( + &self, + rconfig: &request::Config, + lconfig: &line::Config, + ) -> Result { + // SAFETY: The `gpiod_line_request` returned by libgpiod is guaranteed to live as long + // as the `struct Request`. + let request = unsafe { + gpiod::gpiod_chip_request_lines(self.ichip.chip, rconfig.config, lconfig.config) + }; + + if request.is_null() { + return Err(Error::OperationFailed( + OperationType::ChipRequestLines, + errno::errno(), + )); + } + + request::Request::new(request) + } +} + +impl AsRawFd for Chip { + /// Get the file descriptor associated with the chip. + /// + /// The returned file descriptor must not be closed by the caller, else other methods for the + /// `struct Chip` may fail. + fn as_raw_fd(&self) -> i32 { + // SAFETY: `gpiod_chip` is guaranteed to be valid here. + unsafe { gpiod::gpiod_chip_get_fd(self.ichip.chip) } + } +} + +/// GPIO chip Information +#[derive(Debug, Eq)] +pub struct Info { + info: *mut gpiod::gpiod_chip_info, +} + +impl Info { + /// Find a GPIO chip by path. + fn new(chip: Arc) -> Result { + // SAFETY: `gpiod_chip` is guaranteed to be valid here. + let info = unsafe { gpiod::gpiod_chip_get_info(chip.chip) }; + if info.is_null() { + return Err(Error::OperationFailed( + OperationType::ChipGetInfo, + errno::errno(), + )); + } + + Ok(Self { info }) + } + + /// Get the GPIO chip name as represented in the kernel. + pub fn name(&self) -> Result<&str> { + // SAFETY: The string returned by libgpiod is guaranteed to live as long + // as the `struct Chip`. + let name = unsafe { gpiod::gpiod_chip_info_get_name(self.info) }; + + // SAFETY: The string is guaranteed to be valid here by the C API. + unsafe { CStr::from_ptr(name) } + .to_str() + .map_err(Error::StringNotUtf8) + } + + /// Get the GPIO chip label as represented in the kernel. + pub fn label(&self) -> Result<&str> { + // SAFETY: The string returned by libgpiod is guaranteed to live as long + // as the `struct Chip`. + let label = unsafe { gpiod::gpiod_chip_info_get_label(self.info) }; + + // SAFETY: The string is guaranteed to be valid here by the C API. + unsafe { CStr::from_ptr(label) } + .to_str() + .map_err(Error::StringNotUtf8) + } + + /// Get the number of GPIO lines exposed by the chip. + pub fn num_lines(&self) -> usize { + // SAFETY: `gpiod_chip` is guaranteed to be valid here. + unsafe { gpiod::gpiod_chip_info_get_num_lines(self.info) as usize } + } +} + +impl PartialEq for Info { + fn eq(&self, other: &Self) -> bool { + self.name().unwrap().eq(other.name().unwrap()) + } +} + +impl PartialOrd for Info { + fn partial_cmp(&self, other: &Self) -> Option { + let name = match self.name() { + Ok(name) => name, + _ => return None, + }; + + let other_name = match other.name() { + Ok(name) => name, + _ => return None, + }; + + name.partial_cmp(other_name) + } +} + +impl Drop for Info { + /// Close the GPIO chip info and release all associated resources. + fn drop(&mut self) { + // SAFETY: `gpiod_chip` is guaranteed to be valid here. + unsafe { gpiod::gpiod_chip_info_free(self.info) } + } +} diff --git a/bindings/rust/libgpiod/src/edge_event.rs b/bindings/rust/libgpiod/src/edge_event.rs new file mode 100644 index 000000000000..d8404952b0b0 --- /dev/null +++ b/bindings/rust/libgpiod/src/edge_event.rs @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar + +use std::time::Duration; + +use super::{ + gpiod, + line::{EdgeKind, Offset}, + Error, OperationType, Result, +}; + +/// Line edge events handling +/// +/// An edge event object contains information about a single line edge event. +/// It contains the event type, timestamp and the offset of the line on which +/// the event occurred as well as two sequence numbers (global for all lines +/// in the associated request and local for this line only). +/// +/// Edge events are stored into an edge-event buffer object to improve +/// performance and to limit the number of memory allocations when a large +/// number of events are being read. + +#[derive(Debug, Eq, PartialEq)] +pub struct Event(*mut gpiod::gpiod_edge_event); + +impl Event { + pub fn event_clone(event: &Event) -> Result { + // SAFETY: `gpiod_edge_event` is guaranteed to be valid here. + let event = unsafe { gpiod::gpiod_edge_event_copy(event.0) }; + if event.is_null() { + return Err(Error::OperationFailed( + OperationType::EdgeEventCopy, + errno::errno(), + )); + } + + Ok(Self(event)) + } + + /// Get the event type. + pub fn event_type(&self) -> Result { + // SAFETY: `gpiod_edge_event` is guaranteed to be valid here. + EdgeKind::new(unsafe { gpiod::gpiod_edge_event_get_event_type(self.0) } as u32) + } + + /// Get the timestamp of the event. + pub fn timestamp(&self) -> Duration { + // SAFETY: `gpiod_edge_event` is guaranteed to be valid here. + Duration::from_nanos(unsafe { gpiod::gpiod_edge_event_get_timestamp_ns(self.0) }) + } + + /// Get the offset of the line on which the event was triggered. + pub fn line_offset(&self) -> Offset { + // SAFETY: `gpiod_edge_event` is guaranteed to be valid here. + unsafe { gpiod::gpiod_edge_event_get_line_offset(self.0) } + } + + /// Get the global sequence number of the event. + /// + /// Returns sequence number of the event relative to all lines in the + /// associated line request. + pub fn global_seqno(&self) -> usize { + // SAFETY: `gpiod_edge_event` is guaranteed to be valid here. + unsafe { + gpiod::gpiod_edge_event_get_global_seqno(self.0) + .try_into() + .unwrap() + } + } + + /// Get the event sequence number specific to concerned line. + /// + /// Returns sequence number of the event relative to the line within the + /// lifetime of the associated line request. + pub fn line_seqno(&self) -> usize { + // SAFETY: `gpiod_edge_event` is guaranteed to be valid here. + unsafe { + gpiod::gpiod_edge_event_get_line_seqno(self.0) + .try_into() + .unwrap() + } + } +} + +impl Drop for Event { + /// Free the edge event. + fn drop(&mut self) { + // SAFETY: `gpiod_edge_event` is guaranteed to be valid here. + unsafe { gpiod::gpiod_edge_event_free(self.0) }; + } +} diff --git a/bindings/rust/libgpiod/src/event_buffer.rs b/bindings/rust/libgpiod/src/event_buffer.rs new file mode 100644 index 000000000000..b56be9a27dc0 --- /dev/null +++ b/bindings/rust/libgpiod/src/event_buffer.rs @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar + +use std::os::raw::c_ulong; +use std::ptr; + +use super::{ + gpiod, + request::{Event, Request}, + Error, OperationType, Result, +}; + +/// Line edge events +/// +/// An iterator over the elements of type `Event`. + +pub struct Events<'a> { + buffer: &'a mut Buffer, + read_index: usize, + len: usize, +} + +impl<'a> Events<'a> { + pub fn new(buffer: &'a mut Buffer, len: usize) -> Self { + Self { + buffer, + read_index: 0, + len, + } + } + + /// Get the number of contained events in the snapshot, this doesn't change + /// on reading events from the iterator. + pub fn len(&self) -> usize { + self.len + } + + /// Check if buffer is empty. + pub fn is_empty(&self) -> bool { + self.len == 0 + } +} + +impl<'a> Iterator for Events<'a> { + type Item = Result<&'a Event>; + + fn nth(&mut self, n: usize) -> Option { + if self.read_index + n >= self.len { + return None; + } + + self.read_index += n + 1; + Some(self.buffer.event(self.read_index - 1)) + } + + fn next(&mut self) -> Option { + self.nth(0) + } +} + +/// Line edge events buffer +#[derive(Debug, Eq, PartialEq)] +pub struct Buffer { + pub(crate) buffer: *mut gpiod::gpiod_edge_event_buffer, + events: Vec<*mut gpiod::gpiod_edge_event>, +} + +impl Buffer { + /// Create a new edge event buffer. + /// + /// If capacity equals 0, it will be set to a default value of 64. If + /// capacity is larger than 1024, it will be limited to 1024. + pub fn new(capacity: usize) -> Result { + // SAFETY: The `gpiod_edge_event_buffer` returned by libgpiod is guaranteed to live as long + // as the `struct Buffer`. + let buffer = unsafe { gpiod::gpiod_edge_event_buffer_new(capacity as c_ulong) }; + if buffer.is_null() { + return Err(Error::OperationFailed( + OperationType::EdgeEventBufferNew, + errno::errno(), + )); + } + + // SAFETY: `gpiod_edge_event_buffer` is guaranteed to be valid here. + let capacity = unsafe { gpiod::gpiod_edge_event_buffer_get_capacity(buffer) as usize }; + + Ok(Self { + buffer, + events: vec![ptr::null_mut(); capacity], + }) + } + + /// Get the capacity of the event buffer. + pub fn capacity(&self) -> usize { + self.events.len() + } + + /// Get edge events from a line request. + /// + /// This function will block if no event was queued for the line. + pub fn read_edge_events(&mut self, request: &Request) -> Result { + for i in 0..self.events.len() { + self.events[i] = ptr::null_mut(); + } + + // SAFETY: `gpiod_line_request` is guaranteed to be valid here. + let ret = unsafe { + gpiod::gpiod_line_request_read_edge_event( + request.request, + self.buffer, + self.events.len().try_into().unwrap(), + ) + }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::LineRequestReadEdgeEvent, + errno::errno(), + )) + } else { + let ret = ret as usize; + + if ret > self.events.len() { + Err(Error::TooManyEvents(ret, self.events.len())) + } else { + Ok(Events::new(self, ret)) + } + } + } + + /// Read an event stored in the buffer. + fn event<'a>(&mut self, index: usize) -> Result<&'a Event> { + if self.events[index].is_null() { + // SAFETY: The `gpiod_edge_event` returned by libgpiod is guaranteed to live as long + // as the `struct Event`. + let event = unsafe { + gpiod::gpiod_edge_event_buffer_get_event(self.buffer, index.try_into().unwrap()) + }; + + if event.is_null() { + return Err(Error::OperationFailed( + OperationType::EdgeEventBufferGetEvent, + errno::errno(), + )); + } + + self.events[index] = event; + } + + // SAFETY: Safe as the underlying events object won't get freed until the time the returned + // reference is still used. + Ok(unsafe { + // This will not lead to `drop(event)`. + (self.events.as_ptr().add(index) as *const Event) + .as_ref() + .unwrap() + }) + } +} + +impl Drop for Buffer { + /// Free the edge event buffer and release all associated resources. + fn drop(&mut self) { + // SAFETY: `gpiod_edge_event_buffer` is guaranteed to be valid here. + unsafe { gpiod::gpiod_edge_event_buffer_free(self.buffer) }; + } +} diff --git a/bindings/rust/libgpiod/src/info_event.rs b/bindings/rust/libgpiod/src/info_event.rs new file mode 100644 index 000000000000..8bd558532095 --- /dev/null +++ b/bindings/rust/libgpiod/src/info_event.rs @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar + +use std::time::Duration; + +use super::{ + gpiod, + line::{self, InfoChangeKind}, + Error, OperationType, Result, +}; + +/// Line status watch events +/// +/// Accessors for the info event objects allowing to monitor changes in GPIO +/// line state. +/// +/// Callers can be notified about changes in line's state using the interfaces +/// exposed by GPIO chips. Each info event contains information about the event +/// itself (timestamp, type) as well as a snapshot of line's state in the form +/// of a line-info object. + +#[derive(Debug, Eq, PartialEq)] +pub struct Event { + pub(crate) event: *mut gpiod::gpiod_info_event, +} + +impl Event { + /// Get a single chip's line's status change event. + pub(crate) fn new(event: *mut gpiod::gpiod_info_event) -> Self { + Self { event } + } + + /// Get the event type of the status change event. + pub fn event_type(&self) -> Result { + // SAFETY: `gpiod_info_event` is guaranteed to be valid here. + InfoChangeKind::new(unsafe { gpiod::gpiod_info_event_get_event_type(self.event) } as u32) + } + + /// Get the timestamp of the event, read from the monotonic clock. + pub fn timestamp(&self) -> Duration { + // SAFETY: `gpiod_info_event` is guaranteed to be valid here. + Duration::from_nanos(unsafe { gpiod::gpiod_info_event_get_timestamp_ns(self.event) }) + } + + /// Get the line-info object associated with the event. + pub fn line_info(&self) -> Result { + // SAFETY: `gpiod_line_info` is guaranteed to be valid here. + let info = unsafe { gpiod::gpiod_info_event_get_line_info(self.event) }; + + if info.is_null() { + return Err(Error::OperationFailed( + OperationType::InfoEventGetLineInfo, + errno::errno(), + )); + } + + line::Info::new_from_event(info) + } +} + +impl Drop for Event { + /// Free the info event object and release all associated resources. + fn drop(&mut self) { + // SAFETY: `gpiod_info_event` is guaranteed to be valid here. + unsafe { gpiod::gpiod_info_event_free(self.event) } + } +} diff --git a/bindings/rust/libgpiod/src/lib.rs b/bindings/rust/libgpiod/src/lib.rs new file mode 100644 index 000000000000..161de164dddd --- /dev/null +++ b/bindings/rust/libgpiod/src/lib.rs @@ -0,0 +1,478 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar +// +// Rust wrappers for GPIOD APIs + +//! libgpiod public API +//! +//! This is the complete documentation of the public Rust API made available to +//! users of libgpiod. +//! +//! The API is logically split into several parts such as: GPIO chip & line +//! operators, GPIO events handling etc. + +use std::ffi::CStr; +use std::fs; +use std::os::raw::c_char; +use std::path::Path; +use std::time::Duration; +use std::{fmt, str}; + +use intmap::IntMap; +use thiserror::Error as ThisError; + +use libgpiod_sys as gpiod; + +/// Operation types, used with OperationFailed() Error. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum OperationType { + ChipOpen, + ChipWaitInfoEvent, + ChipGetLine, + ChipGetLineInfo, + ChipGetLineOffsetFromName, + ChipGetInfo, + ChipReadInfoEvent, + ChipRequestLines, + ChipWatchLineInfo, + EdgeEventBufferGetEvent, + EdgeEventCopy, + EdgeEventBufferNew, + InfoEventGetLineInfo, + LineConfigNew, + LineConfigAddSettings, + LineConfigGetOffsets, + LineConfigGetSettings, + LineRequestReconfigLines, + LineRequestGetVal, + LineRequestGetValSubset, + LineRequestSetVal, + LineRequestSetValSubset, + LineRequestReadEdgeEvent, + LineRequestWaitEdgeEvent, + LineSettingsNew, + LineSettingsCopy, + LineSettingsGetOutVal, + LineSettingsSetDirection, + LineSettingsSetEdgeDetection, + LineSettingsSetBias, + LineSettingsSetDrive, + LineSettingsSetActiveLow, + LineSettingsSetDebouncePeriod, + LineSettingsSetEventClock, + LineSettingsSetOutputValue, + RequestConfigNew, + RequestConfigGetConsumer, + SimBankGetVal, + SimBankNew, + SimBankSetLabel, + SimBankSetNumLines, + SimBankSetLineName, + SimBankSetPull, + SimBankHogLine, + SimCtxNew, + SimDevNew, + SimDevEnable, + SimDevDisable, +} + +impl fmt::Display for OperationType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +/// Result of libgpiod operations. +pub type Result = std::result::Result; + +/// Error codes for libgpiod operations. +#[derive(Copy, Clone, Debug, Eq, PartialEq, ThisError)] +pub enum Error { + #[error("Failed to get {0}")] + NullString(&'static str), + #[error("String not utf8: {0:?}")] + StringNotUtf8(str::Utf8Error), + #[error("Invalid String")] + InvalidString, + #[error("Invalid enum {0} value: {1}")] + InvalidEnumValue(&'static str, u32), + #[error("Operation {0} Failed: {1}")] + OperationFailed(OperationType, errno::Errno), + #[error("Invalid Arguments")] + InvalidArguments, + #[error("Event count more than buffer capacity: {0} > {1}")] + TooManyEvents(usize, usize), + #[error("Std Io Error")] + IoError, +} + +mod info_event; + +/// GPIO chip related definitions. +pub mod chip; + +mod edge_event; +mod event_buffer; +mod line_request; +mod request_config; + +/// GPIO chip request related definitions. +pub mod request { + pub use crate::edge_event::*; + pub use crate::event_buffer::*; + pub use crate::line_request::*; + pub use crate::request_config::*; +} + +mod line_config; +mod line_info; +mod line_settings; + +/// GPIO chip line related definitions. +pub mod line { + pub use crate::line_config::*; + pub use crate::line_info::*; + pub use crate::line_settings::*; + + use super::*; + + /// Value settings. + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + pub enum Value { + /// Active + Active, + /// Inactive + InActive, + } + + /// Maps offset to Value. + pub type ValueMap = IntMap; + + impl Value { + pub fn new(val: i32) -> Result { + Ok(match val { + 0 => Value::InActive, + 1 => Value::Active, + _ => return Err(Error::InvalidEnumValue("Value", val as u32)), + }) + } + + pub(crate) fn value(&self) -> i32 { + match self { + Value::Active => 1, + Value::InActive => 0, + } + } + } + + /// Offset type. + pub type Offset = u32; + + /// Direction settings. + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + pub enum Direction { + /// Request the line(s), but don't change direction. + AsIs, + /// Direction is input - for reading the value of an externally driven GPIO line. + Input, + /// Direction is output - for driving the GPIO line. + Output, + } + + impl Direction { + pub(crate) fn new(dir: u32) -> Result { + Ok(match dir { + gpiod::GPIOD_LINE_DIRECTION_AS_IS => Direction::AsIs, + gpiod::GPIOD_LINE_DIRECTION_INPUT => Direction::Input, + gpiod::GPIOD_LINE_DIRECTION_OUTPUT => Direction::Output, + _ => return Err(Error::InvalidEnumValue("Direction", dir)), + }) + } + + pub(crate) fn gpiod_direction(&self) -> u32 { + match self { + Direction::AsIs => gpiod::GPIOD_LINE_DIRECTION_AS_IS, + Direction::Input => gpiod::GPIOD_LINE_DIRECTION_INPUT, + Direction::Output => gpiod::GPIOD_LINE_DIRECTION_OUTPUT, + } + } + } + + /// Internal bias settings. + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + pub enum Bias { + /// The internal bias is disabled. + Disabled, + /// The internal pull-up bias is enabled. + PullUp, + /// The internal pull-down bias is enabled. + PullDown, + } + + impl Bias { + pub(crate) fn new(bias: u32) -> Result> { + Ok(match bias { + gpiod::GPIOD_LINE_BIAS_UNKNOWN => None, + gpiod::GPIOD_LINE_BIAS_AS_IS => None, + gpiod::GPIOD_LINE_BIAS_DISABLED => Some(Bias::Disabled), + gpiod::GPIOD_LINE_BIAS_PULL_UP => Some(Bias::PullUp), + gpiod::GPIOD_LINE_BIAS_PULL_DOWN => Some(Bias::PullDown), + _ => return Err(Error::InvalidEnumValue("Bias", bias)), + }) + } + + pub(crate) fn gpiod_bias(bias: Option) -> u32 { + match bias { + None => gpiod::GPIOD_LINE_BIAS_AS_IS, + Some(bias) => match bias { + Bias::Disabled => gpiod::GPIOD_LINE_BIAS_DISABLED, + Bias::PullUp => gpiod::GPIOD_LINE_BIAS_PULL_UP, + Bias::PullDown => gpiod::GPIOD_LINE_BIAS_PULL_DOWN, + }, + } + } + } + + /// Drive settings. + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + pub enum Drive { + /// Drive setting is push-pull. + PushPull, + /// Line output is open-drain. + OpenDrain, + /// Line output is open-source. + OpenSource, + } + + impl Drive { + pub(crate) fn new(drive: u32) -> Result { + Ok(match drive { + gpiod::GPIOD_LINE_DRIVE_PUSH_PULL => Drive::PushPull, + gpiod::GPIOD_LINE_DRIVE_OPEN_DRAIN => Drive::OpenDrain, + gpiod::GPIOD_LINE_DRIVE_OPEN_SOURCE => Drive::OpenSource, + _ => return Err(Error::InvalidEnumValue("Drive", drive)), + }) + } + + pub(crate) fn gpiod_drive(&self) -> u32 { + match self { + Drive::PushPull => gpiod::GPIOD_LINE_DRIVE_PUSH_PULL, + Drive::OpenDrain => gpiod::GPIOD_LINE_DRIVE_OPEN_DRAIN, + Drive::OpenSource => gpiod::GPIOD_LINE_DRIVE_OPEN_SOURCE, + } + } + } + + /// Edge detection settings. + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + pub enum Edge { + /// Line detects rising edge events. + Rising, + /// Line detects falling edge events. + Falling, + /// Line detects both rising and falling edge events. + Both, + } + + impl Edge { + pub(crate) fn new(edge: u32) -> Result> { + Ok(match edge { + gpiod::GPIOD_LINE_EDGE_NONE => None, + gpiod::GPIOD_LINE_EDGE_RISING => Some(Edge::Rising), + gpiod::GPIOD_LINE_EDGE_FALLING => Some(Edge::Falling), + gpiod::GPIOD_LINE_EDGE_BOTH => Some(Edge::Both), + _ => return Err(Error::InvalidEnumValue("Edge", edge)), + }) + } + + pub(crate) fn gpiod_edge(edge: Option) -> u32 { + match edge { + None => gpiod::GPIOD_LINE_EDGE_NONE, + Some(edge) => match edge { + Edge::Rising => gpiod::GPIOD_LINE_EDGE_RISING, + Edge::Falling => gpiod::GPIOD_LINE_EDGE_FALLING, + Edge::Both => gpiod::GPIOD_LINE_EDGE_BOTH, + }, + } + } + } + + /// Line setting kind. + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + pub enum SettingKind { + /// Line direction. + Direction, + /// Bias. + Bias, + /// Drive. + Drive, + /// Edge detection. + EdgeDetection, + /// Active-low setting. + ActiveLow, + /// Debounce period. + DebouncePeriod, + /// Event clock type. + EventClock, + /// Output value. + OutputValue, + } + + /// Line settings. + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + pub enum SettingVal { + /// Line direction. + Direction(Direction), + /// Bias. + Bias(Option), + /// Drive. + Drive(Drive), + /// Edge detection. + EdgeDetection(Option), + /// Active-low setting. + ActiveLow(bool), + /// Debounce period. + DebouncePeriod(Duration), + /// Event clock type. + EventClock(EventClock), + /// Output value. + OutputValue(Value), + } + + impl fmt::Display for SettingVal { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } + } + + /// Event clock settings. + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + pub enum EventClock { + /// Line uses the monotonic clock for edge event timestamps. + Monotonic, + /// Line uses the realtime clock for edge event timestamps. + Realtime, + /// Line uses the hardware timestamp engine clock for edge event timestamps. + HTE, + } + + impl EventClock { + pub(crate) fn new(clock: u32) -> Result { + Ok(match clock { + gpiod::GPIOD_LINE_EVENT_CLOCK_MONOTONIC => EventClock::Monotonic, + gpiod::GPIOD_LINE_EVENT_CLOCK_REALTIME => EventClock::Realtime, + gpiod::GPIOD_LINE_EVENT_CLOCK_HTE => EventClock::HTE, + _ => return Err(Error::InvalidEnumValue("Eventclock", clock)), + }) + } + + pub(crate) fn gpiod_clock(&self) -> u32 { + match self { + EventClock::Monotonic => gpiod::GPIOD_LINE_EVENT_CLOCK_MONOTONIC, + EventClock::Realtime => gpiod::GPIOD_LINE_EVENT_CLOCK_REALTIME, + EventClock::HTE => gpiod::GPIOD_LINE_EVENT_CLOCK_HTE, + } + } + } + + /// Line status change event types. + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + pub enum InfoChangeKind { + /// Line has been requested. + LineRequested, + /// Previously requested line has been released. + LineReleased, + /// Line configuration has changed. + LineConfigChanged, + } + + impl InfoChangeKind { + pub(crate) fn new(kind: u32) -> Result { + Ok(match kind { + gpiod::GPIOD_INFO_EVENT_LINE_REQUESTED => InfoChangeKind::LineRequested, + gpiod::GPIOD_INFO_EVENT_LINE_RELEASED => InfoChangeKind::LineReleased, + gpiod::GPIOD_INFO_EVENT_LINE_CONFIG_CHANGED => InfoChangeKind::LineConfigChanged, + _ => return Err(Error::InvalidEnumValue("InfoChangeKind", kind)), + }) + } + } + + /// Edge event types. + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + pub enum EdgeKind { + /// Rising edge event. + Rising, + /// Falling edge event. + Falling, + } + + impl EdgeKind { + pub(crate) fn new(kind: u32) -> Result { + Ok(match kind { + gpiod::GPIOD_EDGE_EVENT_RISING_EDGE => EdgeKind::Rising, + gpiod::GPIOD_EDGE_EVENT_FALLING_EDGE => EdgeKind::Falling, + _ => return Err(Error::InvalidEnumValue("EdgeEvent", kind)), + }) + } + } +} + +/// Various libgpiod-related functions. + +/// Check if the file pointed to by path is a GPIO chip character device. +/// +/// Returns true if the file exists and is a GPIO chip character device or a +/// symbolic link to it. +pub fn is_gpiochip_device>(path: &P) -> bool { + // Null-terminate the string + let path = path.as_ref().to_string_lossy() + "\0"; + + // SAFETY: libgpiod won't access the path reference once the call returns. + unsafe { gpiod::gpiod_is_gpiochip_device(path.as_ptr() as *const c_char) } +} + +/// GPIO devices. +/// +/// Returns a vector of unique available GPIO Chips. +/// +/// The chips are sorted in ascending order of the chip names. +pub fn gpiochip_devices>(path: &P) -> Result> { + let mut devices = Vec::new(); + + for entry in fs::read_dir(path).map_err(|_| Error::IoError)?.flatten() { + let path = entry.path(); + + if is_gpiochip_device(&path) { + let chip = chip::Chip::open(&path)?; + let info = chip.info()?; + + devices.push((chip, info)); + } + } + + devices.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap()); + devices.dedup_by(|a, b| a.1.eq(&b.1)); + + Ok(devices.into_iter().map(|a| a.0).collect()) +} + +/// Get the API version of the libgpiod library as a human-readable string. +pub fn libgpiod_version() -> Result<&'static str> { + // SAFETY: The string returned by libgpiod is guaranteed to live forever. + let version = unsafe { gpiod::gpiod_version_string() }; + + if version.is_null() { + return Err(Error::NullString("GPIO library version")); + } + + // SAFETY: The string is guaranteed to be valid here by the C API. + unsafe { CStr::from_ptr(version) } + .to_str() + .map_err(Error::StringNotUtf8) +} + +/// Get the API version of the libgpiod crate as a human-readable string. +pub fn crate_version() -> &'static str { + env!("CARGO_PKG_VERSION") +} diff --git a/bindings/rust/libgpiod/src/line_config.rs b/bindings/rust/libgpiod/src/line_config.rs new file mode 100644 index 000000000000..19dc187e6cbd --- /dev/null +++ b/bindings/rust/libgpiod/src/line_config.rs @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar + +use std::os::raw::{c_ulong, c_void}; +use std::slice; + +use super::{ + gpiod, + line::{Offset, Settings}, + Error, OperationType, Result, +}; + +/// Line configuration objects. +/// +/// The line-config object contains the configuration for lines that can be +/// used in two cases: +/// - when making a line request +/// - when reconfiguring a set of already requested lines. +/// +/// A new line-config object is empty. Using it in a request will lead to an +/// error. In order for a line-config to become useful, it needs to be assigned +/// at least one offset-to-settings mapping by calling +/// ::gpiod_line_config_add_line_settings. +/// +/// When calling ::gpiod_chip_request_lines, the library will request all +/// offsets that were assigned settings in the order that they were assigned. + +#[derive(Debug, Eq, PartialEq)] +pub struct Config { + pub(crate) config: *mut gpiod::gpiod_line_config, +} + +impl Config { + /// Create a new line config object. + pub fn new() -> Result { + // SAFETY: The `gpiod_line_config` returned by libgpiod is guaranteed to live as long + // as the `struct Config`. + let config = unsafe { gpiod::gpiod_line_config_new() }; + + if config.is_null() { + return Err(Error::OperationFailed( + OperationType::LineConfigNew, + errno::errno(), + )); + } + + Ok(Self { config }) + } + + /// Resets the entire configuration stored in the object. This is useful if + /// the user wants to reuse the object without reallocating it. + pub fn reset(&mut self) { + // SAFETY: `gpiod_line_config` is guaranteed to be valid here. + unsafe { gpiod::gpiod_line_config_reset(self.config) } + } + + /// Add line settings for a set of offsets. + pub fn add_line_settings(&self, offsets: &[Offset], settings: Settings) -> Result<()> { + // SAFETY: `gpiod_line_config` is guaranteed to be valid here. + let ret = unsafe { + gpiod::gpiod_line_config_add_line_settings( + self.config, + offsets.as_ptr(), + offsets.len() as c_ulong, + settings.settings, + ) + }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::LineConfigAddSettings, + errno::errno(), + )) + } else { + Ok(()) + } + } + + /// Get line settings for offset. + pub fn line_settings(&self, offset: Offset) -> Result { + // SAFETY: `gpiod_line_config` is guaranteed to be valid here. + let settings = unsafe { gpiod::gpiod_line_config_get_line_settings(self.config, offset) }; + + if settings.is_null() { + return Err(Error::OperationFailed( + OperationType::LineConfigGetSettings, + errno::errno(), + )); + } + + Ok(Settings::new_with_settings(settings)) + } + + /// Get configured offsets. + pub fn offsets(&self) -> Result> { + let mut num: u64 = 0; + let mut ptr: *mut Offset = std::ptr::null_mut(); + + // SAFETY: The `ptr` array returned by libgpiod is guaranteed to live as long + // as it is not explicitly freed with `free()`. + let ret = unsafe { + gpiod::gpiod_line_config_get_offsets( + self.config, + &mut num as *mut _ as *mut _, + &mut ptr, + ) + }; + + if ret == -1 { + return Err(Error::OperationFailed( + OperationType::LineConfigGetOffsets, + errno::errno(), + )); + } + + // SAFETY: The `ptr` array returned by libgpiod is guaranteed to live as long + // as it is not explicitly freed with `free()`. + let offsets = unsafe { slice::from_raw_parts(ptr as *const Offset, num as usize).to_vec() }; + + // SAFETY: The `ptr` array is guaranteed to be valid here. + unsafe { libc::free(ptr as *mut c_void) }; + + Ok(offsets) + } +} + +impl Drop for Config { + /// Free the line config object and release all associated resources. + fn drop(&mut self) { + // SAFETY: `gpiod_line_config` is guaranteed to be valid here. + unsafe { gpiod::gpiod_line_config_free(self.config) } + } +} diff --git a/bindings/rust/libgpiod/src/line_info.rs b/bindings/rust/libgpiod/src/line_info.rs new file mode 100644 index 000000000000..1784cde27e2f --- /dev/null +++ b/bindings/rust/libgpiod/src/line_info.rs @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar + +use std::ffi::CStr; +use std::str; +use std::time::Duration; + +use super::{ + gpiod, + line::{Bias, Direction, Drive, Edge, EventClock, Offset}, + Error, Result, +}; + +/// Line info +/// +/// Exposes functions for retrieving kernel information about both requested and +/// free lines. Line info object contains an immutable snapshot of a line's status. +/// +/// The line info contains all the publicly available information about a +/// line, which does not include the line value. The line must be requested +/// to access the line value. + +#[derive(Debug, Eq, PartialEq)] +pub struct Info { + info: *mut gpiod::gpiod_line_info, + contained: bool, +} + +impl Info { + fn new_internal(info: *mut gpiod::gpiod_line_info, contained: bool) -> Result { + Ok(Self { info, contained }) + } + + /// Get a snapshot of information about the line. + pub(crate) fn new(info: *mut gpiod::gpiod_line_info) -> Result { + Info::new_internal(info, false) + } + + /// Get a snapshot of information about the line and start watching it for changes. + pub(crate) fn new_watch(info: *mut gpiod::gpiod_line_info) -> Result { + Info::new_internal(info, false) + } + + /// Get the Line info object associated with an event. + pub(crate) fn new_from_event(info: *mut gpiod::gpiod_line_info) -> Result { + Info::new_internal(info, true) + } + + /// Get the offset of the line within the GPIO chip. + /// + /// The offset uniquely identifies the line on the chip. The combination of the chip and offset + /// uniquely identifies the line within the system. + + pub fn offset(&self) -> Offset { + // SAFETY: `gpiod_line_info` is guaranteed to be valid here. + unsafe { gpiod::gpiod_line_info_get_offset(self.info) } + } + + /// Get GPIO line's name. + pub fn name(&self) -> Result<&str> { + // SAFETY: The string returned by libgpiod is guaranteed to live as long + // as the `struct Info`. + let name = unsafe { gpiod::gpiod_line_info_get_name(self.info) }; + if name.is_null() { + return Err(Error::NullString("GPIO line's name")); + } + + // SAFETY: The string is guaranteed to be valid here by the C API. + unsafe { CStr::from_ptr(name) } + .to_str() + .map_err(Error::StringNotUtf8) + } + + /// Returns True if the line is in use, false otherwise. + /// + /// The user space can't know exactly why a line is busy. It may have been + /// requested by another process or hogged by the kernel. It only matters that + /// the line is used and we can't request it. + pub fn is_used(&self) -> bool { + // SAFETY: `gpiod_line_info` is guaranteed to be valid here. + unsafe { gpiod::gpiod_line_info_is_used(self.info) } + } + + /// Get the GPIO line's consumer name. + pub fn consumer(&self) -> Result<&str> { + // SAFETY: The string returned by libgpiod is guaranteed to live as long + // as the `struct Info`. + let name = unsafe { gpiod::gpiod_line_info_get_consumer(self.info) }; + if name.is_null() { + return Err(Error::NullString("GPIO line's consumer name")); + } + + // SAFETY: The string is guaranteed to be valid here by the C API. + unsafe { CStr::from_ptr(name) } + .to_str() + .map_err(Error::StringNotUtf8) + } + + /// Get the GPIO line's direction. + pub fn direction(&self) -> Result { + // SAFETY: `gpiod_line_info` is guaranteed to be valid here. + Direction::new(unsafe { gpiod::gpiod_line_info_get_direction(self.info) } as u32) + } + + /// Returns true if the line is "active-low", false otherwise. + pub fn is_active_low(&self) -> bool { + // SAFETY: `gpiod_line_info` is guaranteed to be valid here. + unsafe { gpiod::gpiod_line_info_is_active_low(self.info) } + } + + /// Get the GPIO line's bias setting. + pub fn bias(&self) -> Result> { + // SAFETY: `gpiod_line_info` is guaranteed to be valid here. + Bias::new(unsafe { gpiod::gpiod_line_info_get_bias(self.info) } as u32) + } + + /// Get the GPIO line's drive setting. + pub fn drive(&self) -> Result { + // SAFETY: `gpiod_line_info` is guaranteed to be valid here. + Drive::new(unsafe { gpiod::gpiod_line_info_get_drive(self.info) } as u32) + } + + /// Get the current edge detection setting of the line. + pub fn edge_detection(&self) -> Result> { + // SAFETY: `gpiod_line_info` is guaranteed to be valid here. + Edge::new(unsafe { gpiod::gpiod_line_info_get_edge_detection(self.info) } as u32) + } + + /// Get the current event clock setting used for edge event timestamps. + pub fn event_clock(&self) -> Result { + // SAFETY: `gpiod_line_info` is guaranteed to be valid here. + EventClock::new(unsafe { gpiod::gpiod_line_info_get_event_clock(self.info) } as u32) + } + + /// Returns true if the line is debounced (either by hardware or by the + /// kernel software debouncer), false otherwise. + pub fn is_debounced(&self) -> bool { + // SAFETY: `gpiod_line_info` is guaranteed to be valid here. + unsafe { gpiod::gpiod_line_info_is_debounced(self.info) } + } + + /// Get the debounce period of the line. + pub fn debounce_period(&self) -> Duration { + // SAFETY: `gpiod_line_info` is guaranteed to be valid here. + Duration::from_micros(unsafe { + gpiod::gpiod_line_info_get_debounce_period_us(self.info) as u64 + }) + } +} + +impl Drop for Info { + fn drop(&mut self) { + // We must not free the Line info object created from `struct chip::Event` by calling + // libgpiod API. + if !self.contained { + // SAFETY: `gpiod_line_info` is guaranteed to be valid here. + unsafe { gpiod::gpiod_line_info_free(self.info) } + } + } +} diff --git a/bindings/rust/libgpiod/src/line_request.rs b/bindings/rust/libgpiod/src/line_request.rs new file mode 100644 index 000000000000..3215ab816434 --- /dev/null +++ b/bindings/rust/libgpiod/src/line_request.rs @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar + +use std::os::{raw::c_ulong, unix::prelude::AsRawFd}; +use std::time::Duration; + +use super::{ + gpiod, + line::{self, Offset, Value, ValueMap}, + request, Error, OperationType, Result, +}; + +/// Line request operations +/// +/// Allows interaction with a set of requested lines. +#[derive(Debug, Eq, PartialEq)] +pub struct Request { + pub(crate) request: *mut gpiod::gpiod_line_request, +} + +impl Request { + /// Request a set of lines for exclusive usage. + pub(crate) fn new(request: *mut gpiod::gpiod_line_request) -> Result { + Ok(Self { request }) + } + + /// Get the number of lines in the request. + pub fn num_lines(&self) -> usize { + // SAFETY: `gpiod_line_request` is guaranteed to be valid here. + unsafe { gpiod::gpiod_line_request_get_num_lines(self.request) as usize } + } + + /// Get the offsets of lines in the request. + pub fn offsets(&self) -> Vec { + let mut offsets = vec![0; self.num_lines() as usize]; + + // SAFETY: `gpiod_line_request` is guaranteed to be valid here. + unsafe { gpiod::gpiod_line_request_get_offsets(self.request, offsets.as_mut_ptr()) }; + offsets + } + + /// Get the value (0 or 1) of a single line associated with the request. + pub fn value(&self, offset: Offset) -> Result { + // SAFETY: `gpiod_line_request` is guaranteed to be valid here. + let value = unsafe { gpiod::gpiod_line_request_get_value(self.request, offset) }; + + if value != 0 && value != 1 { + Err(Error::OperationFailed( + OperationType::LineRequestGetVal, + errno::errno(), + )) + } else { + Value::new(value) + } + } + + /// Get values of a subset of lines associated with the request. + pub fn values_subset(&self, offsets: &[Offset]) -> Result { + let mut values = vec![0; offsets.len()]; + + // SAFETY: `gpiod_line_request` is guaranteed to be valid here. + let ret = unsafe { + gpiod::gpiod_line_request_get_values_subset( + self.request, + offsets.len() as c_ulong, + offsets.as_ptr(), + values.as_mut_ptr(), + ) + }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::LineRequestGetValSubset, + errno::errno(), + )) + } else { + let mut map = ValueMap::new(); + + for (i, val) in values.iter().enumerate() { + map.insert(offsets[i].into(), Value::new(*val)?); + } + + Ok(map) + } + } + + /// Get values of all lines associated with the request. + pub fn values(&self) -> Result { + self.values_subset(&self.offsets()) + } + + /// Set the value of a single line associated with the request. + pub fn set_value(&self, offset: Offset, value: Value) -> Result<()> { + // SAFETY: `gpiod_line_request` is guaranteed to be valid here. + let ret = + unsafe { gpiod::gpiod_line_request_set_value(self.request, offset, value.value()) }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::LineRequestSetVal, + errno::errno(), + )) + } else { + Ok(()) + } + } + + /// Get values of a subset of lines associated with the request. + pub fn set_values_subset(&self, map: ValueMap) -> Result<()> { + let mut offsets = Vec::new(); + let mut values = Vec::new(); + + for (offset, value) in map { + offsets.push(offset as u32); + values.push(value.value()); + } + + // SAFETY: `gpiod_line_request` is guaranteed to be valid here. + let ret = unsafe { + gpiod::gpiod_line_request_set_values_subset( + self.request, + offsets.len() as c_ulong, + offsets.as_ptr(), + values.as_ptr(), + ) + }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::LineRequestSetValSubset, + errno::errno(), + )) + } else { + Ok(()) + } + } + + /// Get values of all lines associated with the request. + pub fn set_values(&self, values: &[Value]) -> Result<()> { + if values.len() != self.num_lines() as usize { + return Err(Error::InvalidArguments); + } + + let mut new_values = Vec::new(); + for value in values { + new_values.push(value.value()); + } + + // SAFETY: `gpiod_line_request` is guaranteed to be valid here. + let ret = + unsafe { gpiod::gpiod_line_request_set_values(self.request, new_values.as_ptr()) }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::LineRequestSetVal, + errno::errno(), + )) + } else { + Ok(()) + } + } + + /// Update the configuration of lines associated with the line request. + pub fn reconfigure_lines(&self, lconfig: &line::Config) -> Result<()> { + // SAFETY: `gpiod_line_request` is guaranteed to be valid here. + let ret = + unsafe { gpiod::gpiod_line_request_reconfigure_lines(self.request, lconfig.config) }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::LineRequestReconfigLines, + errno::errno(), + )) + } else { + Ok(()) + } + } + + /// Wait for edge events on any of the lines associated with the request. + pub fn wait_edge_event(&self, timeout: Option) -> Result { + let timeout = match timeout { + Some(x) => x.as_nanos() as i64, + // Block indefinitely + None => -1, + }; + + // SAFETY: `gpiod_line_request` is guaranteed to be valid here. + let ret = unsafe { gpiod::gpiod_line_request_wait_edge_event(self.request, timeout) }; + + match ret { + -1 => Err(Error::OperationFailed( + OperationType::LineRequestWaitEdgeEvent, + errno::errno(), + )), + 0 => Ok(false), + _ => Ok(true), + } + } + + /// Get a number of edge events from a line request. + /// + /// This function will block if no event was queued for the line. + pub fn read_edge_events<'a>( + &'a self, + buffer: &'a mut request::Buffer, + ) -> Result { + buffer.read_edge_events(self) + } +} + +impl AsRawFd for Request { + /// Get the file descriptor associated with the line request. + fn as_raw_fd(&self) -> i32 { + // SAFETY: `gpiod_line_request` is guaranteed to be valid here. + unsafe { gpiod::gpiod_line_request_get_fd(self.request) } + } +} + +impl Drop for Request { + /// Release the requested lines and free all associated resources. + fn drop(&mut self) { + // SAFETY: `gpiod_line_request` is guaranteed to be valid here. + unsafe { gpiod::gpiod_line_request_release(self.request) } + } +} diff --git a/bindings/rust/libgpiod/src/line_settings.rs b/bindings/rust/libgpiod/src/line_settings.rs new file mode 100644 index 000000000000..cedf7cabafcc --- /dev/null +++ b/bindings/rust/libgpiod/src/line_settings.rs @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar + +use std::time::Duration; + +use super::{ + gpiod, + line::{Bias, Direction, Drive, Edge, EventClock, SettingKind, SettingVal, Value}, + Error, OperationType, Result, +}; + +/// Line settings objects. +/// +/// Line settings object contains a set of line properties that can be used +/// when requesting lines or reconfiguring an existing request. +/// +/// Mutators in general can only fail if the new property value is invalid. The +/// return values can be safely ignored - the object remains valid even after +/// a mutator fails and simply uses the sane default appropriate for given +/// property. + +#[derive(Debug, Eq, PartialEq)] +pub struct Settings { + pub(crate) settings: *mut gpiod::gpiod_line_settings, +} + +impl Settings { + /// Create a new line settings object. + pub fn new() -> Result { + // SAFETY: The `gpiod_line_settings` returned by libgpiod is guaranteed to live as long + // as the `struct Settings`. + let settings = unsafe { gpiod::gpiod_line_settings_new() }; + + if settings.is_null() { + return Err(Error::OperationFailed( + OperationType::LineSettingsNew, + errno::errno(), + )); + } + + Ok(Self { settings }) + } + + pub fn new_with_settings(settings: *mut gpiod::gpiod_line_settings) -> Self { + Self { settings } + } + + /// Resets the line settings object to its default values. + pub fn reset(&mut self) { + // SAFETY: `gpiod_line_settings` is guaranteed to be valid here. + unsafe { gpiod::gpiod_line_settings_reset(self.settings) } + } + + /// Makes copy of the settings object. + pub fn settings_clone(&self) -> Result { + // SAFETY: `gpiod_line_settings` is guaranteed to be valid here. + let settings = unsafe { gpiod::gpiod_line_settings_copy(self.settings) }; + if settings.is_null() { + return Err(Error::OperationFailed( + OperationType::LineSettingsCopy, + errno::errno(), + )); + } + + Ok(Self { settings }) + } + + /// Set line prop setting. + pub fn set_prop(&mut self, props: &[SettingVal]) -> Result<&mut Self> { + for property in props { + match property { + SettingVal::Direction(prop) => self.set_direction(*prop)?, + SettingVal::EdgeDetection(prop) => self.set_edge_detection(*prop)?, + SettingVal::Bias(prop) => self.set_bias(*prop)?, + SettingVal::Drive(prop) => self.set_drive(*prop)?, + SettingVal::ActiveLow(prop) => self.set_active_low(*prop), + SettingVal::DebouncePeriod(prop) => self.set_debounce_period(*prop), + SettingVal::EventClock(prop) => self.set_event_clock(*prop)?, + SettingVal::OutputValue(prop) => self.set_output_value(*prop)?, + }; + } + + Ok(self) + } + + /// Get the line prop setting. + pub fn prop(&self, property: SettingKind) -> Result { + Ok(match property { + SettingKind::Direction => SettingVal::Direction(self.direction()?), + SettingKind::EdgeDetection => SettingVal::EdgeDetection(self.edge_detection()?), + SettingKind::Bias => SettingVal::Bias(self.bias()?), + SettingKind::Drive => SettingVal::Drive(self.drive()?), + SettingKind::ActiveLow => SettingVal::ActiveLow(self.active_low()), + SettingKind::DebouncePeriod => SettingVal::DebouncePeriod(self.debounce_period()?), + SettingKind::EventClock => SettingVal::EventClock(self.event_clock()?), + SettingKind::OutputValue => SettingVal::OutputValue(self.output_value()?), + }) + } + + /// Set the line direction. + pub fn set_direction(&mut self, direction: Direction) -> Result<&mut Self> { + // SAFETY: `gpiod_line_settings` is guaranteed to be valid here. + let ret = unsafe { + gpiod::gpiod_line_settings_set_direction( + self.settings, + direction.gpiod_direction() as i32, + ) + }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::LineSettingsSetDirection, + errno::errno(), + )) + } else { + Ok(self) + } + } + + /// Get the direction setting. + pub fn direction(&self) -> Result { + // SAFETY: `gpiod_line_settings` is guaranteed to be valid here. + Direction::new(unsafe { gpiod::gpiod_line_settings_get_direction(self.settings) } as u32) + } + + /// Set the edge event detection setting. + pub fn set_edge_detection(&mut self, edge: Option) -> Result<&mut Self> { + // SAFETY: `gpiod_line_settings` is guaranteed to be valid here. + let ret = unsafe { + gpiod::gpiod_line_settings_set_edge_detection( + self.settings, + Edge::gpiod_edge(edge) as i32, + ) + }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::LineSettingsSetEdgeDetection, + errno::errno(), + )) + } else { + Ok(self) + } + } + + /// Get the edge event detection setting. + pub fn edge_detection(&self) -> Result> { + // SAFETY: `gpiod_line_settings` is guaranteed to be valid here. + Edge::new(unsafe { gpiod::gpiod_line_settings_get_edge_detection(self.settings) } as u32) + } + + /// Set the bias setting. + pub fn set_bias(&mut self, bias: Option) -> Result<&mut Self> { + // SAFETY: `gpiod_line_settings` is guaranteed to be valid here. + let ret = unsafe { + gpiod::gpiod_line_settings_set_bias(self.settings, Bias::gpiod_bias(bias) as i32) + }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::LineSettingsSetBias, + errno::errno(), + )) + } else { + Ok(self) + } + } + + /// Get the bias setting. + pub fn bias(&self) -> Result> { + // SAFETY: `gpiod_line_settings` is guaranteed to be valid here. + Bias::new(unsafe { gpiod::gpiod_line_settings_get_bias(self.settings) } as u32) + } + + /// Set the drive setting. + pub fn set_drive(&mut self, drive: Drive) -> Result<&mut Self> { + // SAFETY: `gpiod_line_settings` is guaranteed to be valid here. + let ret = unsafe { + gpiod::gpiod_line_settings_set_drive(self.settings, drive.gpiod_drive() as i32) + }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::LineSettingsSetDrive, + errno::errno(), + )) + } else { + Ok(self) + } + } + + /// Get the drive setting. + pub fn drive(&self) -> Result { + // SAFETY: `gpiod_line_settings` is guaranteed to be valid here. + Drive::new(unsafe { gpiod::gpiod_line_settings_get_drive(self.settings) } as u32) + } + + /// Set active-low setting. + pub fn set_active_low(&mut self, active_low: bool) -> &mut Self { + // SAFETY: `gpiod_line_settings` is guaranteed to be valid here. + unsafe { + gpiod::gpiod_line_settings_set_active_low(self.settings, active_low); + } + self + } + + /// Check the active-low setting. + pub fn active_low(&self) -> bool { + // SAFETY: `gpiod_line_settings` is guaranteed to be valid here. + unsafe { gpiod::gpiod_line_settings_get_active_low(self.settings) } + } + + /// Set the debounce period setting. + pub fn set_debounce_period(&mut self, period: Duration) -> &mut Self { + // SAFETY: `gpiod_line_settings` is guaranteed to be valid here. + unsafe { + gpiod::gpiod_line_settings_set_debounce_period_us( + self.settings, + period.as_micros().try_into().unwrap(), + ); + } + + self + } + + /// Get the debounce period. + pub fn debounce_period(&self) -> Result { + // SAFETY: `gpiod_line_settings` is guaranteed to be valid here. + Ok(Duration::from_micros(unsafe { + gpiod::gpiod_line_settings_get_debounce_period_us(self.settings) as u64 + })) + } + + /// Set the event clock setting. + pub fn set_event_clock(&mut self, clock: EventClock) -> Result<&mut Self> { + // SAFETY: `gpiod_line_settings` is guaranteed to be valid here. + let ret = unsafe { + gpiod::gpiod_line_settings_set_event_clock(self.settings, clock.gpiod_clock() as i32) + }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::LineSettingsSetEventClock, + errno::errno(), + )) + } else { + Ok(self) + } + } + + /// Get the event clock setting. + pub fn event_clock(&self) -> Result { + // SAFETY: `gpiod_line_settings` is guaranteed to be valid here. + EventClock::new(unsafe { gpiod::gpiod_line_settings_get_event_clock(self.settings) } as u32) + } + + /// Set the output value setting. + pub fn set_output_value(&mut self, value: Value) -> Result<&mut Self> { + // SAFETY: `gpiod_line_settings` is guaranteed to be valid here. + let ret = + unsafe { gpiod::gpiod_line_settings_set_output_value(self.settings, value.value()) }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::LineSettingsSetOutputValue, + errno::errno(), + )) + } else { + Ok(self) + } + } + + /// Get the output value, 0 or 1. + pub fn output_value(&self) -> Result { + // SAFETY: `gpiod_line_settings` is guaranteed to be valid here. + let value = unsafe { gpiod::gpiod_line_settings_get_output_value(self.settings) }; + + if value != 0 && value != 1 { + Err(Error::OperationFailed( + OperationType::LineSettingsGetOutVal, + errno::errno(), + )) + } else { + Value::new(value) + } + } +} + +impl Drop for Settings { + /// Free the line settings object and release all associated resources. + fn drop(&mut self) { + // SAFETY: `gpiod_line_settings` is guaranteed to be valid here. + unsafe { gpiod::gpiod_line_settings_free(self.settings) } + } +} diff --git a/bindings/rust/libgpiod/src/request_config.rs b/bindings/rust/libgpiod/src/request_config.rs new file mode 100644 index 000000000000..9d38548dd817 --- /dev/null +++ b/bindings/rust/libgpiod/src/request_config.rs @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar + +use std::ffi::{CStr, CString}; +use std::os::raw::{c_char, c_ulong}; +use std::str; + +use super::{gpiod, Error, OperationType, Result}; + +/// Request configuration objects +/// +/// Request config objects are used to pass a set of options to the kernel at +/// the time of the line request. The mutators don't return error values. If the +/// values are invalid, in general they are silently adjusted to acceptable +/// ranges. + +#[derive(Debug, Eq, PartialEq)] +pub struct Config { + pub(crate) config: *mut gpiod::gpiod_request_config, +} + +impl Config { + /// Create a new request config object. + pub fn new() -> Result { + // SAFETY: The `gpiod_request_config` returned by libgpiod is guaranteed to live as long + // as the `struct Config`. + let config = unsafe { gpiod::gpiod_request_config_new() }; + if config.is_null() { + return Err(Error::OperationFailed( + OperationType::RequestConfigNew, + errno::errno(), + )); + } + + Ok(Self { config }) + } + + /// Set the consumer name for the request. + /// + /// If the consumer string is too long, it will be truncated to the max + /// accepted length. + pub fn set_consumer(&self, consumer: &str) -> Result<()> { + let consumer = CString::new(consumer).map_err(|_| Error::InvalidString)?; + + // SAFETY: `gpiod_request_config` is guaranteed to be valid here. + unsafe { + gpiod::gpiod_request_config_set_consumer( + self.config, + consumer.as_ptr() as *const c_char, + ) + } + + Ok(()) + } + + /// Get the consumer name configured in the request config. + pub fn consumer(&self) -> Result<&str> { + // SAFETY: The string returned by libgpiod is guaranteed to live as long + // as the `struct Config`. + let consumer = unsafe { gpiod::gpiod_request_config_get_consumer(self.config) }; + if consumer.is_null() { + return Err(Error::OperationFailed( + OperationType::RequestConfigGetConsumer, + errno::errno(), + )); + } + + // SAFETY: The string is guaranteed to be valid here by the C API. + unsafe { CStr::from_ptr(consumer) } + .to_str() + .map_err(Error::StringNotUtf8) + } + + /// Set the size of the kernel event buffer for the request. + pub fn set_event_buffer_size(&self, size: usize) { + // SAFETY: `gpiod_request_config` is guaranteed to be valid here. + unsafe { gpiod::gpiod_request_config_set_event_buffer_size(self.config, size as c_ulong) } + } + + /// Get the edge event buffer size setting for the request config. + pub fn event_buffer_size(&self) -> usize { + // SAFETY: `gpiod_request_config` is guaranteed to be valid here. + unsafe { gpiod::gpiod_request_config_get_event_buffer_size(self.config) as usize } + } +} + +impl Drop for Config { + /// Free the request config object and release all associated resources. + fn drop(&mut self) { + // SAFETY: `gpiod_request_config` is guaranteed to be valid here. + unsafe { gpiod::gpiod_request_config_free(self.config) } + } +} From patchwork Fri Nov 18 10:44:41 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Viresh Kumar X-Patchwork-Id: 626466 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 52B52C4332F for ; Fri, 18 Nov 2022 10:46:16 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S241053AbiKRKqL (ORCPT ); Fri, 18 Nov 2022 05:46:11 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:35456 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S241720AbiKRKpw (ORCPT ); Fri, 18 Nov 2022 05:45:52 -0500 Received: from mail-pj1-x1035.google.com (mail-pj1-x1035.google.com [IPv6:2607:f8b0:4864:20::1035]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 9BA5499E9F for ; Fri, 18 Nov 2022 02:45:21 -0800 (PST) Received: by mail-pj1-x1035.google.com with SMTP id w15-20020a17090a380f00b0021873113cb4so4271921pjb.0 for ; Fri, 18 Nov 2022 02:45:21 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=j+UBAzg3xIzDsNbZZn3du7a1aLnhNNUqO6Og7qPm99U=; b=wzocFElOFWF58dKf0XtC176wfDcz4z8lFC7mGDI4EbIcxel4TQllEvuQyRtQ8htLSp d0wrYUSCJ90IkPOCFh6HzbXbDJa01CBmeCPSlbXK92G0sSdF0idAkUYD/GTctAr2uIIX QMzkNtpgCPAgO65+a6jRCzGscpvdyFHFzCh5YYD3sIKs/RE/4bGV2K8qubnlHk0PdFmD 0CUsNTkmXDJuzkAQuhdb+F9yanA6VHzH/x8kkFh2VCmSjOXnQ7ouzNDsSqK2wRBOoUlI x3H/MQFeYou4ef3XKlzMIroVWYniiJsX9eSQu4y2ovldC4BdUGSohyMg8MABnbFURvsE yWvQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=j+UBAzg3xIzDsNbZZn3du7a1aLnhNNUqO6Og7qPm99U=; b=yqhvC9doRpY9gRnBi9DwpUbG5I3PXjjukxfSQ2jclA8MhP2hU1v9ptDKmkJfBAq2iz v+1pbC2rvIAEKtooEeItkOrO8jvmclU22HuaKAa+NBNxg18azXnIpPoS8CB8X1zbzW9q V5s1fVTl+ANUzDRj26M+E57wyNKATqP3UadUeOOB3O3GozgPQYAdppnSL+Kw/9YgVLrP CAGYNMbOJAvaW3lpmS6IDObNv9W3QR1llap9anBYFK+Ba73xttZeN4b8Hb6/LOIFwjU0 Vf8FiOwR15I6aI8zSRSBoEEyaVhXabzjt3yhwb7hjjOegrd+CxbNt2JVw9x38D2sHwUf LOIg== X-Gm-Message-State: ANoB5pmM/jjWAKHpki+gFok7IzHHCGdgvXOaxW6xsgbvJmfv8lpAFj1t uUIUORuOArxma+sl9W9i7O3J+A== X-Google-Smtp-Source: AA0mqf5PZyZmYAOIymbz31l1oEEY6DAbiA5BFtZCTyBVy+4frH96Qt37Bn3cIgrpq0kTbLrUjn0vHg== X-Received: by 2002:a17:90a:7183:b0:212:ede4:3c19 with SMTP id i3-20020a17090a718300b00212ede43c19mr7309344pjk.151.1668768320284; Fri, 18 Nov 2022 02:45:20 -0800 (PST) Received: from localhost ([122.172.85.60]) by smtp.gmail.com with ESMTPSA id t15-20020a170902b20f00b00188ef2314b0sm3245940plr.39.2022.11.18.02.45.19 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 18 Nov 2022 02:45:19 -0800 (PST) From: Viresh Kumar To: Linus Walleij , Bartosz Golaszewski , Kent Gibson Cc: Viresh Kumar , Vincent Guittot , linux-gpio@vger.kernel.org, Miguel Ojeda , Wedson Almeida Filho , =?utf-8?q?Alex_Benn=C3=A9e?= , stratos-dev@op-lists.linaro.org, Gerard Ryan , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_Roy_Baron?= , y86-dev Subject: [libgpiod][PATCH V10 5/6] bindings: rust: Add tests for libgpiod crate Date: Fri, 18 Nov 2022 16:14:41 +0530 Message-Id: X-Mailer: git-send-email 2.31.1.272.g89b43f80a514 In-Reply-To: References: MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-gpio@vger.kernel.org Add tests for the rust bindings, quite similar to the ones in cxx bindings. Reviewed-by: Kent Gibson Signed-off-by: Viresh Kumar --- bindings/rust/libgpiod/Cargo.toml | 3 + bindings/rust/libgpiod/tests/chip.rs | 97 ++++ bindings/rust/libgpiod/tests/common/config.rs | 142 +++++ bindings/rust/libgpiod/tests/common/mod.rs | 9 + bindings/rust/libgpiod/tests/edge_event.rs | 297 ++++++++++ bindings/rust/libgpiod/tests/info_event.rs | 166 ++++++ bindings/rust/libgpiod/tests/line_config.rs | 95 ++++ bindings/rust/libgpiod/tests/line_info.rs | 275 ++++++++++ bindings/rust/libgpiod/tests/line_request.rs | 509 ++++++++++++++++++ bindings/rust/libgpiod/tests/line_settings.rs | 203 +++++++ .../rust/libgpiod/tests/request_config.rs | 38 ++ 11 files changed, 1834 insertions(+) create mode 100644 bindings/rust/libgpiod/tests/chip.rs create mode 100644 bindings/rust/libgpiod/tests/common/config.rs create mode 100644 bindings/rust/libgpiod/tests/common/mod.rs create mode 100644 bindings/rust/libgpiod/tests/edge_event.rs create mode 100644 bindings/rust/libgpiod/tests/info_event.rs create mode 100644 bindings/rust/libgpiod/tests/line_config.rs create mode 100644 bindings/rust/libgpiod/tests/line_info.rs create mode 100644 bindings/rust/libgpiod/tests/line_request.rs create mode 100644 bindings/rust/libgpiod/tests/line_settings.rs create mode 100644 bindings/rust/libgpiod/tests/request_config.rs diff --git a/bindings/rust/libgpiod/Cargo.toml b/bindings/rust/libgpiod/Cargo.toml index a38df1c0b10d..655e7a2ce107 100644 --- a/bindings/rust/libgpiod/Cargo.toml +++ b/bindings/rust/libgpiod/Cargo.toml @@ -20,3 +20,6 @@ intmap = "2.0.0" libc = "0.2.39" libgpiod-sys = { path = "../libgpiod-sys" } thiserror = "1.0" + +[dev-dependencies] +gpiosim-sys = { path = "../gpiosim-sys" } diff --git a/bindings/rust/libgpiod/tests/chip.rs b/bindings/rust/libgpiod/tests/chip.rs new file mode 100644 index 000000000000..702af93e5f2c --- /dev/null +++ b/bindings/rust/libgpiod/tests/chip.rs @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar + +mod common; + +mod chip { + use libc::{ENODEV, ENOENT, ENOTTY}; + use std::path::PathBuf; + + use gpiosim_sys::Sim; + use libgpiod::{chip::Chip, Error as ChipError, OperationType}; + + mod open { + use super::*; + + #[test] + fn nonexistent_file() { + assert_eq!( + Chip::open(&PathBuf::from("/dev/nonexistent")).unwrap_err(), + ChipError::OperationFailed(OperationType::ChipOpen, errno::Errno(ENOENT)) + ); + } + + #[test] + fn no_dev_file() { + assert_eq!( + Chip::open(&PathBuf::from("/tmp")).unwrap_err(), + ChipError::OperationFailed(OperationType::ChipOpen, errno::Errno(ENOTTY)) + ); + } + + #[test] + fn non_gpio_char_dev_file() { + assert_eq!( + Chip::open(&PathBuf::from("/dev/null")).unwrap_err(), + ChipError::OperationFailed(OperationType::ChipOpen, errno::Errno(ENODEV)) + ); + } + + #[test] + fn gpiosim_file() { + let sim = Sim::new(None, None, true).unwrap(); + assert!(Chip::open(&sim.dev_path()).is_ok()); + } + } + + mod verify { + use super::*; + const NGPIO: usize = 16; + const LABEL: &str = "foobar"; + + #[test] + fn basic_helpers() { + let sim = Sim::new(Some(NGPIO), Some(LABEL), true).unwrap(); + let chip = Chip::open(&sim.dev_path()).unwrap(); + let info = chip.info().unwrap(); + + assert_eq!(info.label().unwrap(), LABEL); + assert_eq!(info.name().unwrap(), sim.chip_name()); + assert_eq!(chip.path().unwrap(), sim.dev_path().to_str().unwrap()); + assert_eq!(info.num_lines(), NGPIO as usize); + } + + #[test] + fn find_line() { + let sim = Sim::new(Some(NGPIO), None, false).unwrap(); + sim.set_line_name(0, "zero").unwrap(); + sim.set_line_name(2, "two").unwrap(); + sim.set_line_name(3, "three").unwrap(); + sim.set_line_name(5, "five").unwrap(); + sim.set_line_name(10, "ten").unwrap(); + sim.set_line_name(11, "ten").unwrap(); + sim.enable().unwrap(); + + let chip = Chip::open(&sim.dev_path()).unwrap(); + + // Success case + assert_eq!(chip.line_offset_from_name("zero").unwrap(), 0); + assert_eq!(chip.line_offset_from_name("two").unwrap(), 2); + assert_eq!(chip.line_offset_from_name("three").unwrap(), 3); + assert_eq!(chip.line_offset_from_name("five").unwrap(), 5); + + // Success with duplicate names, should return first entry + assert_eq!(chip.line_offset_from_name("ten").unwrap(), 10); + + // Failure + assert_eq!( + chip.line_offset_from_name("nonexistent").unwrap_err(), + ChipError::OperationFailed( + OperationType::ChipGetLineOffsetFromName, + errno::Errno(ENOENT), + ) + ); + } + } +} diff --git a/bindings/rust/libgpiod/tests/common/config.rs b/bindings/rust/libgpiod/tests/common/config.rs new file mode 100644 index 000000000000..842a70a9d70e --- /dev/null +++ b/bindings/rust/libgpiod/tests/common/config.rs @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar + +use std::sync::{Arc, Mutex}; +use std::time::Duration; + +use gpiosim_sys::{Pull, Sim, Value as SimValue}; +use libgpiod::{ + chip::Chip, + line::{self, Bias, Direction, Drive, Edge, EventClock, Offset, SettingVal, Value}, + request, Result, +}; + +pub(crate) struct TestConfig { + sim: Arc>, + chip: Option, + request: Option, + rconfig: request::Config, + lconfig: line::Config, + lsettings: Option, +} + +impl TestConfig { + pub(crate) fn new(ngpio: usize) -> Result { + Ok(Self { + sim: Arc::new(Mutex::new(Sim::new(Some(ngpio), None, true)?)), + chip: None, + request: None, + rconfig: request::Config::new().unwrap(), + lconfig: line::Config::new().unwrap(), + lsettings: Some(line::Settings::new().unwrap()), + }) + } + + pub(crate) fn set_pull(&self, offsets: &[Offset], pulls: &[Pull]) { + for i in 0..pulls.len() { + self.sim + .lock() + .unwrap() + .set_pull(offsets[i], pulls[i]) + .unwrap(); + } + } + + pub(crate) fn rconfig_set_consumer(&self, consumer: &str) { + self.rconfig.set_consumer(consumer).unwrap(); + } + + pub(crate) fn lconfig_val(&mut self, dir: Option, val: Option) { + let mut settings = Vec::new(); + + if let Some(dir) = dir { + settings.push(SettingVal::Direction(dir)); + } + + if let Some(val) = val { + settings.push(SettingVal::OutputValue(val)); + } + + if !settings.is_empty() { + self.lsettings().set_prop(&settings).unwrap(); + } + } + + pub(crate) fn lconfig_bias(&mut self, dir: Direction, bias: Option) { + let settings = vec![SettingVal::Direction(dir), SettingVal::Bias(bias)]; + self.lsettings().set_prop(&settings).unwrap(); + } + + pub(crate) fn lconfig_clock(&mut self, clock: EventClock) { + let settings = vec![SettingVal::EventClock(clock)]; + self.lsettings().set_prop(&settings).unwrap(); + } + + pub(crate) fn lconfig_debounce(&mut self, duration: Duration) { + let settings = vec![ + SettingVal::Direction(Direction::Input), + SettingVal::DebouncePeriod(duration), + ]; + self.lsettings().set_prop(&settings).unwrap(); + } + + pub(crate) fn lconfig_drive(&mut self, dir: Direction, drive: Drive) { + let settings = vec![SettingVal::Direction(dir), SettingVal::Drive(drive)]; + self.lsettings().set_prop(&settings).unwrap(); + } + + pub(crate) fn lconfig_edge(&mut self, dir: Option, edge: Option) { + let mut settings = Vec::new(); + + if let Some(dir) = dir { + settings.push(SettingVal::Direction(dir)); + } + + settings.push(SettingVal::EdgeDetection(edge)); + self.lsettings().set_prop(&settings).unwrap(); + } + + pub(crate) fn lconfig_add_settings(&mut self, offsets: &[Offset]) { + self.lconfig + .add_line_settings(offsets, self.lsettings.take().unwrap()) + .unwrap() + } + + pub(crate) fn request_lines(&mut self) -> Result<()> { + let chip = Chip::open(&self.sim.lock().unwrap().dev_path())?; + + self.request = Some(chip.request_lines(&self.rconfig, &self.lconfig)?); + self.chip = Some(chip); + + Ok(()) + } + + pub(crate) fn sim(&self) -> Arc> { + self.sim.clone() + } + + pub(crate) fn sim_val(&self, offset: Offset) -> Result { + self.sim.lock().unwrap().val(offset) + } + + pub(crate) fn chip(&self) -> &Chip { + self.chip.as_ref().unwrap() + } + + pub(crate) fn lsettings(&mut self) -> &mut line::Settings { + self.lsettings.as_mut().unwrap() + } + + pub(crate) fn request(&self) -> &request::Request { + self.request.as_ref().unwrap() + } +} + +impl Drop for TestConfig { + fn drop(&mut self) { + // Explicit freeing is important to make sure "request" get freed + // before "sim" and "chip". + self.request = None; + } +} diff --git a/bindings/rust/libgpiod/tests/common/mod.rs b/bindings/rust/libgpiod/tests/common/mod.rs new file mode 100644 index 000000000000..5f725c61f905 --- /dev/null +++ b/bindings/rust/libgpiod/tests/common/mod.rs @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar + +#[allow(dead_code)] +mod config; + +#[allow(unused_imports)] +pub(crate) use config::*; diff --git a/bindings/rust/libgpiod/tests/edge_event.rs b/bindings/rust/libgpiod/tests/edge_event.rs new file mode 100644 index 000000000000..571e574fa15e --- /dev/null +++ b/bindings/rust/libgpiod/tests/edge_event.rs @@ -0,0 +1,297 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar + +mod common; + +mod edge_event { + use std::time::Duration; + + use crate::common::*; + use gpiosim_sys::{Pull, Sim}; + use libgpiod::{ + line::{Edge, EdgeKind, Offset}, + request, + }; + + const NGPIO: usize = 8; + + mod buffer_capacity { + use super::*; + + #[test] + fn default_capacity() { + assert_eq!(request::Buffer::new(0).unwrap().capacity(), 64); + } + + #[test] + fn user_defined_capacity() { + assert_eq!(request::Buffer::new(123).unwrap().capacity(), 123); + } + + #[test] + fn max_capacity() { + assert_eq!(request::Buffer::new(1024 * 2).unwrap().capacity(), 1024); + } + } + + mod trigger { + use super::*; + use std::{ + sync::{Arc, Mutex}, + thread, + }; + + // Helpers to generate events + fn trigger_falling_and_rising_edge(sim: Arc>, offset: Offset) { + thread::spawn(move || { + thread::sleep(Duration::from_millis(30)); + sim.lock().unwrap().set_pull(offset, Pull::Up).unwrap(); + + thread::sleep(Duration::from_millis(30)); + sim.lock().unwrap().set_pull(offset, Pull::Down).unwrap(); + }); + } + + fn trigger_rising_edge_events_on_two_offsets(sim: Arc>, offset: [Offset; 2]) { + thread::spawn(move || { + thread::sleep(Duration::from_millis(30)); + sim.lock().unwrap().set_pull(offset[0], Pull::Up).unwrap(); + + thread::sleep(Duration::from_millis(30)); + sim.lock().unwrap().set_pull(offset[1], Pull::Up).unwrap(); + }); + } + + fn trigger_multiple_events(sim: Arc>, offset: Offset) { + sim.lock().unwrap().set_pull(offset, Pull::Up).unwrap(); + thread::sleep(Duration::from_millis(10)); + + sim.lock().unwrap().set_pull(offset, Pull::Down).unwrap(); + thread::sleep(Duration::from_millis(10)); + + sim.lock().unwrap().set_pull(offset, Pull::Up).unwrap(); + thread::sleep(Duration::from_millis(10)); + } + + #[test] + fn both_edges() { + const GPIO: Offset = 2; + let mut buf = request::Buffer::new(0).unwrap(); + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_edge(None, Some(Edge::Both)); + config.lconfig_add_settings(&[GPIO]); + config.request_lines().unwrap(); + + // Generate events + trigger_falling_and_rising_edge(config.sim(), GPIO); + + // Rising event + assert!(config + .request() + .wait_edge_event(Some(Duration::from_secs(1))) + .unwrap()); + + let mut events = config.request().read_edge_events(&mut buf).unwrap(); + assert_eq!(events.len(), 1); + + let event = events.next().unwrap().unwrap(); + let ts_rising = event.timestamp(); + assert_eq!(event.event_type().unwrap(), EdgeKind::Rising); + assert_eq!(event.line_offset(), GPIO); + + // Falling event + assert!(config + .request() + .wait_edge_event(Some(Duration::from_secs(1))) + .unwrap()); + + let mut events = config.request().read_edge_events(&mut buf).unwrap(); + assert_eq!(events.len(), 1); + + let event = events.next().unwrap().unwrap(); + let ts_falling = event.timestamp(); + assert_eq!(event.event_type().unwrap(), EdgeKind::Falling); + assert_eq!(event.line_offset(), GPIO); + + // No events available + assert!(!config + .request() + .wait_edge_event(Some(Duration::from_millis(100))) + .unwrap()); + + assert!(ts_falling > ts_rising); + } + + #[test] + fn rising_edge() { + const GPIO: Offset = 6; + let mut buf = request::Buffer::new(0).unwrap(); + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_edge(None, Some(Edge::Rising)); + config.lconfig_add_settings(&[GPIO]); + config.request_lines().unwrap(); + + // Generate events + trigger_falling_and_rising_edge(config.sim(), GPIO); + + // Rising event + assert!(config + .request() + .wait_edge_event(Some(Duration::from_secs(1))) + .unwrap()); + + let mut events = config.request().read_edge_events(&mut buf).unwrap(); + assert_eq!(events.len(), 1); + + let event = events.next().unwrap().unwrap(); + assert_eq!(event.event_type().unwrap(), EdgeKind::Rising); + assert_eq!(event.line_offset(), GPIO); + + // No events available + assert!(!config + .request() + .wait_edge_event(Some(Duration::from_millis(100))) + .unwrap()); + } + + #[test] + fn falling_edge() { + const GPIO: Offset = 7; + let mut buf = request::Buffer::new(0).unwrap(); + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_edge(None, Some(Edge::Falling)); + config.lconfig_add_settings(&[GPIO]); + config.request_lines().unwrap(); + + // Generate events + trigger_falling_and_rising_edge(config.sim(), GPIO); + + // Falling event + assert!(config + .request() + .wait_edge_event(Some(Duration::from_secs(1))) + .unwrap()); + + let mut events = config.request().read_edge_events(&mut buf).unwrap(); + assert_eq!(events.len(), 1); + + let event = events.next().unwrap().unwrap(); + assert_eq!(event.event_type().unwrap(), EdgeKind::Falling); + assert_eq!(event.line_offset(), GPIO); + + // No events available + assert!(!config + .request() + .wait_edge_event(Some(Duration::from_millis(100))) + .unwrap()); + } + + #[test] + fn edge_sequence() { + const GPIO: [u32; 2] = [0, 1]; + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_edge(None, Some(Edge::Both)); + config.lconfig_add_settings(&GPIO); + config.request_lines().unwrap(); + + // Generate events + trigger_rising_edge_events_on_two_offsets(config.sim(), GPIO); + + // Rising event GPIO 0 + let mut buf = request::Buffer::new(0).unwrap(); + assert!(config + .request() + .wait_edge_event(Some(Duration::from_secs(1))) + .unwrap()); + + let mut events = config.request().read_edge_events(&mut buf).unwrap(); + assert_eq!(events.len(), 1); + + let event = events.next().unwrap().unwrap(); + assert_eq!(event.event_type().unwrap(), EdgeKind::Rising); + assert_eq!(event.line_offset(), GPIO[0]); + assert_eq!(event.global_seqno(), 1); + assert_eq!(event.line_seqno(), 1); + + // Rising event GPIO 1 + assert!(config + .request() + .wait_edge_event(Some(Duration::from_secs(1))) + .unwrap()); + + let mut events = config.request().read_edge_events(&mut buf).unwrap(); + assert_eq!(events.len(), 1); + + let event = events.next().unwrap().unwrap(); + assert_eq!(event.event_type().unwrap(), EdgeKind::Rising); + assert_eq!(event.line_offset(), GPIO[1]); + assert_eq!(event.global_seqno(), 2); + assert_eq!(event.line_seqno(), 1); + + // No events available + assert!(!config + .request() + .wait_edge_event(Some(Duration::from_millis(100))) + .unwrap()); + } + + #[test] + fn multiple_events() { + const GPIO: Offset = 1; + let mut buf = request::Buffer::new(0).unwrap(); + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_edge(None, Some(Edge::Both)); + config.lconfig_add_settings(&[GPIO]); + config.request_lines().unwrap(); + + // Generate events + trigger_multiple_events(config.sim(), GPIO); + + // Read multiple events + assert!(config + .request() + .wait_edge_event(Some(Duration::from_secs(1))) + .unwrap()); + + let events = config.request().read_edge_events(&mut buf).unwrap(); + assert_eq!(events.len(), 3); + + let mut global_seqno = 1; + let mut line_seqno = 1; + + // Verify sequence number of events + for event in events { + let event = event.unwrap(); + assert_eq!(event.line_offset(), GPIO); + assert_eq!(event.global_seqno(), global_seqno); + assert_eq!(event.line_seqno(), line_seqno); + + global_seqno += 1; + line_seqno += 1; + } + } + + #[test] + fn over_capacity() { + const GPIO: Offset = 2; + let mut buf = request::Buffer::new(2).unwrap(); + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_edge(None, Some(Edge::Both)); + config.lconfig_add_settings(&[GPIO]); + config.request_lines().unwrap(); + + // Generate events + trigger_multiple_events(config.sim(), GPIO); + + // Read multiple events + assert!(config + .request() + .wait_edge_event(Some(Duration::from_secs(1))) + .unwrap()); + + let events = config.request().read_edge_events(&mut buf).unwrap(); + assert_eq!(events.len(), 2); + } + } +} diff --git a/bindings/rust/libgpiod/tests/info_event.rs b/bindings/rust/libgpiod/tests/info_event.rs new file mode 100644 index 000000000000..bfa0058e313e --- /dev/null +++ b/bindings/rust/libgpiod/tests/info_event.rs @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar + +mod common; + +mod info_event { + use libc::EINVAL; + use std::{ + sync::{ + mpsc::{self, Receiver, Sender}, + Arc, Mutex, + }, + thread, + time::Duration, + }; + + use gpiosim_sys::Sim; + use libgpiod::{ + chip::Chip, + line::{self, Direction, InfoChangeKind, Offset}, + request, Error as ChipError, OperationType, + }; + + fn request_reconfigure_line(chip: Arc>, tx: Sender<()>, rx: Receiver<()>) { + thread::spawn(move || { + let lconfig1 = line::Config::new().unwrap(); + let lsettings = line::Settings::new().unwrap(); + lconfig1.add_line_settings(&[7], lsettings).unwrap(); + let rconfig = request::Config::new().unwrap(); + + let request = chip + .lock() + .unwrap() + .request_lines(&rconfig, &lconfig1) + .unwrap(); + + // Signal the parent to continue + tx.send(()).expect("Could not send signal on channel"); + + // Wait for parent to signal + rx.recv().expect("Could not receive from channel"); + + let lconfig2 = line::Config::new().unwrap(); + let mut lsettings = line::Settings::new().unwrap(); + lsettings.set_direction(Direction::Output).unwrap(); + lconfig2.add_line_settings(&[7], lsettings).unwrap(); + + request.reconfigure_lines(&lconfig2).unwrap(); + + // Signal the parent to continue + tx.send(()).expect("Could not send signal on channel"); + + // Wait for parent to signal + rx.recv().expect("Could not receive from channel"); + }); + } + + mod watch { + use super::*; + const NGPIO: usize = 8; + const GPIO: Offset = 7; + + #[test] + fn line_info() { + let sim = Sim::new(Some(NGPIO), None, true).unwrap(); + let chip = Chip::open(&sim.dev_path()).unwrap(); + + assert_eq!( + chip.watch_line_info(NGPIO as u32).unwrap_err(), + ChipError::OperationFailed(OperationType::ChipWatchLineInfo, errno::Errno(EINVAL)) + ); + + let info = chip.watch_line_info(GPIO).unwrap(); + assert_eq!(info.offset(), GPIO); + + // No events available + assert!(!chip + .wait_info_event(Some(Duration::from_millis(100))) + .unwrap()); + } + + #[test] + fn reconfigure() { + let sim = Sim::new(Some(NGPIO), None, true).unwrap(); + let chip = Arc::new(Mutex::new(Chip::open(&sim.dev_path()).unwrap())); + let info = chip.lock().unwrap().watch_line_info(GPIO).unwrap(); + + assert_eq!(info.direction().unwrap(), Direction::Input); + + // Thread synchronizing mechanism + let (tx_main, rx_thread) = mpsc::channel(); + let (tx_thread, rx_main) = mpsc::channel(); + + // Generate events + request_reconfigure_line(chip.clone(), tx_thread, rx_thread); + + // Wait for thread to signal + rx_main.recv().expect("Could not receive from channel"); + + // Line requested event + assert!(chip + .lock() + .unwrap() + .wait_info_event(Some(Duration::from_secs(1))) + .unwrap()); + let event = chip.lock().unwrap().read_info_event().unwrap(); + let ts_req = event.timestamp(); + + assert_eq!(event.event_type().unwrap(), InfoChangeKind::LineRequested); + assert_eq!( + event.line_info().unwrap().direction().unwrap(), + Direction::Input + ); + + // Signal the thread to continue + tx_main.send(()).expect("Could not send signal on channel"); + + // Wait for thread to signal + rx_main.recv().expect("Could not receive from channel"); + + // Line changed event + assert!(chip + .lock() + .unwrap() + .wait_info_event(Some(Duration::from_secs(1))) + .unwrap()); + let event = chip.lock().unwrap().read_info_event().unwrap(); + let ts_rec = event.timestamp(); + + assert_eq!( + event.event_type().unwrap(), + InfoChangeKind::LineConfigChanged + ); + assert_eq!( + event.line_info().unwrap().direction().unwrap(), + Direction::Output + ); + + // Signal the thread to continue + tx_main.send(()).expect("Could not send signal on channel"); + + // Line released event + assert!(chip + .lock() + .unwrap() + .wait_info_event(Some(Duration::from_secs(1))) + .unwrap()); + let event = chip.lock().unwrap().read_info_event().unwrap(); + let ts_rel = event.timestamp(); + + assert_eq!(event.event_type().unwrap(), InfoChangeKind::LineReleased); + + // No events available + assert!(!chip + .lock() + .unwrap() + .wait_info_event(Some(Duration::from_millis(100))) + .unwrap()); + + // Check timestamps are really monotonic. + assert!(ts_rel > ts_rec); + assert!(ts_rec > ts_req); + } + } +} diff --git a/bindings/rust/libgpiod/tests/line_config.rs b/bindings/rust/libgpiod/tests/line_config.rs new file mode 100644 index 000000000000..bebf106f3f8b --- /dev/null +++ b/bindings/rust/libgpiod/tests/line_config.rs @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar + +mod common; + +mod line_config { + use libgpiod::line::{ + self, Bias, Direction, Drive, Edge, EventClock, SettingKind, SettingVal, Value, + }; + + #[test] + fn settings() { + let mut lsettings1 = line::Settings::new().unwrap(); + lsettings1 + .set_direction(Direction::Input) + .unwrap() + .set_edge_detection(Some(Edge::Both)) + .unwrap() + .set_bias(Some(Bias::PullDown)) + .unwrap() + .set_drive(Drive::PushPull) + .unwrap(); + + let mut lsettings2 = line::Settings::new().unwrap(); + lsettings2 + .set_direction(Direction::Output) + .unwrap() + .set_active_low(true) + .set_event_clock(EventClock::Realtime) + .unwrap() + .set_output_value(Value::Active) + .unwrap(); + + // Add settings for multiple lines + let lconfig = line::Config::new().unwrap(); + lconfig.add_line_settings(&[0, 1, 2], lsettings1).unwrap(); + lconfig.add_line_settings(&[4, 5], lsettings2).unwrap(); + + // Retrieve settings + let lsettings = lconfig.line_settings(1).unwrap(); + assert_eq!( + lsettings.prop(SettingKind::Direction).unwrap(), + SettingVal::Direction(Direction::Input) + ); + assert_eq!( + lsettings.prop(SettingKind::EdgeDetection).unwrap(), + SettingVal::EdgeDetection(Some(Edge::Both)) + ); + assert_eq!( + lsettings.prop(SettingKind::Bias).unwrap(), + SettingVal::Bias(Some(Bias::PullDown)) + ); + assert_eq!( + lsettings.prop(SettingKind::Drive).unwrap(), + SettingVal::Drive(Drive::PushPull) + ); + + let lsettings = lconfig.line_settings(5).unwrap(); + assert_eq!( + lsettings.prop(SettingKind::Direction).unwrap(), + SettingVal::Direction(Direction::Output) + ); + assert_eq!( + lsettings.prop(SettingKind::ActiveLow).unwrap(), + SettingVal::ActiveLow(true) + ); + assert_eq!( + lsettings.prop(SettingKind::EventClock).unwrap(), + SettingVal::EventClock(EventClock::Realtime) + ); + assert_eq!( + lsettings.prop(SettingKind::OutputValue).unwrap(), + SettingVal::OutputValue(Value::Active) + ); + } + + #[test] + fn offsets() { + let mut lsettings1 = line::Settings::new().unwrap(); + lsettings1.set_direction(Direction::Input).unwrap(); + + let mut lsettings2 = line::Settings::new().unwrap(); + lsettings2.set_event_clock(EventClock::Realtime).unwrap(); + + // Add settings for multiple lines + let lconfig = line::Config::new().unwrap(); + lconfig.add_line_settings(&[0, 1, 2], lsettings1).unwrap(); + lconfig.add_line_settings(&[4, 5], lsettings2).unwrap(); + + // Verify offsets + let offsets = lconfig.offsets().unwrap(); + assert_eq!(offsets, [0, 1, 2, 4, 5]); + } +} diff --git a/bindings/rust/libgpiod/tests/line_info.rs b/bindings/rust/libgpiod/tests/line_info.rs new file mode 100644 index 000000000000..9e4928b49682 --- /dev/null +++ b/bindings/rust/libgpiod/tests/line_info.rs @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar + +mod common; + +mod line_info { + use libc::EINVAL; + use std::time::Duration; + + use crate::common::*; + use gpiosim_sys::{Direction as SimDirection, Sim}; + use libgpiod::{ + chip::Chip, + line::{Bias, Direction, Drive, Edge, EventClock}, + Error as ChipError, OperationType, + }; + + const NGPIO: usize = 8; + + mod properties { + use super::*; + + #[test] + fn default() { + let sim = Sim::new(Some(NGPIO), None, false).unwrap(); + sim.set_line_name(4, "four").unwrap(); + sim.hog_line(4, "hog4", SimDirection::OutputLow).unwrap(); + sim.enable().unwrap(); + + let chip = Chip::open(&sim.dev_path()).unwrap(); + + let info4 = chip.line_info(4).unwrap(); + assert_eq!(info4.offset(), 4); + assert_eq!(info4.name().unwrap(), "four"); + assert!(info4.is_used()); + assert_eq!(info4.consumer().unwrap(), "hog4"); + assert_eq!(info4.direction().unwrap(), Direction::Output); + assert!(!info4.is_active_low()); + assert_eq!(info4.bias().unwrap(), None); + assert_eq!(info4.drive().unwrap(), Drive::PushPull); + assert_eq!(info4.edge_detection().unwrap(), None); + assert_eq!(info4.event_clock().unwrap(), EventClock::Monotonic); + assert!(!info4.is_debounced()); + assert_eq!(info4.debounce_period(), Duration::from_millis(0)); + + assert_eq!( + chip.line_info(NGPIO as u32).unwrap_err(), + ChipError::OperationFailed(OperationType::ChipGetLineInfo, errno::Errno(EINVAL)) + ); + } + + #[test] + fn name_and_offset() { + let sim = Sim::new(Some(NGPIO), None, false).unwrap(); + + // Line 0 has no name + for i in 1..NGPIO { + sim.set_line_name(i as u32, &i.to_string()).unwrap(); + } + sim.enable().unwrap(); + + let chip = Chip::open(&sim.dev_path()).unwrap(); + let info = chip.line_info(0).unwrap(); + + assert_eq!(info.offset(), 0); + assert_eq!( + info.name().unwrap_err(), + ChipError::NullString("GPIO line's name") + ); + + for i in 1..NGPIO { + let info = chip.line_info(i as u32).unwrap(); + + assert_eq!(info.offset(), i as u32); + assert_eq!(info.name().unwrap(), &i.to_string()); + } + } + + #[test] + fn is_used() { + let sim = Sim::new(Some(NGPIO), None, false).unwrap(); + sim.hog_line(0, "hog", SimDirection::OutputHigh).unwrap(); + sim.enable().unwrap(); + + let chip = Chip::open(&sim.dev_path()).unwrap(); + + let info = chip.line_info(0).unwrap(); + assert!(info.is_used()); + + let info = chip.line_info(1).unwrap(); + assert!(!info.is_used()); + } + + #[test] + fn consumer() { + let sim = Sim::new(Some(NGPIO), None, false).unwrap(); + sim.hog_line(0, "hog", SimDirection::OutputHigh).unwrap(); + sim.enable().unwrap(); + + let chip = Chip::open(&sim.dev_path()).unwrap(); + + let info = chip.line_info(0).unwrap(); + assert_eq!(info.consumer().unwrap(), "hog"); + + let info = chip.line_info(1).unwrap(); + assert_eq!( + info.consumer().unwrap_err(), + ChipError::NullString("GPIO line's consumer name") + ); + } + + #[test] + fn direction() { + let sim = Sim::new(Some(NGPIO), None, false).unwrap(); + sim.hog_line(0, "hog", SimDirection::Input).unwrap(); + sim.hog_line(1, "hog", SimDirection::OutputHigh).unwrap(); + sim.hog_line(2, "hog", SimDirection::OutputLow).unwrap(); + sim.enable().unwrap(); + + let chip = Chip::open(&sim.dev_path()).unwrap(); + + let info = chip.line_info(0).unwrap(); + assert_eq!(info.direction().unwrap(), Direction::Input); + + let info = chip.line_info(1).unwrap(); + assert_eq!(info.direction().unwrap(), Direction::Output); + + let info = chip.line_info(2).unwrap(); + assert_eq!(info.direction().unwrap(), Direction::Output); + } + + #[test] + fn bias() { + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.bias().unwrap(), None); + + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_bias(Direction::Input, Some(Bias::PullUp)); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.bias().unwrap(), Some(Bias::PullUp)); + + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_bias(Direction::Input, Some(Bias::PullDown)); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.bias().unwrap(), Some(Bias::PullDown)); + + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_bias(Direction::Input, Some(Bias::Disabled)); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.bias().unwrap(), Some(Bias::Disabled)); + } + + #[test] + fn drive() { + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.drive().unwrap(), Drive::PushPull); + + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_drive(Direction::Input, Drive::PushPull); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.drive().unwrap(), Drive::PushPull); + + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_drive(Direction::Output, Drive::OpenDrain); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.drive().unwrap(), Drive::OpenDrain); + + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_drive(Direction::Output, Drive::OpenSource); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.drive().unwrap(), Drive::OpenSource); + } + + #[test] + fn edge() { + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.edge_detection().unwrap(), None); + + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_edge(Some(Direction::Input), Some(Edge::Both)); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.edge_detection().unwrap(), Some(Edge::Both)); + + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_edge(Some(Direction::Input), Some(Edge::Rising)); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.edge_detection().unwrap(), Some(Edge::Rising)); + + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_edge(Some(Direction::Input), Some(Edge::Falling)); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.edge_detection().unwrap(), Some(Edge::Falling)); + } + + #[test] + fn event_clock() { + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.event_clock().unwrap(), EventClock::Monotonic); + + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_clock(EventClock::Monotonic); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.event_clock().unwrap(), EventClock::Monotonic); + + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_clock(EventClock::Realtime); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.event_clock().unwrap(), EventClock::Realtime); + } + + #[test] + #[ignore] + fn event_clock_hte() { + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_clock(EventClock::HTE); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.event_clock().unwrap(), EventClock::HTE); + } + + #[test] + fn debounce() { + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert!(!info.is_debounced()); + assert_eq!(info.debounce_period(), Duration::from_millis(0)); + + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_debounce(Duration::from_millis(100)); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert!(info.is_debounced()); + assert_eq!(info.debounce_period(), Duration::from_millis(100)); + } + } +} diff --git a/bindings/rust/libgpiod/tests/line_request.rs b/bindings/rust/libgpiod/tests/line_request.rs new file mode 100644 index 000000000000..286cd6ca6722 --- /dev/null +++ b/bindings/rust/libgpiod/tests/line_request.rs @@ -0,0 +1,509 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar + +mod common; + +mod line_request { + use libc::{E2BIG, EINVAL}; + use std::time::Duration; + + use crate::common::*; + use gpiosim_sys::{Pull, Value as SimValue}; + use libgpiod::{ + line::{ + self, Bias, Direction, Drive, Edge, EventClock, Offset, SettingVal, Value, ValueMap, + }, + Error as ChipError, OperationType, + }; + + const NGPIO: usize = 8; + + mod invalid_arguments { + use super::*; + + #[test] + fn no_offsets() { + let mut config = TestConfig::new(NGPIO).unwrap(); + + assert_eq!( + config.request_lines().unwrap_err(), + ChipError::OperationFailed(OperationType::ChipRequestLines, errno::Errno(EINVAL)) + ); + } + + #[test] + fn out_of_bound_offsets() { + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_add_settings(&[2, 0, 8, 4]); + + assert_eq!( + config.request_lines().unwrap_err(), + ChipError::OperationFailed(OperationType::ChipRequestLines, errno::Errno(EINVAL)) + ); + } + + #[test] + fn dir_out_edge_failure() { + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_edge(Some(Direction::Output), Some(Edge::Both)); + config.lconfig_add_settings(&[0]); + + assert_eq!( + config.request_lines().unwrap_err(), + ChipError::OperationFailed(OperationType::ChipRequestLines, errno::Errno(EINVAL)) + ); + } + } + + mod verify { + use super::*; + + #[test] + fn custom_consumer() { + const GPIO: Offset = 2; + const CONSUMER: &str = "foobar"; + let mut config = TestConfig::new(NGPIO).unwrap(); + config.rconfig_set_consumer(CONSUMER); + config.lconfig_add_settings(&[GPIO]); + config.request_lines().unwrap(); + + let info = config.chip().line_info(GPIO).unwrap(); + + assert!(info.is_used()); + assert_eq!(info.consumer().unwrap(), CONSUMER); + } + + #[test] + fn empty_consumer() { + const GPIO: Offset = 2; + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_add_settings(&[GPIO]); + config.request_lines().unwrap(); + + let info = config.chip().line_info(GPIO).unwrap(); + + assert!(info.is_used()); + assert_eq!(info.consumer().unwrap(), "?"); + } + + #[test] + fn read_values() { + let offsets = [7, 1, 0, 6, 2]; + let pulls = [Pull::Up, Pull::Up, Pull::Down, Pull::Up, Pull::Down]; + let mut config = TestConfig::new(NGPIO).unwrap(); + config.set_pull(&offsets, &pulls); + config.lconfig_val(Some(Direction::Input), None); + config.lconfig_add_settings(&offsets); + config.request_lines().unwrap(); + + let request = config.request(); + + // Single values read properly + assert_eq!(request.value(7).unwrap(), Value::Active); + + // Values read properly + let map = request.values().unwrap(); + for i in 0..offsets.len() { + assert_eq!( + *map.get(offsets[i].into()).unwrap(), + match pulls[i] { + Pull::Down => Value::InActive, + _ => Value::Active, + } + ); + } + + // Subset of values read properly + let map = request.values_subset(&[2, 0, 6]).unwrap(); + assert_eq!(*map.get(2).unwrap(), Value::InActive); + assert_eq!(*map.get(0).unwrap(), Value::InActive); + assert_eq!(*map.get(6).unwrap(), Value::Active); + + // Value read properly after reconfigure + let mut lsettings = line::Settings::new().unwrap(); + lsettings.set_active_low(true); + let lconfig = line::Config::new().unwrap(); + lconfig.add_line_settings(&offsets, lsettings).unwrap(); + request.reconfigure_lines(&lconfig).unwrap(); + assert_eq!(request.value(7).unwrap(), Value::InActive); + } + + #[test] + fn set_output_values() { + let offsets = [0, 1, 3, 4]; + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_val(Some(Direction::Output), Some(Value::Active)); + config.lconfig_add_settings(&offsets); + config.request_lines().unwrap(); + + assert_eq!(config.sim_val(0).unwrap(), SimValue::Active); + assert_eq!(config.sim_val(1).unwrap(), SimValue::Active); + assert_eq!(config.sim_val(3).unwrap(), SimValue::Active); + assert_eq!(config.sim_val(4).unwrap(), SimValue::Active); + + // Default + assert_eq!(config.sim_val(2).unwrap(), SimValue::InActive); + } + + #[test] + fn update_output_values() { + let offsets = [0, 1, 3, 4]; + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_val(Some(Direction::Output), Some(Value::InActive)); + config.lconfig_add_settings(&offsets); + config.request_lines().unwrap(); + let request = config.request(); + + // Set single value + request.set_value(1, Value::Active).unwrap(); + assert_eq!(config.sim_val(0).unwrap(), SimValue::InActive); + assert_eq!(config.sim_val(1).unwrap(), SimValue::Active); + assert_eq!(config.sim_val(3).unwrap(), SimValue::InActive); + assert_eq!(config.sim_val(4).unwrap(), SimValue::InActive); + request.set_value(1, Value::InActive).unwrap(); + assert_eq!(config.sim_val(1).unwrap(), SimValue::InActive); + + // Set values of subset + let mut map = ValueMap::new(); + map.insert(4, Value::Active); + map.insert(3, Value::Active); + request.set_values_subset(map).unwrap(); + assert_eq!(config.sim_val(0).unwrap(), SimValue::InActive); + assert_eq!(config.sim_val(1).unwrap(), SimValue::InActive); + assert_eq!(config.sim_val(3).unwrap(), SimValue::Active); + assert_eq!(config.sim_val(4).unwrap(), SimValue::Active); + + let mut map = ValueMap::new(); + map.insert(4, Value::InActive); + map.insert(3, Value::InActive); + request.set_values_subset(map).unwrap(); + assert_eq!(config.sim_val(3).unwrap(), SimValue::InActive); + assert_eq!(config.sim_val(4).unwrap(), SimValue::InActive); + + // Set all values + request + .set_values(&[ + Value::Active, + Value::InActive, + Value::Active, + Value::InActive, + ]) + .unwrap(); + assert_eq!(config.sim_val(0).unwrap(), SimValue::Active); + assert_eq!(config.sim_val(1).unwrap(), SimValue::InActive); + assert_eq!(config.sim_val(3).unwrap(), SimValue::Active); + assert_eq!(config.sim_val(4).unwrap(), SimValue::InActive); + request + .set_values(&[ + Value::InActive, + Value::InActive, + Value::InActive, + Value::InActive, + ]) + .unwrap(); + assert_eq!(config.sim_val(0).unwrap(), SimValue::InActive); + assert_eq!(config.sim_val(1).unwrap(), SimValue::InActive); + assert_eq!(config.sim_val(3).unwrap(), SimValue::InActive); + assert_eq!(config.sim_val(4).unwrap(), SimValue::InActive); + } + + #[test] + fn set_bias() { + let offsets = [3]; + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_bias(Direction::Input, Some(Bias::PullUp)); + config.lconfig_add_settings(&offsets); + config.request_lines().unwrap(); + config.request(); + + // Set single value + assert_eq!(config.sim_val(3).unwrap(), SimValue::Active); + } + + #[test] + fn no_events() { + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_edge(None, Some(Edge::Both)); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + + // No events available + assert!(!config + .request() + .wait_edge_event(Some(Duration::from_millis(100))) + .unwrap()); + } + } + + mod reconfigure { + use super::*; + + #[test] + fn e2big() { + let offsets = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + let mut config = TestConfig::new(16).unwrap(); + config.lconfig_add_settings(&offsets); + config.request_lines().unwrap(); + + let request = config.request(); + + // Reconfigure + let mut lsettings = line::Settings::new().unwrap(); + lsettings.set_direction(Direction::Input).unwrap(); + let lconfig = line::Config::new().unwrap(); + + // The uAPI config has only 10 attribute slots, this should pass. + for offset in offsets { + lsettings.set_debounce_period(Duration::from_millis((100 + offset).into())); + lconfig + .add_line_settings(&[offset as Offset], lsettings.settings_clone().unwrap()) + .unwrap(); + } + + assert!(request.reconfigure_lines(&lconfig).is_ok()); + + // The uAPI config has only 10 attribute slots, and this is the 11th entry. + // This should fail with E2BIG. + lsettings.set_debounce_period(Duration::from_millis(100 + 11)); + lconfig.add_line_settings(&[11], lsettings).unwrap(); + + assert_eq!( + request.reconfigure_lines(&lconfig).unwrap_err(), + ChipError::OperationFailed( + OperationType::LineRequestReconfigLines, + errno::Errno(E2BIG), + ) + ); + } + + #[test] + fn bias() { + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.bias().unwrap(), None); + + let request = config.request(); + + // Reconfigure + let lconfig = line::Config::new().unwrap(); + let mut lsettings = line::Settings::new().unwrap(); + lsettings + .set_prop(&[ + SettingVal::Direction(Direction::Input), + SettingVal::Bias(Some(Bias::PullUp)), + ]) + .unwrap(); + lconfig.add_line_settings(&[0], lsettings).unwrap(); + request.reconfigure_lines(&lconfig).unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.bias().unwrap(), Some(Bias::PullUp)); + + let lconfig = line::Config::new().unwrap(); + let mut lsettings = line::Settings::new().unwrap(); + lsettings + .set_prop(&[ + SettingVal::Direction(Direction::Input), + SettingVal::Bias(Some(Bias::PullDown)), + ]) + .unwrap(); + lconfig.add_line_settings(&[0], lsettings).unwrap(); + request.reconfigure_lines(&lconfig).unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.bias().unwrap(), Some(Bias::PullDown)); + + let lconfig = line::Config::new().unwrap(); + let mut lsettings = line::Settings::new().unwrap(); + lsettings + .set_prop(&[ + SettingVal::Direction(Direction::Input), + SettingVal::Bias(Some(Bias::Disabled)), + ]) + .unwrap(); + lconfig.add_line_settings(&[0], lsettings).unwrap(); + request.reconfigure_lines(&lconfig).unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.bias().unwrap(), Some(Bias::Disabled)); + } + + #[test] + fn drive() { + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.drive().unwrap(), Drive::PushPull); + + let request = config.request(); + + // Reconfigure + let lconfig = line::Config::new().unwrap(); + let mut lsettings = line::Settings::new().unwrap(); + lsettings + .set_prop(&[ + SettingVal::Direction(Direction::Input), + SettingVal::Drive(Drive::PushPull), + ]) + .unwrap(); + lconfig.add_line_settings(&[0], lsettings).unwrap(); + request.reconfigure_lines(&lconfig).unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.drive().unwrap(), Drive::PushPull); + + let lconfig = line::Config::new().unwrap(); + let mut lsettings = line::Settings::new().unwrap(); + lsettings + .set_prop(&[ + SettingVal::Direction(Direction::Output), + SettingVal::Drive(Drive::OpenDrain), + ]) + .unwrap(); + lconfig.add_line_settings(&[0], lsettings).unwrap(); + request.reconfigure_lines(&lconfig).unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.drive().unwrap(), Drive::OpenDrain); + + let lconfig = line::Config::new().unwrap(); + let mut lsettings = line::Settings::new().unwrap(); + lsettings + .set_prop(&[ + SettingVal::Direction(Direction::Output), + SettingVal::Drive(Drive::OpenSource), + ]) + .unwrap(); + lconfig.add_line_settings(&[0], lsettings).unwrap(); + request.reconfigure_lines(&lconfig).unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.drive().unwrap(), Drive::OpenSource); + } + + #[test] + fn edge() { + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.edge_detection().unwrap(), None); + + let request = config.request(); + + // Reconfigure + let lconfig = line::Config::new().unwrap(); + let mut lsettings = line::Settings::new().unwrap(); + lsettings + .set_prop(&[ + SettingVal::Direction(Direction::Input), + SettingVal::EdgeDetection(Some(Edge::Both)), + ]) + .unwrap(); + lconfig.add_line_settings(&[0], lsettings).unwrap(); + request.reconfigure_lines(&lconfig).unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.edge_detection().unwrap(), Some(Edge::Both)); + + let lconfig = line::Config::new().unwrap(); + let mut lsettings = line::Settings::new().unwrap(); + lsettings + .set_prop(&[ + SettingVal::Direction(Direction::Input), + SettingVal::EdgeDetection(Some(Edge::Rising)), + ]) + .unwrap(); + lconfig.add_line_settings(&[0], lsettings).unwrap(); + request.reconfigure_lines(&lconfig).unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.edge_detection().unwrap(), Some(Edge::Rising)); + + let lconfig = line::Config::new().unwrap(); + let mut lsettings = line::Settings::new().unwrap(); + lsettings + .set_prop(&[ + SettingVal::Direction(Direction::Input), + SettingVal::EdgeDetection(Some(Edge::Falling)), + ]) + .unwrap(); + lconfig.add_line_settings(&[0], lsettings).unwrap(); + request.reconfigure_lines(&lconfig).unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.edge_detection().unwrap(), Some(Edge::Falling)); + } + + #[test] + fn event_clock() { + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.event_clock().unwrap(), EventClock::Monotonic); + + let request = config.request(); + + // Reconfigure + let lconfig = line::Config::new().unwrap(); + let mut lsettings = line::Settings::new().unwrap(); + lsettings.set_event_clock(EventClock::Monotonic).unwrap(); + lconfig.add_line_settings(&[0], lsettings).unwrap(); + request.reconfigure_lines(&lconfig).unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.event_clock().unwrap(), EventClock::Monotonic); + + let lconfig = line::Config::new().unwrap(); + let mut lsettings = line::Settings::new().unwrap(); + lsettings.set_event_clock(EventClock::Realtime).unwrap(); + lconfig.add_line_settings(&[0], lsettings).unwrap(); + request.reconfigure_lines(&lconfig).unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.event_clock().unwrap(), EventClock::Realtime); + } + + #[test] + #[ignore] + fn event_clock_hte() { + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.event_clock().unwrap(), EventClock::Monotonic); + + let request = config.request(); + + // Reconfigure + let lconfig = line::Config::new().unwrap(); + let mut lsettings = line::Settings::new().unwrap(); + lsettings.set_event_clock(EventClock::HTE).unwrap(); + lconfig.add_line_settings(&[0], lsettings).unwrap(); + request.reconfigure_lines(&lconfig).unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert_eq!(info.event_clock().unwrap(), EventClock::HTE); + } + + #[test] + fn debounce() { + let mut config = TestConfig::new(NGPIO).unwrap(); + config.lconfig_add_settings(&[0]); + config.request_lines().unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert!(!info.is_debounced()); + assert_eq!(info.debounce_period(), Duration::from_millis(0)); + + let request = config.request(); + + // Reconfigure + let lconfig = line::Config::new().unwrap(); + let mut lsettings = line::Settings::new().unwrap(); + lsettings + .set_prop(&[ + SettingVal::Direction(Direction::Input), + SettingVal::DebouncePeriod(Duration::from_millis(100)), + ]) + .unwrap(); + lconfig.add_line_settings(&[0], lsettings).unwrap(); + request.reconfigure_lines(&lconfig).unwrap(); + let info = config.chip().line_info(0).unwrap(); + assert!(info.is_debounced()); + assert_eq!(info.debounce_period(), Duration::from_millis(100)); + } + } +} diff --git a/bindings/rust/libgpiod/tests/line_settings.rs b/bindings/rust/libgpiod/tests/line_settings.rs new file mode 100644 index 000000000000..de2148ee6018 --- /dev/null +++ b/bindings/rust/libgpiod/tests/line_settings.rs @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar + +mod common; + +mod line_settings { + use std::time::Duration; + + use libgpiod::line::{ + self, Bias, Direction, Drive, Edge, EventClock, SettingKind, SettingVal, Value, + }; + + #[test] + fn direction() { + let mut lsettings = line::Settings::new().unwrap(); + assert_eq!( + lsettings.prop(SettingKind::Direction).unwrap(), + SettingVal::Direction(Direction::AsIs) + ); + + lsettings.set_direction(Direction::Input).unwrap(); + assert_eq!( + lsettings.prop(SettingKind::Direction).unwrap(), + SettingVal::Direction(Direction::Input) + ); + + lsettings.set_direction(Direction::Output).unwrap(); + assert_eq!( + lsettings.prop(SettingKind::Direction).unwrap(), + SettingVal::Direction(Direction::Output) + ); + } + + #[test] + fn edge_detection() { + let mut lsettings = line::Settings::new().unwrap(); + assert_eq!( + lsettings.prop(SettingKind::EdgeDetection).unwrap(), + SettingVal::EdgeDetection(None) + ); + + lsettings.set_edge_detection(Some(Edge::Both)).unwrap(); + assert_eq!( + lsettings.prop(SettingKind::EdgeDetection).unwrap(), + SettingVal::EdgeDetection(Some(Edge::Both)) + ); + + lsettings.set_edge_detection(Some(Edge::Rising)).unwrap(); + assert_eq!( + lsettings.prop(SettingKind::EdgeDetection).unwrap(), + SettingVal::EdgeDetection(Some(Edge::Rising)) + ); + + lsettings.set_edge_detection(Some(Edge::Falling)).unwrap(); + assert_eq!( + lsettings.prop(SettingKind::EdgeDetection).unwrap(), + SettingVal::EdgeDetection(Some(Edge::Falling)) + ); + } + + #[test] + fn bias() { + let mut lsettings = line::Settings::new().unwrap(); + assert_eq!( + lsettings.prop(SettingKind::Bias).unwrap(), + SettingVal::Bias(None) + ); + + lsettings.set_bias(Some(Bias::PullDown)).unwrap(); + assert_eq!( + lsettings.prop(SettingKind::Bias).unwrap(), + SettingVal::Bias(Some(Bias::PullDown)) + ); + + lsettings.set_bias(Some(Bias::PullUp)).unwrap(); + assert_eq!( + lsettings.prop(SettingKind::Bias).unwrap(), + SettingVal::Bias(Some(Bias::PullUp)) + ); + } + + #[test] + fn drive() { + let mut lsettings = line::Settings::new().unwrap(); + assert_eq!( + lsettings.prop(SettingKind::Drive).unwrap(), + SettingVal::Drive(Drive::PushPull) + ); + + lsettings.set_drive(Drive::PushPull).unwrap(); + assert_eq!( + lsettings.prop(SettingKind::Drive).unwrap(), + SettingVal::Drive(Drive::PushPull) + ); + + lsettings.set_drive(Drive::OpenDrain).unwrap(); + assert_eq!( + lsettings.prop(SettingKind::Drive).unwrap(), + SettingVal::Drive(Drive::OpenDrain) + ); + + lsettings.set_drive(Drive::OpenSource).unwrap(); + assert_eq!( + lsettings.prop(SettingKind::Drive).unwrap(), + SettingVal::Drive(Drive::OpenSource) + ); + } + + #[test] + fn active_low() { + let mut lsettings = line::Settings::new().unwrap(); + assert_eq!( + lsettings.prop(SettingKind::ActiveLow).unwrap(), + SettingVal::ActiveLow(false) + ); + + lsettings.set_active_low(true); + assert_eq!( + lsettings.prop(SettingKind::ActiveLow).unwrap(), + SettingVal::ActiveLow(true) + ); + + lsettings.set_active_low(false); + assert_eq!( + lsettings.prop(SettingKind::ActiveLow).unwrap(), + SettingVal::ActiveLow(false) + ); + } + + #[test] + fn debounce_period() { + let mut lsettings = line::Settings::new().unwrap(); + assert_eq!( + lsettings.prop(SettingKind::DebouncePeriod).unwrap(), + SettingVal::DebouncePeriod(Duration::from_millis(0)) + ); + + lsettings.set_debounce_period(Duration::from_millis(5)); + assert_eq!( + lsettings.prop(SettingKind::DebouncePeriod).unwrap(), + SettingVal::DebouncePeriod(Duration::from_millis(5)) + ); + } + + #[test] + fn event_clock() { + let mut lsettings = line::Settings::new().unwrap(); + assert_eq!( + lsettings.prop(SettingKind::EventClock).unwrap(), + SettingVal::EventClock(EventClock::Monotonic) + ); + + lsettings.set_event_clock(EventClock::Realtime).unwrap(); + assert_eq!( + lsettings.prop(SettingKind::EventClock).unwrap(), + SettingVal::EventClock(EventClock::Realtime) + ); + + lsettings.set_event_clock(EventClock::Monotonic).unwrap(); + assert_eq!( + lsettings.prop(SettingKind::EventClock).unwrap(), + SettingVal::EventClock(EventClock::Monotonic) + ); + } + + #[test] + #[ignore] + fn event_clock_hte() { + let mut lsettings = line::Settings::new().unwrap(); + assert_eq!( + lsettings.prop(SettingKind::EventClock).unwrap(), + SettingVal::EventClock(EventClock::Monotonic) + ); + + lsettings.set_event_clock(EventClock::HTE).unwrap(); + assert_eq!( + lsettings.prop(SettingKind::EventClock).unwrap(), + SettingVal::EventClock(EventClock::HTE) + ); + } + + #[test] + fn output_value() { + let mut lsettings = line::Settings::new().unwrap(); + assert_eq!( + lsettings.prop(SettingKind::OutputValue).unwrap(), + SettingVal::OutputValue(Value::InActive) + ); + + lsettings.set_output_value(Value::Active).unwrap(); + assert_eq!( + lsettings.prop(SettingKind::OutputValue).unwrap(), + SettingVal::OutputValue(Value::Active) + ); + + lsettings.set_output_value(Value::InActive).unwrap(); + assert_eq!( + lsettings.prop(SettingKind::OutputValue).unwrap(), + SettingVal::OutputValue(Value::InActive) + ); + } +} diff --git a/bindings/rust/libgpiod/tests/request_config.rs b/bindings/rust/libgpiod/tests/request_config.rs new file mode 100644 index 000000000000..8c676384b7c3 --- /dev/null +++ b/bindings/rust/libgpiod/tests/request_config.rs @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-FileCopyrightText: 2022 Linaro Ltd. +// SPDX-FileCopyrightTest: 2022 Viresh Kumar + +mod common; + +mod request_config { + use libgpiod::{request, Error as ChipError, OperationType}; + + mod verify { + use super::*; + + #[test] + fn default() { + let rconfig = request::Config::new().unwrap(); + + assert_eq!(rconfig.event_buffer_size(), 0); + assert_eq!( + rconfig.consumer().unwrap_err(), + ChipError::OperationFailed( + OperationType::RequestConfigGetConsumer, + errno::Errno(0), + ) + ); + } + + #[test] + fn initialized() { + const CONSUMER: &str = "foobar"; + let rconfig = request::Config::new().unwrap(); + rconfig.set_consumer(CONSUMER).unwrap(); + rconfig.set_event_buffer_size(64); + + assert_eq!(rconfig.event_buffer_size(), 64); + assert_eq!(rconfig.consumer().unwrap(), CONSUMER); + } + } +}