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
use crate::{
    raw::v2::*, utils::*, Active, AsValuesMut, Bias, Direction, Drive, Edge, EdgeDetect, Event,
    LineId, LineInfo, LineMap, Result, Values,
};

/// Raw event ro read from fd
pub type RawEvent = GpioLineEvent;

impl GpioLineInfo {
    pub fn as_info(&self) -> Result<LineInfo> {
        let direction = if is_set(self.flags, GPIO_LINE_FLAG_OUTPUT) {
            Direction::Output
        } else {
            Direction::Input
        };

        let active = if is_set(self.flags, GPIO_LINE_FLAG_ACTIVE_LOW) {
            Active::Low
        } else {
            Active::High
        };

        let edge = match (
            is_set(self.flags, GPIO_LINE_FLAG_EDGE_RISING),
            is_set(self.flags, GPIO_LINE_FLAG_EDGE_FALLING),
        ) {
            (true, false) => EdgeDetect::Rising,
            (false, true) => EdgeDetect::Falling,
            (true, true) => EdgeDetect::Both,
            _ => EdgeDetect::Disable,
        };

        let used = is_set(self.flags, GPIO_LINE_FLAG_USED);

        let bias = match (
            is_set(self.flags, GPIO_LINE_FLAG_BIAS_PULL_UP),
            is_set(self.flags, GPIO_LINE_FLAG_BIAS_PULL_DOWN),
        ) {
            (true, false) => Bias::PullUp,
            (false, true) => Bias::PullDown,
            _ => Bias::Disable,
        };

        let drive = match (
            is_set(self.flags, GPIO_LINE_FLAG_OPEN_DRAIN),
            is_set(self.flags, GPIO_LINE_FLAG_OPEN_SOURCE),
        ) {
            (true, false) => Drive::OpenDrain,
            (false, true) => Drive::OpenSource,
            _ => Drive::PushPull,
        };
        let name = safe_get_str(&self.name)?.into();
        let consumer = safe_get_str(&self.consumer)?.into();

        Ok(LineInfo {
            direction,
            active,
            edge,
            used,
            bias,
            drive,
            name,
            consumer,
        })
    }
}

impl AsMut<GpioLineValues> for Values {
    fn as_mut(&mut self) -> &mut GpioLineValues {
        // it's safe because memory layout is same
        unsafe { &mut *(self as *mut _ as *mut _) }
    }
}

impl GpioLineRequest {
    #[allow(clippy::too_many_arguments)]
    pub fn new(
        lines: &[LineId],
        direction: Direction,
        active: Active,
        edge: Option<EdgeDetect>,
        bias: Option<Bias>,
        drive: Option<Drive>,
        values: Option<Values>,
        consumer: &str,
    ) -> Result<Self> {
        let mut request = GpioLineRequest::default();

        check_len(lines, &request.offsets)?;

        request.num_lines = lines.len() as _;

        request.offsets[..lines.len()].copy_from_slice(lines);

        let config = &mut request.config;

        config.flags |= match direction {
            Direction::Input => GPIO_LINE_FLAG_INPUT,
            // Mixing input and output flags is not allowed
            // see https://github.com/torvalds/linux/blob/v5.18/drivers/gpio/gpiolib-cdev.c#L895-L901
            Direction::Output => GPIO_LINE_FLAG_OUTPUT,
        };

        if matches!(active, Active::Low) {
            config.flags |= GPIO_LINE_FLAG_ACTIVE_LOW;
        }

        if matches!(direction, Direction::Input) {
            // Set edge flags is valid only for input
            // see https://github.com/torvalds/linux/blob/v5.18/drivers/gpio/gpiolib-cdev.c#L903-L906
            if let Some(edge) = edge {
                match edge {
                    EdgeDetect::Rising => config.flags |= GPIO_LINE_FLAG_EDGE_RISING,
                    EdgeDetect::Falling => config.flags |= GPIO_LINE_FLAG_EDGE_FALLING,
                    EdgeDetect::Both => config.flags |= GPIO_LINE_FLAG_EDGE_BOTH,
                    _ => {}
                }
            }
        }

        if let Some(bias) = bias {
            config.flags |= match bias {
                Bias::PullUp => GPIO_LINE_FLAG_BIAS_PULL_UP,
                Bias::PullDown => GPIO_LINE_FLAG_BIAS_PULL_DOWN,
                Bias::Disable => GPIO_LINE_FLAG_BIAS_DISABLED,
            }
        }

        if matches!(direction, Direction::Output) {
            // Set drive flags is valid only for output
            // see https://github.com/torvalds/linux/blob/v5.18/drivers/gpio/gpiolib-cdev.c#L917-L920
            if let Some(drive) = drive {
                match drive {
                    Drive::OpenDrain => config.flags |= GPIO_LINE_FLAG_OPEN_DRAIN,
                    Drive::OpenSource => config.flags |= GPIO_LINE_FLAG_OPEN_SOURCE,
                    _ => (),
                }
            }

            if let Some(mut values) = values {
                values.truncate(lines.len() as _);

                config.num_attrs = 1;
                let attr = &mut config.attrs[0];
                attr.attr.id = GPIO_LINE_ATTR_ID_OUTPUT_VALUES;
                attr.mask = values.mask;
                attr.attr.val.values = values.bits;
            }
        }

        safe_set_str(&mut request.consumer, consumer)?;

        Ok(request)
    }
}

impl GpioLineEvent {
    pub fn as_event(&self, line_map: &LineMap) -> Result<Event> {
        let line = line_map.get(self.offset)?;

        let edge = match self.id {
            GPIO_LINE_EVENT_RISING_EDGE => Edge::Rising,
            GPIO_LINE_EVENT_FALLING_EDGE => Edge::Falling,
            _ => return Err(invalid_data("Unknown edge")),
        };

        let time = time_from_nanos(self.timestamp_ns);

        Ok(Event { line, edge, time })
    }
}