Skip to content

Commit 448d6fd

Browse files
bors[bot]mgottschlagposborne
authored
Merge #35
35: RFC: Line event stream support for Tokio r=posborne a=mgottschlag The second commit (on top of #34 for my convenience, but completely independent) adds support for asynchronous handling of line events/interrupts based on Tokio. This patch probably fixes #18 . This probably needs some further discussion: * I set the Rust edition to 2018 to be able to use `async`/`await` in the example. * The API is just a light wrapper around `LineEventHandle`. I implemented `AsRef<LineEventHandle>` instead of adding a separate `get_value()` function. Do we want a function to destroy an `AsyncLineEventHandle` and get the original `LineEventHandle` back? * I placed the type in an `async_tokio` module and behind an `async_tokio` feature flag, under the expectation that one day there might be wrapper types for other async I/O frameworks (async_std?) as well. Co-authored-by: Mathias Gottschlag <[email protected]> Co-authored-by: Paul Osborne <[email protected]>
2 parents 944a086 + 735f8f7 commit 448d6fd

File tree

6 files changed

+268
-50
lines changed

6 files changed

+268
-50
lines changed

.travis.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ matrix:
5454
if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master)
5555
- env: TARGET=powerpc-unknown-linux-gnu
5656
if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master)
57-
- env: TARGET=powerpc64-unknown-linux-gnu
58-
if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master)
57+
# - env: TARGET=powerpc64-unknown-linux-gnu
58+
# if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master)
5959
- env: TARGET=powerpc64le-unknown-linux-gnu
6060
if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master)
6161
- env: TARGET=s390x-unknown-linux-gnu DISABLE_TESTS=1
@@ -72,7 +72,7 @@ matrix:
7272

7373
# MSRV
7474
- env: TARGET=x86_64-unknown-linux-gnu
75-
rust: 1.34.0
75+
rust: 1.38.0
7676
if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master)
7777

7878
before_install:

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"

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ to be considered reliable.
175175

176176
## Minimum Supported Rust Version (MSRV)
177177

178-
This crate is guaranteed to compile on stable Rust 1.34.0 and up. It *might*
178+
This crate is guaranteed to compile on stable Rust 1.38.0 and up. It *might*
179179
compile with older versions but that may change in any new patch release.
180180

181181
## License

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+
}

src/async_tokio.rs

+142
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
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::os::unix::io::AsRawFd;
21+
use std::pin::Pin;
22+
23+
use super::errors::event_err;
24+
use super::{LineEvent, LineEventHandle, Result};
25+
26+
struct PollWrapper {
27+
handle: LineEventHandle,
28+
}
29+
30+
impl Evented for PollWrapper {
31+
fn register(
32+
&self,
33+
poll: &mio::Poll,
34+
token: Token,
35+
interest: Ready,
36+
opts: PollOpt,
37+
) -> io::Result<()> {
38+
EventedFd(&self.handle.file.as_raw_fd()).register(poll, token, interest, opts)
39+
}
40+
41+
fn reregister(
42+
&self,
43+
poll: &mio::Poll,
44+
token: Token,
45+
interest: Ready,
46+
opts: PollOpt,
47+
) -> io::Result<()> {
48+
EventedFd(&self.handle.file.as_raw_fd()).reregister(poll, token, interest, opts)
49+
}
50+
51+
fn deregister(&self, poll: &mio::Poll) -> io::Result<()> {
52+
EventedFd(&self.handle.file.as_raw_fd()).deregister(poll)
53+
}
54+
}
55+
56+
/// Wrapper around a `LineEventHandle` which implements a `futures::stream::Stream` for interrupts.
57+
///
58+
/// # Example
59+
///
60+
/// The following example waits for state changes on an input line.
61+
///
62+
/// ```no_run
63+
/// # type Result<T> = std::result::Result<T, gpio_cdev::errors::Error>;
64+
/// use futures::stream::StreamExt;
65+
/// use gpio_cdev::{AsyncLineEventHandle, Chip, EventRequestFlags, LineRequestFlags};
66+
///
67+
/// async fn print_events(line: u32) -> Result<()> {
68+
/// let mut chip = Chip::new("/dev/gpiochip0")?;
69+
/// let line = chip.get_line(line)?;
70+
/// let mut events = AsyncLineEventHandle::new(line.events(
71+
/// LineRequestFlags::INPUT,
72+
/// EventRequestFlags::BOTH_EDGES,
73+
/// "gpioevents",
74+
/// )?)?;
75+
///
76+
/// loop {
77+
/// match events.next().await {
78+
/// Some(event) => println!("{:?}", event?),
79+
/// None => break,
80+
/// };
81+
/// }
82+
///
83+
/// Ok(())
84+
/// }
85+
///
86+
/// # #[tokio::main]
87+
/// # async fn main() {
88+
/// # print_events(42).await.unwrap();
89+
/// # }
90+
/// ```
91+
pub struct AsyncLineEventHandle {
92+
evented: PollEvented<PollWrapper>,
93+
}
94+
95+
impl AsyncLineEventHandle {
96+
/// Wraps the specified `LineEventHandle`.
97+
///
98+
/// # Arguments
99+
///
100+
/// * `handle` - handle to be wrapped.
101+
pub fn new(handle: LineEventHandle) -> Result<AsyncLineEventHandle> {
102+
// The file descriptor needs to be configured for non-blocking I/O for PollEvented to work.
103+
let fd = handle.file.as_raw_fd();
104+
unsafe {
105+
let flags = libc::fcntl(fd, libc::F_GETFL, 0);
106+
libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK);
107+
}
108+
109+
Ok(AsyncLineEventHandle {
110+
evented: PollEvented::new(PollWrapper { handle })?,
111+
})
112+
}
113+
}
114+
115+
impl Stream for AsyncLineEventHandle {
116+
type Item = Result<LineEvent>;
117+
118+
fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
119+
let ready = Ready::readable();
120+
if let Err(e) = ready!(self.evented.poll_read_ready(cx, ready)) {
121+
return Poll::Ready(Some(Err(e.into())));
122+
}
123+
124+
match self.evented.get_ref().handle.read_event() {
125+
Ok(Some(event)) => Poll::Ready(Some(Ok(event))),
126+
Ok(None) => Poll::Ready(Some(Err(event_err(nix::Error::Sys(
127+
nix::errno::Errno::EIO,
128+
))))),
129+
Err(nix::Error::Sys(nix::errno::Errno::EAGAIN)) => {
130+
self.evented.clear_read_ready(cx, ready)?;
131+
Poll::Pending
132+
}
133+
Err(e) => Poll::Ready(Some(Err(event_err(e)))),
134+
}
135+
}
136+
}
137+
138+
impl AsRef<LineEventHandle> for AsyncLineEventHandle {
139+
fn as_ref(&self) -> &LineEventHandle {
140+
&self.evented.get_ref().handle
141+
}
142+
}

0 commit comments

Comments
 (0)