use super::line::Offset;
use crate::{
line, line::InfoChangeEvent, AbiSupportKind, AbiVersion, AbiVersion::*, Error, Result, UapiCall,
};
#[cfg(all(feature = "uapi_v1", not(feature = "uapi_v2")))]
use gpiocdev_uapi::v1 as uapi;
#[cfg(any(feature = "uapi_v2", not(feature = "uapi_v1")))]
use gpiocdev_uapi::v2 as uapi;
#[cfg(all(feature = "uapi_v1", feature = "uapi_v2"))]
use gpiocdev_uapi::{v1, v2};
use std::collections::HashSet;
use std::fmt;
use std::fs;
use std::mem;
use std::ops::Range;
use std::os::linux::fs::MetadataExt;
use std::os::unix::prelude::{AsRawFd, RawFd};
use std::path::{Path, PathBuf};
use std::time::Duration;
const CHARDEV_MODE: u32 = 0x2000;
pub fn is_chip<P: AsRef<Path>>(path: P) -> Result<PathBuf> {
let pb = fs::canonicalize(&path).map_err(crate::errno_from_ioerr)?;
let m = fs::symlink_metadata(&pb).map_err(crate::errno_from_ioerr)?;
if m.st_mode() & CHARDEV_MODE == 0 {
return Err(Error::GpioChip(pb, ErrorKind::NotCharacterDevice));
}
let mut sysfs_dev = PathBuf::from("/sys/bus/gpio/devices");
sysfs_dev.push(pb.file_name().unwrap());
sysfs_dev.push("dev");
if let Ok(rdev) = fs::read_to_string(sysfs_dev) {
let st_rdev = m.st_rdev();
let dev_str = format!("{}:{}", (st_rdev as u16 >> 8) as u8, st_rdev as u8);
if rdev.trim_end() == dev_str {
return Ok(pb);
}
}
Err(Error::GpioChip(pb, ErrorKind::NotGpioDevice))
}
pub fn chips() -> Result<Vec<PathBuf>> {
let rd = std::fs::read_dir("/dev").map_err(crate::errno_from_ioerr)?;
let mut paths = HashSet::new();
rd.filter_map(|x| x.ok())
.map(|de| de.path())
.flat_map(is_chip)
.for_each(|p| {
paths.insert(p);
});
let mut chips = Vec::from_iter(paths);
chips.sort();
Ok(chips)
}
pub struct LineInfoIterator<'a> {
chip: &'a Chip,
offsets: Range<Offset>,
}
impl<'a> Iterator for LineInfoIterator<'a> {
type Item = Result<line::Info>;
fn next(&mut self) -> Option<Result<line::Info>> {
match self.offsets.next() {
Some(offset) => Some(self.chip.line_info(offset)),
None => None,
}
}
}
#[derive(Debug)]
pub struct Chip {
path: PathBuf,
_f: fs::File,
pub(crate) fd: RawFd,
#[cfg(all(feature = "uapi_v1", feature = "uapi_v2"))]
abiv: AbiVersion,
}
impl Chip {
pub fn from_path<P: AsRef<Path>>(p: P) -> Result<Chip> {
let path = is_chip(p.as_ref())?;
let f = fs::File::open(&path).map_err(crate::errno_from_ioerr)?;
let fd = f.as_raw_fd();
Ok(Chip {
path,
_f: f,
fd,
#[cfg(all(feature = "uapi_v1", feature = "uapi_v2"))]
abiv: V2,
})
}
pub fn from_name(n: &str) -> Result<Chip> {
let path = is_chip(format!("/dev/{}", n))?;
let f = fs::File::open(&path).map_err(crate::errno_from_ioerr)?;
let fd = f.as_raw_fd();
Ok(Chip {
path,
_f: f,
fd,
#[cfg(all(feature = "uapi_v1", feature = "uapi_v2"))]
abiv: V2,
})
}
pub fn info(&self) -> Result<Info> {
Ok(Info::from(
uapi::get_chip_info(self.fd).map_err(|e| Error::UapiError(UapiCall::GetChipInfo, e))?,
))
}
pub fn name(&self) -> String {
String::from(self.path.file_name().unwrap().to_string_lossy())
}
pub fn path(&self) -> &Path {
self.path.as_ref()
}
pub fn find_line_info(&self, name: &str) -> Option<line::Info> {
if let Ok(iter) = self.line_info_iter() {
return iter.filter_map(|x| x.ok()).find(|li| li.name == name);
}
None
}
pub fn line_info(&self, offset: Offset) -> Result<line::Info> {
self.do_line_info(offset)
}
#[cfg(all(feature = "uapi_v1", feature = "uapi_v2"))]
fn do_line_info(&self, offset: Offset) -> Result<line::Info> {
let res = match self.abiv {
V1 => v1::get_line_info(self.fd, offset).map(|li| line::Info::from(&li)),
V2 => v2::get_line_info(self.fd, offset).map(|li| line::Info::from(&li)),
};
res.map_err(|e| Error::UapiError(UapiCall::GetLineInfo, e))
}
#[cfg(not(all(feature = "uapi_v1", feature = "uapi_v2")))]
fn do_line_info(&self, offset: Offset) -> Result<line::Info> {
uapi::get_line_info(self.fd, offset)
.map(|li| line::Info::from(&li))
.map_err(|e| Error::UapiError(UapiCall::GetLineInfo, e))
}
pub fn line_info_iter(&self) -> Result<LineInfoIterator> {
let cinfo = self.info()?;
Ok(LineInfoIterator {
chip: self,
offsets: Range {
start: 0,
end: cinfo.num_lines,
},
})
}
pub fn watch_line_info(&self, offset: Offset) -> Result<line::Info> {
self.do_watch_line_info(offset)
}
#[cfg(all(feature = "uapi_v1", feature = "uapi_v2"))]
fn do_watch_line_info(&self, offset: Offset) -> Result<line::Info> {
let res = match self.abiv {
V1 => v1::watch_line_info(self.fd, offset).map(|li| line::Info::from(&li)),
V2 => v2::watch_line_info(self.fd, offset).map(|li| line::Info::from(&li)),
};
res.map_err(|e| Error::UapiError(UapiCall::WatchLineInfo, e))
}
#[cfg(not(all(feature = "uapi_v1", feature = "uapi_v2")))]
fn do_watch_line_info(&self, offset: Offset) -> Result<line::Info> {
uapi::watch_line_info(self.fd, offset)
.map(|li| line::Info::from(&li))
.map_err(|e| Error::UapiError(UapiCall::WatchLineInfo, e))
}
pub fn unwatch_line_info(&self, offset: Offset) -> Result<()> {
uapi::unwatch_line_info(self.fd, offset)
.map_err(|e| Error::UapiError(UapiCall::UnwatchLineInfo, e))
}
pub fn has_line_info_change_event(&self) -> Result<bool> {
gpiocdev_uapi::has_event(self.fd).map_err(|e| Error::UapiError(UapiCall::HasEvent, e))
}
pub fn wait_line_info_change_event(&self, timeout: Duration) -> Result<bool> {
gpiocdev_uapi::wait_event(self.fd, timeout)
.map_err(|e| Error::UapiError(UapiCall::WaitEvent, e))
}
pub fn read_line_info_change_event(&self) -> Result<InfoChangeEvent> {
self.do_read_line_info_change_event()
}
#[cfg(all(feature = "uapi_v1", feature = "uapi_v2"))]
fn do_read_line_info_change_event(&self) -> Result<InfoChangeEvent> {
debug_assert!(
mem::size_of::<v2::LineInfoChangeEvent>() >= mem::size_of::<v1::LineInfoChangeEvent>()
);
let mut bbuf = [0; mem::size_of::<v2::LineInfoChangeEvent>()];
let evsize = self.line_info_change_event_size();
let buf = &mut bbuf[0..evsize];
let n = gpiocdev_uapi::read_event(self.fd, buf)
.map_err(|e| Error::UapiError(UapiCall::ReadEvent, e))?;
self.line_info_change_event_from_slice(&buf[0..n])
}
#[cfg(not(all(feature = "uapi_v1", feature = "uapi_v2")))]
fn do_read_line_info_change_event(&self) -> Result<InfoChangeEvent> {
let mut buf = [0; mem::size_of::<uapi::LineInfoChangeEvent>()];
let n = gpiocdev_uapi::read_event(self.fd, &mut buf)
.map_err(|e| Error::UapiError(UapiCall::ReadEvent, e))?;
self.line_info_change_event_from_slice(&buf[0..n])
}
pub fn info_change_events(&self) -> InfoChangeIterator {
InfoChangeIterator {
chip: self,
buf: vec![0; self.line_info_change_event_size()],
}
}
pub fn detect_abi_version(&self) -> Result<AbiVersion> {
for abiv in [V2, V1] {
if self.supports_abi_version(abiv).is_ok() {
return Ok(abiv);
}
}
Err(Error::UnsupportedAbi(
AbiVersion::V2,
AbiSupportKind::Platform,
))
}
pub fn supports_abi_version(&self, abiv: AbiVersion) -> Result<()> {
self.do_supports_abi_version(abiv)
}
#[cfg(all(feature = "uapi_v1", feature = "uapi_v2"))]
fn do_supports_abi_version(&self, abiv: AbiVersion) -> Result<()> {
let res = match abiv {
V1 => v1::get_line_info(self.fd, 0).map(|_| ()),
V2 => v2::get_line_info(self.fd, 0).map(|_| ()),
};
res.map_err(|_| Error::UnsupportedAbi(abiv, AbiSupportKind::Platform))
}
#[cfg(all(feature = "uapi_v1", not(feature = "uapi_v2")))]
fn do_supports_abi_version(&self, abiv: AbiVersion) -> Result<()> {
match abiv {
V1 => uapi::get_line_info(self.fd, 0)
.map(|_| ())
.map_err(|_| Error::UnsupportedAbi(abiv, AbiSupportKind::Platform)),
V2 => Err(Error::UnsupportedAbi(
AbiVersion::V2,
AbiSupportKind::Library,
)),
}
}
#[cfg(not(feature = "uapi_v1"))]
fn do_supports_abi_version(&self, abiv: AbiVersion) -> Result<()> {
match abiv {
V2 => uapi::get_line_info(self.fd, 0)
.map(|_| ())
.map_err(|_| Error::UnsupportedAbi(abiv, AbiSupportKind::Platform)),
V1 => Err(Error::UnsupportedAbi(
AbiVersion::V1,
AbiSupportKind::Library,
)),
}
}
#[cfg(all(feature = "uapi_v1", feature = "uapi_v2"))]
pub fn using_abi_version(&mut self, abiv: AbiVersion) -> &mut Self {
self.abiv = abiv;
self
}
#[cfg(all(feature = "uapi_v1", feature = "uapi_v2"))]
fn line_info_change_event_from_slice(&self, d: &[u8]) -> Result<InfoChangeEvent> {
Ok(match self.abiv {
V1 => InfoChangeEvent::from(
v1::LineInfoChangeEvent::from_slice(d)
.map_err(|e| Error::UapiError(UapiCall::LICEFromBuf, e))?,
),
V2 => InfoChangeEvent::from(
v2::LineInfoChangeEvent::from_slice(d)
.map_err(|e| Error::UapiError(UapiCall::LICEFromBuf, e))?,
),
})
}
#[cfg(not(all(feature = "uapi_v1", feature = "uapi_v2")))]
fn line_info_change_event_from_slice(&self, d: &[u8]) -> Result<InfoChangeEvent> {
Ok(InfoChangeEvent::from(
uapi::LineInfoChangeEvent::from_slice(d)
.map_err(|e| Error::UapiError(UapiCall::LICEFromBuf, e))?,
))
}
#[cfg(all(feature = "uapi_v1", feature = "uapi_v2"))]
fn line_info_change_event_size(&self) -> usize {
match self.abiv {
V1 => mem::size_of::<v1::LineInfoChangeEvent>(),
V2 => mem::size_of::<v2::LineInfoChangeEvent>(),
}
}
#[cfg(not(all(feature = "uapi_v1", feature = "uapi_v2")))]
fn line_info_change_event_size(&self) -> usize {
mem::size_of::<uapi::LineInfoChangeEvent>()
}
}
impl AsRawFd for Chip {
fn as_raw_fd(&self) -> i32 {
self.fd
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct Info {
pub name: String,
pub label: String,
pub num_lines: u32,
}
impl From<uapi::ChipInfo> for Info {
fn from(ci: uapi::ChipInfo) -> Self {
Info {
name: String::from(&ci.name),
label: String::from(&ci.label),
num_lines: ci.num_lines,
}
}
}
pub struct InfoChangeIterator<'a> {
chip: &'a Chip,
buf: Vec<u8>,
}
impl<'a> InfoChangeIterator<'a> {
fn read_event(&mut self) -> Result<InfoChangeEvent> {
let n = gpiocdev_uapi::read_event(self.chip.fd, &mut self.buf)
.map_err(|e| Error::UapiError(UapiCall::ReadEvent, e))?;
self.chip.line_info_change_event_from_slice(&self.buf[0..n])
}
}
impl<'a> Iterator for InfoChangeIterator<'a> {
type Item = Result<InfoChangeEvent>;
fn next(&mut self) -> Option<Self::Item> {
Some(self.read_event())
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ErrorKind {
NotCharacterDevice,
NotGpioDevice,
}
impl fmt::Display for ErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let msg = match self {
ErrorKind::NotCharacterDevice => "is not a character device",
ErrorKind::NotGpioDevice => "is not a GPIO character device",
};
write!(f, "{}", msg)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(all(feature = "uapi_v1", not(feature = "uapi_v2")))]
use gpiocdev_uapi::v1 as uapi;
#[cfg(any(feature = "uapi_v2", not(feature = "uapi_v1")))]
use gpiocdev_uapi::v2 as uapi;
mod info {
use super::{uapi, Info};
#[test]
fn from_uapi() {
let ui = uapi::ChipInfo {
name: "banana".into(),
label: "peel".into(),
num_lines: 42,
};
let i = Info::from(ui);
assert_eq!(i.num_lines, 42);
assert_eq!(i.name.as_str(), "banana");
assert_eq!(i.label.as_str(), "peel");
}
}
}