1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
// SPDX-FileCopyrightText: 2021 Kent Gibson <warthog618@gmail.com>
//
// SPDX-License-Identifier: Apache-2.0 OR MIT

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;

/// Check if a path corresponds to a GPIO character device.
///
/// Returns the resolved path to the character device.
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))
}

/// Returns the paths of all the GPIO character devices on the system.
///
/// The returned paths are sorted in name order and are confirmed to be GPIO character devices,
/// so there is no need to check them with [`is_chip`].
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)
}

/// An iterator that returns the info for each line on the [`Chip`].
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,
        }
    }
}

/// A GPIO character device.
#[derive(Debug)]
pub struct Chip {
    /// The resolved path of the GPIO character device.
    path: PathBuf,
    _f: fs::File,
    /// Cached copy of _f.as_raw_fd() for syscalls, to avoid Arc<Mutex<>> overheads for ops.
    pub(crate) fd: RawFd,
    #[cfg(all(feature = "uapi_v1", feature = "uapi_v2"))]
    abiv: AbiVersion,
}

impl Chip {
    /// Constructs a Chip using the given path.
    ///
    /// The path must resolve to a valid GPIO character device.
    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,
        })
    }

    /// Constructs a Chip using the given name.
    ///
    /// The name must resolve to a valid GPIO character device.
    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,
        })
    }

    /// Get the information for the chip.
    pub fn info(&self) -> Result<Info> {
        Ok(Info::from(
            uapi::get_chip_info(self.fd).map_err(|e| Error::UapiError(UapiCall::GetChipInfo, e))?,
        ))
    }

    /// Return the name of the chip.
    ///
    /// This is based on the filename component of the resolved chip path, not the name
    /// from the [`Info`], so it does not involve any system calls.
    ///
    /// [`Info`]: Info
    pub fn name(&self) -> String {
        // The unwrap can only fail for directories, and the path is known to refer to a file.
        String::from(self.path.file_name().unwrap().to_string_lossy())
    }

    /// Return the path of the chip.
    pub fn path(&self) -> &Path {
        self.path.as_ref()
    }

    /// Find the info for the named line.
    ///
    /// Returns the first matching line.
    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
    }

    /// Get the information for a line on the chip.
    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))
    }

    /// An iterator that returns the info for each line on the chip.
    pub fn line_info_iter(&self) -> Result<LineInfoIterator> {
        let cinfo = self.info()?;
        Ok(LineInfoIterator {
            chip: self,
            offsets: Range {
                start: 0,
                end: cinfo.num_lines,
            },
        })
    }

    /// Add a watch for changes to the publicly available information on a line.
    ///
    /// This is a null operation if there is already a watch on the line.
    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))
    }

    /// Remove a watch for changes to the publicly available information on a line.
    ///
    /// This is a null operation if there is no existing watch on the line.
    pub fn unwatch_line_info(&self, offset: Offset) -> Result<()> {
        uapi::unwatch_line_info(self.fd, offset)
            .map_err(|e| Error::UapiError(UapiCall::UnwatchLineInfo, e))
    }

    /// Check if the request has at least one info change event available to read.
    pub fn has_line_info_change_event(&self) -> Result<bool> {
        gpiocdev_uapi::has_event(self.fd).map_err(|e| Error::UapiError(UapiCall::HasEvent, e))
    }

    /// Wait for an info change event to be available.
    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))
    }

    /// Read a single line info change event from the chip.
    ///
    /// Will block until an edge event is available.
    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> {
        // bbuf is statically sized to the greater of the v1/v2 size so it can be placed on the stack.
        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();
        // and dynamically sliced down to the required size, if necessary
        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])
    }

    /// An iterator for info change events from the chip.
    pub fn info_change_events(&self) -> InfoChangeIterator {
        InfoChangeIterator {
            chip: self,
            buf: vec![0; self.line_info_change_event_size()],
        }
    }

    /// Detect the most recent uAPI ABI supported by the library for the chip.
    pub fn detect_abi_version(&self) -> Result<AbiVersion> {
        // check in preferred order
        for abiv in [V2, V1] {
            if self.supports_abi_version(abiv).is_ok() {
                return Ok(abiv);
            }
        }
        Err(Error::UnsupportedAbi(
            AbiVersion::V2,
            AbiSupportKind::Platform,
        ))
    }

    /// Check if the platform and library support a specific ABI version.
    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,
            )),
        }
    }

    /// Set the ABI version to use for subsequent operations.
    #[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
    }
}

/// The publicly available information for a GPIO chip.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct Info {
    /// The system name for the chip, such as "*gpiochip0*".
    pub name: String,

    /// A functional name for the chip.
    ///
    /// This typically identifies the type of GPIO chip.
    pub label: String,

    /// The number of lines provided by the chip.
    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,
        }
    }
}

/// An iterator for reading info change events from a [`Chip`].
///
/// Blocks until events are available.
pub struct InfoChangeIterator<'a> {
    chip: &'a Chip,

    /// The buffer for uAPI edge events, sized by event size and capacity
    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())
    }
}

/// Reasons a file cannot be opened as a GPIO character device.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ErrorKind {
    /// File is not a character device.
    NotCharacterDevice,

    /// File is not a GPIO character device.
    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;

    // Chip, ChipIterator and InfoChangeIterator tests are all integration
    // tests as Chip construction requires GPIO chips.

    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");
        }
    }
}