From patchwork Mon Sep 26 11:08:15 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Viresh Kumar X-Patchwork-Id: 609412 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 2892FC07E9D for ; Mon, 26 Sep 2022 12:31:20 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S237091AbiIZMbR (ORCPT ); Mon, 26 Sep 2022 08:31:17 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:59886 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S235840AbiIZMaz (ORCPT ); Mon, 26 Sep 2022 08:30:55 -0400 Received: from mail-pg1-x531.google.com (mail-pg1-x531.google.com [IPv6:2607:f8b0:4864:20::531]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id B568C11C29 for ; Mon, 26 Sep 2022 04:09:56 -0700 (PDT) Received: by mail-pg1-x531.google.com with SMTP id 129so4804008pgc.5 for ; Mon, 26 Sep 2022 04:09:56 -0700 (PDT) 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; bh=cSgqB3X4vpEITvS9HGJW+7YJCm7oi6IxBKzvFv4Xgew=; b=BoY0jMTzPCQMLQpod7JU6e5ZlyGylxVE3Gqr2f56stI59hvyUh55PjqbNZnnKYHqv8 kIhVCjpWIzhTyTqzzjXHBMsH6Am9hZj2n8SQIXA1fnF9qyJkHiCQHiaThTRwfxNAx+qm Sgq6GyuuQn1gqEzBcdKm5nK3ng+aZNp07+j3qUblgkTvoT2s/A/pRu8NeBTa4A326F/j FV/DfJZ2Oc99YMzemL/8jZXSh72jMoQknkHwqVKyxPCU80T50j6UA5a/U+fuAO5oUyCw cGZg/c9q9a3KV8AxPKUVHBhjKBPQ3dc7G+qmdsu4rUHuLlUN4NULREarOQavOGZSXZvc xs9g== 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; bh=cSgqB3X4vpEITvS9HGJW+7YJCm7oi6IxBKzvFv4Xgew=; b=B8WLaIp/FYv9OmBCLGJ23HormfE6IFQt7XrowjOPxvnj82e9bYOXNwmyzannMfMXg7 DUk6q3WNtjtjsKPwB/YyMTF2pNntxo+icR7c+0kIkvpg5Tin179p63QFSIunXFp2wdQM TSi1N8lssA/TMSXeuLU8QWiYMJ2YSkX/YkpnROiftfiP2QlIyZ8VOlMIiWUtlq2lTJ4E ddgYDHMK1C2pRWSBtlfz4FKsqBz69uZ32QDiI7lsVtcysnQBPjSoy0yd7tH4NXENWwe1 SZU3Bgi3FNXlR5AiXOnrILGA68oEgqWZjCdYwwlkoRJRkKkTcHL95YVwUttcHIOZJ9ri W/lQ== X-Gm-Message-State: ACrzQf0cthL2TNF/ro5wKJNOrNmcmpIKwxRm7wOeT3bxI6L3S2u3X35J +9VLBv/+TgvHoCQNc1DJYj75MA== X-Google-Smtp-Source: AMsMyM6ODD5Vv2uo1wRnWsv/URqdZVc8Zl51GRojQhXCQTu4P+Si5ztrMoflLaMrqDoiIY4EgBy9Hg== X-Received: by 2002:a63:8648:0:b0:43b:5126:73fb with SMTP id x69-20020a638648000000b0043b512673fbmr19814656pgd.290.1664190515219; Mon, 26 Sep 2022 04:08:35 -0700 (PDT) Received: from localhost ([122.172.85.153]) by smtp.gmail.com with ESMTPSA id q14-20020a170902a3ce00b001750792f20asm2600097plb.238.2022.09.26.04.08.33 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 26 Sep 2022 04:08:34 -0700 (PDT) From: Viresh Kumar To: Linus Walleij , Bartosz Golaszewski Cc: Viresh Kumar , Vincent Guittot , linux-gpio@vger.kernel.org, Kent Gibson , Miguel Ojeda , Wedson Almeida Filho , =?utf-8?q?Alex_Benn=C3=A9e?= , stratos-dev@op-lists.linaro.org, Gerard Ryan Subject: [PATCH V6 3/8] libgpiod: Add rust wrapper crate Date: Mon, 26 Sep 2022 16:38:15 +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. Signed-off-by: Viresh Kumar --- bindings/rust/Cargo.toml | 1 + bindings/rust/libgpiod/Cargo.toml | 13 + bindings/rust/libgpiod/src/chip.rs | 253 ++++++++++ bindings/rust/libgpiod/src/edge_event.rs | 102 ++++ bindings/rust/libgpiod/src/event_buffer.rs | 90 ++++ bindings/rust/libgpiod/src/info_event.rs | 68 +++ bindings/rust/libgpiod/src/lib.rs | 471 +++++++++++++++++++ bindings/rust/libgpiod/src/line_config.rs | 118 +++++ bindings/rust/libgpiod/src/line_info.rs | 180 +++++++ bindings/rust/libgpiod/src/line_request.rs | 246 ++++++++++ bindings/rust/libgpiod/src/line_settings.rs | 277 +++++++++++ bindings/rust/libgpiod/src/request_config.rs | 96 ++++ 12 files changed, 1915 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 d0b3a3c88ff1..1e57ef2c0002 100644 --- a/bindings/rust/Cargo.toml +++ b/bindings/rust/Cargo.toml @@ -2,4 +2,5 @@ members = [ "libgpiod-sys", + "libgpiod" ] diff --git a/bindings/rust/libgpiod/Cargo.toml b/bindings/rust/libgpiod/Cargo.toml new file mode 100644 index 000000000000..f25242abb153 --- /dev/null +++ b/bindings/rust/libgpiod/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "libgpiod" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +intmap = "2.0.0" +libc = ">=0.2.39" +libgpiod-sys = { path = "../libgpiod-sys" } +thiserror = "1.0" +vmm-sys-util = "=0.10.0" diff --git a/bindings/rust/libgpiod/src/chip.rs b/bindings/rust/libgpiod/src/chip.rs new file mode 100644 index 000000000000..4f52c3f141f4 --- /dev/null +++ b/bindings/rust/libgpiod/src/chip.rs @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar + +use libc::strlen; +use std::os::raw::c_char; +use std::path::Path; +use std::sync::Arc; +use std::time::Duration; +use std::{slice, str}; + +use vmm_sys_util::errno::Error as Errno; + +use super::{gpiod, info, line, request, Error, Offset, OperationType, Result}; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub(crate) struct Internal { + chip: *mut gpiod::gpiod_chip, +} + +impl Internal { + /// Find a chip by path. + pub(crate) fn open>(path: &P) -> Result { + // Null-terminate the string + let path = path.as_ref().to_string_lossy() + "\0"; + + 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::last(), + )); + } + + Ok(Self { chip }) + } + + /// Private helper, Returns gpiod_chip + pub(crate) fn chip(&self) -> *mut gpiod::gpiod_chip { + self.chip + } +} + +impl Drop for Internal { + /// Close the chip and release all associated resources. + fn drop(&mut self) { + 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(Clone, Debug, Eq, PartialEq)] +pub struct Chip { + ichip: Arc, + info: Info, +} + +unsafe impl Send for Chip {} +unsafe impl Sync for Chip {} + +impl Chip { + /// Find a chip by path. + pub fn open>(path: &P) -> Result { + let ichip = Arc::new(Internal::open(path)?); + let info = Info::new(ichip.clone())?; + + Ok(Self { ichip, info }) + } + + /// Get the chip name as represented in the kernel. + pub fn name(&self) -> Result<&str> { + self.info.name() + } + + /// Get the chip label as represented in the kernel. + pub fn label(&self) -> Result<&str> { + self.info.label() + } + + /// Get the number of GPIO lines exposed by the chip. + pub fn num_lines(&self) -> usize { + self.info.num_lines() + } + + /// 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. + str::from_utf8(unsafe { slice::from_raw_parts(path as *const u8, strlen(path) as usize) }) + .map_err(Error::StringNotUtf8) + } + + /// Get information about the chip. + pub fn info(&self) -> Result { + Info::new(self.ichip.clone()) + } + + /// Get a snapshot of information about the line. + pub fn line_info(&self, offset: Offset) -> Result { + line::Info::new(self.ichip.clone(), offset) + } + + /// 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 { + line::Info::new_watch(self.ichip.clone(), offset) + } + + /// Stop watching a line + pub fn unwatch(&self, offset: Offset) { + unsafe { + gpiod::gpiod_chip_unwatch_line_info(self.ichip.chip(), offset); + } + } + + /// Get the file descriptor associated with the chip. + /// + /// The returned file descriptor must not be closed by the caller, else other methods for the + /// `struct Chip` may fail. + pub fn fd(&self) -> Result { + let fd = unsafe { gpiod::gpiod_chip_get_fd(self.ichip.chip()) }; + + if fd < 0 { + Err(Error::OperationFailed( + OperationType::ChipGetFd, + Errno::last(), + )) + } else { + Ok(fd as u32) + } + } + + /// Wait for line status events on any of the watched lines on the chip. + pub fn wait_info_event(&self, timeout: Option) -> Result { + let timeout = match timeout { + Some(x) => x.as_nanos() as i64, + // Block indefinitely + None => -1, + }; + + let ret = unsafe { gpiod::gpiod_chip_wait_info_event(self.ichip.chip(), timeout) }; + + match ret { + -1 => Err(Error::OperationFailed( + OperationType::ChipWaitInfoEvent, + Errno::last(), + )), + 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 { + info::Event::new(&self.ichip) + } + + /// Map a GPIO line's name to its offset within the chip. + pub fn line_offset_from_name(&self, name: &str) -> Result { + // Null-terminate the string + let name = name.to_owned() + "\0"; + + 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::ChipGetLine, + Errno::last(), + )) + } 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 { + line::Request::new(&self.ichip, rconfig, lconfig) + } +} + +/// GPIO chip Information +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Info { + info: *mut gpiod::gpiod_chip_info, +} + +impl Info { + /// Find a GPIO chip by path. + pub(crate) fn new(chip: Arc) -> Result { + let info = unsafe { gpiod::gpiod_chip_get_info(chip.chip()) }; + if info.is_null() { + return Err(Error::OperationFailed( + OperationType::ChipInfoGet, + Errno::last(), + )); + } + + Ok(Self { info }) + } + + /// Get the GPIO chip name as represented in the kernel. + pub(crate) fn name(&self) -> Result<&str> { + // SAFETY: The string returned by libgpiod is guaranteed to live as long + // as the `struct Chip`. + let name = unsafe { gpiod::gpiod_chip_info_get_name(self.info) }; + + // SAFETY: The string is guaranteed to be valid here by the C API. + str::from_utf8(unsafe { slice::from_raw_parts(name as *const u8, strlen(name) as usize) }) + .map_err(Error::StringNotUtf8) + } + + /// Get the GPIO chip label as represented in the kernel. + pub(crate) fn label(&self) -> Result<&str> { + // SAFETY: The string returned by libgpiod is guaranteed to live as long + // as the `struct Chip`. + let label = unsafe { gpiod::gpiod_chip_info_get_label(self.info) }; + + // SAFETY: The string is guaranteed to be valid here by the C API. + str::from_utf8(unsafe { slice::from_raw_parts(label as *const u8, strlen(label) as usize) }) + .map_err(Error::StringNotUtf8) + } + + /// Get the number of GPIO lines exposed by the chip. + pub(crate) fn num_lines(&self) -> usize { + unsafe { gpiod::gpiod_chip_info_get_num_lines(self.info) as usize } + } +} + +impl Drop for Info { + /// Close the GPIO chip info and release all associated resources. + fn drop(&mut self) { + 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..ce583916a2e3 --- /dev/null +++ b/bindings/rust/libgpiod/src/edge_event.rs @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar + +use std::sync::Arc; +use std::time::Duration; + +use vmm_sys_util::errno::Error as Errno; + +use super::{edge::event::BufferInternal, gpiod, EdgeKind, Error, Offset, 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 { + ibuffer: Option>, + event: *mut gpiod::gpiod_edge_event, +} + +impl Event { + /// Get an event stored in the buffer. + pub(crate) fn new(ibuffer: &Arc, index: u64) -> Result { + let event = unsafe { gpiod::gpiod_edge_event_buffer_get_event(ibuffer.buffer(), index) }; + if event.is_null() { + return Err(Error::OperationFailed( + OperationType::EdgeEventBufferGetEvent, + Errno::last(), + )); + } + + Ok(Self { + ibuffer: Some(ibuffer.clone()), + event, + }) + } + + pub fn event_clone(&self) -> Result { + let event = unsafe { gpiod::gpiod_edge_event_copy(self.event) }; + if event.is_null() { + return Err(Error::OperationFailed( + OperationType::EdgeEventCopy, + Errno::last(), + )); + } + + Ok(Self { + ibuffer: None, + event, + }) + } + + /// Get the event type. + pub fn event_type(&self) -> Result { + EdgeKind::new(unsafe { gpiod::gpiod_edge_event_get_event_type(self.event) } as u32) + } + + /// Get the timestamp of the event. + pub fn timestamp(&self) -> Duration { + Duration::from_nanos(unsafe { gpiod::gpiod_edge_event_get_timestamp_ns(self.event) }) + } + + /// Get the offset of the line on which the event was triggered. + pub fn line_offset(&self) -> Offset { + unsafe { gpiod::gpiod_edge_event_get_line_offset(self.event) } + } + + /// Get the global sequence number of the event. + /// + /// Returns sequence number of the event relative to all lines in the + /// associated line request. + pub fn global_seqno(&self) -> u64 { + unsafe { gpiod::gpiod_edge_event_get_global_seqno(self.event) } + } + + /// Get the event sequence number specific to concerned line. + /// + /// Returns sequence number of the event relative to the line within the + /// lifetime of the associated line request. + pub fn line_seqno(&self) -> u64 { + unsafe { gpiod::gpiod_edge_event_get_line_seqno(self.event) } + } +} + +impl Drop for Event { + /// Free the edge event. + fn drop(&mut self) { + // Free the event only if a copy is made + if self.ibuffer.is_none() { + unsafe { gpiod::gpiod_edge_event_free(self.event) }; + } + } +} diff --git a/bindings/rust/libgpiod/src/event_buffer.rs b/bindings/rust/libgpiod/src/event_buffer.rs new file mode 100644 index 000000000000..e272e7aa9e9d --- /dev/null +++ b/bindings/rust/libgpiod/src/event_buffer.rs @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar + +use std::os::raw::c_ulong; +use std::sync::Arc; + +use vmm_sys_util::errno::Error as Errno; + +use super::{edge, gpiod, Error, OperationType, Result}; + +/// Line edge events buffer +#[derive(Clone, Debug, Eq, PartialEq)] +pub(crate) struct BufferInternal { + buffer: *mut gpiod::gpiod_edge_event_buffer, +} + +impl BufferInternal { + /// 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 { + let buffer = unsafe { gpiod::gpiod_edge_event_buffer_new(capacity as c_ulong) }; + if buffer.is_null() { + return Err(Error::OperationFailed( + OperationType::EdgeEventBufferNew, + Errno::last(), + )); + } + + Ok(Self { buffer }) + } + + /// Private helper, Returns gpiod_edge_event_buffer + pub(crate) fn buffer(&self) -> *mut gpiod::gpiod_edge_event_buffer { + self.buffer + } +} + +impl Drop for BufferInternal { + /// Free the edge event buffer and release all associated resources. + fn drop(&mut self) { + unsafe { gpiod::gpiod_edge_event_buffer_free(self.buffer) }; + } +} + +/// Line edge events buffer +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Buffer { + ibuffer: Arc, +} + +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 { + Ok(Self { + ibuffer: Arc::new(BufferInternal::new(capacity)?), + }) + } + + /// Private helper, Returns gpiod_edge_event_buffer + pub(crate) fn buffer(&self) -> *mut gpiod::gpiod_edge_event_buffer { + self.ibuffer.buffer() + } + + /// Get the capacity of the event buffer. + pub fn capacity(&self) -> usize { + unsafe { gpiod::gpiod_edge_event_buffer_get_capacity(self.buffer()) as usize } + } + + /// Read an event stored in the buffer. + pub fn event(&self, index: u64) -> Result { + edge::Event::new(&self.ibuffer, index) + } + + /// Get the number of events the buffer contains. + pub fn len(&self) -> usize { + unsafe { gpiod::gpiod_edge_event_buffer_get_num_events(self.buffer()) as usize } + } + + /// Check if buffer is empty. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} diff --git a/bindings/rust/libgpiod/src/info_event.rs b/bindings/rust/libgpiod/src/info_event.rs new file mode 100644 index 000000000000..d8be87df6679 --- /dev/null +++ b/bindings/rust/libgpiod/src/info_event.rs @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar + +use std::sync::Arc; +use std::time::Duration; + +use vmm_sys_util::errno::Error as Errno; + +use super::{chip, gpiod, line, Error, InfoChangeKind, 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(Clone, Debug, Eq, PartialEq)] +pub struct Event { + event: *mut gpiod::gpiod_info_event, +} + +impl Event { + /// Get a single chip's line's status change event. + pub(crate) fn new(ichip: &Arc) -> Result { + let event = unsafe { gpiod::gpiod_chip_read_info_event(ichip.chip()) }; + if event.is_null() { + return Err(Error::OperationFailed( + OperationType::ChipReadInfoEvent, + Errno::last(), + )); + } + + Ok(Self { event }) + } + + /// Private helper, Returns gpiod_info_event + pub(crate) fn event(&self) -> *mut gpiod::gpiod_info_event { + self.event + } + + /// Get the event type of the status change event. + pub fn event_type(&self) -> Result { + 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 { + 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 { + line::Info::new_from_event(self) + } +} + +impl Drop for Event { + /// Free the info event object and release all associated resources. + fn drop(&mut self) { + 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..c5d974a9f4ea --- /dev/null +++ b/bindings/rust/libgpiod/src/lib.rs @@ -0,0 +1,471 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Rust wrappers for GPIOD APIs +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar + +//! 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. + +/// GPIO chip related definitions. +pub mod chip; + +mod edge_event; +mod event_buffer; + +/// GPIO chip edge event related definitions. +pub mod edge { + pub use crate::edge_event::*; + + /// GPIO chip edge event buffer related definitions. + pub mod event { + pub use crate::event_buffer::*; + } +} + +mod info_event; + +/// GPIO chip info event related definitions. +pub mod info { + pub use crate::info_event::*; +} + +mod line_config; +mod line_info; +mod line_request; +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_request::*; + pub use crate::line_settings::*; +} + +mod request_config; + +/// GPIO chip request related definitions. +pub mod request { + pub use crate::request_config::*; +} + +use libgpiod_sys as gpiod; + +use intmap::IntMap; +use libc::strlen; +use std::fs; +use std::os::raw::c_char; +use std::path::Path; +use std::time::Duration; +use std::{fmt, slice, str}; + +use thiserror::Error as ThisError; +use vmm_sys_util::errno::Error as Errno; + +/// Operation types, used with OperationFailed() Error. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum OperationType { + ChipOpen, + ChipGetFd, + ChipWaitInfoEvent, + ChipGetLine, + ChipGetLineInfo, + ChipInfoGet, + ChipReadInfoEvent, + ChipWatchLineInfo, + EdgeEventBufferGetEvent, + EdgeEventCopy, + EdgeEventBufferNew, + InfoEventGetLineInfo, + LineConfigNew, + LineConfigAddSettings, + LineConfigGetOffsets, + LineConfigGetSetting, + LineRequest, + 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, PartialEq, ThisError)] +pub enum Error { + #[error("Failed to get {0}")] + NullString(&'static str), + #[error("String not utf8: {0:?}")] + StringNotUtf8(str::Utf8Error), + #[error("Invalid enum {0} value: {1}")] + InvalidEnumValue(&'static str, u32), + #[error("Operation {0} Failed: {1}")] + OperationFailed(OperationType, Errno), + #[error("Invalid Arguments")] + InvalidArguments, + #[error("Std Io Error")] + IoError, +} + +/// 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 { + match val { + 0 => Ok(Value::InActive), + 1 => Ok(Value::Active), + _ => Err(Error::InvalidEnumValue("Value", val as u32)), + } + } + + 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 { + fn new(dir: u32) -> Result { + match dir { + gpiod::GPIOD_LINE_DIRECTION_AS_IS => Ok(Direction::AsIs), + gpiod::GPIOD_LINE_DIRECTION_INPUT => Ok(Direction::Input), + gpiod::GPIOD_LINE_DIRECTION_OUTPUT => Ok(Direction::Output), + _ => Err(Error::InvalidEnumValue("Direction", dir)), + } + } + + 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 { + fn new(bias: u32) -> Result> { + match bias { + gpiod::GPIOD_LINE_BIAS_UNKNOWN => Ok(None), + gpiod::GPIOD_LINE_BIAS_AS_IS => Ok(None), + gpiod::GPIOD_LINE_BIAS_DISABLED => Ok(Some(Bias::Disabled)), + gpiod::GPIOD_LINE_BIAS_PULL_UP => Ok(Some(Bias::PullUp)), + gpiod::GPIOD_LINE_BIAS_PULL_DOWN => Ok(Some(Bias::PullDown)), + _ => Err(Error::InvalidEnumValue("Bias", bias)), + } + } + + 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 { + fn new(drive: u32) -> Result { + match drive { + gpiod::GPIOD_LINE_DRIVE_PUSH_PULL => Ok(Drive::PushPull), + gpiod::GPIOD_LINE_DRIVE_OPEN_DRAIN => Ok(Drive::OpenDrain), + gpiod::GPIOD_LINE_DRIVE_OPEN_SOURCE => Ok(Drive::OpenSource), + _ => Err(Error::InvalidEnumValue("Drive", drive)), + } + } + + 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 { + fn new(edge: u32) -> Result> { + match edge { + gpiod::GPIOD_LINE_EDGE_NONE => Ok(None), + gpiod::GPIOD_LINE_EDGE_RISING => Ok(Some(Edge::Rising)), + gpiod::GPIOD_LINE_EDGE_FALLING => Ok(Some(Edge::Falling)), + gpiod::GPIOD_LINE_EDGE_BOTH => Ok(Some(Edge::Both)), + _ => Err(Error::InvalidEnumValue("Edge", edge)), + } + } + + 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 { + fn new(clock: u32) -> Result { + match clock { + gpiod::GPIOD_LINE_EVENT_CLOCK_MONOTONIC => Ok(EventClock::Monotonic), + gpiod::GPIOD_LINE_EVENT_CLOCK_REALTIME => Ok(EventClock::Realtime), + gpiod::GPIOD_LINE_EVENT_CLOCK_HTE => Ok(EventClock::HTE), + _ => Err(Error::InvalidEnumValue("Eventclock", clock)), + } + } + + 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 { + fn new(kind: u32) -> Result { + match kind { + gpiod::GPIOD_INFO_EVENT_LINE_REQUESTED => Ok(InfoChangeKind::LineRequested), + gpiod::GPIOD_INFO_EVENT_LINE_RELEASED => Ok(InfoChangeKind::LineReleased), + gpiod::GPIOD_INFO_EVENT_LINE_CONFIG_CHANGED => Ok(InfoChangeKind::LineConfigChanged), + _ => 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 { + fn new(kind: u32) -> Result { + match kind { + gpiod::GPIOD_EDGE_EVENT_RISING_EDGE => Ok(EdgeKind::Rising), + gpiod::GPIOD_EDGE_EVENT_FALLING_EDGE => Ok(EdgeKind::Falling), + _ => 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"; + + unsafe { gpiod::gpiod_is_gpiochip_device(path.as_ptr() as *const c_char) } +} + +/// Iterator for GPIO devices. +pub fn gpiochip_devices>(path: &P) -> Result> { + let mut chips = Vec::new(); + + for entry in fs::read_dir(path).map_err(|_| Error::IoError)?.flatten() { + let path = entry.path(); + + if is_gpiochip_device(&path) { + chips.push(chip::Chip::open(&path)?); + } + } + + Ok(chips) +} + +/// Get the API version of the library as a human-readable string. +pub fn version_string() -> 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. + str::from_utf8(unsafe { slice::from_raw_parts(version as *const u8, strlen(version) as usize) }) + .map_err(Error::StringNotUtf8) +} diff --git a/bindings/rust/libgpiod/src/line_config.rs b/bindings/rust/libgpiod/src/line_config.rs new file mode 100644 index 000000000000..ea25452fd1d6 --- /dev/null +++ b/bindings/rust/libgpiod/src/line_config.rs @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar + +use std::os::raw::c_ulong; +use std::slice::from_raw_parts; + +use vmm_sys_util::errno::Error as Errno; + +use super::{gpiod, line::Settings, Error, Offset, 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 to 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(Clone, Debug, Eq, PartialEq)] +pub struct Config { + config: *mut gpiod::gpiod_line_config, +} + +impl Config { + /// Create a new line config object. + pub fn new() -> Result { + let config = unsafe { gpiod::gpiod_line_config_new() }; + + if config.is_null() { + return Err(Error::OperationFailed( + OperationType::LineConfigNew, + Errno::last(), + )); + } + + Ok(Self { config }) + } + + /// Private helper, Returns gpiod_line_config + pub(crate) fn config(&self) -> *mut gpiod::gpiod_line_config { + self.config + } + + /// Resets the entire configuration stored in the object. This is useful if + /// the user wants to reuse the object without reallocating it. + pub fn reset(&mut self) { + unsafe { 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<()> { + 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::last(), + )) + } else { + Ok(()) + } + } + + /// Get line settings for offset. + pub fn line_settings(&self, offset: Offset) -> Result { + let settings = unsafe { gpiod::gpiod_line_config_get_line_settings(self.config, offset) }; + + if settings.is_null() { + return Err(Error::OperationFailed( + OperationType::LineConfigGetSetting, + Errno::last(), + )); + } + + Ok(Settings::new_with_settings(settings)) + } + + /// Get configured offsets. + pub fn offsets(&self) -> Result> { + let mut num: u64 = 0; + let mut offsets: *mut Offset = std::ptr::null_mut(); + + let ret = + unsafe { gpiod::gpiod_line_config_get_offsets(self.config, &mut num, &mut offsets) }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::LineConfigGetOffsets, + Errno::last(), + )) + } else { + Ok(unsafe { from_raw_parts(offsets as *const Offset, num as usize).to_vec() }) + } + } +} + +impl Drop for Config { + /// Free the line config object and release all associated resources. + fn drop(&mut self) { + 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..9db51fc30efd --- /dev/null +++ b/bindings/rust/libgpiod/src/line_info.rs @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar + +use libc::strlen; +use std::sync::Arc; +use std::time::Duration; +use std::{slice, str}; + +use vmm_sys_util::errno::Error as Errno; + +use super::{ + chip, gpiod, info, Bias, Direction, Drive, Edge, Error, EventClock, Offset, OperationType, + 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(Clone, Debug, Eq, PartialEq)] +pub struct Info { + info: *mut gpiod::gpiod_line_info, + info_event: bool, +} + +impl Info { + /// Get a snapshot of information about the line and optionally start watching it for changes. + pub(crate) fn new(ichip: Arc, offset: Offset) -> Result { + let info = unsafe { gpiod::gpiod_chip_get_line_info(ichip.chip(), offset) }; + + if info.is_null() { + return Err(Error::OperationFailed( + OperationType::ChipGetLineInfo, + Errno::last(), + )); + } + + Ok(Self { + info, + info_event: false, + }) + } + + pub(crate) fn new_watch(ichip: Arc, offset: Offset) -> Result { + let info = unsafe { gpiod::gpiod_chip_watch_line_info(ichip.chip(), offset) }; + + if info.is_null() { + return Err(Error::OperationFailed( + OperationType::ChipWatchLineInfo, + Errno::last(), + )); + } + + Ok(Self { + info, + info_event: false, + }) + } + + /// Get the Line info object associated with an event. + pub(crate) fn new_from_event(event: &info::Event) -> Result { + let info = unsafe { gpiod::gpiod_info_event_get_line_info(event.event()) }; + + if info.is_null() { + return Err(Error::OperationFailed( + OperationType::InfoEventGetLineInfo, + Errno::last(), + )); + } + + Ok(Self { + info, + info_event: 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 { + 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. + str::from_utf8(unsafe { slice::from_raw_parts(name as *const u8, strlen(name) as usize) }) + .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 { + 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. + str::from_utf8(unsafe { slice::from_raw_parts(name as *const u8, strlen(name) as usize) }) + .map_err(Error::StringNotUtf8) + } + + /// Get the GPIO line's direction. + pub fn direction(&self) -> Result { + 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 { + unsafe { gpiod::gpiod_line_info_is_active_low(self.info) } + } + + /// Get the GPIO line's bias setting. + pub fn bias(&self) -> Result> { + Bias::new(unsafe { gpiod::gpiod_line_info_get_bias(self.info) } as u32) + } + + /// Get the GPIO line's drive setting. + pub fn drive(&self) -> Result { + Drive::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> { + Edge::new(unsafe { gpiod::gpiod_line_info_get_edge_detection(self.info) } as u32) + } + + /// Get the current event clock setting used for edge event timestamps. + pub fn event_clock(&self) -> Result { + EventClock::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 { + unsafe { gpiod::gpiod_line_info_is_debounced(self.info) } + } + + /// Get the debounce period of the line. + pub fn debounce_period(&self) -> Duration { + Duration::from_micros(unsafe { gpiod::gpiod_line_info_get_debounce_period_us(self.info) }) + } +} + +impl Drop for Info { + fn drop(&mut self) { + // We must not free the Line info object created from `struct info::Event` by calling + // libgpiod API. + if !self.info_event { + 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..617efaa34d58 --- /dev/null +++ b/bindings/rust/libgpiod/src/line_request.rs @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar + +use libc::EINVAL; +use std::os::raw::c_ulong; +use std::sync::Arc; +use std::time::Duration; + +use vmm_sys_util::errno::Error as Errno; + +use super::{ + chip, edge, gpiod, line, request, Error, Offset, OperationType, Result, Value, ValueMap, +}; + +/// Line request operations +/// +/// Allows interaction with a set of requested lines. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Request { + request: *mut gpiod::gpiod_line_request, +} + +impl Request { + /// Request a set of lines for exclusive usage. + pub(crate) fn new( + ichip: &Arc, + rconfig: &request::Config, + lconfig: &line::Config, + ) -> Result { + let request = unsafe { + gpiod::gpiod_chip_request_lines(ichip.chip(), rconfig.config(), lconfig.config()) + }; + + if request.is_null() { + return Err(Error::OperationFailed( + OperationType::LineRequest, + Errno::last(), + )); + } + + Ok(Self { request }) + } + + /// Get the number of lines in the request. + pub fn num_lines(&self) -> usize { + 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]; + + 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 { + let value = unsafe { gpiod::gpiod_line_request_get_value(self.request, offset) }; + + if value != 0 && value != 1 { + Err(Error::OperationFailed( + OperationType::LineRequestGetVal, + Errno::last(), + )) + } 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()]; + + 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::last(), + )) + } 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<()> { + let ret = + unsafe { gpiod::gpiod_line_request_set_value(self.request, offset, value.value()) }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::LineRequestSetVal, + Errno::last(), + )) + } 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()); + } + + 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::last(), + )) + } 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::OperationFailed( + OperationType::LineRequestSetVal, + Errno::new(EINVAL), + )); + } + + let mut new_values = Vec::new(); + for value in values { + new_values.push(value.value()); + } + + let ret = + unsafe { gpiod::gpiod_line_request_set_values(self.request, new_values.as_ptr()) }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::LineRequestSetVal, + Errno::last(), + )) + } else { + Ok(()) + } + } + + /// Update the configuration of lines associated with the line request. + pub fn reconfigure_lines(&self, lconfig: &line::Config) -> Result<()> { + let ret = + unsafe { gpiod::gpiod_line_request_reconfigure_lines(self.request, lconfig.config()) }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::LineRequestReconfigLines, + Errno::last(), + )) + } else { + Ok(()) + } + } + + /// Get the file descriptor associated with the line request. + pub fn fd(&self) -> u32 { + unsafe { gpiod::gpiod_line_request_get_fd(self.request) as u32 } + } + + /// Wait for edge events on any of the lines associated with the request. + pub fn wait_edge_event(&self, timeout: Option) -> Result { + let timeout = match timeout { + Some(x) => x.as_nanos() as i64, + // Block indefinitely + None => -1, + }; + + let ret = unsafe { gpiod::gpiod_line_request_wait_edge_event(self.request, timeout) }; + + match ret { + -1 => Err(Error::OperationFailed( + OperationType::LineRequestWaitEdgeEvent, + Errno::last(), + )), + 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(&self, buffer: &edge::event::Buffer) -> Result { + let ret = unsafe { + gpiod::gpiod_line_request_read_edge_event( + self.request, + buffer.buffer(), + buffer.capacity() as u64, + ) + }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::LineRequestReadEdgeEvent, + Errno::last(), + )) + } else { + Ok(ret as u32) + } + } +} + +impl Drop for Request { + /// Release the requested lines and free all associated resources. + fn drop(&mut self) { + 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..2c3090132ea5 --- /dev/null +++ b/bindings/rust/libgpiod/src/line_settings.rs @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar + +use std::time::Duration; + +use vmm_sys_util::errno::Error as Errno; + +use super::{ + gpiod, Bias, Direction, Drive, Edge, Error, EventClock, OperationType, Result, SettingKind, + SettingVal, Value, +}; + +/// 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(Clone, Debug, Eq, PartialEq)] +pub struct Settings { + settings: *mut gpiod::gpiod_line_settings, +} + +impl Settings { + /// Create a new line settings object. + pub fn new() -> Result { + let settings = unsafe { gpiod::gpiod_line_settings_new() }; + + if settings.is_null() { + return Err(Error::OperationFailed( + OperationType::LineSettingsNew, + Errno::last(), + )); + } + + Ok(Self { settings }) + } + + pub fn new_with_settings(settings: *mut gpiod::gpiod_line_settings) -> Self { + Self { settings } + } + + /// Private helper, Returns gpiod_line_settings + pub(crate) fn settings(&self) -> *mut gpiod::gpiod_line_settings { + self.settings + } + + /// Resets the line settings object to its default values. + pub fn reset(&mut self) { + unsafe { gpiod::gpiod_line_settings_reset(self.settings) } + } + + /// Makes copy of the settings object. + pub fn settings_clone(&self) -> Result { + let settings = unsafe { gpiod::gpiod_line_settings_copy(self.settings) }; + if settings.is_null() { + return Err(Error::OperationFailed( + OperationType::LineSettingsCopy, + Errno::last(), + )); + } + + Ok(Self { settings }) + } + + /// Set line prop setting. + pub fn set_prop(&mut self, values: &[SettingVal]) -> Result<()> { + for value in values { + match value { + SettingVal::Direction(val) => self.set_direction(*val)?, + SettingVal::EdgeDetection(val) => self.set_edge_detection(*val)?, + SettingVal::Bias(val) => self.set_bias(*val)?, + SettingVal::Drive(val) => self.set_drive(*val)?, + SettingVal::ActiveLow(val) => self.set_active_low(*val), + SettingVal::DebouncePeriod(val) => self.set_debounce_period(*val), + SettingVal::EventClock(val) => self.set_event_clock(*val)?, + SettingVal::OutputValue(val) => self.set_output_value(*val)?, + } + } + + Ok(()) + } + + /// 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. + fn set_direction(&mut self, direction: Direction) -> Result<()> { + 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::last(), + )) + } else { + Ok(()) + } + } + + /// Get the direction setting. + fn direction(&self) -> Result { + Direction::new(unsafe { gpiod::gpiod_line_settings_get_direction(self.settings) } as u32) + } + + /// Set the edge event detection setting. + fn set_edge_detection(&mut self, edge: Option) -> Result<()> { + 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::last(), + )) + } else { + Ok(()) + } + } + + /// Get the edge event detection setting. + fn edge_detection(&self) -> Result> { + Edge::new(unsafe { gpiod::gpiod_line_settings_get_edge_detection(self.settings) } as u32) + } + + /// Set the bias setting. + fn set_bias(&mut self, bias: Option) -> Result<()> { + 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::last(), + )) + } else { + Ok(()) + } + } + + /// Get the bias setting. + fn bias(&self) -> Result> { + Bias::new(unsafe { gpiod::gpiod_line_settings_get_bias(self.settings) } as u32) + } + + /// Set the drive setting. + fn set_drive(&mut self, drive: Drive) -> Result<()> { + 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::last(), + )) + } else { + Ok(()) + } + } + + /// Get the drive setting. + fn drive(&self) -> Result { + Drive::new(unsafe { gpiod::gpiod_line_settings_get_drive(self.settings) } as u32) + } + + /// Set active-low setting. + fn set_active_low(&mut self, active_low: bool) { + unsafe { gpiod::gpiod_line_settings_set_active_low(self.settings, active_low) } + } + + /// Check the active-low setting. + fn active_low(&self) -> bool { + unsafe { gpiod::gpiod_line_settings_get_active_low(self.settings) } + } + + /// Set the debounce period setting. + fn set_debounce_period(&mut self, period: Duration) { + unsafe { + gpiod::gpiod_line_settings_set_debounce_period_us( + self.settings, + period.as_micros() as u64, + ) + } + } + + /// Get the debounce period. + fn debounce_period(&self) -> Result { + Ok(Duration::from_micros(unsafe { + gpiod::gpiod_line_settings_get_debounce_period_us(self.settings) + })) + } + + /// Set the event clock setting. + fn set_event_clock(&mut self, clock: EventClock) -> Result<()> { + 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::last(), + )) + } else { + Ok(()) + } + } + + /// Get the event clock setting. + fn event_clock(&self) -> Result { + EventClock::new(unsafe { gpiod::gpiod_line_settings_get_event_clock(self.settings) } as u32) + } + + /// Set the output value setting. + fn set_output_value(&mut self, value: Value) -> Result<()> { + let ret = + unsafe { gpiod::gpiod_line_settings_set_output_value(self.settings, value.value()) }; + + if ret == -1 { + Err(Error::OperationFailed( + OperationType::LineSettingsSetOutputValue, + Errno::last(), + )) + } else { + Ok(()) + } + } + + /// Get the output value, 0 or 1. + fn output_value(&self) -> Result { + let value = unsafe { gpiod::gpiod_line_settings_get_output_value(self.settings) }; + + if value != 0 && value != 1 { + Err(Error::OperationFailed( + OperationType::LineSettingsGetOutVal, + Errno::last(), + )) + } else { + Value::new(value) + } + } +} + +impl Drop for Settings { + /// Free the line settings object and release all associated resources. + fn drop(&mut self) { + 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..760d9c755c86 --- /dev/null +++ b/bindings/rust/libgpiod/src/request_config.rs @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause +// +// Copyright 2022 Linaro Ltd. All Rights Reserved. +// Viresh Kumar + +use libc::strlen; +use std::os::raw::{c_char, c_ulong}; +use std::{slice, str}; + +use vmm_sys_util::errno::Error as Errno; + +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(Clone, Debug, Eq, PartialEq)] +pub struct Config { + config: *mut gpiod::gpiod_request_config, +} + +impl Config { + /// Create a new request config object. + pub fn new() -> Result { + let config = unsafe { gpiod::gpiod_request_config_new() }; + if config.is_null() { + return Err(Error::OperationFailed( + OperationType::RequestConfigNew, + Errno::last(), + )); + } + + Ok(Self { config }) + } + + /// Private helper, Returns gpiod_request_config + pub(crate) fn config(&self) -> *mut gpiod::gpiod_request_config { + self.config + } + + /// Set the consumer name for the request. + /// + /// If the consumer string is too long, it will be truncated to the max + /// accepted length. + pub fn set_consumer(&self, consumer: &str) { + // Null-terminate the string + let consumer = consumer.to_owned() + "\0"; + + unsafe { + gpiod::gpiod_request_config_set_consumer( + self.config, + consumer.as_ptr() as *const c_char, + ) + } + } + + /// Get the consumer name configured in the request config. + pub fn 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::last(), + )); + } + + // SAFETY: The string is guaranteed to be valid here by the C API. + str::from_utf8(unsafe { + slice::from_raw_parts(consumer as *const u8, strlen(consumer) as usize) + }) + .map_err(Error::StringNotUtf8) + } + + /// Set the size of the kernel event buffer for the request. + pub fn set_event_buffer_size(&self, size: usize) { + 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 { + 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) { + unsafe { gpiod::gpiod_request_config_free(self.config) } + } +}