| // 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())), |
| } |
| } |
| } |