#![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::{AsRawFd, 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; 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() as *const libc::c_char,
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,
}
#[derive(Debug)]
pub struct Chip {
inner: Arc<InnerChip>,
}
#[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
}
}
pub fn chips() -> Result<ChipIterator> {
Ok(ChipIterator {
readdir: read_dir("/dev")?,
})
}
impl Chip {
pub fn new<P: AsRef<Path>>(path: P) -> Result<Chip> {
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(Chip {
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,
}),
})
}
pub fn path(&self) -> &Path {
self.inner.path.as_path()
}
pub fn name(&self) -> &str {
self.inner.name.as_str()
}
pub fn label(&self) -> &str {
self.inner.label.as_str()
}
pub fn num_lines(&self) -> u32 {
self.inner.lines
}
pub fn get_line(&mut self, offset: u32) -> Result<Line> {
Line::new(self.inner.clone(), offset)
}
pub fn get_lines(&mut self, offsets: &[u32]) -> Result<Lines> {
Lines::new(self.inner.clone(), offsets)
}
pub fn get_all_lines(&mut self) -> Result<Lines> {
let offsets: Vec<u32> = (0..self.num_lines()).collect();
self.get_lines(&offsets)
}
pub fn lines(&self) -> LineIterator {
LineIterator {
chip: self.inner.clone(),
idx: 0,
}
}
}
#[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;
Some(Line::new(self.chip.clone(), idx).unwrap())
} else {
None
}
}
}
#[derive(Debug, Clone)]
pub struct Line {
chip: Arc<InnerChip>,
offset: u32,
}
#[derive(Debug, Clone)]
pub struct LineInfo {
line: Line,
flags: LineFlags,
name: Option<String>,
consumer: Option<String>,
}
bitflags! {
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! {
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! {
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);
}
}
#[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<Line> {
if offset >= chip.lines {
return Err(offset_err(offset));
}
Ok(Line { chip, offset })
}
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[..]) },
})
}
pub fn offset(&self) -> u32 {
self.offset
}
pub fn chip(&self) -> Chip {
Chip {
inner: self.chip.clone(),
}
}
pub fn request(
&self,
flags: LineRequestFlags,
default: u8,
consumer: &str,
) -> Result<LineHandle> {
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) },
})
}
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)?;
Ok(AsyncLineEventHandle::new(events)?)
}
}
impl LineInfo {
pub fn line(&self) -> &Line {
&self.line
}
pub fn name(&self) -> Option<&str> {
self.name.as_deref()
}
pub fn consumer(&self) -> Option<&str> {
self.consumer.as_deref()
}
pub fn direction(&self) -> LineDirection {
match self.flags.contains(LineFlags::IS_OUT) {
true => LineDirection::Out,
false => LineDirection::In,
}
}
pub fn is_used(&self) -> bool {
!self.flags.is_empty()
}
pub fn is_kernel(&self) -> bool {
self.flags.contains(LineFlags::KERNEL)
}
pub fn is_active_low(&self) -> bool {
self.flags.contains(LineFlags::ACTIVE_LOW)
}
pub fn is_open_drain(&self) -> bool {
self.flags.contains(LineFlags::OPEN_DRAIN)
}
pub fn is_open_source(&self) -> bool {
self.flags.contains(LineFlags::OPEN_SOURCE)
}
}
#[derive(Debug)]
pub struct LineHandle {
line: Line,
flags: LineRequestFlags,
file: File,
}
impl LineHandle {
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])
}
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(())
}
pub fn line(&self) -> &Line {
&self.line
}
pub fn flags(&self) -> LineRequestFlags {
self.flags
}
}
impl AsRawFd for LineHandle {
fn as_raw_fd(&self) -> RawFd {
self.file.as_raw_fd()
}
}
#[derive(Debug)]
pub struct Lines {
lines: Vec<Line>,
}
impl Lines {
fn new(chip: Arc<InnerChip>, offsets: &[u32]) -> Result<Lines> {
let res: Result<Vec<Line>> = offsets
.iter()
.map(|off| Line::new(chip.clone(), *off))
.collect();
let lines = res?;
Ok(Lines { lines })
}
pub fn chip(&self) -> Chip {
self.lines[0].chip()
}
pub fn is_empty(&self) -> bool {
self.lines.is_empty()
}
pub fn len(&self) -> usize {
self.lines.len()
}
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()));
}
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)] 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: Lines { 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]
}
}
#[derive(Debug)]
pub struct MultiLineHandle {
lines: Lines,
file: File,
}
impl MultiLineHandle {
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)
}
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(())
}
pub fn num_lines(&self) -> usize {
self.lines.len()
}
pub fn lines(&self) -> &Lines {
&self.lines
}
}
impl AsRawFd for MultiLineHandle {
fn as_raw_fd(&self) -> RawFd {
self.file.as_raw_fd()
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum EventType {
RisingEdge,
FallingEdge,
}
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 {
pub fn timestamp(&self) -> u64 {
self.0.timestamp
}
pub fn event_type(&self) -> EventType {
if self.0.id == 0x01 {
EventType::RisingEdge
} else {
EventType::FallingEdge
}
}
}
#[derive(Debug)]
pub struct LineEventHandle {
line: Line,
file: File,
}
impl LineEventHandle {
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()),
}
}
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])
}
pub fn line(&self) -> &Line {
&self.line
}
pub(crate) fn read_event(&mut self) -> std::io::Result<Option<LineEvent>> {
let mut data: ffi::gpioevent_data = unsafe { mem::zeroed() };
let mut data_as_buf = unsafe {
slice::from_raw_parts_mut(
&mut data as *mut ffi::gpioevent_data as *mut u8,
mem::size_of::<ffi::gpioevent_data>(),
)
};
let bytes_read = self.file.read(&mut data_as_buf)?;
if bytes_read != mem::size_of::<ffi::gpioevent_data>() {
Ok(None)
} else {
Ok(Some(LineEvent(data)))
}
}
}
impl AsRawFd for LineEventHandle {
fn as_raw_fd(&self) -> RawFd {
self.file.as_raw_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())),
}
}
}