From c98c15f6125d8d3a455257ab004210137f9eb573 Mon Sep 17 00:00:00 2001 From: Rene van der Meer <9930578+golemparts@users.noreply.github.com> Date: Thu, 8 Aug 2024 12:22:14 +0200 Subject: [PATCH] Replace GPIO v1 API with v2 --- src/gpio.rs | 20 +- src/gpio/interrupt.rs | 73 ++--- src/gpio/ioctl.rs | 702 ++++++++++++++++++++++++++++++++++++++++- src/gpio/ioctl/v1.rs | 455 --------------------------- src/gpio/ioctl/v2.rs | 706 ------------------------------------------ src/gpio/pin.rs | 20 +- 6 files changed, 767 insertions(+), 1209 deletions(-) delete mode 100644 src/gpio/ioctl/v1.rs delete mode 100644 src/gpio/ioctl/v2.rs diff --git a/src/gpio.rs b/src/gpio.rs index ade88e5d..4dfb2c40 100644 --- a/src/gpio.rs +++ b/src/gpio.rs @@ -122,7 +122,7 @@ use std::os::unix::io::AsRawFd; use std::result; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex, Once, Weak}; -use std::time::{Duration, SystemTime}; +use std::time::Duration; mod epoll; mod gpiomem; @@ -344,12 +344,22 @@ impl fmt::Display for Trigger { /// Interrupt trigger event. #[derive(Debug, Copy, Clone)] pub struct Event { - /// Best estimate of time of event occurrence. - pub timestamp: SystemTime, + /// Best estimate of time of event occurrence, measured in elapsed time since the system was booted. + pub timestamp: Duration, /// Sequence number for this event in the sequence of interrupt trigger events for this pin. pub seqno: u32, - /// Pin level that triggered the interrupt. - pub level: Level, + /// Interrupt trigger. + pub trigger: Trigger, +} + +impl Default for Event { + fn default() -> Self { + Self { + timestamp: Duration::default(), + seqno: 0, + trigger: Trigger::Both, + } + } } // Store Gpio's state separately, so we can conveniently share it through diff --git a/src/gpio/interrupt.rs b/src/gpio/interrupt.rs index 19b96394..06f9bc02 100644 --- a/src/gpio/interrupt.rs +++ b/src/gpio/interrupt.rs @@ -2,12 +2,12 @@ use std::fmt; use std::thread; -use std::time::{Duration, Instant, SystemTime}; +use std::time::{Duration, Instant}; use crate::gpio::epoll::{epoll_event, Epoll, EventFd, EPOLLERR, EPOLLET, EPOLLIN, EPOLLPRI}; use crate::gpio::ioctl; use crate::gpio::pin::InputPin; -use crate::gpio::{Error, Event, Level, Result, Trigger}; +use crate::gpio::{Error, Event, Result, Trigger}; pub(crate) struct EventLoop { poll: Epoll, @@ -20,17 +20,24 @@ pub(crate) struct EventLoop { struct Interrupt { pin: u8, trigger: Trigger, + debounce: Option, cdev_fd: i32, event_request: ioctl::EventRequest, } impl Interrupt { - fn new(cdev_fd: i32, pin: u8, trigger: Trigger) -> Result { + fn new( + cdev_fd: i32, + pin: u8, + trigger: Trigger, + debounce: Option, + ) -> Result { Ok(Interrupt { pin, trigger, + debounce, cdev_fd, - event_request: ioctl::EventRequest::new(cdev_fd, pin, trigger)?, + event_request: ioctl::EventRequest::new(cdev_fd, pin, trigger, debounce)?, }) } @@ -39,7 +46,7 @@ impl Interrupt { } fn fd(&self) -> i32 { - self.event_request.fd + self.event_request.fd() } fn pin(&self) -> u8 { @@ -52,15 +59,16 @@ impl Interrupt { self.reset() } - fn event(&mut self) -> Result { + fn event(&mut self) -> Result { // This might block if there are no events waiting - ioctl::get_event(self.event_request.fd) + Ok(ioctl::LineEvent::new(self.event_request.fd())?.into_event()) } fn reset(&mut self) -> Result<()> { // Close the old event fd before opening a new one self.event_request.close(); - self.event_request = ioctl::EventRequest::new(self.cdev_fd, self.pin, self.trigger)?; + self.event_request = + ioctl::EventRequest::new(self.cdev_fd, self.pin, self.trigger, self.debounce)?; Ok(()) } @@ -70,7 +78,7 @@ impl Interrupt { struct TriggerStatus { interrupt: Option, triggered: bool, - level: Level, + event: Event, } impl fmt::Debug for EventLoop { @@ -93,7 +101,7 @@ impl EventLoop { trigger_status.push(TriggerStatus { interrupt: None, triggered: false, - level: Level::Low, + event: Event::default(), }); } @@ -119,14 +127,7 @@ impl EventLoop { trigger_status.triggered = false; if !reset { - return Ok(Some(( - pin, - Event { - timestamp: SystemTime::now(), // TODO: Finish implementation - seqno: 0, - level: trigger_status.level, - }, - ))); + return Ok(Some((pin, trigger_status.event))); } } @@ -160,7 +161,7 @@ impl EventLoop { let trigger_status = &mut self.trigger_status[pin]; if let Some(ref mut interrupt) = trigger_status.interrupt { - trigger_status.level = interrupt.event()?.level(); + trigger_status.event = interrupt.event()?; trigger_status.triggered = true; }; } @@ -172,14 +173,7 @@ impl EventLoop { if trigger_status.triggered { trigger_status.triggered = false; - return Ok(Some(( - pin, - Event { - timestamp: SystemTime::now(), // TODO: Finish implementation - seqno: 0, - level: trigger_status.level, - }, - ))); + return Ok(Some((pin, trigger_status.event))); } } @@ -195,7 +189,12 @@ impl EventLoop { } } - pub fn set_interrupt(&mut self, pin: u8, trigger: Trigger) -> Result<()> { + pub fn set_interrupt( + &mut self, + pin: u8, + trigger: Trigger, + debounce: Option, + ) -> Result<()> { let trigger_status = &mut self.trigger_status[pin as usize]; trigger_status.triggered = false; @@ -214,7 +213,7 @@ impl EventLoop { } // Register a new interrupt - let interrupt = Interrupt::new(self.cdev_fd, pin, trigger)?; + let interrupt = Interrupt::new(self.cdev_fd, pin, trigger, debounce)?; self.poll .add(interrupt.fd(), u64::from(pin), EPOLLIN | EPOLLPRI)?; trigger_status.interrupt = Some(interrupt); @@ -242,7 +241,13 @@ pub struct AsyncInterrupt { } impl AsyncInterrupt { - pub fn new(fd: i32, pin: u8, trigger: Trigger, mut callback: C) -> Result + pub fn new( + fd: i32, + pin: u8, + trigger: Trigger, + debounce: Option, + mut callback: C, + ) -> Result where C: FnMut(Event) + Send + 'static, { @@ -255,7 +260,7 @@ impl AsyncInterrupt { // rx becomes readable when the main thread calls notify() poll.add(rx, rx as u64, EPOLLERR | EPOLLET | EPOLLIN)?; - let mut interrupt = Interrupt::new(fd, pin, trigger)?; + let mut interrupt = Interrupt::new(fd, pin, trigger, debounce)?; poll.add(interrupt.fd(), interrupt.fd() as u64, EPOLLIN | EPOLLPRI)?; let mut events = [epoll_event { events: 0, u64: 0 }; 2]; @@ -267,11 +272,7 @@ impl AsyncInterrupt { if fd == rx { return Ok(()); // The main thread asked us to stop } else if fd == interrupt.fd() { - callback(Event { - timestamp: SystemTime::now(), // TODO: Finish implementation - seqno: 0, - level: interrupt.event()?.level(), - }); + callback(interrupt.event()?); } } } diff --git a/src/gpio/ioctl.rs b/src/gpio/ioctl.rs index 4c0c2127..b7c28ba5 100644 --- a/src/gpio/ioctl.rs +++ b/src/gpio/ioctl.rs @@ -1,5 +1,699 @@ -mod v1; -mod v2; +#![allow(clippy::unnecessary_cast)] +#![allow(dead_code)] -pub use v1::*; -// pub use v2::*; +use crate::gpio::{Error, Event, Result, Trigger}; +use libc::{self, c_int, c_void, ENOENT}; +use std::ffi::CString; +use std::fmt; +use std::fs::{File, OpenOptions}; +use std::io; +use std::mem; +use std::os::unix::io::AsRawFd; +use std::time::Duration; + +#[cfg(target_env = "gnu")] +type IoctlLong = libc::c_ulong; +#[cfg(target_env = "musl")] +type IoctlLong = c_int; + +const PATH_GPIOCHIP: &str = "/dev/gpiochip"; +const CONSUMER_LABEL: &str = "RPPAL"; +const DRIVER_NAME: &[u8] = b"pinctrl-bcm2835\0"; +const DRIVER_NAME_BCM2711: &[u8] = b"pinctrl-bcm2711\0"; +const DRIVER_NAME_BCM2712: &[u8] = b"pinctrl-rp1\0"; + +// Minimum number of interrupt trigger events buffered by the kernel. +// This is only a suggested value. The kernel may alter it. +const EVENT_BUFFER_SIZE: u32 = 1024; + +// The first 27 offsets correspond to the 40-pin header +pub const MAX_OFFSET: u32 = 27; + +const BITS_NR: u8 = 8; +const BITS_TYPE: u8 = 8; +const BITS_SIZE: u8 = 14; +const BITS_DIR: u8 = 2; + +const SHIFT_NR: u8 = 0; +const SHIFT_TYPE: u8 = SHIFT_NR + BITS_NR; +const SHIFT_SIZE: u8 = SHIFT_TYPE + BITS_TYPE; +const SHIFT_DIR: u8 = SHIFT_SIZE + BITS_SIZE; + +const DIR_NONE: IoctlLong = 0; +const DIR_WRITE: IoctlLong = 1 << SHIFT_DIR; +const DIR_READ: IoctlLong = 2 << SHIFT_DIR; +const DIR_READ_WRITE: IoctlLong = DIR_READ | DIR_WRITE; + +const TYPE_GPIO: IoctlLong = (0xB4 as IoctlLong) << SHIFT_TYPE; + +const NR_GET_CHIP_INFO: IoctlLong = 0x01 << SHIFT_NR; +const NR_GET_LINE_INFO: IoctlLong = 0x05 << SHIFT_NR; +const NR_GET_LINE_INFO_WATCH: IoctlLong = 0x06 << SHIFT_NR; +const NR_GET_LINE_INFO_UNWATCH: IoctlLong = 0x0C << SHIFT_NR; +const NR_GET_LINE: IoctlLong = 0x07 << SHIFT_NR; +const NR_LINE_SET_CONFIG: IoctlLong = 0x0D << SHIFT_NR; +const NR_LINE_GET_VALUES: IoctlLong = 0x0E << SHIFT_NR; +const NR_LINE_SET_VALUES: IoctlLong = 0x0F << SHIFT_NR; + +const SIZE_CHIP_INFO: IoctlLong = (mem::size_of::() as IoctlLong) << SHIFT_SIZE; +const SIZE_LINE_INFO: IoctlLong = (mem::size_of::() as IoctlLong) << SHIFT_SIZE; +const SIZE_U32: IoctlLong = (mem::size_of::() as IoctlLong) << SHIFT_SIZE; +const SIZE_LINE_REQUEST: IoctlLong = (mem::size_of::() as IoctlLong) << SHIFT_SIZE; +const SIZE_LINE_CONFIG: IoctlLong = (mem::size_of::() as IoctlLong) << SHIFT_SIZE; +const SIZE_LINE_VALUES: IoctlLong = (mem::size_of::() as IoctlLong) << SHIFT_SIZE; + +const GPIO_GET_CHIPINFO_IOCTL: IoctlLong = DIR_READ | TYPE_GPIO | NR_GET_CHIP_INFO | SIZE_CHIP_INFO; +const GPIO_V2_GET_LINEINFO_IOCTL: IoctlLong = + DIR_READ_WRITE | TYPE_GPIO | NR_GET_LINE_INFO | SIZE_LINE_INFO; +const GPIO_V2_GET_LINEINFO_WATCH_IOCTL: IoctlLong = + DIR_READ_WRITE | TYPE_GPIO | NR_GET_LINE_INFO_WATCH | SIZE_LINE_INFO; +const GPIO_GET_LINEINFO_UNWATCH_IOCTL: IoctlLong = + DIR_READ_WRITE | TYPE_GPIO | NR_GET_LINE_INFO_UNWATCH | SIZE_U32; +const GPIO_V2_GET_LINE_IOCTL: IoctlLong = + DIR_READ_WRITE | TYPE_GPIO | NR_GET_LINE | SIZE_LINE_REQUEST; +const GPIO_V2_LINE_SET_CONFIG_IOCTL: IoctlLong = + DIR_READ_WRITE | TYPE_GPIO | NR_LINE_SET_CONFIG | SIZE_LINE_CONFIG; +const GPIO_V2_LINE_GET_VALUES_IOCTL: IoctlLong = + DIR_READ_WRITE | TYPE_GPIO | NR_LINE_GET_VALUES | SIZE_LINE_VALUES; +const GPIO_V2_LINE_SET_VALUES_IOCTL: IoctlLong = + DIR_READ_WRITE | TYPE_GPIO | NR_LINE_SET_VALUES | SIZE_LINE_VALUES; + +// Maximum name and label length. +const NAME_BUFSIZE: usize = 32; +const LABEL_BUFSIZE: usize = 32; + +// Maximum number of requested lines. +const LINES_MAX: usize = 64; +// Maximum number of configuration attributes. +const LINE_NUM_ATTRS_MAX: usize = 10; + +const LINE_FLAG_USED: u64 = 0x01; +const LINE_FLAG_ACTIVE_LOW: u64 = 0x02; +const LINE_FLAG_INPUT: u64 = 0x04; +const LINE_FLAG_OUTPUT: u64 = 0x08; +const LINE_FLAG_EDGE_RISING: u64 = 0x10; +const LINE_FLAG_EDGE_FALLING: u64 = 0x20; +const LINE_FLAG_OPEN_DRAIN: u64 = 0x40; +const LINE_FLAG_OPEN_SOURCE: u64 = 0x80; +const LINE_FLAG_BIAS_PULL_UP: u64 = 0x1000; +const LINE_FLAG_BIAS_PULL_DOWN: u64 = 0x2000; +const LINE_FLAG_BIAS_DISABLED: u64 = 0x4000; +const LINE_FLAG_EVENT_CLOCK_REALTIME: u64 = 0x8000; +const LINE_FLAG_EVENT_CLOCK_HTE: u64 = 0x100000; + +const LINE_ATTR_ID_FLAGS: u32 = 1; +const LINE_ATTR_ID_OUTPUT_VALUES: u32 = 2; +const LINE_ATTR_ID_DEBOUNCE: u32 = 3; + +const LINE_CHANGED_REQUESTED: u32 = 1; +const LINE_CHANGED_RELEASED: u32 = 2; +const LINE_CHANGED_CONFIG: u32 = 3; + +const LINE_EVENT_RISING_EDGE: u32 = 1; +const LINE_EVENT_FALLING_EDGE: u32 = 2; + +#[derive(Copy, Clone)] +#[repr(C)] +pub struct ChipInfo { + pub name: [u8; NAME_BUFSIZE], + pub label: [u8; LABEL_BUFSIZE], + pub lines: u32, +} + +impl ChipInfo { + pub fn new(cdev_fd: c_int) -> Result { + let mut chip_info = ChipInfo { + name: [0u8; NAME_BUFSIZE], + label: [0u8; LABEL_BUFSIZE], + lines: 0, + }; + + parse_retval!(unsafe { libc::ioctl(cdev_fd, GPIO_GET_CHIPINFO_IOCTL, &mut chip_info) })?; + + Ok(chip_info) + } +} + +impl fmt::Debug for ChipInfo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ChipInfo") + .field("name", &cbuf_to_cstring(&self.name)) + .field("label", &cbuf_to_cstring(&self.label)) + .field("lines", &self.lines) + .finish() + } +} + +#[derive(Copy, Clone, Default)] +#[repr(C)] +pub struct LineFlags { + flags: u64, +} + +impl fmt::Debug for LineFlags { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("LineFlags") + .field("flags", &self.flags) + .finish() + } +} + +impl fmt::Display for LineFlags { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut string_list = Vec::new(); + + if self.used() { + string_list.push("used"); + } + + if self.active_low() { + string_list.push("active_low"); + } + + if self.input() { + string_list.push("input"); + } + + if self.output() { + string_list.push("output"); + } + + if self.edge_rising() { + string_list.push("edge_rising"); + } + + if self.edge_falling() { + string_list.push("edge_falling"); + } + + if self.open_drain() { + string_list.push("open_drain"); + } + + if self.open_source() { + string_list.push("open_source"); + } + + if self.bias_pull_up() { + string_list.push("bias_pull_up"); + } + + if self.bias_pull_down() { + string_list.push("bias_pull_down"); + } + + if self.bias_disabled() { + string_list.push("bias_disabled"); + } + + if self.event_clock_realtime() { + string_list.push("event_clock_realtime"); + } + + if self.event_clock_hte() { + string_list.push("event_clock_hte"); + } + + write!(f, "{}", string_list.join(" ")) + } +} + +impl LineFlags { + pub fn new(flags: u64) -> LineFlags { + LineFlags { flags } + } + + pub fn used(&self) -> bool { + (self.flags & LINE_FLAG_USED) > 0 + } + + pub fn active_low(&self) -> bool { + (self.flags & LINE_FLAG_ACTIVE_LOW) > 0 + } + + pub fn input(&self) -> bool { + (self.flags & LINE_FLAG_INPUT) > 0 + } + + pub fn output(&self) -> bool { + (self.flags & LINE_FLAG_OUTPUT) > 0 + } + + pub fn edge_rising(&self) -> bool { + (self.flags & LINE_FLAG_EDGE_RISING) > 0 + } + + pub fn edge_falling(&self) -> bool { + (self.flags & LINE_FLAG_EDGE_FALLING) > 0 + } + + pub fn open_drain(&self) -> bool { + (self.flags & LINE_FLAG_OPEN_DRAIN) > 0 + } + + pub fn open_source(&self) -> bool { + (self.flags & LINE_FLAG_OPEN_SOURCE) > 0 + } + + pub fn bias_pull_up(&self) -> bool { + (self.flags & LINE_FLAG_BIAS_PULL_UP) > 0 + } + + pub fn bias_pull_down(&self) -> bool { + (self.flags & LINE_FLAG_BIAS_PULL_DOWN) > 0 + } + + pub fn bias_disabled(&self) -> bool { + (self.flags & LINE_FLAG_EDGE_FALLING) > 0 + } + + pub fn event_clock_realtime(&self) -> bool { + (self.flags & LINE_FLAG_EVENT_CLOCK_REALTIME) > 0 + } + + pub fn event_clock_hte(&self) -> bool { + (self.flags & LINE_FLAG_EVENT_CLOCK_HTE) > 0 + } +} + +#[derive(Copy, Clone)] +#[repr(C)] +pub union LineAttributeValue { + pub flags: u64, + pub values: u64, + pub debounce_period_us: u32, +} + +#[derive(Copy, Clone)] +#[repr(C)] +pub struct LineAttribute { + pub id: u32, + pub padding: u32, + pub value: LineAttributeValue, +} + +impl Default for LineAttribute { + fn default() -> Self { + Self { + id: Default::default(), + padding: Default::default(), + value: LineAttributeValue { flags: 0 }, + } + } +} + +impl fmt::Debug for LineAttribute { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("LineAttribute") + .field("id", &self.id) + .field("padding", &self.padding) + .field("value", unsafe { + match self.id { + LINE_ATTR_ID_FLAGS => &self.value.flags, + LINE_ATTR_ID_OUTPUT_VALUES => &self.value.values, + LINE_ATTR_ID_DEBOUNCE => &self.value.debounce_period_us, + _ => &"", + } + }) + .finish() + } +} + +#[derive(Copy, Clone)] +#[repr(C)] +pub struct LineInfo { + pub name: [u8; NAME_BUFSIZE], + pub consumer: [u8; LABEL_BUFSIZE], + pub offset: u32, + pub num_attrs: u32, + pub flags: LineFlags, + pub attrs: [LineAttribute; LINE_NUM_ATTRS_MAX], + pub padding: [u32; 4], +} + +impl LineInfo { + pub fn new(cdev_fd: c_int, offset: u32) -> Result { + let mut line_info = LineInfo { + name: [0u8; NAME_BUFSIZE], + consumer: [0u8; LABEL_BUFSIZE], + offset, + num_attrs: 0, + flags: LineFlags::default(), + attrs: [LineAttribute::default(); 10], + padding: [0u32; 4], + }; + + parse_retval!(unsafe { libc::ioctl(cdev_fd, GPIO_V2_GET_LINEINFO_IOCTL, &mut line_info) })?; + + Ok(line_info) + } + + pub fn flags(&self) -> LineFlags { + self.flags + } +} + +impl fmt::Debug for LineInfo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("LineInfo") + .field("name", &cbuf_to_cstring(&self.name)) + .field("consumer", &cbuf_to_cstring(&self.consumer)) + .field("offset", &self.offset) + .field("num_attrs", &self.num_attrs) + .field("flags", &self.flags) + .field("attrs", &self.attrs) + .field("padding", &self.padding) + .finish() + } +} + +#[derive(Copy, Clone)] +#[repr(C)] +pub struct LineInfoChanged { + pub info: LineInfo, + pub timestamp_ns: u64, + pub event_type: u32, + pub padding: [u32; 5], +} + +#[derive(Copy, Clone, Default)] +#[repr(C)] +pub struct LineConfigAttribute { + pub attr: LineAttribute, + pub mask: u64, +} + +impl fmt::Debug for LineConfigAttribute { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("LineConfigAttribute") + .field("attr", &self.attr) + .field("mask", &self.mask) + .finish() + } +} + +#[derive(Copy, Clone, Default)] +#[repr(C)] +pub struct LineConfig { + pub flags: u64, + pub num_attrs: u32, + pub padding: [u32; 5], + pub attrs: [LineConfigAttribute; LINE_NUM_ATTRS_MAX], +} + +impl fmt::Debug for LineConfig { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("LineConfig") + .field("flags", &self.flags) + .field("num_attrs", &self.num_attrs) + .field("padding", &self.padding) + .field("attrs", &self.attrs) + .finish() + } +} + +impl LineConfig { + fn set_debounce(&mut self, duration: u32) { + self.num_attrs = 1; + self.attrs[0] = LineConfigAttribute { + attr: LineAttribute { + id: LINE_ATTR_ID_DEBOUNCE, + padding: 0, + value: LineAttributeValue { + debounce_period_us: duration, + }, + }, + mask: 1, + }; + } +} + +#[derive(Clone)] +#[repr(C)] +pub struct LineRequest { + pub offsets: [u32; LINES_MAX], + pub consumer: [u8; LABEL_BUFSIZE], + pub config: LineConfig, + pub num_lines: u32, + pub event_buffer_size: u32, + pub padding: [u32; 5], + pub fd: c_int, +} + +impl Default for LineRequest { + fn default() -> Self { + Self { + offsets: [0u32; LINES_MAX], + consumer: [0u8; LABEL_BUFSIZE], + config: Default::default(), + num_lines: Default::default(), + event_buffer_size: 0, + padding: [0u32; 5], + fd: 0, + } + } +} + +impl fmt::Debug for LineRequest { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("LineRequest") + .field("offsets", &self.offsets) + .field("consumer", &cbuf_to_cstring(&self.consumer)) + .field("config", &self.config) + .field("num_lines", &self.num_lines) + .field("event_buffer_size", &self.event_buffer_size) + .field("padding", &self.padding) + .field("fd", &self.fd) + .finish() + } +} + +impl LineRequest { + pub fn new(cdev_fd: c_int, offset: u32) -> Result { + let mut line_request = LineRequest::default(); + line_request.offsets[0] = offset; + line_request.num_lines = 1; + + // Set consumer label, so other processes know we're monitoring this event + line_request.consumer[0..CONSUMER_LABEL.len()].copy_from_slice(CONSUMER_LABEL.as_bytes()); + + parse_retval!(unsafe { libc::ioctl(cdev_fd, GPIO_V2_GET_LINE_IOCTL, &mut line_request) })?; + + // If the fd is zero or negative, an error occurred + if line_request.fd <= 0 { + Err(Error::Io(std::io::Error::last_os_error())) + } else { + Ok(line_request) + } + } + + pub fn levels(&self) -> Result { + let mut line_values = LineValues::new(0, 0x01); + + parse_retval!(unsafe { + libc::ioctl(self.fd, GPIO_V2_LINE_GET_VALUES_IOCTL, &mut line_values) + })?; + + Ok(line_values) + } + + pub fn close(&mut self) { + if self.fd > 0 { + unsafe { + libc::close(self.fd); + } + + self.fd = 0; + } + } +} + +impl Drop for LineRequest { + fn drop(&mut self) { + self.close(); + } +} + +#[derive(Copy, Clone, Default)] +#[repr(C)] +pub struct LineValues { + pub bits: u64, + pub mask: u64, +} + +impl fmt::Debug for LineValues { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("LineValues") + .field("bits", &self.bits) + .field("mask", &self.mask) + .finish() + } +} + +impl LineValues { + pub fn new(bits: u64, mask: u64) -> LineValues { + LineValues { bits, mask } + } +} + +#[derive(Debug, Copy, Clone, Default)] +#[repr(C)] +pub(crate) struct LineEvent { + pub timestamp_ns: u64, + pub id: u32, + pub offset: u32, + pub seqno: u32, + pub line_seqno: u32, + pub padding: [u32; 6], +} + +impl LineEvent { + pub(crate) fn new(event_fd: c_int) -> Result { + let mut line_event = LineEvent::default(); + + let bytes_read = parse_retval!(unsafe { + libc::read( + event_fd, + &mut line_event as *mut LineEvent as *mut c_void, + mem::size_of::(), + ) + })?; + + if bytes_read < mem::size_of::() as isize { + Err(std::io::Error::new( + std::io::ErrorKind::UnexpectedEof, + "failed to fill whole buffer", + ) + .into()) + } else { + Ok(line_event) + } + } + + pub(crate) fn into_event(self) -> Event { + Event { + timestamp: Duration::from_nanos(self.timestamp_ns), + seqno: self.line_seqno, + trigger: match self.id { + LINE_EVENT_RISING_EDGE => Trigger::RisingEdge, + _ => Trigger::FallingEdge, + }, + } + } +} + +#[derive(Debug)] +#[repr(C)] +pub struct EventRequest { + pub line_request: LineRequest, +} + +impl EventRequest { + pub fn new( + cdev_fd: c_int, + pin: u8, + trigger: Trigger, + debounce: Option, + ) -> Result { + let mut line_request = LineRequest::default(); + line_request.offsets[0] = u32::from(pin); + line_request.num_lines = 1; + + // Suggest a minimum number of buffered events + line_request.event_buffer_size = EVENT_BUFFER_SIZE; + + // Set consumer label, so other processes know we're monitoring this event + line_request.consumer[0..CONSUMER_LABEL.len()].copy_from_slice(CONSUMER_LABEL.as_bytes()); + + // Configure triggers + line_request.config = LineConfig { + flags: LINE_FLAG_INPUT + + match trigger { + Trigger::Disabled => 0, + Trigger::RisingEdge => LINE_FLAG_EDGE_RISING, + Trigger::FallingEdge => LINE_FLAG_EDGE_FALLING, + Trigger::Both => LINE_FLAG_EDGE_RISING + LINE_FLAG_EDGE_FALLING, + }, + ..Default::default() + }; + + if let Some(debounce) = debounce { + line_request + .config + .set_debounce(debounce.as_micros() as u32); + } + + parse_retval!(unsafe { libc::ioctl(cdev_fd, GPIO_V2_GET_LINE_IOCTL, &mut line_request) })?; + + // If the fd is zero or negative, an error occurred + if line_request.fd <= 0 { + Err(Error::Io(std::io::Error::last_os_error())) + } else { + Ok(EventRequest { line_request }) + } + } + + pub fn fd(&self) -> c_int { + self.line_request.fd + } + + pub fn close(&mut self) { + if self.line_request.fd > 0 { + unsafe { + libc::close(self.line_request.fd); + } + + self.line_request.fd = 0; + } + } +} + +impl Drop for EventRequest { + fn drop(&mut self) { + self.close(); + } +} + +// Find the correct gpiochip device based on its label +pub fn find_gpiochip() -> Result { + for id in 0..=255 { + let gpiochip = match OpenOptions::new() + .read(true) + .write(true) + .open(format!("{}{}", PATH_GPIOCHIP, id)) + { + Ok(file) => file, + Err(ref e) if e.kind() == io::ErrorKind::PermissionDenied => { + return Err(Error::PermissionDenied(format!("{}{}", PATH_GPIOCHIP, id))); + } + Err(e) => return Err(Error::from(e)), + }; + + let chip_info = ChipInfo::new(gpiochip.as_raw_fd())?; + if chip_info.label[0..DRIVER_NAME.len()] == DRIVER_NAME[..] + || chip_info.label[0..DRIVER_NAME_BCM2711.len()] == DRIVER_NAME_BCM2711[..] + || chip_info.label[0..DRIVER_NAME_BCM2712.len()] == DRIVER_NAME_BCM2712[..] + { + return Ok(gpiochip); + } + } + + // File Not Found I/O error + Err(Error::Io(io::Error::from_raw_os_error(ENOENT))) +} + +// Create a CString from a C-style NUL-terminated char array. This workaround +// is needed for fixed-length buffers that fill the remaining bytes with NULs, +// because CString::new() interprets those as a NUL in the middle of the byte +// slice and returns a NulError. +pub fn cbuf_to_cstring(buf: &[u8]) -> CString { + CString::new({ + let pos = buf.iter().position(|&c| c == b'\0').unwrap_or(buf.len()); + &buf[..pos] + }) + .unwrap_or_default() +} + +pub fn cbuf_to_string(buf: &[u8]) -> String { + cbuf_to_cstring(buf).into_string().unwrap_or_default() +} diff --git a/src/gpio/ioctl/v1.rs b/src/gpio/ioctl/v1.rs deleted file mode 100644 index a86bf08a..00000000 --- a/src/gpio/ioctl/v1.rs +++ /dev/null @@ -1,455 +0,0 @@ -#![allow(clippy::unnecessary_cast)] -#![allow(dead_code)] - -use crate::gpio::{Error, Level, Result, Trigger}; -use libc::{self, c_int, c_ulong, c_void, ENOENT}; -use std::ffi::CString; -use std::fmt; -use std::fs::{File, OpenOptions}; -use std::io; -use std::mem; -use std::os::unix::io::AsRawFd; -use std::time::Duration; - -#[cfg(target_env = "gnu")] -type IoctlLong = libc::c_ulong; -#[cfg(target_env = "musl")] -type IoctlLong = c_int; - -const PATH_GPIOCHIP: &str = "/dev/gpiochip"; -const CONSUMER_LABEL: &str = "RPPAL"; -const DRIVER_NAME: &[u8] = b"pinctrl-bcm2835\0"; -const DRIVER_NAME_BCM2711: &[u8] = b"pinctrl-bcm2711\0"; -const DRIVER_NAME_BCM2712: &[u8] = b"pinctrl-rp1\0"; - -const NRBITS: u8 = 8; -const TYPEBITS: u8 = 8; -const SIZEBITS: u8 = 14; -const DIRBITS: u8 = 2; -const NRSHIFT: u8 = 0; -const TYPESHIFT: u8 = NRSHIFT + NRBITS; -const SIZESHIFT: u8 = TYPESHIFT + TYPEBITS; -const DIRSHIFT: u8 = SIZESHIFT + SIZEBITS; - -const NR_GET_CHIP_INFO: IoctlLong = 0x01 << NRSHIFT; -const NR_GET_LINE_INFO: IoctlLong = 0x02 << NRSHIFT; -const NR_GET_LINE_HANDLE: IoctlLong = 0x03 << NRSHIFT; -const NR_GET_LINE_EVENT: IoctlLong = 0x04 << NRSHIFT; -const NR_GET_LINE_VALUES: IoctlLong = 0x08 << NRSHIFT; -const NR_SET_LINE_VALUES: IoctlLong = 0x09 << NRSHIFT; - -const TYPE_GPIO: IoctlLong = (0xB4 as IoctlLong) << TYPESHIFT; - -const SIZE_CHIP_INFO: IoctlLong = (mem::size_of::() as IoctlLong) << SIZESHIFT; -const SIZE_LINE_INFO: IoctlLong = (mem::size_of::() as IoctlLong) << SIZESHIFT; -const SIZE_HANDLE_REQUEST: IoctlLong = (mem::size_of::() as IoctlLong) << SIZESHIFT; -const SIZE_EVENT_REQUEST: IoctlLong = (mem::size_of::() as IoctlLong) << SIZESHIFT; -const SIZE_HANDLE_DATA: IoctlLong = (mem::size_of::() as IoctlLong) << SIZESHIFT; - -const DIR_NONE: c_ulong = 0; -const DIR_WRITE: IoctlLong = 1 << DIRSHIFT; -const DIR_READ: IoctlLong = 2 << DIRSHIFT; -const DIR_READ_WRITE: IoctlLong = DIR_READ | DIR_WRITE; - -const REQ_GET_CHIP_INFO: IoctlLong = DIR_READ | TYPE_GPIO | NR_GET_CHIP_INFO | SIZE_CHIP_INFO; -const REQ_GET_LINE_INFO: IoctlLong = DIR_READ_WRITE | TYPE_GPIO | NR_GET_LINE_INFO | SIZE_LINE_INFO; -const REQ_GET_LINE_HANDLE: IoctlLong = - DIR_READ_WRITE | TYPE_GPIO | NR_GET_LINE_HANDLE | SIZE_HANDLE_REQUEST; -const REQ_GET_LINE_EVENT: IoctlLong = - DIR_READ_WRITE | TYPE_GPIO | NR_GET_LINE_EVENT | SIZE_EVENT_REQUEST; -const REQ_GET_LINE_VALUES: IoctlLong = - DIR_READ_WRITE | TYPE_GPIO | NR_GET_LINE_VALUES | SIZE_HANDLE_DATA; -const REQ_SET_LINE_VALUES: IoctlLong = - DIR_READ_WRITE | TYPE_GPIO | NR_SET_LINE_VALUES | SIZE_HANDLE_DATA; - -const NAME_BUFSIZE: usize = 32; -const LABEL_BUFSIZE: usize = 32; - -#[derive(Copy, Clone)] -#[repr(C)] -pub struct ChipInfo { - pub name: [u8; NAME_BUFSIZE], - pub label: [u8; LABEL_BUFSIZE], - pub lines: u32, -} - -impl ChipInfo { - pub fn new(cdev_fd: c_int) -> Result { - let mut chip_info = ChipInfo { - name: [0u8; NAME_BUFSIZE], - label: [0u8; LABEL_BUFSIZE], - lines: 0, - }; - - parse_retval!(unsafe { libc::ioctl(cdev_fd, REQ_GET_CHIP_INFO, &mut chip_info) })?; - - Ok(chip_info) - } -} - -impl fmt::Debug for ChipInfo { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("ChipInfo") - .field("name", &cbuf_to_cstring(&self.name)) - .field("label", &cbuf_to_cstring(&self.label)) - .field("lines", &self.lines) - .finish() - } -} - -const LINE_FLAG_KERNEL: u32 = 0x01; -const LINE_FLAG_IS_OUT: u32 = 0x02; -const LINE_FLAG_ACTIVE_LOW: u32 = 0x04; -const LINE_FLAG_OPEN_DRAIN: u32 = 0x08; -const LINE_FLAG_OPEN_SOURCE: u32 = 0x10; - -#[derive(Copy, Clone)] -#[repr(C)] -pub struct LineInfo { - pub line_offset: u32, - pub flags: u32, - pub name: [u8; NAME_BUFSIZE], - pub consumer: [u8; LABEL_BUFSIZE], -} - -impl LineInfo { - pub fn new() -> LineInfo { - LineInfo { - line_offset: 0, - flags: 0, - name: [0u8; NAME_BUFSIZE], - consumer: [0u8; LABEL_BUFSIZE], - } - } -} - -impl fmt::Debug for LineInfo { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("LineInfo") - .field("line_offset", &self.line_offset) - .field("flags", &self.flags) - .field("name", &cbuf_to_cstring(&self.name)) - .field("consumer", &cbuf_to_cstring(&self.consumer)) - .finish() - } -} - -const HANDLES_MAX: usize = 64; -const HANDLE_FLAG_INPUT: u32 = 0x01; -const HANDLE_FLAG_OUTPUT: u32 = 0x02; -const HANDLE_FLAG_ACTIVE_LOW: u32 = 0x04; -const HANDLE_FLAG_OPEN_DRAIN: u32 = 0x08; -const HANDLE_FLAG_OPEN_SOURCE: u32 = 0x10; - -#[repr(C)] -pub struct HandleRequest { - pub line_offsets: [u32; HANDLES_MAX], - pub flags: u32, - pub default_values: [u8; HANDLES_MAX], - pub consumer_label: [u8; LABEL_BUFSIZE], - pub lines: u32, - pub fd: c_int, -} - -impl HandleRequest { - pub fn new(cdev_fd: c_int, pins: &[u8]) -> Result { - let mut handle_request = HandleRequest { - line_offsets: [0u32; HANDLES_MAX], - flags: 0, - default_values: [0u8; HANDLES_MAX], - consumer_label: [0u8; LABEL_BUFSIZE], - lines: 0, - fd: 0, - }; - - let pins: &[u8] = if pins.len() > HANDLES_MAX { - handle_request.lines = HANDLES_MAX as u32; - &pins[0..HANDLES_MAX] - } else { - handle_request.lines = pins.len() as u32; - pins - }; - - for (idx, pin) in pins.iter().enumerate() { - handle_request.line_offsets[idx] = u32::from(*pin); - } - - // Set consumer label, so other processes know we're using these pins - handle_request.consumer_label[0..CONSUMER_LABEL.len()] - .copy_from_slice(CONSUMER_LABEL.as_bytes()); - - parse_retval!(unsafe { libc::ioctl(cdev_fd, REQ_GET_LINE_HANDLE, &mut handle_request) })?; - - // If the handle fd is zero or negative, an error occurred - if handle_request.fd <= 0 { - Err(Error::Io(std::io::Error::last_os_error())) - } else { - Ok(handle_request) - } - } - - pub fn levels(&self) -> Result { - let mut handle_data = HandleData::new(); - - parse_retval!(unsafe { libc::ioctl(self.fd, REQ_GET_LINE_VALUES, &mut handle_data) })?; - - Ok(handle_data) - } - - pub fn set_levels(&mut self, levels: &[Level]) -> Result<()> { - let mut handle_data = HandleData::new(); - let levels: &[Level] = if levels.len() > HANDLES_MAX { - &levels[0..HANDLES_MAX] - } else { - levels - }; - - for (idx, level) in levels.iter().enumerate() { - handle_data.values[idx] = *level as u8; - } - - parse_retval!(unsafe { libc::ioctl(self.fd, REQ_SET_LINE_VALUES, &mut handle_data) })?; - - Ok(()) - } - - pub fn close(&mut self) { - if self.fd > 0 { - unsafe { - libc::close(self.fd); - } - - self.fd = 0; - } - } -} - -impl Drop for HandleRequest { - fn drop(&mut self) { - self.close(); - } -} - -impl fmt::Debug for HandleRequest { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("HandleRequest") - .field( - "line_offsets", - &format_args!("{:?}", &self.line_offsets[..self.lines as usize]), - ) - .field("flags", &self.flags) - .field( - "default_values", - &format_args!("{:?}", &self.default_values[..self.lines as usize]), - ) - .field("consumer_label", &cbuf_to_cstring(&self.consumer_label)) - .field("lines", &self.lines) - .field("fd", &self.fd) - .finish() - } -} - -#[derive(Copy, Clone)] -#[repr(C)] -pub struct HandleData { - pub values: [u8; HANDLES_MAX], -} - -impl HandleData { - pub fn new() -> HandleData { - HandleData { - values: [0u8; HANDLES_MAX], - } - } -} - -impl fmt::Debug for HandleData { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("HandleRequest") - .field("values", &&self.values[..]) - .finish() - } -} - -const EVENT_FLAG_RISING_EDGE: u32 = 0x01; -const EVENT_FLAG_FALLING_EDGE: u32 = 0x02; -const EVENT_FLAG_BOTH_EDGES: u32 = EVENT_FLAG_RISING_EDGE | EVENT_FLAG_FALLING_EDGE; - -#[repr(C)] -pub struct EventRequest { - pub line_offset: u32, - pub handle_flags: u32, - pub event_flags: u32, - pub consumer_label: [u8; LABEL_BUFSIZE], - pub fd: c_int, -} - -impl EventRequest { - pub fn new(cdev_fd: c_int, pin: u8, trigger: Trigger) -> Result { - let mut event_request = EventRequest { - line_offset: u32::from(pin), - handle_flags: HANDLE_FLAG_INPUT, - event_flags: trigger as u32, - consumer_label: [0u8; LABEL_BUFSIZE], - fd: 0, - }; - - // Set consumer label, so other processes know we're monitoring this event - event_request.consumer_label[0..CONSUMER_LABEL.len()] - .copy_from_slice(CONSUMER_LABEL.as_bytes()); - - parse_retval!(unsafe { libc::ioctl(cdev_fd, REQ_GET_LINE_EVENT, &mut event_request) })?; - - // If the event fd is zero or negative, an error occurred - if event_request.fd <= 0 { - Err(Error::Io(std::io::Error::last_os_error())) - } else { - Ok(event_request) - } - } - - pub fn close(&mut self) { - if self.fd > 0 { - unsafe { - libc::close(self.fd); - } - - self.fd = 0; - } - } -} - -impl Drop for EventRequest { - fn drop(&mut self) { - self.close(); - } -} - -impl fmt::Debug for EventRequest { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("EventRequest") - .field("line_offset", &self.line_offset) - .field("handle_flags", &self.handle_flags) - .field("event_flags", &self.event_flags) - .field("consumer_label", &cbuf_to_cstring(&self.consumer_label)) - .field("fd", &self.fd) - .finish() - } -} - -const EVENT_TYPE_RISING_EDGE: u32 = 0x01; -const EVENT_TYPE_FALLING_EDGE: u32 = 0x02; - -#[derive(Debug, Copy, Clone, Default)] -#[repr(C)] -struct EventData { - timestamp: u64, - id: u32, -} - -impl EventData { - fn new(event_fd: c_int) -> Result { - let mut event_data = EventData { - timestamp: 0, - id: 0, - }; - - let bytes_read = parse_retval!(unsafe { - libc::read( - event_fd, - &mut event_data as *mut EventData as *mut c_void, - mem::size_of::(), - ) - })?; - - if bytes_read < mem::size_of::() as isize { - Err(std::io::Error::new( - std::io::ErrorKind::UnexpectedEof, - "failed to fill whole buffer", - ) - .into()) - } else { - Ok(event_data) - } - } -} - -#[derive(Debug, Copy, Clone)] -pub struct Event { - trigger: Trigger, - timestamp: Duration, -} - -impl Event { - fn from_event_data(event_data: EventData) -> Event { - Event { - trigger: match event_data.id { - EVENT_TYPE_RISING_EDGE => Trigger::RisingEdge, - EVENT_TYPE_FALLING_EDGE => Trigger::FallingEdge, - _ => unreachable!(), - }, - timestamp: Duration::from_nanos(event_data.timestamp), - } - } - - pub fn trigger(&self) -> Trigger { - self.trigger - } - - pub fn level(&self) -> Level { - match self.trigger { - Trigger::RisingEdge => Level::High, - Trigger::FallingEdge => Level::Low, - _ => { - // SAFETY: `Event` can only be constructed with either `RisingEdge` or `FallingEdge`. - unsafe { std::hint::unreachable_unchecked() } - } - } - } -} - -// Read interrupt event -pub fn get_event(event_fd: c_int) -> Result { - let event_data = EventData::new(event_fd)?; - Ok(Event::from_event_data(event_data)) -} - -// Find the correct gpiochip device based on its label -pub fn find_gpiochip() -> Result { - for id in 0..=255 { - let gpiochip = match OpenOptions::new() - .read(true) - .write(true) - .open(format!("{}{}", PATH_GPIOCHIP, id)) - { - Ok(file) => file, - Err(ref e) if e.kind() == io::ErrorKind::PermissionDenied => { - return Err(Error::PermissionDenied(format!("{}{}", PATH_GPIOCHIP, id))); - } - Err(e) => return Err(Error::from(e)), - }; - - let chip_info = ChipInfo::new(gpiochip.as_raw_fd())?; - if chip_info.label[0..DRIVER_NAME.len()] == DRIVER_NAME[..] - || chip_info.label[0..DRIVER_NAME_BCM2711.len()] == DRIVER_NAME_BCM2711[..] - || chip_info.label[0..DRIVER_NAME_BCM2712.len()] == DRIVER_NAME_BCM2712[..] - { - return Ok(gpiochip); - } - } - - // File Not Found I/O error - Err(Error::Io(io::Error::from_raw_os_error(ENOENT))) -} - -// Create a CString from a C-style NUL-terminated char array. This workaround -// is needed for fixed-length buffers that fill the remaining bytes with NULs, -// because CString::new() interprets those as a NUL in the middle of the byte -// slice and returns a NulError. -fn cbuf_to_cstring(buf: &[u8]) -> CString { - CString::new({ - let pos = buf.iter().position(|&c| c == b'\0').unwrap_or(buf.len()); - &buf[..pos] - }) - .unwrap_or_default() -} diff --git a/src/gpio/ioctl/v2.rs b/src/gpio/ioctl/v2.rs deleted file mode 100644 index cdad26cd..00000000 --- a/src/gpio/ioctl/v2.rs +++ /dev/null @@ -1,706 +0,0 @@ -#![allow(clippy::unnecessary_cast)] -#![allow(dead_code)] - -use crate::gpio::{Error, Level, Result, Trigger}; -use libc::{self, c_int, c_void, ENOENT}; -use std::ffi::CString; -use std::fmt; -use std::fs::{File, OpenOptions}; -use std::io; -use std::mem; -use std::os::unix::io::AsRawFd; -use std::time::Duration; - -#[cfg(target_env = "gnu")] -type IoctlLong = libc::c_ulong; -#[cfg(target_env = "musl")] -type IoctlLong = c_int; - -const PATH_GPIOCHIP: &str = "/dev/gpiochip"; -const CONSUMER_LABEL: &str = "RPPAL"; -const DRIVER_NAME: &[u8] = b"pinctrl-bcm2835\0"; -const DRIVER_NAME_BCM2711: &[u8] = b"pinctrl-bcm2711\0"; -const DRIVER_NAME_BCM2712: &[u8] = b"pinctrl-rp1\0"; - -// The first 27 offsets correspond to the 40-pin header -pub const MAX_OFFSET: u32 = 27; - -const BITS_NR: u8 = 8; -const BITS_TYPE: u8 = 8; -const BITS_SIZE: u8 = 14; -const BITS_DIR: u8 = 2; - -const SHIFT_NR: u8 = 0; -const SHIFT_TYPE: u8 = SHIFT_NR + BITS_NR; -const SHIFT_SIZE: u8 = SHIFT_TYPE + BITS_TYPE; -const SHIFT_DIR: u8 = SHIFT_SIZE + BITS_SIZE; - -const DIR_NONE: IoctlLong = 0; -const DIR_WRITE: IoctlLong = 1 << SHIFT_DIR; -const DIR_READ: IoctlLong = 2 << SHIFT_DIR; -const DIR_READ_WRITE: IoctlLong = DIR_READ | DIR_WRITE; - -const TYPE_GPIO: IoctlLong = (0xB4 as IoctlLong) << SHIFT_TYPE; - -const NR_GET_CHIP_INFO: IoctlLong = 0x01 << SHIFT_NR; -const NR_GET_LINE_INFO: IoctlLong = 0x05 << SHIFT_NR; -const NR_GET_LINE_INFO_WATCH: IoctlLong = 0x06 << SHIFT_NR; -const NR_GET_LINE_INFO_UNWATCH: IoctlLong = 0x0C << SHIFT_NR; -const NR_GET_LINE: IoctlLong = 0x07 << SHIFT_NR; -const NR_LINE_SET_CONFIG: IoctlLong = 0x0D << SHIFT_NR; -const NR_LINE_GET_VALUES: IoctlLong = 0x0E << SHIFT_NR; -const NR_LINE_SET_VALUES: IoctlLong = 0x0F << SHIFT_NR; - -const SIZE_CHIP_INFO: IoctlLong = (mem::size_of::() as IoctlLong) << SHIFT_SIZE; -const SIZE_LINE_INFO: IoctlLong = (mem::size_of::() as IoctlLong) << SHIFT_SIZE; -const SIZE_U32: IoctlLong = (mem::size_of::() as IoctlLong) << SHIFT_SIZE; -const SIZE_LINE_REQUEST: IoctlLong = (mem::size_of::() as IoctlLong) << SHIFT_SIZE; -const SIZE_LINE_CONFIG: IoctlLong = (mem::size_of::() as IoctlLong) << SHIFT_SIZE; -const SIZE_LINE_VALUES: IoctlLong = (mem::size_of::() as IoctlLong) << SHIFT_SIZE; - -const GPIO_GET_CHIPINFO_IOCTL: IoctlLong = DIR_READ | TYPE_GPIO | NR_GET_CHIP_INFO | SIZE_CHIP_INFO; -const GPIO_V2_GET_LINEINFO_IOCTL: IoctlLong = - DIR_READ_WRITE | TYPE_GPIO | NR_GET_LINE_INFO | SIZE_LINE_INFO; -const GPIO_V2_GET_LINEINFO_WATCH_IOCTL: IoctlLong = - DIR_READ_WRITE | TYPE_GPIO | NR_GET_LINE_INFO_WATCH | SIZE_LINE_INFO; -const GPIO_GET_LINEINFO_UNWATCH_IOCTL: IoctlLong = - DIR_READ_WRITE | TYPE_GPIO | NR_GET_LINE_INFO_UNWATCH | SIZE_U32; -const GPIO_V2_GET_LINE_IOCTL: IoctlLong = - DIR_READ_WRITE | TYPE_GPIO | NR_GET_LINE | SIZE_LINE_REQUEST; -const GPIO_V2_LINE_SET_CONFIG_IOCTL: IoctlLong = - DIR_READ_WRITE | TYPE_GPIO | NR_LINE_SET_CONFIG | SIZE_LINE_CONFIG; -const GPIO_V2_LINE_GET_VALUES_IOCTL: IoctlLong = - DIR_READ_WRITE | TYPE_GPIO | NR_LINE_GET_VALUES | SIZE_LINE_VALUES; -const GPIO_V2_LINE_SET_VALUES_IOCTL: IoctlLong = - DIR_READ_WRITE | TYPE_GPIO | NR_LINE_SET_VALUES | SIZE_LINE_VALUES; - -// Maximum name and label length. -const NAME_BUFSIZE: usize = 32; -const LABEL_BUFSIZE: usize = 32; - -// Maximum number of requested lines. -const LINES_MAX: usize = 64; -// Maximum number of configuration attributes. -const LINE_NUM_ATTRS_MAX: usize = 10; - -const LINE_FLAG_USED: u64 = 0x01; -const LINE_FLAG_ACTIVE_LOW: u64 = 0x02; -const LINE_FLAG_INPUT: u64 = 0x04; -const LINE_FLAG_OUTPUT: u64 = 0x08; -const LINE_FLAG_EDGE_RISING: u64 = 0x10; -const LINE_FLAG_EDGE_FALLING: u64 = 0x20; -const LINE_FLAG_OPEN_DRAIN: u64 = 0x40; -const LINE_FLAG_OPEN_SOURCE: u64 = 0x80; -const LINE_FLAG_BIAS_PULL_UP: u64 = 0x1000; -const LINE_FLAG_BIAS_PULL_DOWN: u64 = 0x2000; -const LINE_FLAG_BIAS_DISABLED: u64 = 0x4000; -const LINE_FLAG_EVENT_CLOCK_REALTIME: u64 = 0x8000; -const LINE_FLAG_EVENT_CLOCK_HTE: u64 = 0x100000; - -const LINE_ATTR_ID_FLAGS: u32 = 1; -const LINE_ATTR_ID_OUTPUT_VALUES: u32 = 2; -const LINE_ATTR_ID_DEBOUNCE: u32 = 3; - -const LINE_CHANGED_REQUESTED: u32 = 1; -const LINE_CHANGED_RELEASED: u32 = 2; -const LINE_CHANGED_CONFIG: u32 = 3; - -const LINE_EVENT_RISING_EDGE: u32 = 1; -const LINE_EVENT_FALLING_EDGE: u32 = 2; - -#[derive(Copy, Clone)] -#[repr(C)] -pub struct ChipInfo { - pub name: [u8; NAME_BUFSIZE], - pub label: [u8; LABEL_BUFSIZE], - pub lines: u32, -} - -impl ChipInfo { - pub fn new(cdev_fd: c_int) -> Result { - let mut chip_info = ChipInfo { - name: [0u8; NAME_BUFSIZE], - label: [0u8; LABEL_BUFSIZE], - lines: 0, - }; - - parse_retval!(unsafe { libc::ioctl(cdev_fd, GPIO_GET_CHIPINFO_IOCTL, &mut chip_info) })?; - - Ok(chip_info) - } -} - -impl fmt::Debug for ChipInfo { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("ChipInfo") - .field("name", &cbuf_to_cstring(&self.name)) - .field("label", &cbuf_to_cstring(&self.label)) - .field("lines", &self.lines) - .finish() - } -} - -pub struct LineFlags { - flags: u64, -} - -impl fmt::Debug for LineFlags { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("LineFlags") - .field("flags", &self.flags) - .finish() - } -} - -impl fmt::Display for LineFlags { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut string_list = Vec::new(); - - if self.used() { - string_list.push("used"); - } - - if self.active_low() { - string_list.push("active_low"); - } - - if self.input() { - string_list.push("input"); - } - - if self.output() { - string_list.push("output"); - } - - if self.edge_rising() { - string_list.push("edge_rising"); - } - - if self.edge_falling() { - string_list.push("edge_falling"); - } - - if self.open_drain() { - string_list.push("open_drain"); - } - - if self.open_source() { - string_list.push("open_source"); - } - - if self.bias_pull_up() { - string_list.push("bias_pull_up"); - } - - if self.bias_pull_down() { - string_list.push("bias_pull_down"); - } - - if self.bias_disabled() { - string_list.push("bias_disabled"); - } - - if self.event_clock_realtime() { - string_list.push("event_clock_realtime"); - } - - if self.event_clock_hte() { - string_list.push("event_clock_hte"); - } - - write!(f, "{}", string_list.join(" ")) - } -} - -impl LineFlags { - pub fn new(flags: u64) -> LineFlags { - LineFlags { flags } - } - - pub fn used(&self) -> bool { - (self.flags & LINE_FLAG_USED) > 0 - } - - pub fn active_low(&self) -> bool { - (self.flags & LINE_FLAG_ACTIVE_LOW) > 0 - } - - pub fn input(&self) -> bool { - (self.flags & LINE_FLAG_INPUT) > 0 - } - - pub fn output(&self) -> bool { - (self.flags & LINE_FLAG_OUTPUT) > 0 - } - - pub fn edge_rising(&self) -> bool { - (self.flags & LINE_FLAG_EDGE_RISING) > 0 - } - - pub fn edge_falling(&self) -> bool { - (self.flags & LINE_FLAG_EDGE_FALLING) > 0 - } - - pub fn open_drain(&self) -> bool { - (self.flags & LINE_FLAG_OPEN_DRAIN) > 0 - } - - pub fn open_source(&self) -> bool { - (self.flags & LINE_FLAG_OPEN_SOURCE) > 0 - } - - pub fn bias_pull_up(&self) -> bool { - (self.flags & LINE_FLAG_BIAS_PULL_UP) > 0 - } - - pub fn bias_pull_down(&self) -> bool { - (self.flags & LINE_FLAG_BIAS_PULL_DOWN) > 0 - } - - pub fn bias_disabled(&self) -> bool { - (self.flags & LINE_FLAG_EDGE_FALLING) > 0 - } - - pub fn event_clock_realtime(&self) -> bool { - (self.flags & LINE_FLAG_EVENT_CLOCK_REALTIME) > 0 - } - - pub fn event_clock_hte(&self) -> bool { - (self.flags & LINE_FLAG_EVENT_CLOCK_HTE) > 0 - } -} - -#[derive(Copy, Clone, Default)] -#[repr(C)] -pub struct LineAttribute { - pub id: u32, - pub padding: u32, - pub values: u64, -} - -impl LineAttribute { - pub fn new() -> LineAttribute { - LineAttribute { - id: 0, - padding: 0, - values: 0, - } - } -} - -impl fmt::Debug for LineAttribute { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("LineAttribute") - .field("id", &self.id) - .field("padding", &self.padding) - .field("values", &self.values) - .finish() - } -} - -#[derive(Copy, Clone)] -#[repr(C)] -pub struct LineInfo { - pub name: [u8; NAME_BUFSIZE], - pub consumer: [u8; LABEL_BUFSIZE], - pub offset: u32, - pub num_attrs: u32, - pub flags: u64, - pub attrs: [LineAttribute; LINE_NUM_ATTRS_MAX], - pub padding: [u32; 4], -} - -impl LineInfo { - pub fn new(cdev_fd: c_int, offset: u32) -> Result { - let mut line_info = LineInfo { - name: [0u8; NAME_BUFSIZE], - consumer: [0u8; LABEL_BUFSIZE], - offset, - num_attrs: 0, - flags: 0, - attrs: [LineAttribute::new(); 10], - padding: [0u32; 4], - }; - - parse_retval!(unsafe { libc::ioctl(cdev_fd, GPIO_V2_GET_LINEINFO_IOCTL, &mut line_info) })?; - - Ok(line_info) - } - - pub fn flags(&self) -> LineFlags { - LineFlags::new(self.flags) - } -} - -impl fmt::Debug for LineInfo { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("LineInfo") - .field("name", &cbuf_to_cstring(&self.name)) - .field("consumer", &cbuf_to_cstring(&self.consumer)) - .field("offset", &self.offset) - .field("num_attrs", &self.num_attrs) - .field("flags", &self.flags) - .field("attrs", &self.attrs) - .field("padding", &self.padding) - .finish() - } -} - -#[derive(Copy, Clone)] -#[repr(C)] -pub struct LineInfoChanged { - pub info: LineInfo, - pub timestamp_ns: u64, - pub event_type: u32, - pub padding: [u32; 5], -} - -#[derive(Copy, Clone, Default)] -#[repr(C)] -pub struct LineConfigAttribute { - pub attr: LineAttribute, - pub mask: u64, -} - -impl fmt::Debug for LineConfigAttribute { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("LineConfigAttribute") - .field("attr", &self.attr) - .field("mask", &self.mask) - .finish() - } -} - -#[derive(Copy, Clone, Default)] -#[repr(C)] -pub struct LineConfig { - pub flags: u64, - pub num_attrs: u32, - pub padding: [u32; 5], - pub attrs: [LineConfigAttribute; LINE_NUM_ATTRS_MAX], -} - -impl fmt::Debug for LineConfig { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("LineConfig") - .field("flags", &self.flags) - .field("num_attrs", &self.num_attrs) - .field("padding", &self.padding) - .field("attrs", &self.attrs) - .finish() - } -} - -#[derive(Clone)] -#[repr(C)] -pub struct LineRequest { - pub offsets: [u32; LINES_MAX], - pub consumer: [u8; LABEL_BUFSIZE], - pub config: LineConfig, - pub num_lines: u32, - pub event_buffer_size: u32, - pub padding: [u32; 5], - pub fd: c_int, -} - -impl Default for LineRequest { - fn default() -> Self { - Self { - offsets: [0u32; LINES_MAX], - consumer: [0u8; LABEL_BUFSIZE], - config: Default::default(), - num_lines: Default::default(), - event_buffer_size: 0, - padding: [0u32; 5], - fd: 0, - } - } -} - -impl fmt::Debug for LineRequest { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("LineRequest") - .field("offsets", &self.offsets) - .field("consumer", &cbuf_to_cstring(&self.consumer)) - .field("config", &self.config) - .field("num_lines", &self.num_lines) - .field("event_buffer_size", &self.event_buffer_size) - .field("padding", &self.padding) - .field("fd", &self.fd) - .finish() - } -} - -impl LineRequest { - pub fn new(cdev_fd: c_int, offset: u32) -> Result { - let mut line_request = LineRequest::default(); - line_request.offsets[0] = offset; - line_request.num_lines = 1; - - // Set consumer label, so other processes know we're monitoring this event - line_request.consumer[0..CONSUMER_LABEL.len()].copy_from_slice(CONSUMER_LABEL.as_bytes()); - - parse_retval!(unsafe { libc::ioctl(cdev_fd, GPIO_V2_GET_LINE_IOCTL, &mut line_request) })?; - - // If the fd is zero or negative, an error occurred - if line_request.fd <= 0 { - Err(Error::Io(std::io::Error::last_os_error())) - } else { - Ok(line_request) - } - } - - pub fn levels(&self) -> Result { - let mut line_values = LineValues::new(0, 0x01); - - parse_retval!(unsafe { - libc::ioctl(self.fd, GPIO_V2_LINE_GET_VALUES_IOCTL, &mut line_values) - })?; - - Ok(line_values) - } - - pub fn close(&mut self) { - if self.fd > 0 { - unsafe { - libc::close(self.fd); - } - - self.fd = 0; - } - } -} - -impl Drop for LineRequest { - fn drop(&mut self) { - self.close(); - } -} - -#[derive(Copy, Clone, Default)] -#[repr(C)] -pub struct LineValues { - pub bits: u64, - pub mask: u64, -} - -impl fmt::Debug for LineValues { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("LineValues") - .field("bits", &self.bits) - .field("mask", &self.mask) - .finish() - } -} - -impl LineValues { - pub fn new(bits: u64, mask: u64) -> LineValues { - LineValues { bits, mask } - } -} - -#[derive(Copy, Clone)] -#[repr(C)] -pub struct LineEvent { - pub timestamp_ns: u64, - pub id: u32, - pub offset: u32, - pub seqno: u32, - pub line_seqno: u32, - pub padding: [u32; 6], -} - -// Find the correct gpiochip device based on its label -pub fn find_gpiochip() -> Result { - for id in 0..=255 { - let gpiochip = match OpenOptions::new() - .read(true) - .write(true) - .open(format!("{}{}", PATH_GPIOCHIP, id)) - { - Ok(file) => file, - Err(ref e) if e.kind() == io::ErrorKind::PermissionDenied => { - return Err(Error::PermissionDenied(format!("{}{}", PATH_GPIOCHIP, id))); - } - Err(e) => return Err(Error::from(e)), - }; - - let chip_info = ChipInfo::new(gpiochip.as_raw_fd())?; - if chip_info.label[0..DRIVER_NAME.len()] == DRIVER_NAME[..] - || chip_info.label[0..DRIVER_NAME_BCM2711.len()] == DRIVER_NAME_BCM2711[..] - || chip_info.label[0..DRIVER_NAME_BCM2712.len()] == DRIVER_NAME_BCM2712[..] - { - return Ok(gpiochip); - } - } - - // File Not Found I/O error - Err(Error::Io(io::Error::from_raw_os_error(ENOENT))) -} - -// Create a CString from a C-style NUL-terminated char array. This workaround -// is needed for fixed-length buffers that fill the remaining bytes with NULs, -// because CString::new() interprets those as a NUL in the middle of the byte -// slice and returns a NulError. -pub fn cbuf_to_cstring(buf: &[u8]) -> CString { - CString::new({ - let pos = buf.iter().position(|&c| c == b'\0').unwrap_or(buf.len()); - &buf[..pos] - }) - .unwrap_or_default() -} - -pub fn cbuf_to_string(buf: &[u8]) -> String { - cbuf_to_cstring(buf).into_string().unwrap_or_default() -} - -// Deprecated v1 API requests -const NR_GET_LINE_EVENT: IoctlLong = 0x04 << SHIFT_NR; - -const SIZE_EVENT_REQUEST: IoctlLong = (mem::size_of::() as IoctlLong) << SHIFT_SIZE; - -const GPIO_GET_LINEEVENT_IOCTL: IoctlLong = - DIR_READ_WRITE | TYPE_GPIO | NR_GET_LINE_EVENT | SIZE_EVENT_REQUEST; - -const HANDLE_FLAG_INPUT: u32 = 0x01; - -#[repr(C)] -pub struct EventRequest { - pub line_offset: u32, - pub handle_flags: u32, - pub event_flags: u32, - pub consumer_label: [u8; LABEL_BUFSIZE], - pub fd: c_int, -} - -impl EventRequest { - pub fn new(cdev_fd: c_int, pin: u8, trigger: Trigger) -> Result { - let mut event_request = EventRequest { - line_offset: u32::from(pin), - handle_flags: HANDLE_FLAG_INPUT, - event_flags: trigger as u32, - consumer_label: [0u8; LABEL_BUFSIZE], - fd: 0, - }; - - // Set consumer label, so other processes know we're monitoring this event - event_request.consumer_label[0..CONSUMER_LABEL.len()] - .copy_from_slice(CONSUMER_LABEL.as_bytes()); - - parse_retval!(unsafe { - libc::ioctl(cdev_fd, GPIO_GET_LINEEVENT_IOCTL, &mut event_request) - })?; - - // If the event fd is zero or negative, an error occurred - if event_request.fd <= 0 { - Err(Error::Io(std::io::Error::last_os_error())) - } else { - Ok(event_request) - } - } - - pub fn close(&mut self) { - if self.fd > 0 { - unsafe { - libc::close(self.fd); - } - - self.fd = 0; - } - } -} - -impl Drop for EventRequest { - fn drop(&mut self) { - self.close(); - } -} - -impl fmt::Debug for EventRequest { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("EventRequest") - .field("line_offset", &self.line_offset) - .field("handle_flags", &self.handle_flags) - .field("event_flags", &self.event_flags) - .field("consumer_label", &cbuf_to_cstring(&self.consumer_label)) - .field("fd", &self.fd) - .finish() - } -} - -const EVENT_TYPE_RISING_EDGE: u32 = 0x01; -const EVENT_TYPE_FALLING_EDGE: u32 = 0x02; - -#[derive(Debug, Copy, Clone, Default)] -#[repr(C)] -struct EventData { - timestamp: u64, - id: u32, -} - -impl EventData { - fn new(event_fd: c_int) -> Result { - let mut event_data = EventData { - timestamp: 0, - id: 0, - }; - - let bytes_read = parse_retval!(unsafe { - libc::read( - event_fd, - &mut event_data as *mut EventData as *mut c_void, - mem::size_of::(), - ) - })?; - - if bytes_read < mem::size_of::() as isize { - Err(std::io::Error::new( - std::io::ErrorKind::UnexpectedEof, - "failed to fill whole buffer", - ) - .into()) - } else { - Ok(event_data) - } - } -} - -#[derive(Debug, Copy, Clone)] -pub struct Event { - trigger: Trigger, - timestamp: Duration, -} - -impl Event { - fn from_event_data(event_data: EventData) -> Event { - Event { - trigger: match event_data.id { - EVENT_TYPE_RISING_EDGE => Trigger::RisingEdge, - EVENT_TYPE_FALLING_EDGE => Trigger::FallingEdge, - _ => unreachable!(), - }, - timestamp: Duration::from_nanos(event_data.timestamp), - } - } - - pub fn trigger(&self) -> Trigger { - self.trigger - } - - pub fn level(&self) -> Level { - match self.trigger { - Trigger::RisingEdge => Level::High, - Trigger::FallingEdge => Level::Low, - _ => { - // SAFETY: `Event` can only be constructed with either `RisingEdge` or `FallingEdge`. - unsafe { std::hint::unreachable_unchecked() } - } - } - } -} - -// Read interrupt event -pub fn get_event(event_fd: c_int) -> Result { - let event_data = EventData::new(event_fd)?; - Ok(Event::from_event_data(event_data)) -} diff --git a/src/gpio/pin.rs b/src/gpio/pin.rs index 75318a00..0d2f424e 100644 --- a/src/gpio/pin.rs +++ b/src/gpio/pin.rs @@ -456,6 +456,8 @@ impl InputPin { /// Configures a synchronous interrupt trigger. /// + /// An optional debounce duration can be specified to filter unwanted input noise. + //// /// After configuring a synchronous interrupt trigger, call [`poll_interrupt`] or /// [`Gpio::poll_interrupts`] to block while waiting for a trigger event. /// @@ -463,11 +465,15 @@ impl InputPin { /// /// [`poll_interrupt`]: #method.poll_interrupt /// [`Gpio::poll_interrupts`]: struct.Gpio.html#method.poll_interrupts - pub fn set_interrupt(&mut self, trigger: Trigger) -> Result<()> { + pub fn set_interrupt(&mut self, trigger: Trigger, debounce: Option) -> Result<()> { self.clear_async_interrupt()?; // Each pin can only be configured for a single trigger type - (*self.pin.gpio_state.sync_interrupts.lock().unwrap()).set_interrupt(self.pin(), trigger) + (*self.pin.gpio_state.sync_interrupts.lock().unwrap()).set_interrupt( + self.pin(), + trigger, + debounce, + ) } /// Removes a previously configured synchronous interrupt trigger. @@ -513,6 +519,8 @@ impl InputPin { /// Configures an asynchronous interrupt trigger, which executes the callback on a /// separate thread when the interrupt is triggered. /// + /// An optional debounce duration can be specified to filter unwanted input noise. + /// /// The callback closure or function pointer is called with a single [`Event`] argument. /// /// Any previously configured (a)synchronous interrupt triggers for this pin are cleared @@ -520,7 +528,12 @@ impl InputPin { /// /// [`clear_async_interrupt`]: #method.clear_async_interrupt /// [`Event`]: enum.Event.html - pub fn set_async_interrupt(&mut self, trigger: Trigger, callback: C) -> Result<()> + pub fn set_async_interrupt( + &mut self, + trigger: Trigger, + debounce: Option, + callback: C, + ) -> Result<()> where C: FnMut(Event) + Send + 'static, { @@ -531,6 +544,7 @@ impl InputPin { self.pin.gpio_state.cdev.as_raw_fd(), self.pin(), trigger, + debounce, callback, )?);