diff options
Diffstat (limited to 'src/lib.rs')
-rw-r--r-- | src/lib.rs | 1041 |
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())), + } + } +} |