gpiocdev/
line.rs

1// SPDX-FileCopyrightText: 2021 Kent Gibson <warthog618@gmail.com>
2//
3// SPDX-License-Identifier: Apache-2.0 OR MIT
4
5mod config;
6pub use self::config::Config;
7
8mod event;
9pub use self::event::{EdgeEvent, EdgeKind, InfoChangeEvent, InfoChangeKind};
10
11mod info;
12pub use self::info::Info;
13
14mod value;
15pub use self::value::{Value, Values};
16
17#[cfg(feature = "uapi_v1")]
18use gpiocdev_uapi::v1;
19#[cfg(feature = "uapi_v2")]
20use gpiocdev_uapi::v2;
21#[cfg(feature = "serde")]
22use serde_derive::{Deserialize, Serialize};
23use std::collections::HashMap;
24use std::hash::{BuildHasherDefault, Hasher};
25
26/// An identifier for a line on a particular chip.
27///
28/// Valid offsets are in the range 0..`num_lines` as reported in the chip [`Info`](super::chip::Info).
29pub type Offset = u32;
30
31/// A map from offset to T.
32pub type OffsetMap<T> = HashMap<Offset, T, BuildHasherDefault<OffsetHasher>>;
33
34/// A simple identity hasher for maps using Offsets as keys.
35#[derive(Default)]
36pub struct OffsetHasher(u64);
37
38impl Hasher for OffsetHasher {
39    fn finish(&self) -> u64 {
40        self.0
41    }
42
43    fn write(&mut self, _: &[u8]) {
44        panic!("OffsetHasher key must be u32")
45    }
46
47    fn write_u32(&mut self, n: u32) {
48        self.0 = n.into()
49    }
50}
51
52/// A collection of line offsets.
53pub type Offsets = Vec<Offset>;
54
55/// The direction of a line.
56#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
57#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
58pub enum Direction {
59    /// The line is an input.
60    #[default]
61    Input,
62
63    /// The line is an output.
64    Output,
65}
66
67#[cfg(feature = "uapi_v1")]
68impl From<v1::LineInfoFlags> for Direction {
69    fn from(flags: v1::LineInfoFlags) -> Self {
70        if flags.contains(v1::LineInfoFlags::OUTPUT) {
71            return Direction::Output;
72        }
73        Direction::Input
74    }
75}
76#[cfg(any(feature = "uapi_v2", not(feature = "uapi_v1")))]
77impl From<v2::LineFlags> for Direction {
78    fn from(flags: v2::LineFlags) -> Self {
79        if flags.contains(v2::LineFlags::OUTPUT) {
80            return Direction::Output;
81        }
82        Direction::Input
83    }
84}
85
86/// The bias settings for a line.
87#[derive(Clone, Copy, Debug, Eq, PartialEq)]
88#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
89pub enum Bias {
90    /// The line has pull-up enabled.
91    PullUp,
92
93    /// The line has pull-down enabled.
94    PullDown,
95
96    /// The line has bias disabled and will float unless externally driven.
97    Disabled,
98}
99
100#[cfg(feature = "uapi_v1")]
101impl TryFrom<v1::LineInfoFlags> for Bias {
102    type Error = ();
103
104    fn try_from(flags: v1::LineInfoFlags) -> Result<Self, Self::Error> {
105        if flags.contains(v1::LineInfoFlags::BIAS_PULL_UP) {
106            return Ok(Bias::PullUp);
107        }
108        if flags.contains(v1::LineInfoFlags::BIAS_PULL_DOWN) {
109            return Ok(Bias::PullDown);
110        }
111        if flags.contains(v1::LineInfoFlags::BIAS_DISABLED) {
112            return Ok(Bias::Disabled);
113        }
114        Err(())
115    }
116}
117#[cfg(any(feature = "uapi_v2", not(feature = "uapi_v1")))]
118impl TryFrom<v2::LineFlags> for Bias {
119    type Error = ();
120
121    fn try_from(flags: v2::LineFlags) -> Result<Self, Self::Error> {
122        if flags.contains(v2::LineFlags::BIAS_PULL_UP) {
123            return Ok(Bias::PullUp);
124        }
125        if flags.contains(v2::LineFlags::BIAS_PULL_DOWN) {
126            return Ok(Bias::PullDown);
127        }
128        if flags.contains(v2::LineFlags::BIAS_DISABLED) {
129            return Ok(Bias::Disabled);
130        }
131        Err(())
132    }
133}
134
135/// The drive policy settings for an output line.
136#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
137#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
138pub enum Drive {
139    /// The line is driven when both active and inactive.
140    ///
141    /// This is the default if drive is not specified.
142    #[default]
143    PushPull,
144
145    /// The line is driven when low and set high impedance when high.
146    OpenDrain,
147
148    /// The line is driven when high and set high impedance when low.
149    OpenSource,
150}
151
152#[cfg(feature = "uapi_v1")]
153impl TryFrom<v1::LineInfoFlags> for Drive {
154    type Error = ();
155
156    fn try_from(flags: v1::LineInfoFlags) -> Result<Self, Self::Error> {
157        if flags.contains(v1::LineInfoFlags::OPEN_DRAIN) {
158            return Ok(Drive::OpenDrain);
159        }
160        if flags.contains(v1::LineInfoFlags::OPEN_SOURCE) {
161            return Ok(Drive::OpenSource);
162        }
163        if flags.contains(v1::LineInfoFlags::OUTPUT) {
164            return Ok(Drive::PushPull);
165        }
166        Err(())
167    }
168}
169#[cfg(any(feature = "uapi_v2", not(feature = "uapi_v1")))]
170impl TryFrom<v2::LineFlags> for Drive {
171    type Error = ();
172
173    fn try_from(flags: v2::LineFlags) -> Result<Self, Self::Error> {
174        if flags.contains(v2::LineFlags::OPEN_DRAIN) {
175            return Ok(Drive::OpenDrain);
176        }
177        if flags.contains(v2::LineFlags::OPEN_SOURCE) {
178            return Ok(Drive::OpenSource);
179        }
180        if flags.contains(v2::LineFlags::OUTPUT) {
181            return Ok(Drive::PushPull);
182        }
183        Err(())
184    }
185}
186
187/// The edge detection options for an input line.
188#[derive(Clone, Copy, Debug, Eq, PartialEq)]
189#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
190pub enum EdgeDetection {
191    /// Edge detection is only enabled on rising edges.
192    ///
193    /// A rising edge means a transition from an inactive state to an active state.
194    RisingEdge,
195
196    /// Edge detection is only enabled on falling edges.
197    ///
198    /// A falling edge means a transition from an active state to an inactive state.
199    FallingEdge,
200
201    /// Edge detection is enabled on both rising and falling edges.
202    BothEdges,
203}
204#[cfg(any(feature = "uapi_v2", not(feature = "uapi_v1")))]
205impl TryFrom<v2::LineFlags> for EdgeDetection {
206    type Error = ();
207
208    fn try_from(flags: v2::LineFlags) -> Result<Self, Self::Error> {
209        if flags.contains(v2::LineFlags::EDGE_RISING | v2::LineFlags::EDGE_FALLING) {
210            return Ok(EdgeDetection::BothEdges);
211        }
212        if flags.contains(v2::LineFlags::EDGE_RISING) {
213            return Ok(EdgeDetection::RisingEdge);
214        }
215        if flags.contains(v2::LineFlags::EDGE_FALLING) {
216            return Ok(EdgeDetection::FallingEdge);
217        }
218        Err(())
219    }
220}
221
222/// The available clock sources for [`EdgeEvent`] timestamps.
223#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
224#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
225pub enum EventClock {
226    /// The **CLOCK_MONOTONIC** is used as the source for edge event timestamps.
227    ///
228    /// This is the default for ABI v2.
229    #[default]
230    Monotonic,
231
232    /// The **CLOCK_REALTIME** is used as the source for edge event timestamps.
233    Realtime,
234
235    /// The hardware timestamp engine provides event timestamps.
236    ///
237    /// This source requires a Linux kernel 5.19 or later with CONFIG_HTE
238    /// enabled and suitable supporting hardware.
239    Hte,
240}
241
242#[cfg(any(feature = "uapi_v2", not(feature = "uapi_v1")))]
243impl From<v2::LineFlags> for EventClock {
244    fn from(flags: v2::LineFlags) -> Self {
245        if flags.contains(v2::LineFlags::EVENT_CLOCK_REALTIME) {
246            return EventClock::Realtime;
247        }
248        if flags.contains(v2::LineFlags::EVENT_CLOCK_HTE) {
249            return EventClock::Hte;
250        }
251        EventClock::Monotonic
252    }
253}
254
255#[cfg(test)]
256mod tests {
257    use super::*;
258    mod direction {
259        use super::*;
260
261        #[test]
262        fn default() {
263            assert_eq!(Direction::default(), Direction::Input);
264        }
265
266        #[test]
267        #[cfg(feature = "uapi_v1")]
268        fn from_v1_line_info_flags() {
269            assert_eq!(
270                Direction::from(v1::LineInfoFlags::OUTPUT),
271                Direction::Output
272            );
273            assert_eq!(
274                Direction::from(v1::LineInfoFlags::ACTIVE_LOW),
275                Direction::Input
276            );
277        }
278
279        #[test]
280        #[cfg(any(feature = "uapi_v2", not(feature = "uapi_v1")))]
281        fn from_v2_line_flags() {
282            assert_eq!(Direction::from(v2::LineFlags::OUTPUT), Direction::Output);
283            assert_eq!(Direction::from(v2::LineFlags::INPUT), Direction::Input);
284        }
285    }
286
287    mod bias {
288        use super::*;
289
290        #[test]
291        #[cfg(feature = "uapi_v1")]
292        fn try_from_v1_line_info_flags() {
293            assert_eq!(Bias::try_from(v1::LineInfoFlags::ACTIVE_LOW), Err(()));
294            assert_eq!(
295                Bias::try_from(v1::LineInfoFlags::BIAS_PULL_DOWN),
296                Ok(Bias::PullDown)
297            );
298            assert_eq!(
299                Bias::try_from(v1::LineInfoFlags::BIAS_PULL_UP),
300                Ok(Bias::PullUp)
301            );
302            assert_eq!(
303                Bias::try_from(v1::LineInfoFlags::BIAS_DISABLED),
304                Ok(Bias::Disabled)
305            );
306        }
307
308        #[test]
309        #[cfg(any(feature = "uapi_v2", not(feature = "uapi_v1")))]
310        fn from_v2_line_flags() {
311            assert_eq!(Bias::try_from(v2::LineFlags::INPUT), Err(()));
312            assert_eq!(
313                Bias::try_from(v2::LineFlags::BIAS_PULL_DOWN),
314                Ok(Bias::PullDown)
315            );
316            assert_eq!(
317                Bias::try_from(v2::LineFlags::BIAS_PULL_UP),
318                Ok(Bias::PullUp)
319            );
320            assert_eq!(
321                Bias::try_from(v2::LineFlags::BIAS_DISABLED),
322                Ok(Bias::Disabled)
323            );
324        }
325    }
326
327    mod drive {
328        use super::*;
329
330        #[test]
331        fn default() {
332            assert_eq!(Drive::default(), Drive::PushPull);
333        }
334
335        #[test]
336        #[cfg(feature = "uapi_v1")]
337        fn try_from_v1_line_info_flags() {
338            assert_eq!(Drive::try_from(v1::LineInfoFlags::ACTIVE_LOW), Err(()));
339            assert_eq!(
340                Drive::try_from(v1::LineInfoFlags::OUTPUT),
341                Ok(Drive::PushPull)
342            );
343            assert_eq!(
344                Drive::try_from(v1::LineInfoFlags::OUTPUT | v1::LineInfoFlags::OPEN_DRAIN),
345                Ok(Drive::OpenDrain)
346            );
347            assert_eq!(
348                Drive::try_from(v1::LineInfoFlags::OUTPUT | v1::LineInfoFlags::OPEN_SOURCE),
349                Ok(Drive::OpenSource)
350            );
351        }
352
353        #[test]
354        #[cfg(any(feature = "uapi_v2", not(feature = "uapi_v1")))]
355        fn try_from_v2_line_flags() {
356            assert_eq!(Drive::try_from(v2::LineFlags::INPUT), Err(()));
357            assert_eq!(Drive::try_from(v2::LineFlags::OUTPUT), Ok(Drive::PushPull));
358            assert_eq!(
359                Drive::try_from(v2::LineFlags::OUTPUT | v2::LineFlags::OPEN_DRAIN),
360                Ok(Drive::OpenDrain)
361            );
362            assert_eq!(
363                Drive::try_from(v2::LineFlags::OUTPUT | v2::LineFlags::OPEN_SOURCE),
364                Ok(Drive::OpenSource)
365            );
366        }
367    }
368
369    #[cfg(any(feature = "uapi_v2", not(feature = "uapi_v1")))]
370    mod edge_detection {
371        use super::{v2, EdgeDetection};
372
373        #[test]
374        fn try_from_v2_line_flags() {
375            assert_eq!(EdgeDetection::try_from(v2::LineFlags::INPUT), Err(()));
376            assert_eq!(
377                EdgeDetection::try_from(v2::LineFlags::EDGE_RISING),
378                Ok(EdgeDetection::RisingEdge)
379            );
380            assert_eq!(
381                EdgeDetection::try_from(v2::LineFlags::EDGE_FALLING),
382                Ok(EdgeDetection::FallingEdge)
383            );
384            assert_eq!(
385                EdgeDetection::try_from(v2::LineFlags::EDGE_RISING | v2::LineFlags::EDGE_FALLING),
386                Ok(EdgeDetection::BothEdges)
387            );
388        }
389    }
390
391    #[cfg(any(feature = "uapi_v2", not(feature = "uapi_v1")))]
392    mod event_clock {
393        use super::{v2, EventClock};
394
395        #[test]
396        fn default() {
397            assert_eq!(EventClock::default(), EventClock::Monotonic);
398        }
399
400        #[test]
401        fn from_v2_line_flags() {
402            assert_eq!(
403                EventClock::from(v2::LineFlags::INPUT),
404                EventClock::Monotonic
405            );
406            assert_eq!(
407                EventClock::from(v2::LineFlags::EVENT_CLOCK_REALTIME),
408                EventClock::Realtime
409            );
410        }
411    }
412
413    mod offset_hasher {
414        use super::OffsetHasher;
415        use std::hash::Hasher;
416
417        #[test]
418        fn write_u32() {
419            let mut h = OffsetHasher::default();
420            h.write_u32(2042);
421            assert_eq!(2042, h.finish());
422        }
423
424        #[test]
425        #[should_panic]
426        fn write() {
427            let mut h = OffsetHasher::default();
428            h.write(&[42]);
429        }
430
431        #[test]
432        #[should_panic]
433        fn write_u64() {
434            let mut h = OffsetHasher::default();
435            h.write_u64(2042);
436        }
437
438        #[test]
439        #[should_panic]
440        fn write_u8() {
441            let mut h = OffsetHasher::default();
442            h.write_u8(42);
443        }
444    }
445}