Skip to content

Commit 139be3f

Browse files
committed
Add asynchronous line event stream for Tokio.
1 parent e2ae6cf commit 139be3f

File tree

4 files changed

+236
-0
lines changed

4 files changed

+236
-0
lines changed

Diff for: Cargo.toml

+12
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,23 @@ readme = "README.md"
99
categories = ["embedded", "hardware-support", "os", "os::unix-apis"]
1010
keywords = ["linux", "gpio", "gpiochip", "embedded"]
1111
license = "MIT OR Apache-2.0"
12+
edition = "2018"
13+
14+
[features]
15+
default = []
16+
async-tokio = ["tokio", "futures", "mio"]
17+
18+
[[example]]
19+
name = "async_tokio"
20+
required-features = ["async-tokio"]
1221

1322
[dependencies]
1423
bitflags = "1.0"
1524
libc = "0.2"
1625
nix = "0.14"
26+
tokio = { version = "0.2", features = ["io-driver", "rt-threaded", "macros"], optional = true }
27+
futures = { version = "0.3", optional = true }
28+
mio = { version = "0.6", optional = true }
1729

1830
[dev-dependencies]
1931
quicli = "0.2"

Diff for: examples/async_tokio.rs

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright (c) 2018 The rust-gpio-cdev Project Developers.
2+
//
3+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6+
// option. This file may not be copied, modified, or distributed
7+
// except according to those terms.
8+
9+
use futures::stream::StreamExt;
10+
use gpio_cdev::*;
11+
use quicli::prelude::*;
12+
13+
#[derive(Debug, StructOpt)]
14+
struct Cli {
15+
/// The gpiochip device (e.g. /dev/gpiochip0)
16+
chip: String,
17+
/// The offset of the GPIO line for the provided chip
18+
line: u32,
19+
}
20+
21+
async fn do_main(args: Cli) -> std::result::Result<(), errors::Error> {
22+
let mut chip = Chip::new(args.chip)?;
23+
let line = chip.get_line(args.line)?;
24+
let mut events = AsyncLineEventHandle::new(line.events(
25+
LineRequestFlags::INPUT,
26+
EventRequestFlags::BOTH_EDGES,
27+
"gpioevents",
28+
)?)?;
29+
30+
loop {
31+
match events.next().await {
32+
Some(event) => println!("{:?}", event?),
33+
None => break,
34+
};
35+
}
36+
37+
Ok(())
38+
}
39+
40+
#[tokio::main]
41+
async fn main() {
42+
let args = Cli::from_args();
43+
do_main(args).await.unwrap();
44+
}

Diff for: src/async_tokio.rs

+159
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
// Copyright (c) 2018 The rust-gpio-cdev Project Developers.
2+
//
3+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6+
// option. This file may not be copied, modified, or distributed
7+
// except according to those terms.
8+
9+
//! Wrapper for asynchronous programming using Tokio.
10+
11+
use futures::ready;
12+
use futures::stream::Stream;
13+
use futures::task::{Context, Poll};
14+
use mio::event::Evented;
15+
use mio::unix::EventedFd;
16+
use mio::{PollOpt, Ready, Token};
17+
use tokio::io::PollEvented;
18+
19+
use std::io;
20+
use std::mem;
21+
use std::os::unix::io::AsRawFd;
22+
use std::pin::Pin;
23+
use std::slice;
24+
25+
use super::errors::event_err;
26+
use super::{ffi, LineEvent, LineEventHandle, Result};
27+
28+
struct PollWrapper {
29+
handle: LineEventHandle,
30+
}
31+
32+
impl Evented for PollWrapper {
33+
fn register(
34+
&self,
35+
poll: &mio::Poll,
36+
token: Token,
37+
interest: Ready,
38+
opts: PollOpt,
39+
) -> io::Result<()> {
40+
EventedFd(&self.handle.file.as_raw_fd()).register(poll, token, interest, opts)
41+
}
42+
43+
fn reregister(
44+
&self,
45+
poll: &mio::Poll,
46+
token: Token,
47+
interest: Ready,
48+
opts: PollOpt,
49+
) -> io::Result<()> {
50+
EventedFd(&self.handle.file.as_raw_fd()).reregister(poll, token, interest, opts)
51+
}
52+
53+
fn deregister(&self, poll: &mio::Poll) -> io::Result<()> {
54+
EventedFd(&self.handle.file.as_raw_fd()).deregister(poll)
55+
}
56+
}
57+
58+
/// Wrapper around a `LineEventHandle` which implements a `futures::stream::Stream` for interrupts.
59+
///
60+
/// # Example
61+
///
62+
/// The following example waits for state changes on an input line.
63+
///
64+
/// ```no_run
65+
/// # type Result<T> = std::result::Result<T, gpio_cdev::errors::Error>;
66+
/// use futures::stream::StreamExt;
67+
/// use gpio_cdev::{AsyncLineEventHandle, Chip, EventRequestFlags, LineRequestFlags};
68+
///
69+
/// async fn print_events(line: u32) -> Result<()> {
70+
/// let mut chip = Chip::new("/dev/gpiochip0")?;
71+
/// let line = chip.get_line(line)?;
72+
/// let mut events = AsyncLineEventHandle::new(line.events(
73+
/// LineRequestFlags::INPUT,
74+
/// EventRequestFlags::BOTH_EDGES,
75+
/// "gpioevents",
76+
/// )?)?;
77+
///
78+
/// loop {
79+
/// match events.next().await {
80+
/// Some(event) => println!("{:?}", event?),
81+
/// None => break,
82+
/// };
83+
/// }
84+
///
85+
/// Ok(())
86+
/// }
87+
///
88+
/// # #[tokio::main]
89+
/// # async fn main() {
90+
/// # print_events(42).await.unwrap();
91+
/// # }
92+
/// ```
93+
pub struct AsyncLineEventHandle {
94+
evented: PollEvented<PollWrapper>,
95+
}
96+
97+
impl AsyncLineEventHandle {
98+
/// Wraps the specified `LineEventHandle`.
99+
///
100+
/// # Arguments
101+
///
102+
/// * `handle` - handle to be wrapped.
103+
pub fn new(handle: LineEventHandle) -> Result<AsyncLineEventHandle> {
104+
// The file descriptor needs to be configured for non-blocking I/O for PollEvented to work.
105+
let fd = handle.file.as_raw_fd();
106+
unsafe {
107+
let flags = libc::fcntl(fd, libc::F_GETFL, 0);
108+
libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK);
109+
}
110+
111+
Ok(AsyncLineEventHandle {
112+
evented: PollEvented::new(PollWrapper { handle })?,
113+
})
114+
}
115+
}
116+
117+
impl Stream for AsyncLineEventHandle {
118+
type Item = Result<LineEvent>;
119+
120+
fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
121+
let ready = Ready::readable();
122+
if let Err(e) = ready!(self.evented.poll_read_ready(cx, ready)) {
123+
return Poll::Ready(Some(Err(e.into())));
124+
}
125+
126+
// TODO: This code should not be duplicated here.
127+
let mut data: ffi::gpioevent_data = unsafe { mem::zeroed() };
128+
let mut data_as_buf = unsafe {
129+
slice::from_raw_parts_mut(
130+
&mut data as *mut ffi::gpioevent_data as *mut u8,
131+
mem::size_of::<ffi::gpioevent_data>(),
132+
)
133+
};
134+
match nix::unistd::read(
135+
self.evented.get_ref().handle.file.as_raw_fd(),
136+
&mut data_as_buf,
137+
) {
138+
Ok(bytes_read) => {
139+
if bytes_read != mem::size_of::<ffi::gpioevent_data>() {
140+
let e = nix::Error::Sys(nix::errno::Errno::EIO);
141+
Poll::Ready(Some(Err(event_err(e))))
142+
} else {
143+
Poll::Ready(Some(Ok(LineEvent(data))))
144+
}
145+
}
146+
Err(nix::Error::Sys(nix::errno::Errno::EAGAIN)) => {
147+
self.evented.clear_read_ready(cx, ready)?;
148+
Poll::Pending
149+
}
150+
Err(e) => Poll::Ready(Some(Err(event_err(e)))),
151+
}
152+
}
153+
}
154+
155+
impl AsRef<LineEventHandle> for AsyncLineEventHandle {
156+
fn as_ref(&self) -> &LineEventHandle {
157+
&self.evented.get_ref().handle
158+
}
159+
}

Diff for: src/lib.rs

+21
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,12 @@ extern crate bitflags;
8989
extern crate libc;
9090
#[macro_use]
9191
extern crate nix;
92+
#[cfg(feature = "async-tokio")]
93+
extern crate futures;
94+
#[cfg(feature = "async-tokio")]
95+
extern crate mio;
96+
#[cfg(feature = "async-tokio")]
97+
extern crate tokio;
9298

9399
use std::cmp::min;
94100
use std::ffi::CStr;
@@ -101,9 +107,13 @@ use std::ptr;
101107
use std::slice;
102108
use std::sync::Arc;
103109

110+
#[cfg(feature = "async-tokio")]
111+
mod async_tokio;
104112
pub mod errors;
105113
mod ffi;
106114

115+
#[cfg(feature = "async-tokio")]
116+
pub use crate::async_tokio::AsyncLineEventHandle;
107117
use errors::*;
108118

109119
unsafe fn rstr_lcpy(dst: *mut libc::c_char, src: &str, length: usize) {
@@ -550,6 +560,17 @@ impl Line {
550560
file: unsafe { File::from_raw_fd(request.fd) },
551561
})
552562
}
563+
564+
#[cfg(feature = "async-tokio")]
565+
pub fn async_events(
566+
&self,
567+
handle_flags: LineRequestFlags,
568+
event_flags: EventRequestFlags,
569+
consumer: &str,
570+
) -> Result<AsyncLineEventHandle> {
571+
let events = self.events(handle_flags, event_flags, consumer)?;
572+
Ok(AsyncLineEventHandle::new(events)?)
573+
}
553574
}
554575

555576
impl LineInfo {

0 commit comments

Comments
 (0)