// Copyright (c) 2018 The rust-gpio-cdev Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , 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, } /// Iterator over chips #[derive(Debug)] pub struct ChipIterator { readdir: ReadDir, } impl Iterator for ChipIterator { type Item = Result; fn next(&mut self) -> Option> { 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 { Ok(ChipIterator { readdir: read_dir("/dev")?, }) } impl Chip { /// Open the GPIO Chip at the provided path (e.g. `/dev/gpiochip`) pub fn new>(path: P) -> Result { 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::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::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 { let offsets: Vec = (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, idx: u32, } impl Iterator for LineIterator { type Item = Line; fn next(&mut self) -> Option { 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, 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, consumer: Option, } 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 { if buf[0] == 0 { None } else { Some(CStr::from_ptr(buf.as_ptr()).to_string_lossy().into_owned()) } } impl Line { fn new(chip: Arc, offset: u32) -> Result { 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 { 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 { // 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 { 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 { 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 { 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, } impl Lines { fn new(chip: Arc, offsets: &[u32]) -> Result { let res: Result> = 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 { 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 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> { 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 = (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 { 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 { 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> { 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::(), ) }; let bytes_read = self.file.read(data_as_buf)?; if bytes_read == mem::size_of::() { 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; fn next(&mut self) -> Option> { match self.read_event() { Ok(None) => None, Ok(Some(event)) => Some(Ok(event)), Err(e) => Some(Err(e.into())), } } }