| // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause |
| // SPDX-FileCopyrightText: 2022 Linaro Ltd. |
| // SPDX-FileCopyrightText: 2022 Viresh Kumar <viresh.kumar@linaro.org> |
| |
| pub mod info { |
| /// GPIO chip info event related definitions. |
| pub use crate::info_event::*; |
| } |
| |
| use std::cmp::Ordering; |
| use std::ffi::{CStr, CString}; |
| use std::os::{raw::c_char, unix::prelude::AsRawFd}; |
| use std::path::Path; |
| use std::ptr; |
| use std::str; |
| use std::sync::Arc; |
| use std::time::Duration; |
| |
| use super::{ |
| gpiod, |
| line::{self, Offset}, |
| request, Error, OperationType, Result, |
| }; |
| |
| #[derive(Debug, Eq, PartialEq)] |
| struct Internal { |
| chip: *mut gpiod::gpiod_chip, |
| } |
| |
| impl Internal { |
| /// Find a chip by path. |
| fn open<P: AsRef<Path>>(path: &P) -> Result<Self> { |
| // Null-terminate the string |
| let path = path.as_ref().to_string_lossy() + "\0"; |
| |
| // SAFETY: The `gpiod_chip` returned by libgpiod is guaranteed to live as long |
| // as the `struct Internal`. |
| let chip = unsafe { gpiod::gpiod_chip_open(path.as_ptr() as *const c_char) }; |
| if chip.is_null() { |
| return Err(Error::OperationFailed( |
| OperationType::ChipOpen, |
| errno::errno(), |
| )); |
| } |
| |
| Ok(Self { chip }) |
| } |
| } |
| |
| impl Drop for Internal { |
| /// Close the chip and release all associated resources. |
| fn drop(&mut self) { |
| // SAFETY: `gpiod_chip` is guaranteed to be valid here. |
| unsafe { gpiod::gpiod_chip_close(self.chip) } |
| } |
| } |
| |
| /// GPIO chip |
| /// |
| /// A GPIO chip object is associated with an open file descriptor to the GPIO |
| /// character device. It exposes basic information about the chip and allows |
| /// callers to retrieve information about each line, watch lines for state |
| /// changes and make line requests. |
| #[derive(Debug, Eq, PartialEq)] |
| pub struct Chip { |
| ichip: Arc<Internal>, |
| } |
| |
| // SAFETY: Safe as `Internal` won't be freed until the `Chip` is dropped. |
| unsafe impl Send for Chip {} |
| |
| impl Chip { |
| /// Find a chip by path. |
| pub fn open<P: AsRef<Path>>(path: &P) -> Result<Self> { |
| let ichip = Arc::new(Internal::open(path)?); |
| |
| Ok(Self { ichip }) |
| } |
| |
| /// Get the chip name as represented in the kernel. |
| pub fn info(&self) -> Result<Info> { |
| Info::new(self.ichip.clone()) |
| } |
| |
| /// Get the path used to find the chip. |
| pub fn path(&self) -> Result<&str> { |
| // SAFETY: The string returned by libgpiod is guaranteed to live as long |
| // as the `struct Chip`. |
| let path = unsafe { gpiod::gpiod_chip_get_path(self.ichip.chip) }; |
| |
| // SAFETY: The string is guaranteed to be valid here by the C API. |
| unsafe { CStr::from_ptr(path) } |
| .to_str() |
| .map_err(Error::StringNotUtf8) |
| } |
| |
| /// Get a snapshot of information about the line. |
| pub fn line_info(&self, offset: Offset) -> Result<line::Info> { |
| // SAFETY: The `gpiod_line_info` returned by libgpiod is guaranteed to live as long |
| // as the `struct Info`. |
| let info = unsafe { gpiod::gpiod_chip_get_line_info(self.ichip.chip, offset) }; |
| |
| if info.is_null() { |
| return Err(Error::OperationFailed( |
| OperationType::ChipGetLineInfo, |
| errno::errno(), |
| )); |
| } |
| |
| line::Info::new(info) |
| } |
| |
| /// Get the current snapshot of information about the line at given offset and start watching |
| /// it for future changes. |
| pub fn watch_line_info(&self, offset: Offset) -> Result<line::Info> { |
| // SAFETY: `gpiod_line_info` is guaranteed to be valid here. |
| let info = unsafe { gpiod::gpiod_chip_watch_line_info(self.ichip.chip, offset) }; |
| |
| if info.is_null() { |
| return Err(Error::OperationFailed( |
| OperationType::ChipWatchLineInfo, |
| errno::errno(), |
| )); |
| } |
| |
| line::Info::new_watch(info) |
| } |
| |
| /// Stop watching a line |
| pub fn unwatch(&self, offset: Offset) { |
| // SAFETY: `gpiod_chip` is guaranteed to be valid here. |
| unsafe { |
| gpiod::gpiod_chip_unwatch_line_info(self.ichip.chip, offset); |
| } |
| } |
| |
| /// Wait for line status events on any of the watched lines on the chip. |
| pub fn wait_info_event(&self, timeout: Option<Duration>) -> Result<bool> { |
| let timeout = match timeout { |
| Some(x) => x.as_nanos() as i64, |
| // Block indefinitely |
| None => -1, |
| }; |
| |
| // SAFETY: `gpiod_chip` is guaranteed to be valid here. |
| let ret = unsafe { gpiod::gpiod_chip_wait_info_event(self.ichip.chip, timeout) }; |
| |
| match ret { |
| -1 => Err(Error::OperationFailed( |
| OperationType::ChipWaitInfoEvent, |
| errno::errno(), |
| )), |
| 0 => Ok(false), |
| _ => Ok(true), |
| } |
| } |
| |
| /// Read a single line status change event from the chip. If no events are |
| /// pending, this function will block. |
| pub fn read_info_event(&self) -> Result<info::Event> { |
| // SAFETY: The `gpiod_info_event` returned by libgpiod is guaranteed to live as long |
| // as the `struct Event`. |
| let event = unsafe { gpiod::gpiod_chip_read_info_event(self.ichip.chip) }; |
| if event.is_null() { |
| return Err(Error::OperationFailed( |
| OperationType::ChipReadInfoEvent, |
| errno::errno(), |
| )); |
| } |
| |
| Ok(info::Event::new(event)) |
| } |
| |
| /// Map a GPIO line's name to its offset within the chip. |
| pub fn line_offset_from_name(&self, name: &str) -> Result<Offset> { |
| let name = CString::new(name).map_err(|_| Error::InvalidString)?; |
| |
| // SAFETY: `gpiod_chip` is guaranteed to be valid here. |
| let ret = unsafe { |
| gpiod::gpiod_chip_get_line_offset_from_name( |
| self.ichip.chip, |
| name.as_ptr() as *const c_char, |
| ) |
| }; |
| |
| if ret == -1 { |
| Err(Error::OperationFailed( |
| OperationType::ChipGetLineOffsetFromName, |
| errno::errno(), |
| )) |
| } else { |
| Ok(ret as u32) |
| } |
| } |
| |
| /// Request a set of lines for exclusive usage. |
| pub fn request_lines( |
| &self, |
| rconfig: Option<&request::Config>, |
| lconfig: &line::Config, |
| ) -> Result<request::Request> { |
| let req_cfg = match rconfig { |
| Some(cfg) => cfg.config, |
| _ => ptr::null(), |
| } as *mut gpiod::gpiod_request_config; |
| |
| // 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, req_cfg, lconfig.config) }; |
| |
| if request.is_null() { |
| return Err(Error::OperationFailed( |
| OperationType::ChipRequestLines, |
| errno::errno(), |
| )); |
| } |
| |
| request::Request::new(request) |
| } |
| } |
| |
| impl AsRawFd for Chip { |
| /// Get the file descriptor associated with the chip. |
| /// |
| /// The returned file descriptor must not be closed by the caller, else other methods for the |
| /// `struct Chip` may fail. |
| fn as_raw_fd(&self) -> i32 { |
| // SAFETY: `gpiod_chip` is guaranteed to be valid here. |
| unsafe { gpiod::gpiod_chip_get_fd(self.ichip.chip) } |
| } |
| } |
| |
| /// GPIO chip Information |
| #[derive(Debug, Eq)] |
| pub struct Info { |
| info: *mut gpiod::gpiod_chip_info, |
| } |
| |
| impl Info { |
| /// Find a GPIO chip by path. |
| fn new(chip: Arc<Internal>) -> Result<Self> { |
| // SAFETY: `gpiod_chip` is guaranteed to be valid here. |
| let info = unsafe { gpiod::gpiod_chip_get_info(chip.chip) }; |
| if info.is_null() { |
| return Err(Error::OperationFailed( |
| OperationType::ChipGetInfo, |
| errno::errno(), |
| )); |
| } |
| |
| Ok(Self { info }) |
| } |
| |
| /// Get the GPIO chip name as represented in the kernel. |
| pub fn name(&self) -> Result<&str> { |
| // SAFETY: The string returned by libgpiod is guaranteed to live as long |
| // as the `struct Chip`. |
| let name = unsafe { gpiod::gpiod_chip_info_get_name(self.info) }; |
| |
| // SAFETY: The string is guaranteed to be valid here by the C API. |
| unsafe { CStr::from_ptr(name) } |
| .to_str() |
| .map_err(Error::StringNotUtf8) |
| } |
| |
| /// Get the GPIO chip label as represented in the kernel. |
| pub fn label(&self) -> Result<&str> { |
| // SAFETY: The string returned by libgpiod is guaranteed to live as long |
| // as the `struct Chip`. |
| let label = unsafe { gpiod::gpiod_chip_info_get_label(self.info) }; |
| |
| // SAFETY: The string is guaranteed to be valid here by the C API. |
| unsafe { CStr::from_ptr(label) } |
| .to_str() |
| .map_err(Error::StringNotUtf8) |
| } |
| |
| /// Get the number of GPIO lines exposed by the chip. |
| pub fn num_lines(&self) -> usize { |
| // SAFETY: `gpiod_chip` is guaranteed to be valid here. |
| unsafe { gpiod::gpiod_chip_info_get_num_lines(self.info) as usize } |
| } |
| } |
| |
| impl PartialEq for Info { |
| fn eq(&self, other: &Self) -> bool { |
| self.name().unwrap().eq(other.name().unwrap()) |
| } |
| } |
| |
| impl PartialOrd for Info { |
| fn partial_cmp(&self, other: &Self) -> Option<Ordering> { |
| let name = match self.name() { |
| Ok(name) => name, |
| _ => return None, |
| }; |
| |
| let other_name = match other.name() { |
| Ok(name) => name, |
| _ => return None, |
| }; |
| |
| name.partial_cmp(other_name) |
| } |
| } |
| |
| impl Drop for Info { |
| /// Close the GPIO chip info and release all associated resources. |
| fn drop(&mut self) { |
| // SAFETY: `gpiod_chip` is guaranteed to be valid here. |
| unsafe { gpiod::gpiod_chip_info_free(self.info) } |
| } |
| } |