summaryrefslogtreecommitdiff
path: root/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib.rs')
-rw-r--r--src/lib.rs1041
1 files changed, 1041 insertions, 0 deletions
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..b8b6506
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,1041 @@
+// Copyright (c) 2018 The rust-gpio-cdev Project Developers.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//! The `gpio-cdev` crate provides access to the [GPIO character device
+//! ABI](https://www.kernel.org/doc/Documentation/ABI/testing/gpio-cdev). This API,
+//! stabilized with Linux v4.4, deprecates the legacy sysfs interface to GPIOs that is
+//! planned to be removed from the upstream kernel after
+//! year 2020 (which is coming up quickly).
+//!
+//! This crate attempts to wrap this interface in a moderately direction fashion
+//! while retaining safety and using Rust idioms (where doing so could be mapped
+//! to the underlying abstraction without significant overhead or loss of
+//! functionality).
+//!
+//! For additional context for why the kernel is moving from the sysfs API to the
+//! character device API, please see the main [README on Github].
+//!
+//! # Examples
+//!
+//! The following example reads the state of a GPIO line/pin and writes the matching
+//! state to another line/pin.
+//!
+//! ```no_run
+//! use gpio_cdev::{Chip, LineRequestFlags, EventRequestFlags, EventType};
+//!
+//! // Lines are offset within gpiochip0; see docs for more info on chips/lines
+//! fn mirror_gpio(inputline: u32, outputline: u32) -> Result<(), gpio_cdev::Error> {
+//! let mut chip = Chip::new("/dev/gpiochip0")?;
+//! let input = chip.get_line(inputline)?;
+//! let output = chip.get_line(outputline)?;
+//! let output_handle = output.request(LineRequestFlags::OUTPUT, 0, "mirror-gpio")?;
+//! for event in input.events(
+//! LineRequestFlags::INPUT,
+//! EventRequestFlags::BOTH_EDGES,
+//! "mirror-gpio",
+//! )? {
+//! let evt = event?;
+//! println!("{:?}", evt);
+//! match evt.event_type() {
+//! EventType::RisingEdge => {
+//! output_handle.set_value(1)?;
+//! }
+//! EventType::FallingEdge => {
+//! output_handle.set_value(0)?;
+//! }
+//! }
+//! }
+//!
+//! Ok(())
+//! }
+//!
+//! # fn main() -> Result<(), gpio_cdev::Error> {
+//! # mirror_gpio(0, 1)
+//! # }
+//! ```
+//!
+//! To get the state of a GPIO Line on a given chip:
+//!
+//! ```no_run
+//! use gpio_cdev::{Chip, LineRequestFlags};
+//!
+//! # fn main() -> Result<(), gpio_cdev::Error> {
+//! // Read the state of GPIO4 on a raspberry pi. /dev/gpiochip0
+//! // maps to the driver for the SoC (builtin) GPIO controller.
+//! // The LineHandle returned by request must be assigned to a
+//! // variable (in this case the variable handle) to ensure that
+//! // the corresponding file descriptor is not closed.
+//! let mut chip = Chip::new("/dev/gpiochip0")?;
+//! let handle = chip
+//! .get_line(4)?
+//! .request(LineRequestFlags::INPUT, 0, "read-input")?;
+//! for _ in 1..4 {
+//! println!("Value: {:?}", handle.get_value()?);
+//! }
+//! # Ok(()) }
+//! ```
+//!
+//! [README on Github]: https://github.com/rust-embedded/rust-gpio-cdev
+
+#![cfg_attr(docsrs, feature(doc_cfg))]
+
+#[macro_use]
+extern crate bitflags;
+#[macro_use]
+extern crate nix;
+
+use std::cmp::min;
+use std::ffi::CStr;
+use std::fs::{read_dir, File, ReadDir};
+use std::io::Read;
+use std::mem;
+use std::ops::Index;
+use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, RawFd};
+use std::path::{Path, PathBuf};
+use std::ptr;
+use std::slice;
+use std::sync::Arc;
+
+#[cfg(feature = "async-tokio")]
+#[cfg_attr(docsrs, doc(cfg(feature = "async-tokio")))]
+mod async_tokio;
+pub mod errors; // pub portion is deprecated
+mod ffi;
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum IoctlKind {
+ ChipInfo,
+ LineInfo,
+ LineHandle,
+ LineEvent,
+ GetLine,
+ SetLine,
+}
+
+#[cfg(feature = "async-tokio")]
+#[cfg_attr(docsrs, doc(cfg(feature = "async-tokio")))]
+pub use crate::async_tokio::AsyncLineEventHandle;
+pub use errors::*;
+
+unsafe fn rstr_lcpy(dst: *mut libc::c_char, src: &str, length: usize) {
+ let copylen = min(src.len() + 1, length);
+ ptr::copy_nonoverlapping(src.as_bytes().as_ptr().cast(), dst, copylen - 1);
+ slice::from_raw_parts_mut(dst, length)[copylen - 1] = 0;
+}
+
+#[derive(Debug)]
+struct InnerChip {
+ pub path: PathBuf,
+ pub file: File,
+ pub name: String,
+ pub label: String,
+ pub lines: u32,
+}
+
+/// A GPIO Chip maps to the actual device driver instance in hardware that
+/// one interacts with to interact with individual GPIOs. Often these chips
+/// map to IP chunks on an SoC but could also be enumerated within the kernel
+/// via something like a PCI or USB bus.
+///
+/// The Linux kernel itself enumerates GPIO character devices at two paths:
+/// 1. `/dev/gpiochipN`
+/// 2. `/sys/bus/gpiochipN`
+///
+/// It is best not to assume that a device will always be enumerated in the
+/// same order (especially if it is connected via a bus). In order to reliably
+/// find the correct chip, there are a few approaches that one could reasonably
+/// take:
+///
+/// 1. Create a udev rule that will match attributes of the device and
+/// setup a symlink to the device.
+/// 2. Iterate over all available chips using the [`chips()`] call to find the
+/// device with matching criteria.
+/// 3. For simple cases, just using the enumerated path is fine (demo work). This
+/// is discouraged for production.
+///
+/// [`chips()`]: fn.chips.html
+#[derive(Debug)]
+pub struct Chip {
+ inner: Arc<InnerChip>,
+}
+
+/// Iterator over chips
+#[derive(Debug)]
+pub struct ChipIterator {
+ readdir: ReadDir,
+}
+
+impl Iterator for ChipIterator {
+ type Item = Result<Chip>;
+
+ fn next(&mut self) -> Option<Result<Chip>> {
+ for entry in &mut self.readdir {
+ match entry {
+ Ok(entry) => {
+ if entry
+ .path()
+ .as_path()
+ .to_string_lossy()
+ .contains("gpiochip")
+ {
+ return Some(Chip::new(entry.path()));
+ }
+ }
+ Err(e) => {
+ return Some(Err(e.into()));
+ }
+ }
+ }
+
+ None
+ }
+}
+
+/// Iterate over all GPIO chips currently present on this system
+pub fn chips() -> Result<ChipIterator> {
+ Ok(ChipIterator {
+ readdir: read_dir("/dev")?,
+ })
+}
+
+impl Chip {
+ /// Open the GPIO Chip at the provided path (e.g. `/dev/gpiochip<N>`)
+ pub fn new<P: AsRef<Path>>(path: P) -> Result<Self> {
+ let f = File::open(path.as_ref())?;
+ let mut info: ffi::gpiochip_info = unsafe { mem::zeroed() };
+ ffi::gpio_get_chipinfo_ioctl(f.as_raw_fd(), &mut info)?;
+
+ Ok(Self {
+ inner: Arc::new(InnerChip {
+ file: f,
+ path: path.as_ref().to_path_buf(),
+ name: unsafe {
+ CStr::from_ptr(info.name.as_ptr())
+ .to_string_lossy()
+ .into_owned()
+ },
+ label: unsafe {
+ CStr::from_ptr(info.label.as_ptr())
+ .to_string_lossy()
+ .into_owned()
+ },
+ lines: info.lines,
+ }),
+ })
+ }
+
+ /// Get the fs path of this character device (e.g. `/dev/gpiochipN`)
+ pub fn path(&self) -> &Path {
+ self.inner.path.as_path()
+ }
+
+ /// The name of the device driving this GPIO chip in the kernel
+ pub fn name(&self) -> &str {
+ self.inner.name.as_str()
+ }
+
+ /// A functional name for this GPIO chip, such as a product number. Might
+ /// be an empty string.
+ ///
+ /// As an example, the SoC GPIO chip on a Raspberry Pi is "pinctrl-bcm2835"
+ pub fn label(&self) -> &str {
+ self.inner.label.as_str()
+ }
+
+ /// The number of lines/pins indexable through this chip
+ ///
+ /// Not all of these may be usable depending on how the hardware is
+ /// configured/muxed.
+ pub fn num_lines(&self) -> u32 {
+ self.inner.lines
+ }
+
+ /// Get a handle to the GPIO line at a given offset
+ ///
+ /// The actual physical line corresponding to a given offset
+ /// is completely dependent on how the driver/hardware for
+ /// the chip works as well as the associated board layout.
+ ///
+ /// For a device like the NXP i.mx6 SoC GPIO controller there
+ /// are several banks of GPIOs with each bank containing 32
+ /// GPIOs. For this hardware and driver something like
+ /// `GPIO2_5` would map to offset 37.
+ pub fn get_line(&mut self, offset: u32) -> Result<Line> {
+ Line::new(self.inner.clone(), offset)
+ }
+
+ /// Get a handle to multiple GPIO line at a given offsets
+ ///
+ /// The group of lines can be manipulated simultaneously.
+ pub fn get_lines(&mut self, offsets: &[u32]) -> Result<Lines> {
+ Lines::new(self.inner.clone(), offsets)
+ }
+
+ /// Get a handle to all the GPIO lines on the chip
+ ///
+ /// The group of lines can be manipulated simultaneously.
+ pub fn get_all_lines(&mut self) -> Result<Lines> {
+ let offsets: Vec<u32> = (0..self.num_lines()).collect();
+ self.get_lines(&offsets)
+ }
+
+ /// Get an interator over all lines that can be potentially access for this
+ /// chip.
+ pub fn lines(&self) -> LineIterator {
+ LineIterator {
+ chip: self.inner.clone(),
+ idx: 0,
+ }
+ }
+}
+
+/// Iterator over GPIO Lines for a given chip.
+#[derive(Debug)]
+pub struct LineIterator {
+ chip: Arc<InnerChip>,
+ idx: u32,
+}
+
+impl Iterator for LineIterator {
+ type Item = Line;
+
+ fn next(&mut self) -> Option<Line> {
+ if self.idx < self.chip.lines {
+ let idx = self.idx;
+ self.idx += 1;
+ // Since we checked the index, we know this will be Ok
+ Some(Line::new(self.chip.clone(), idx).unwrap())
+ } else {
+ None
+ }
+ }
+}
+
+/// Access to a specific GPIO Line
+///
+/// GPIO Lines must be obtained through a parent [`Chip`] and
+/// represent an actual GPIO pin/line accessible via that chip.
+/// Not all accessible lines for a given chip may actually
+/// map to hardware depending on how the board is setup
+/// in the kernel.
+///
+#[derive(Debug, Clone)]
+pub struct Line {
+ chip: Arc<InnerChip>,
+ offset: u32,
+}
+
+/// Information about a specific GPIO Line
+///
+/// Wraps kernel [`struct gpioline_info`].
+///
+/// [`struct gpioline_info`]: https://elixir.bootlin.com/linux/v4.9.127/source/include/uapi/linux/gpio.h#L36
+#[derive(Debug)]
+pub struct LineInfo {
+ line: Line,
+ flags: LineFlags,
+ name: Option<String>,
+ consumer: Option<String>,
+}
+
+bitflags! {
+ /// Line Request Flags
+ ///
+ /// Maps to kernel [`GPIOHANDLE_REQUEST_*`] flags.
+ ///
+ /// [`GPIOHANDLE_REQUEST_*`]: https://elixir.bootlin.com/linux/v4.9.127/source/include/uapi/linux/gpio.h#L58
+ #[derive(Debug, Clone)]
+ pub struct LineRequestFlags: u32 {
+ const INPUT = (1 << 0);
+ const OUTPUT = (1 << 1);
+ const ACTIVE_LOW = (1 << 2);
+ const OPEN_DRAIN = (1 << 3);
+ const OPEN_SOURCE = (1 << 4);
+ }
+}
+
+bitflags! {
+ /// Event request flags
+ ///
+ /// Maps to kernel [`GPIOEVENT_REQEST_*`] flags.
+ ///
+ /// [`GPIOEVENT_REQUEST_*`]: https://elixir.bootlin.com/linux/v4.9.127/source/include/uapi/linux/gpio.h#L109
+ pub struct EventRequestFlags: u32 {
+ const RISING_EDGE = (1 << 0);
+ const FALLING_EDGE = (1 << 1);
+ const BOTH_EDGES = Self::RISING_EDGE.bits() | Self::FALLING_EDGE.bits();
+ }
+}
+
+bitflags! {
+ /// Informational Flags
+ ///
+ /// Maps to kernel [`GPIOLINE_FLAG_*`] flags.
+ ///
+ /// [`GPIOLINE_FLAG_*`]: https://elixir.bootlin.com/linux/v4.9.127/source/include/uapi/linux/gpio.h#L29
+ #[derive(Debug)]
+ pub struct LineFlags: u32 {
+ const KERNEL = (1 << 0);
+ const IS_OUT = (1 << 1);
+ const ACTIVE_LOW = (1 << 2);
+ const OPEN_DRAIN = (1 << 3);
+ const OPEN_SOURCE = (1 << 4);
+ }
+}
+
+/// In or Out
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum LineDirection {
+ In,
+ Out,
+}
+
+unsafe fn cstrbuf_to_string(buf: &[libc::c_char]) -> Option<String> {
+ if buf[0] == 0 {
+ None
+ } else {
+ Some(CStr::from_ptr(buf.as_ptr()).to_string_lossy().into_owned())
+ }
+}
+
+impl Line {
+ fn new(chip: Arc<InnerChip>, offset: u32) -> Result<Self> {
+ if offset >= chip.lines {
+ return Err(offset_err(offset));
+ }
+ Ok(Self { chip, offset })
+ }
+
+ /// Get info about the line from the kernel.
+ pub fn info(&self) -> Result<LineInfo> {
+ let mut line_info = ffi::gpioline_info {
+ line_offset: self.offset,
+ flags: 0,
+ name: [0; 32],
+ consumer: [0; 32],
+ };
+ ffi::gpio_get_lineinfo_ioctl(self.chip.file.as_raw_fd(), &mut line_info)?;
+
+ Ok(LineInfo {
+ line: self.clone(),
+ flags: LineFlags::from_bits_truncate(line_info.flags),
+ name: unsafe { cstrbuf_to_string(&line_info.name[..]) },
+ consumer: unsafe { cstrbuf_to_string(&line_info.consumer[..]) },
+ })
+ }
+
+ /// Offset of this line within its parent chip
+ pub fn offset(&self) -> u32 {
+ self.offset
+ }
+
+ /// Get a handle to this chip's parent
+ pub fn chip(&self) -> Chip {
+ Chip {
+ inner: self.chip.clone(),
+ }
+ }
+
+ /// Request access to interact with this line from the kernel
+ ///
+ /// This is similar to the "export" operation present in the sysfs
+ /// API with the key difference that we are also able to configure
+ /// the GPIO with `flags` to specify how the line will be used
+ /// at the time of request.
+ ///
+ /// For an output, the `default` parameter specifies the value
+ /// the line should have when it is configured as an output. The
+ /// `consumer` string should describe the process consuming the
+ /// line (this will be truncated to 31 characters if too long).
+ ///
+ /// # Errors
+ ///
+ /// The main source of errors here is if the kernel returns an
+ /// error to the ioctl performing the request here. This will
+ /// result in an [`Error`] being returned with [`ErrorKind::Ioctl`].
+ ///
+ /// One possible cause for an error here would be if the line is
+ /// already in use. One can check for this prior to making the
+ /// request using [`is_kernel`].
+ ///
+ /// [`Error`]: errors/struct.Error.html
+ /// [`ErrorKind::Ioctl`]: errors/enum.ErrorKind.html#variant.Ioctl
+ /// [`is_kernel`]: struct.Line.html#method.is_kernel
+ pub fn request(
+ &self,
+ flags: LineRequestFlags,
+ default: u8,
+ consumer: &str,
+ ) -> Result<LineHandle> {
+ // prepare the request; the kernel consumes some of these values and will
+ // set the fd for us.
+ let mut request = ffi::gpiohandle_request {
+ lineoffsets: unsafe { mem::zeroed() },
+ flags: flags.bits(),
+ default_values: unsafe { mem::zeroed() },
+ consumer_label: unsafe { mem::zeroed() },
+ lines: 1,
+ fd: 0,
+ };
+ request.lineoffsets[0] = self.offset;
+ request.default_values[0] = default;
+ unsafe {
+ rstr_lcpy(
+ request.consumer_label[..].as_mut_ptr(),
+ consumer,
+ request.consumer_label.len(),
+ );
+ }
+ ffi::gpio_get_linehandle_ioctl(self.chip.file.as_raw_fd(), &mut request)?;
+ Ok(LineHandle {
+ line: self.clone(),
+ flags,
+ file: unsafe { File::from_raw_fd(request.fd) },
+ })
+ }
+
+ /// Get an event handle that can be used as a blocking iterator over
+ /// the events (state changes) for this Line
+ ///
+ /// When used as an iterator, it blocks while there is not another event
+ /// available from the kernel for this line matching the subscription
+ /// criteria specified in the `event_flags`. The line will be configured
+ /// with the specified `handle_flags` and `consumer` label.
+ ///
+ /// Note that as compared with the sysfs interface, the character
+ /// device interface maintains a queue of events in the kernel so
+ /// events may happen (e.g. a line changing state faster than can
+ /// be picked up in userspace in real-time). These events will be
+ /// returned on the iterator in order with the event containing the
+ /// associated timestamp attached with high precision within the
+ /// kernel (from an ISR for most drivers).
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// # fn main() -> Result<(), gpio_cdev::Error> {
+ /// use gpio_cdev::{Chip, LineRequestFlags, EventRequestFlags};
+ /// use std::io;
+ ///
+ /// let mut chip = Chip::new("/dev/gpiochip0")?;
+ /// let input = chip.get_line(0)?;
+ ///
+ /// // Show all state changes for this line forever
+ /// for event in input.events(
+ /// LineRequestFlags::INPUT,
+ /// EventRequestFlags::BOTH_EDGES,
+ /// "rust-gpio"
+ /// )? {
+ /// println!("{:?}", event?);
+ /// }
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn events(
+ &self,
+ handle_flags: LineRequestFlags,
+ event_flags: EventRequestFlags,
+ consumer: &str,
+ ) -> Result<LineEventHandle> {
+ let mut request = ffi::gpioevent_request {
+ lineoffset: self.offset,
+ handleflags: handle_flags.bits(),
+ eventflags: event_flags.bits(),
+ consumer_label: unsafe { mem::zeroed() },
+ fd: 0,
+ };
+
+ unsafe {
+ rstr_lcpy(
+ request.consumer_label[..].as_mut_ptr(),
+ consumer,
+ request.consumer_label.len(),
+ );
+ }
+ ffi::gpio_get_lineevent_ioctl(self.chip.file.as_raw_fd(), &mut request)?;
+
+ Ok(LineEventHandle {
+ line: self.clone(),
+ file: unsafe { File::from_raw_fd(request.fd) },
+ })
+ }
+
+ #[cfg(feature = "async-tokio")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "async-tokio")))]
+ pub fn async_events(
+ &self,
+ handle_flags: LineRequestFlags,
+ event_flags: EventRequestFlags,
+ consumer: &str,
+ ) -> Result<AsyncLineEventHandle> {
+ let events = self.events(handle_flags, event_flags, consumer)?;
+ AsyncLineEventHandle::new(events)
+ }
+}
+
+impl LineInfo {
+ /// Get a handle to the line that this info represents
+ pub fn line(&self) -> &Line {
+ &self.line
+ }
+
+ /// Name assigned to this chip if assigned
+ pub fn name(&self) -> Option<&str> {
+ self.name.as_deref()
+ }
+
+ /// The name of this GPIO line, such as the output pin of the line on the
+ /// chip, a rail or a pin header name on a board, as specified by the gpio
+ /// chip.
+ pub fn consumer(&self) -> Option<&str> {
+ self.consumer.as_deref()
+ }
+
+ /// Get the direction of this GPIO if configured
+ ///
+ /// Lines are considered to be inputs if not explicitly
+ /// marked as outputs in the line info flags by the kernel.
+ pub fn direction(&self) -> LineDirection {
+ if self.flags.contains(LineFlags::IS_OUT) {
+ LineDirection::Out
+ } else {
+ LineDirection::In
+ }
+ }
+
+ /// True if the any flags for the device are set (input or output)
+ pub fn is_used(&self) -> bool {
+ !self.flags.is_empty()
+ }
+
+ /// True if this line is being used by something else in the kernel
+ ///
+ /// If another driver or subsystem in the kernel is using the line
+ /// then it cannot be used via the cdev interface. See [relevant kernel code].
+ ///
+ /// [relevant kernel code]: https://elixir.bootlin.com/linux/v4.9.127/source/drivers/gpio/gpiolib.c#L938
+ pub fn is_kernel(&self) -> bool {
+ self.flags.contains(LineFlags::KERNEL)
+ }
+
+ /// True if this line is marked as active low in the kernel
+ pub fn is_active_low(&self) -> bool {
+ self.flags.contains(LineFlags::ACTIVE_LOW)
+ }
+
+ /// True if this line is marked as open drain in the kernel
+ pub fn is_open_drain(&self) -> bool {
+ self.flags.contains(LineFlags::OPEN_DRAIN)
+ }
+
+ /// True if this line is marked as open source in the kernel
+ pub fn is_open_source(&self) -> bool {
+ self.flags.contains(LineFlags::OPEN_SOURCE)
+ }
+}
+
+/// Handle for interacting with a "requested" line
+///
+/// In order for userspace to read/write the value of a GPIO
+/// it must be requested from the chip using [`Line::request`].
+/// On success, the kernel creates an anonymous file descriptor
+/// for interacting with the requested line. This structure
+/// is the go-between for callers and that file descriptor.
+///
+/// [`Line::request`]: struct.Line.html#method.request
+#[derive(Debug)]
+pub struct LineHandle {
+ line: Line,
+ flags: LineRequestFlags,
+ file: File,
+}
+
+impl LineHandle {
+ /// Request the current state of this Line from the kernel
+ ///
+ /// This call is expected to succeed for both input and output
+ /// lines. It should be noted, however, that some drivers may
+ /// not be able to give any useful information when the value
+ /// is requested for an output line.
+ ///
+ /// This value should be 0 or 1 which a "1" representing that
+ /// the line is active. Usually this means that the line is
+ /// at logic-level high but it could mean the opposite if the
+ /// line has been marked as being `ACTIVE_LOW`.
+ pub fn get_value(&self) -> Result<u8> {
+ let mut data: ffi::gpiohandle_data = unsafe { mem::zeroed() };
+ ffi::gpiohandle_get_line_values_ioctl(self.file.as_raw_fd(), &mut data)?;
+ Ok(data.values[0])
+ }
+
+ /// Request that the line be driven to the specified value
+ ///
+ /// The value should be 0 or 1 with 1 representing a request
+ /// to make the line "active". Usually "active" means
+ /// logic level high unless the line has been marked as `ACTIVE_LOW`.
+ ///
+ /// Calling `set_value` on a line that is not an output will
+ /// likely result in an error (from the kernel).
+ pub fn set_value(&self, value: u8) -> Result<()> {
+ let mut data: ffi::gpiohandle_data = unsafe { mem::zeroed() };
+ data.values[0] = value;
+ ffi::gpiohandle_set_line_values_ioctl(self.file.as_raw_fd(), &mut data)?;
+ Ok(())
+ }
+
+ /// Get the Line information associated with this handle.
+ pub fn line(&self) -> &Line {
+ &self.line
+ }
+
+ /// Get the flags with which this handle was created
+ pub fn flags(&self) -> LineRequestFlags {
+ self.flags.clone()
+ }
+}
+
+impl AsRawFd for LineHandle {
+ /// Gets the raw file descriptor for the `LineHandle`.
+ fn as_raw_fd(&self) -> RawFd {
+ self.file.as_raw_fd()
+ }
+}
+
+/// A collection of lines that can be accesses simultaneously
+///
+/// This is a collection of lines, all from the same GPIO chip that can
+/// all be accessed simultaneously
+#[derive(Debug)]
+pub struct Lines {
+ lines: Vec<Line>,
+}
+
+impl Lines {
+ fn new(chip: Arc<InnerChip>, offsets: &[u32]) -> Result<Self> {
+ let res: Result<Vec<Line>> = offsets
+ .iter()
+ .map(|off| Line::new(chip.clone(), *off))
+ .collect();
+ let lines = res?;
+ Ok(Self { lines })
+ }
+
+ /// Get a handle to the parent chip for the lines
+ pub fn chip(&self) -> Chip {
+ self.lines[0].chip()
+ }
+
+ /// Get the number of lines in the collection
+ pub fn is_empty(&self) -> bool {
+ self.lines.is_empty()
+ }
+
+ /// Get the number of lines in the collection
+ pub fn len(&self) -> usize {
+ self.lines.len()
+ }
+
+ /// Request access to interact with these lines from the kernel
+ ///
+ /// This is similar to the "export" operation present in the sysfs
+ /// API with the key difference that we are also able to configure
+ /// the GPIO with `flags` to specify how the line will be used
+ /// at the time of request.
+ ///
+ /// For an output, the `default` parameter specifies the value
+ /// each line should have when it is configured as an output. The
+ /// `consumer` string should describe the process consuming the
+ /// line (this will be truncated to 31 characters if too long).
+ ///
+ /// # Errors
+ ///
+ /// The main source of errors here is if the kernel returns an
+ /// error to the ioctl performing the request here. This will
+ /// result in an [`Error`] being returned with [`ErrorKind::Ioctl`].
+ ///
+ /// One possible cause for an error here would be if the lines are
+ /// already in use. One can check for this prior to making the
+ /// request using [`is_kernel`].
+ ///
+ /// [`Error`]: errors/struct.Error.html
+ /// [`ErrorKind::Ioctl`]: errors/enum.ErrorKind.html#variant.Ioctl
+ /// [`is_kernel`]: struct.Line.html#method.is_kernel
+ pub fn request(
+ &self,
+ flags: LineRequestFlags,
+ default: &[u8],
+ consumer: &str,
+ ) -> Result<MultiLineHandle> {
+ let n = self.lines.len();
+ if default.len() != n {
+ return Err(invalid_err(n, default.len()));
+ }
+ // prepare the request; the kernel consumes some of these values and will
+ // set the fd for us.
+ let mut request = ffi::gpiohandle_request {
+ lineoffsets: unsafe { mem::zeroed() },
+ flags: flags.bits(),
+ default_values: unsafe { mem::zeroed() },
+ consumer_label: unsafe { mem::zeroed() },
+ lines: n as u32,
+ fd: 0,
+ };
+ #[allow(clippy::needless_range_loop)] // clippy does not understand this loop correctly
+ for i in 0..n {
+ request.lineoffsets[i] = self.lines[i].offset();
+ request.default_values[i] = default[i];
+ }
+ unsafe {
+ rstr_lcpy(
+ request.consumer_label[..].as_mut_ptr(),
+ consumer,
+ request.consumer_label.len(),
+ );
+ }
+ ffi::gpio_get_linehandle_ioctl(self.lines[0].chip().inner.file.as_raw_fd(), &mut request)?;
+ let lines = self.lines.clone();
+ Ok(MultiLineHandle {
+ lines: Self { lines },
+ file: unsafe { File::from_raw_fd(request.fd) },
+ })
+ }
+}
+
+impl Index<usize> for Lines {
+ type Output = Line;
+
+ fn index(&self, i: usize) -> &Line {
+ &self.lines[i]
+ }
+}
+
+/// Handle for interacting with a "requested" line
+///
+/// In order for userspace to read/write the value of a GPIO
+/// it must be requested from the chip using [`Line::request`].
+/// On success, the kernel creates an anonymous file descriptor
+/// for interacting with the requested line. This structure
+/// is the go-between for callers and that file descriptor.
+///
+/// [`Line::request`]: struct.Line.html#method.request
+#[derive(Debug)]
+pub struct MultiLineHandle {
+ lines: Lines,
+ file: File,
+}
+
+impl MultiLineHandle {
+ /// Request the current state of this Line from the kernel
+ ///
+ /// This call is expected to succeed for both input and output
+ /// lines. It should be noted, however, that some drivers may
+ /// not be able to give any useful information when the value
+ /// is requested for an output line.
+ ///
+ /// This value should be 0 or 1 which a "1" representing that
+ /// the line is active. Usually this means that the line is
+ /// at logic-level high but it could mean the opposite if the
+ /// line has been marked as being `ACTIVE_LOW`.
+ pub fn get_values(&self) -> Result<Vec<u8>> {
+ let mut data: ffi::gpiohandle_data = unsafe { mem::zeroed() };
+ ffi::gpiohandle_get_line_values_ioctl(self.file.as_raw_fd(), &mut data)?;
+ let n = self.num_lines();
+ let values: Vec<u8> = (0..n).map(|i| data.values[i]).collect();
+ Ok(values)
+ }
+
+ /// Request that the line be driven to the specified value
+ ///
+ /// The value should be 0 or 1 with 1 representing a request
+ /// to make the line "active". Usually "active" means
+ /// logic level high unless the line has been marked as `ACTIVE_LOW`.
+ ///
+ /// Calling `set_value` on a line that is not an output will
+ /// likely result in an error (from the kernel).
+ pub fn set_values(&self, values: &[u8]) -> Result<()> {
+ let n = self.num_lines();
+ if values.len() != n {
+ return Err(invalid_err(n, values.len()));
+ }
+ let mut data: ffi::gpiohandle_data = unsafe { mem::zeroed() };
+ data.values[..n].clone_from_slice(&values[..n]);
+ ffi::gpiohandle_set_line_values_ioctl(self.file.as_raw_fd(), &mut data)?;
+ Ok(())
+ }
+
+ /// Get the number of lines associated with this handle
+ pub fn num_lines(&self) -> usize {
+ self.lines.len()
+ }
+
+ /// Get the Line information associated with this handle.
+ pub fn lines(&self) -> &Lines {
+ &self.lines
+ }
+}
+
+impl AsRawFd for MultiLineHandle {
+ /// Gets the raw file descriptor for the `LineHandle`.
+ fn as_raw_fd(&self) -> RawFd {
+ self.file.as_raw_fd()
+ }
+}
+
+/// Did the Line rise (go active) or fall (go inactive)?
+///
+/// Maps to kernel [`GPIOEVENT_EVENT_*`] definitions.
+///
+/// [`GPIOEVENT_EVENT_*`]: https://elixir.bootlin.com/linux/v4.9.127/source/include/uapi/linux/gpio.h#L136
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum EventType {
+ RisingEdge,
+ FallingEdge,
+}
+
+/// Information about a change to the state of a Line
+///
+/// Wraps kernel [`struct gpioevent_data`].
+///
+/// [`struct gpioevent_data`]: https://elixir.bootlin.com/linux/v4.9.127/source/include/uapi/linux/gpio.h#L142
+pub struct LineEvent(ffi::gpioevent_data);
+
+impl std::fmt::Debug for LineEvent {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(
+ f,
+ "LineEvent {{ timestamp: {:?}, event_type: {:?} }}",
+ self.timestamp(),
+ self.event_type()
+ )
+ }
+}
+
+impl LineEvent {
+ /// Best estimate of event occurrence time, in nanoseconds
+ ///
+ /// In most cases, the timestamp for the event is captured
+ /// in an interrupt handler so it should be very accurate.
+ ///
+ /// The nanosecond timestamp value should are captured
+ /// using the `CLOCK_MONOTONIC` offsets in the kernel and
+ /// should be compared against `CLOCK_MONOTONIC` values.
+ /// Note that kernel versions prior to 5.7 used
+ /// `CLOCK_REALTIME` offsets instead.
+ pub fn timestamp(&self) -> u64 {
+ self.0.timestamp
+ }
+
+ /// Was this a rising or a falling edge?
+ pub fn event_type(&self) -> EventType {
+ if self.0.id == 0x01 {
+ EventType::RisingEdge
+ } else {
+ EventType::FallingEdge
+ }
+ }
+}
+
+/// Handle for retrieving events from the kernel for a line
+///
+/// In order for userspace to retrieve incoming events on a GPIO,
+/// an event handle must be requested from the chip using
+/// [`Line::events`].
+/// On success, the kernel creates an anonymous file descriptor
+/// for reading events. This structure is the go-between for callers
+/// and that file descriptor.
+///
+/// [`Line::events`]: struct.Line.html#method.events
+#[derive(Debug)]
+pub struct LineEventHandle {
+ line: Line,
+ file: File,
+}
+
+impl LineEventHandle {
+ /// Retrieve the next event from the kernel for this line
+ ///
+ /// This blocks while there is not another event available from the
+ /// kernel for the line which matches the subscription criteria
+ /// specified in the `event_flags` when the handle was created.
+ pub fn get_event(&mut self) -> Result<LineEvent> {
+ match self.read_event() {
+ Ok(Some(event)) => Ok(event),
+ Ok(None) => Err(event_err(nix::errno::Errno::EIO)),
+ Err(e) => Err(e.into()),
+ }
+ }
+
+ /// Request the current state of this Line from the kernel
+ ///
+ /// This value should be 0 or 1 which a "1" representing that
+ /// the line is active. Usually this means that the line is
+ /// at logic-level high but it could mean the opposite if the
+ /// line has been marked as being `ACTIVE_LOW`.
+ pub fn get_value(&self) -> Result<u8> {
+ let mut data: ffi::gpiohandle_data = unsafe { mem::zeroed() };
+ ffi::gpiohandle_get_line_values_ioctl(self.file.as_raw_fd(), &mut data)?;
+ Ok(data.values[0])
+ }
+
+ /// Get the Line information associated with this handle.
+ pub fn line(&self) -> &Line {
+ &self.line
+ }
+
+ pub fn file(&self) -> &File {
+ &self.file
+ }
+
+ pub fn file2(&mut self) -> &File {
+ &self.file
+ }
+
+ /// Helper function which returns the line event if a complete event was read, Ok(None) if not
+ /// enough data was read or the error returned by `read()`.
+ pub(crate) fn read_event(&mut self) -> std::io::Result<Option<LineEvent>> {
+ let mut data: ffi::gpioevent_data = unsafe { mem::zeroed() };
+ let data_as_buf = unsafe {
+ slice::from_raw_parts_mut(
+ (&mut data as *mut ffi::gpioevent_data).cast(),
+ mem::size_of::<ffi::gpioevent_data>(),
+ )
+ };
+ let bytes_read = self.file.read(data_as_buf)?;
+ if bytes_read == mem::size_of::<ffi::gpioevent_data>() {
+ Ok(Some(LineEvent(data)))
+ } else {
+ Ok(None)
+ }
+ }
+}
+
+impl AsRawFd for LineEventHandle {
+ /// Gets the raw file descriptor for the `LineEventHandle`.
+ fn as_raw_fd(&self) -> RawFd {
+ self.file.as_raw_fd()
+ }
+}
+
+impl AsFd for LineEventHandle {
+ /// Gets the raw file descriptor for the `LineEventHandle`.
+ fn as_fd(&self) -> BorrowedFd<'_> {
+ self.file.as_fd()
+ }
+}
+
+impl Iterator for LineEventHandle {
+ type Item = Result<LineEvent>;
+
+ fn next(&mut self) -> Option<Result<LineEvent>> {
+ match self.read_event() {
+ Ok(None) => None,
+ Ok(Some(event)) => Some(Ok(event)),
+ Err(e) => Some(Err(e.into())),
+ }
+ }
+}