gpio/
sysfs.rs

1//! Linux `/sys`-fs based GPIO control
2//!
3//! Uses the [Linux GPIO Sysfs](https://www.kernel.org/doc/Documentation/gpio/sysfs.txt) filesystem
4//! operations to control GPIO ports. It tries to reduce the otherwise hefty syscall overhead
5//! by keeping the sysfs files open, instead of reopening them on each read.
6//!
7//! Every `open` call to a GPIO pin will automatically export the necessary pin and unexport it
8//! on close.
9
10use std::{fs, io};
11use std::io::{Read, Seek, SeekFrom, Write};
12use super::{GpioIn, GpioOut, GpioValue};
13
14#[derive(Copy, Clone, Debug, PartialEq, Eq)]
15enum GpioDirection {
16    Input,
17    Output,
18}
19
20#[inline]
21fn export_gpio_if_unexported(gpio_num: u16) -> io::Result<()> {
22    // export port first if not exported
23    if let Err(_) = fs::metadata(&format!("/sys/class/gpio/gpio{}", gpio_num)) {
24        let mut export_fp = fs::File::create("/sys/class/gpio/export")?;
25        write!(export_fp, "{}", gpio_num)?;
26    }
27
28    // ensure we're using '0' as low
29    fs::File::create(format!("/sys/class/gpio/gpio{}/active_low", gpio_num))?.write_all(b"0")
30}
31
32#[inline]
33fn set_gpio_direction(gpio_num: u16, direction: GpioDirection) -> io::Result<()> {
34    fs::File::create(format!("/sys/class/gpio/gpio{}/direction", gpio_num))?.write_all(
35        match direction {
36            GpioDirection::Input => b"in",
37            GpioDirection::Output => b"out",
38        },
39    )
40}
41
42#[inline]
43fn open_gpio(gpio_num: u16, direction: GpioDirection) -> io::Result<fs::File> {
44    let p = format!("/sys/class/gpio/gpio{}/value", gpio_num);
45
46    match direction {
47        GpioDirection::Input => fs::File::open(p),
48        GpioDirection::Output => fs::File::create(p),
49    }
50}
51
52#[derive(Debug)]
53struct SysFsGpio {
54    gpio_num: u16,
55    sysfp: fs::File,
56}
57
58impl SysFsGpio {
59    fn open(gpio_num: u16, direction: GpioDirection) -> io::Result<SysFsGpio> {
60        export_gpio_if_unexported(gpio_num)?;
61
62        // ensure we're using '0' as low.
63        // FIXME: this should be configurable
64        fs::File::create(format!("/sys/class/gpio/gpio{}/active_low", gpio_num))?.write_all(b"0")?;
65
66        set_gpio_direction(gpio_num, direction)?;
67
68        // finally, we can open the device
69        Ok(SysFsGpio {
70            gpio_num,
71            sysfp: open_gpio(gpio_num, direction)?,
72        })
73    }
74
75    #[inline]
76    fn set_direction(&mut self, direction: GpioDirection) -> io::Result<()> {
77        set_gpio_direction(self.gpio_num, direction)?;
78        self.sysfp = open_gpio(self.gpio_num, direction)?;
79
80        Ok(())
81    }
82}
83
84impl Drop for SysFsGpio {
85    #[inline]
86    fn drop(&mut self) {
87        // unexport the pin, if we have not done so already
88        // best effort, failures are ignored
89        let unexport_fp = fs::File::create("/sys/class/gpio/unexport");
90
91        if let Ok(mut fp) = unexport_fp {
92            write!(fp, "{}\n", self.gpio_num).ok();
93        }
94    }
95}
96
97/// `/sys`-fs based GPIO output
98#[derive(Debug)]
99pub struct SysFsGpioOutput {
100    gpio: SysFsGpio,
101}
102
103impl SysFsGpioOutput {
104    /// Open a GPIO port for Output.
105    #[inline]
106    pub fn open(gpio_num: u16) -> io::Result<SysFsGpioOutput> {
107        Ok(SysFsGpioOutput {
108            gpio: SysFsGpio::open(gpio_num, GpioDirection::Output)?,
109        })
110    }
111
112    #[inline]
113    pub fn into_input(mut self) -> io::Result<SysFsGpioInput> {
114        self.gpio.set_direction(GpioDirection::Input)?;
115        Ok(SysFsGpioInput { gpio: self.gpio })
116    }
117}
118
119impl GpioOut for SysFsGpioOutput {
120    type Error = io::Error;
121
122    #[inline]
123    fn set_low(&mut self) -> io::Result<()> {
124        self.gpio.sysfp.write_all(b"0")
125    }
126
127    #[inline]
128    fn set_high(&mut self) -> io::Result<()> {
129        self.gpio.sysfp.write_all(b"1")
130    }
131}
132
133/// `/sys`-fs based GPIO output
134#[derive(Debug)]
135pub struct SysFsGpioInput {
136    gpio: SysFsGpio,
137}
138
139impl SysFsGpioInput {
140    /// Open a GPIO port for Output.
141    #[inline]
142    pub fn open(gpio_num: u16) -> io::Result<SysFsGpioInput> {
143        Ok(SysFsGpioInput {
144            gpio: SysFsGpio::open(gpio_num, GpioDirection::Input)?,
145        })
146    }
147
148    #[inline]
149    pub fn into_output(mut self) -> io::Result<SysFsGpioOutput> {
150        self.gpio.set_direction(GpioDirection::Output)?;
151        Ok(SysFsGpioOutput { gpio: self.gpio })
152    }
153}
154
155impl GpioIn for SysFsGpioInput {
156    type Error = io::Error;
157
158    #[inline]
159    fn read_value(&mut self) -> Result<GpioValue, Self::Error> {
160        let mut buf: [u8; 1] = [0; 1];
161
162        // we rewind the file descriptor first, otherwise read will fail
163        self.gpio.sysfp.seek(SeekFrom::Start(0))?;
164
165        // we read one byte, the trailing byte is a newline
166        self.gpio.sysfp.read_exact(&mut buf)?;
167
168        match buf[0] {
169            b'0' => Ok(GpioValue::Low),
170            b'1' => Ok(GpioValue::High),
171            _ => {
172                println!("BUFFER: {:?}", buf);
173                Err(io::Error::new(
174                    io::ErrorKind::InvalidData,
175                    "read a value that was neither a '0' nor a '1' from Linux sysfs GPIO interface",
176                ))
177            }
178        }
179    }
180}