gpiocdev/
chip.rs

1// SPDX-FileCopyrightText: 2021 Kent Gibson <warthog618@gmail.com>
2//
3// SPDX-License-Identifier: Apache-2.0 OR MIT
4
5use super::line::Offset;
6use crate::{
7    line, line::InfoChangeEvent, AbiSupportKind, AbiVersion, AbiVersion::*, Error, Result, UapiCall,
8};
9#[cfg(all(feature = "uapi_v1", not(feature = "uapi_v2")))]
10use gpiocdev_uapi::v1 as uapi;
11#[cfg(any(feature = "uapi_v2", not(feature = "uapi_v1")))]
12use gpiocdev_uapi::v2 as uapi;
13#[cfg(all(feature = "uapi_v1", feature = "uapi_v2"))]
14use gpiocdev_uapi::{v1, v2};
15#[cfg(feature = "serde")]
16use serde_derive::{Deserialize, Serialize};
17#[cfg(all(feature = "uapi_v1", feature = "uapi_v2"))]
18use std::cell::Cell;
19use std::fmt;
20use std::fs;
21use std::mem;
22use std::ops::Range;
23#[cfg(target_os = "android")]
24use std::os::android::fs::MetadataExt;
25#[cfg(target_os = "linux")]
26use std::os::linux::fs::MetadataExt;
27use std::os::unix::prelude::{AsFd, AsRawFd, BorrowedFd, OsStrExt};
28use std::path::{Path, PathBuf};
29use std::time::Duration;
30
31const CHARDEV_MODE: u32 = 0x2000;
32
33/// Check if a path corresponds to a GPIO character device.
34///
35/// Returns the resolved path to the character device.
36pub fn is_chip<P: AsRef<Path>>(path: P) -> Result<PathBuf> {
37    let pb = fs::canonicalize(&path)?;
38    // if canonical path is of form /dev/gpiochipXX assume we are good
39    if let Some(pbstr) = pb.to_str() {
40        if let Some(num) = pbstr.strip_prefix("/dev/gpiochip") {
41            if num.chars().all(|c| char::is_digit(c, 10)) {
42                return Ok(pb);
43            }
44        }
45    }
46
47    // else take a more detailed look...
48    let m = fs::metadata(&pb)?;
49    if m.st_mode() & CHARDEV_MODE == 0 {
50        return Err(Error::GpioChip(pb, ErrorKind::NotCharacterDevice));
51    }
52    let mut sysfs_dev = PathBuf::from("/sys/bus/gpio/devices");
53    sysfs_dev.push(pb.file_name().unwrap());
54    sysfs_dev.push("dev");
55    if let Ok(rdev) = fs::read_to_string(sysfs_dev) {
56        let st_rdev = m.st_rdev();
57        let dev_str = format!("{}:{}", (st_rdev as u16 >> 8) as u8, st_rdev as u8);
58        if rdev.trim_end() == dev_str {
59            return Ok(pb);
60        }
61    }
62    Err(Error::GpioChip(pb, ErrorKind::NotGpioDevice))
63}
64
65/// Compare two chip paths.
66///
67// Sorts paths naturally, assuming any chip numbering is at the end of the path - as it is for gpiochips.
68pub fn path_compare(a: &Path, b: &Path) -> std::cmp::Ordering {
69    let a = a.as_os_str().as_bytes();
70    let b = b.as_os_str().as_bytes();
71
72    if a.len() == b.len() {
73        // if equal length then just compare lexicographically
74        return a.cmp(b);
75    }
76    for it in a.iter().zip(b.iter()) {
77        let (ai, bi) = it;
78        if *ai != *bi {
79            if !ai.is_ascii_digit() || !bi.is_ascii_digit() {
80                // if either is not a digit then this character is definitive
81                return (*ai).cmp(bi);
82            }
83            // else drop thru to length comparison
84            break;
85        }
86    }
87    // equal up to to the length of the shortest - or to digits and shorter numbers are smaller
88    a.len().cmp(&b.len())
89}
90
91/// Returns the paths of all the GPIO character devices on the system.
92///
93/// The returned paths are sorted in name order and are confirmed to be GPIO character devices,
94/// so there is no need to check them with [`is_chip`].
95pub fn chips() -> Result<Vec<PathBuf>> {
96    let mut chips = std::fs::read_dir("/dev")?
97        .filter_map(|x| x.ok())
98        .flat_map(|de| is_chip(de.path()))
99        .collect::<Vec<PathBuf>>();
100    chips.sort_unstable_by(|a, b| path_compare(a, b));
101    chips.dedup();
102    Ok(chips)
103}
104
105/// An iterator that returns the info for each line on the [`Chip`].
106pub struct LineInfoIterator<'a> {
107    chip: &'a Chip,
108    offsets: Range<Offset>,
109}
110
111impl Iterator for LineInfoIterator<'_> {
112    type Item = Result<line::Info>;
113
114    fn next(&mut self) -> Option<Result<line::Info>> {
115        self.offsets
116            .next()
117            .map(|offset| self.chip.line_info(offset))
118    }
119}
120
121/// A GPIO character device.
122#[derive(Debug)]
123pub struct Chip {
124    /// The resolved path of the GPIO character device.
125    path: PathBuf,
126    /// The open GPIO character device file.
127    pub(crate) f: fs::File,
128    #[cfg(all(feature = "uapi_v1", feature = "uapi_v2"))]
129    abiv: Cell<Option<AbiVersion>>,
130}
131
132impl Chip {
133    /// Constructs a Chip using the given path.
134    ///
135    /// The path must resolve to a valid GPIO character device.
136    ///
137    /// # Examples
138    ///```no_run
139    /// # fn example() -> gpiocdev::Result<gpiocdev::Chip>{
140    /// let chip = gpiocdev::Chip::from_path("/dev/gpiochip0")?;
141    /// # Ok(chip)
142    /// # }
143    ///```
144    pub fn from_path<P: AsRef<Path>>(p: P) -> Result<Chip> {
145        let path = is_chip(p.as_ref())?;
146        let f = fs::File::open(&path)?;
147        Ok(Chip {
148            path,
149            f,
150            #[cfg(all(feature = "uapi_v1", feature = "uapi_v2"))]
151            abiv: Default::default(),
152        })
153    }
154
155    /// Constructs a Chip using the given name.
156    ///
157    /// The name must resolve to a valid GPIO character device.
158    ///
159    /// # Examples
160    ///```no_run
161    /// # fn example() -> gpiocdev::Result<gpiocdev::Chip>{
162    /// let chip = gpiocdev::Chip::from_name("gpiochip0")?;
163    /// # Ok(chip)
164    /// # }
165    ///```
166    pub fn from_name(n: &str) -> Result<Chip> {
167        let path = is_chip(format!("/dev/{n}"))?;
168        let f = fs::File::open(&path)?;
169        Ok(Chip {
170            path,
171            f,
172            #[cfg(all(feature = "uapi_v1", feature = "uapi_v2"))]
173            abiv: Default::default(),
174        })
175    }
176
177    /// Get the information for the chip.
178    pub fn info(&self) -> Result<Info> {
179        Ok(Info::from(
180            uapi::get_chip_info(&self.f).map_err(|e| Error::Uapi(UapiCall::GetChipInfo, e))?,
181        ))
182    }
183
184    /// Return the name of the chip.
185    ///
186    /// This is based on the filename component of the resolved chip path, not the name
187    /// from the [`Info`], so it does not involve any system calls.
188    ///
189    /// [`Info`]: Info
190    pub fn name(&self) -> String {
191        // The unwrap can only fail for directories, and the path is known to refer to a file.
192        String::from(self.path.file_name().unwrap().to_string_lossy())
193    }
194
195    /// Return the path of the chip.
196    pub fn path(&self) -> &Path {
197        self.path.as_ref()
198    }
199
200    // determine the actual abi version to use for subsequent uAPI operations.
201    #[cfg(all(feature = "uapi_v1", feature = "uapi_v2"))]
202    fn actual_abi_version(&self) -> Result<AbiVersion> {
203        Ok(match self.abiv.get() {
204            Some(abiv) => abiv,
205            None => {
206                let abiv = self.detect_abi_version()?;
207                self.abiv.set(Some(abiv));
208                abiv
209            }
210        })
211    }
212
213    /// Find the info for the named line.
214    ///
215    /// Returns the first matching line.
216    pub fn find_line_info(&self, name: &str) -> Option<line::Info> {
217        self.line_info_iter()
218            .ok()
219            .and_then(|iter| iter.filter_map(|x| x.ok()).find(|li| li.name == name))
220    }
221
222    /// Get the information for a line on the chip.
223    pub fn line_info(&self, offset: Offset) -> Result<line::Info> {
224        self.do_line_info(offset)
225    }
226    #[cfg(all(feature = "uapi_v1", feature = "uapi_v2"))]
227    fn do_line_info(&self, offset: Offset) -> Result<line::Info> {
228        match self.actual_abi_version()? {
229            V1 => v1::get_line_info(&self.f, offset)
230                .map(|li| line::Info::from(&li))
231                .map_err(|e| Error::Uapi(UapiCall::GetLineInfo, e)),
232            V2 => {
233                let uli = uapi::get_line_info(&self.f, offset)
234                    .map_err(|e| Error::Uapi(UapiCall::GetLineInfo, e))?;
235                line::Info::try_from(&uli)
236            }
237        }
238    }
239    #[cfg(all(feature = "uapi_v1", not(feature = "uapi_v2")))]
240    fn do_line_info(&self, offset: Offset) -> Result<line::Info> {
241        uapi::get_line_info(&self.f, offset)
242            .map(|li| line::Info::from(&li))
243            .map_err(|e| Error::Uapi(UapiCall::GetLineInfo, e))
244    }
245    #[cfg(all(not(feature = "uapi_v1"), feature = "uapi_v2"))]
246    fn do_line_info(&self, offset: Offset) -> Result<line::Info> {
247        let uli = uapi::get_line_info(&self.f, offset)
248            .map_err(|e| Error::Uapi(UapiCall::GetLineInfo, e))?;
249        line::Info::try_from(&uli)
250    }
251
252    /// An iterator that returns the info for each line on the chip.
253    pub fn line_info_iter(&self) -> Result<LineInfoIterator<'_>> {
254        let cinfo = self.info()?;
255        Ok(LineInfoIterator {
256            chip: self,
257            offsets: Range {
258                start: 0,
259                end: cinfo.num_lines,
260            },
261        })
262    }
263
264    /// Add a watch for changes to the publicly available information on a line.
265    ///
266    /// This is a null operation if there is already a watch on the line.
267    pub fn watch_line_info(&self, offset: Offset) -> Result<line::Info> {
268        self.do_watch_line_info(offset)
269    }
270    #[cfg(all(feature = "uapi_v1", feature = "uapi_v2"))]
271    fn do_watch_line_info(&self, offset: Offset) -> Result<line::Info> {
272        match self.actual_abi_version()? {
273            V1 => v1::watch_line_info(&self.f, offset)
274                .map(|li| line::Info::from(&li))
275                .map_err(|e| Error::Uapi(UapiCall::WatchLineInfo, e)),
276            V2 => {
277                let uli = uapi::watch_line_info(&self.f, offset)
278                    .map_err(|e| Error::Uapi(UapiCall::WatchLineInfo, e))?;
279                line::Info::try_from(&uli)
280            }
281        }
282    }
283    #[cfg(all(feature = "uapi_v1", not(feature = "uapi_v2")))]
284    fn do_watch_line_info(&self, offset: Offset) -> Result<line::Info> {
285        uapi::watch_line_info(&self.f, offset)
286            .map(|li| line::Info::from(&li))
287            .map_err(|e| Error::Uapi(UapiCall::WatchLineInfo, e))
288    }
289    #[cfg(all(not(feature = "uapi_v1"), feature = "uapi_v2"))]
290    fn do_watch_line_info(&self, offset: Offset) -> Result<line::Info> {
291        let uwli = uapi::watch_line_info(&self.f, offset)
292            .map_err(|e| Error::Uapi(UapiCall::WatchLineInfo, e))?;
293        line::Info::try_from(&uwli)
294    }
295
296    /// Remove a watch for changes to the publicly available information on a line.
297    ///
298    /// This is a null operation if there is no existing watch on the line.
299    pub fn unwatch_line_info(&self, offset: Offset) -> Result<()> {
300        uapi::unwatch_line_info(&self.f, offset)
301            .map_err(|e| Error::Uapi(UapiCall::UnwatchLineInfo, e))
302    }
303
304    /// Check if the request has at least one info change event available to read.
305    pub fn has_line_info_change_event(&self) -> Result<bool> {
306        gpiocdev_uapi::has_event(&self.f).map_err(|e| Error::Uapi(UapiCall::HasEvent, e))
307    }
308
309    /// Wait for an info change event to be available.
310    pub fn wait_line_info_change_event(&self, timeout: Duration) -> Result<bool> {
311        gpiocdev_uapi::wait_event(&self.f, timeout).map_err(|e| Error::Uapi(UapiCall::WaitEvent, e))
312    }
313
314    /// Read a single line info change event from the chip.
315    ///
316    /// Will block until an edge event is available.
317    pub fn read_line_info_change_event(&self) -> Result<InfoChangeEvent> {
318        self.do_read_line_info_change_event()
319    }
320    #[cfg(all(feature = "uapi_v1", feature = "uapi_v2"))]
321    fn do_read_line_info_change_event(&self) -> Result<InfoChangeEvent> {
322        self.actual_abi_version()?;
323        // bbuf is statically sized to the greater of the v1/v2 size so it can be placed on the stack.
324        debug_assert!(
325            mem::size_of::<v2::LineInfoChangeEvent>() >= mem::size_of::<v1::LineInfoChangeEvent>()
326        );
327        let mut bbuf = [0_u64; mem::size_of::<v2::LineInfoChangeEvent>() / 8];
328        let evt_u64_size = self.line_info_change_event_u64_size();
329        // and dynamically sliced down to the required size, if necessary
330        let buf = &mut bbuf[0..evt_u64_size];
331        let n = gpiocdev_uapi::read_event(&self.f, buf)
332            .map_err(|e| Error::Uapi(UapiCall::ReadEvent, e))?;
333        self.line_info_change_event_from_slice(&buf[0..n])
334    }
335    #[cfg(not(all(feature = "uapi_v1", feature = "uapi_v2")))]
336    fn do_read_line_info_change_event(&self) -> Result<InfoChangeEvent> {
337        let mut buf = [0_u64; mem::size_of::<uapi::LineInfoChangeEvent>() / 8];
338        let n = gpiocdev_uapi::read_event(&self.f, &mut buf)
339            .map_err(|e| Error::Uapi(UapiCall::ReadEvent, e))?;
340        self.line_info_change_event_from_slice(&buf[0..n])
341    }
342
343    /// An iterator for info change events from the chip.
344    pub fn info_change_events(&self) -> InfoChangeIterator<'_> {
345        InfoChangeIterator {
346            chip: self,
347            buf: vec![0_u64; self.line_info_change_event_u64_size()],
348        }
349    }
350
351    /// Detect the most recent uAPI ABI supported by the library for the chip.
352    pub fn detect_abi_version(&self) -> Result<AbiVersion> {
353        // check in preferred order
354        for abiv in [V2, V1] {
355            if self.supports_abi_version(abiv).is_ok() {
356                return Ok(abiv);
357            }
358        }
359        Err(Error::NoAbiSupport())
360    }
361
362    /// Check if the platform and library support a specific ABI version.
363    pub fn supports_abi_version(&self, abiv: AbiVersion) -> Result<()> {
364        self.do_supports_abi_version(abiv)
365    }
366    #[cfg(all(feature = "uapi_v1", feature = "uapi_v2"))]
367    fn do_supports_abi_version(&self, abiv: AbiVersion) -> Result<()> {
368        let res = match abiv {
369            V1 => v1::get_line_info(&self.f, 0).map(|_| ()),
370            V2 => v2::get_line_info(&self.f, 0).map(|_| ()),
371        };
372        res.map_err(|_| Error::UnsupportedAbi(abiv, AbiSupportKind::Kernel))
373    }
374    #[cfg(all(feature = "uapi_v1", not(feature = "uapi_v2")))]
375    fn do_supports_abi_version(&self, abiv: AbiVersion) -> Result<()> {
376        match abiv {
377            V1 => uapi::get_line_info(&self.f, 0)
378                .map(|_| ())
379                .map_err(|_| Error::UnsupportedAbi(V1, AbiSupportKind::Kernel)),
380            V2 => Err(Error::UnsupportedAbi(AbiVersion::V2, AbiSupportKind::Build)),
381        }
382    }
383    #[cfg(not(feature = "uapi_v1"))]
384    fn do_supports_abi_version(&self, abiv: AbiVersion) -> Result<()> {
385        match abiv {
386            V2 => uapi::get_line_info(&self.f, 0)
387                .map(|_| ())
388                .map_err(|_| Error::UnsupportedAbi(V2, AbiSupportKind::Kernel)),
389            V1 => Err(Error::UnsupportedAbi(AbiVersion::V1, AbiSupportKind::Build)),
390        }
391    }
392
393    /// Set the ABI version to use for subsequent operations.
394    #[cfg(all(feature = "uapi_v1", feature = "uapi_v2"))]
395    pub fn using_abi_version(&mut self, abiv: AbiVersion) -> &mut Self {
396        self.abiv.set(Some(abiv));
397        self
398    }
399
400    #[cfg(all(feature = "uapi_v1", feature = "uapi_v2"))]
401    fn line_info_change_event_from_slice(&self, d: &[u64]) -> Result<InfoChangeEvent> {
402        match self.actual_abi_version()? {
403            V1 => InfoChangeEvent::try_from(
404                v1::LineInfoChangeEvent::from_slice(d)
405                    .map_err(|e| Error::Uapi(UapiCall::LICEFromBuf, e))?,
406            ),
407            V2 => InfoChangeEvent::try_from(
408                v2::LineInfoChangeEvent::from_slice(d)
409                    .map_err(|e| Error::Uapi(UapiCall::LICEFromBuf, e))?,
410            ),
411        }
412    }
413    #[cfg(not(all(feature = "uapi_v1", feature = "uapi_v2")))]
414    fn line_info_change_event_from_slice(&self, d: &[u64]) -> Result<InfoChangeEvent> {
415        InfoChangeEvent::try_from(
416            uapi::LineInfoChangeEvent::from_slice(d)
417                .map_err(|e| Error::Uapi(UapiCall::LICEFromBuf, e))?,
418        )
419    }
420
421    fn line_info_change_event_u64_size(&self) -> usize {
422        self.line_info_change_event_size() / 8
423    }
424
425    #[cfg(all(feature = "uapi_v1", feature = "uapi_v2"))]
426    fn line_info_change_event_size(&self) -> usize {
427        // may not be initialised if called via info_change_events()
428        // in which case return the larger of the two
429        match self.abiv.get().unwrap_or(V2) {
430            V1 => mem::size_of::<v1::LineInfoChangeEvent>(),
431            V2 => mem::size_of::<v2::LineInfoChangeEvent>(),
432        }
433    }
434    #[cfg(not(all(feature = "uapi_v1", feature = "uapi_v2")))]
435    fn line_info_change_event_size(&self) -> usize {
436        mem::size_of::<uapi::LineInfoChangeEvent>()
437    }
438}
439
440impl AsFd for Chip {
441    #[inline]
442    fn as_fd(&self) -> BorrowedFd<'_> {
443        self.f.as_fd()
444    }
445}
446
447impl AsRawFd for Chip {
448    #[inline]
449    fn as_raw_fd(&self) -> i32 {
450        self.f.as_raw_fd()
451    }
452}
453
454impl AsRef<Chip> for Chip {
455    #[inline]
456    fn as_ref(&self) -> &Chip {
457        self
458    }
459}
460
461/// The publicly available information for a GPIO chip.
462#[derive(Clone, Debug, Default, Eq, PartialEq)]
463#[cfg_attr(
464    feature = "serde",
465    derive(Serialize, Deserialize),
466    serde(rename_all = "camelCase")
467)]
468pub struct Info {
469    /// The system name for the chip, such as "*gpiochip0*".
470    pub name: String,
471
472    /// A functional name for the chip.
473    ///
474    /// This typically identifies the type of GPIO chip.
475    pub label: String,
476
477    /// The number of lines provided by the chip.
478    pub num_lines: u32,
479}
480
481impl From<uapi::ChipInfo> for Info {
482    fn from(ci: uapi::ChipInfo) -> Self {
483        Info {
484            name: String::from(&ci.name),
485            label: String::from(&ci.label),
486            num_lines: ci.num_lines,
487        }
488    }
489}
490
491/// An iterator for reading info change events from a [`Chip`].
492///
493/// Blocks until events are available.
494pub struct InfoChangeIterator<'a> {
495    chip: &'a Chip,
496
497    /// The buffer for uAPI edge events, sized by event size and capacity
498    buf: Vec<u64>,
499}
500
501impl InfoChangeIterator<'_> {
502    fn read_event(&mut self) -> Result<InfoChangeEvent> {
503        let n = gpiocdev_uapi::read_event(&self.chip.f, &mut self.buf)
504            .map_err(|e| Error::Uapi(UapiCall::ReadEvent, e))?;
505        self.chip.line_info_change_event_from_slice(&self.buf[0..n])
506    }
507}
508
509impl Iterator for InfoChangeIterator<'_> {
510    type Item = Result<InfoChangeEvent>;
511
512    fn next(&mut self) -> Option<Self::Item> {
513        Some(self.read_event())
514    }
515}
516
517/// Reasons a file cannot be opened as a GPIO character device.
518#[derive(Clone, Copy, Debug, Eq, PartialEq)]
519pub enum ErrorKind {
520    /// File is not a character device.
521    NotCharacterDevice,
522
523    /// File is not a GPIO character device.
524    NotGpioDevice,
525}
526
527impl fmt::Display for ErrorKind {
528    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
529        let msg = match self {
530            ErrorKind::NotCharacterDevice => "is not a character device",
531            ErrorKind::NotGpioDevice => "is not a GPIO character device",
532        };
533        write!(f, "{msg}")
534    }
535}
536
537#[cfg(test)]
538mod tests {
539    use super::*;
540    #[cfg(all(feature = "uapi_v1", not(feature = "uapi_v2")))]
541    use gpiocdev_uapi::v1 as uapi;
542    #[cfg(any(feature = "uapi_v2", not(feature = "uapi_v1")))]
543    use gpiocdev_uapi::v2 as uapi;
544
545    // Chip, ChipIterator and InfoChangeIterator tests are all integration
546    // tests as Chip construction requires GPIO chips.
547
548    mod info {
549        use super::{uapi, Info};
550
551        #[test]
552        fn from_uapi() {
553            let ui = uapi::ChipInfo {
554                name: "banana".into(),
555                label: "peel".into(),
556                num_lines: 42,
557            };
558            let i = Info::from(ui);
559            assert_eq!(i.num_lines, 42);
560            assert_eq!(i.name.as_str(), "banana");
561            assert_eq!(i.label.as_str(), "peel");
562        }
563    }
564
565    #[test]
566    fn path_compare() {
567        use super::path_compare;
568        use std::cmp::Ordering;
569
570        assert_eq!(
571            path_compare(Path::new("/dev/gpiochip0"), Path::new("/dev/gpiochip0")),
572            Ordering::Equal
573        );
574        assert_eq!(
575            path_compare(Path::new("/dev/gpiochip0"), Path::new("/dev/gpiochip1")),
576            Ordering::Less
577        );
578        assert_eq!(
579            path_compare(Path::new("/dev/gpiochip3"), Path::new("/dev/gpiochip10")),
580            Ordering::Less
581        );
582        assert_eq!(
583            path_compare(Path::new("/dev/gpiochip3"), Path::new("/dev/gpiochip30")),
584            Ordering::Less
585        );
586        assert_eq!(
587            path_compare(Path::new("/dev/gpiochip10"), Path::new("/dev/gpiochip3")),
588            Ordering::Greater
589        );
590        assert_eq!(
591            path_compare(Path::new("/dev/gpiochip"), Path::new("/dev/gpiochip1")),
592            Ordering::Less
593        );
594        assert_eq!(
595            path_compare(Path::new("/dev/gpiochip0"), Path::new("/dev/gpiochip1")),
596            Ordering::Less
597        );
598    }
599}