diff --git a/.travis.yml b/.travis.yml index 7f95148..998a185 100644 --- a/.travis.yml +++ b/.travis.yml @@ -54,8 +54,8 @@ matrix: if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) - env: TARGET=powerpc-unknown-linux-gnu if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) - - env: TARGET=powerpc64-unknown-linux-gnu - if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) + # - env: TARGET=powerpc64-unknown-linux-gnu + # if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) - env: TARGET=powerpc64le-unknown-linux-gnu if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) - env: TARGET=s390x-unknown-linux-gnu DISABLE_TESTS=1 @@ -72,7 +72,7 @@ matrix: # MSRV - env: TARGET=x86_64-unknown-linux-gnu - rust: 1.34.0 + rust: 1.38.0 if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) before_install: diff --git a/Cargo.toml b/Cargo.toml index 5a7b910..c2b94db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,11 +9,23 @@ readme = "README.md" categories = ["embedded", "hardware-support", "os", "os::unix-apis"] keywords = ["linux", "gpio", "gpiochip", "embedded"] license = "MIT OR Apache-2.0" +edition = "2018" + +[features] +default = [] +async-tokio = ["tokio", "futures", "mio"] + +[[example]] +name = "async_tokio" +required-features = ["async-tokio"] [dependencies] bitflags = "1.0" libc = "0.2" nix = "0.14" +tokio = { version = "0.2", features = ["io-driver", "rt-threaded", "macros"], optional = true } +futures = { version = "0.3", optional = true } +mio = { version = "0.6", optional = true } [dev-dependencies] quicli = "0.2" diff --git a/README.md b/README.md index bfd5efe..97fde7c 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,7 @@ to be considered reliable. ## Minimum Supported Rust Version (MSRV) -This crate is guaranteed to compile on stable Rust 1.34.0 and up. It *might* +This crate is guaranteed to compile on stable Rust 1.38.0 and up. It *might* compile with older versions but that may change in any new patch release. ## License diff --git a/examples/async_tokio.rs b/examples/async_tokio.rs new file mode 100644 index 0000000..6e1fb62 --- /dev/null +++ b/examples/async_tokio.rs @@ -0,0 +1,44 @@ +// Copyright (c) 2018 The rust-gpio-cdev Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use futures::stream::StreamExt; +use gpio_cdev::*; +use quicli::prelude::*; + +#[derive(Debug, StructOpt)] +struct Cli { + /// The gpiochip device (e.g. /dev/gpiochip0) + chip: String, + /// The offset of the GPIO line for the provided chip + line: u32, +} + +async fn do_main(args: Cli) -> std::result::Result<(), errors::Error> { + let mut chip = Chip::new(args.chip)?; + let line = chip.get_line(args.line)?; + let mut events = AsyncLineEventHandle::new(line.events( + LineRequestFlags::INPUT, + EventRequestFlags::BOTH_EDGES, + "gpioevents", + )?)?; + + loop { + match events.next().await { + Some(event) => println!("{:?}", event?), + None => break, + }; + } + + Ok(()) +} + +#[tokio::main] +async fn main() { + let args = Cli::from_args(); + do_main(args).await.unwrap(); +} diff --git a/src/async_tokio.rs b/src/async_tokio.rs new file mode 100644 index 0000000..3c439fb --- /dev/null +++ b/src/async_tokio.rs @@ -0,0 +1,142 @@ +// Copyright (c) 2018 The rust-gpio-cdev Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Wrapper for asynchronous programming using Tokio. + +use futures::ready; +use futures::stream::Stream; +use futures::task::{Context, Poll}; +use mio::event::Evented; +use mio::unix::EventedFd; +use mio::{PollOpt, Ready, Token}; +use tokio::io::PollEvented; + +use std::io; +use std::os::unix::io::AsRawFd; +use std::pin::Pin; + +use super::errors::event_err; +use super::{LineEvent, LineEventHandle, Result}; + +struct PollWrapper { + handle: LineEventHandle, +} + +impl Evented for PollWrapper { + fn register( + &self, + poll: &mio::Poll, + token: Token, + interest: Ready, + opts: PollOpt, + ) -> io::Result<()> { + EventedFd(&self.handle.file.as_raw_fd()).register(poll, token, interest, opts) + } + + fn reregister( + &self, + poll: &mio::Poll, + token: Token, + interest: Ready, + opts: PollOpt, + ) -> io::Result<()> { + EventedFd(&self.handle.file.as_raw_fd()).reregister(poll, token, interest, opts) + } + + fn deregister(&self, poll: &mio::Poll) -> io::Result<()> { + EventedFd(&self.handle.file.as_raw_fd()).deregister(poll) + } +} + +/// Wrapper around a `LineEventHandle` which implements a `futures::stream::Stream` for interrupts. +/// +/// # Example +/// +/// The following example waits for state changes on an input line. +/// +/// ```no_run +/// # type Result = std::result::Result; +/// use futures::stream::StreamExt; +/// use gpio_cdev::{AsyncLineEventHandle, Chip, EventRequestFlags, LineRequestFlags}; +/// +/// async fn print_events(line: u32) -> Result<()> { +/// let mut chip = Chip::new("/dev/gpiochip0")?; +/// let line = chip.get_line(line)?; +/// let mut events = AsyncLineEventHandle::new(line.events( +/// LineRequestFlags::INPUT, +/// EventRequestFlags::BOTH_EDGES, +/// "gpioevents", +/// )?)?; +/// +/// loop { +/// match events.next().await { +/// Some(event) => println!("{:?}", event?), +/// None => break, +/// }; +/// } +/// +/// Ok(()) +/// } +/// +/// # #[tokio::main] +/// # async fn main() { +/// # print_events(42).await.unwrap(); +/// # } +/// ``` +pub struct AsyncLineEventHandle { + evented: PollEvented, +} + +impl AsyncLineEventHandle { + /// Wraps the specified `LineEventHandle`. + /// + /// # Arguments + /// + /// * `handle` - handle to be wrapped. + pub fn new(handle: LineEventHandle) -> Result { + // The file descriptor needs to be configured for non-blocking I/O for PollEvented to work. + let fd = handle.file.as_raw_fd(); + unsafe { + let flags = libc::fcntl(fd, libc::F_GETFL, 0); + libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK); + } + + Ok(AsyncLineEventHandle { + evented: PollEvented::new(PollWrapper { handle })?, + }) + } +} + +impl Stream for AsyncLineEventHandle { + type Item = Result; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + let ready = Ready::readable(); + if let Err(e) = ready!(self.evented.poll_read_ready(cx, ready)) { + return Poll::Ready(Some(Err(e.into()))); + } + + match self.evented.get_ref().handle.read_event() { + Ok(Some(event)) => Poll::Ready(Some(Ok(event))), + Ok(None) => Poll::Ready(Some(Err(event_err(nix::Error::Sys( + nix::errno::Errno::EIO, + ))))), + Err(nix::Error::Sys(nix::errno::Errno::EAGAIN)) => { + self.evented.clear_read_ready(cx, ready)?; + Poll::Pending + } + Err(e) => Poll::Ready(Some(Err(event_err(e)))), + } + } +} + +impl AsRef for AsyncLineEventHandle { + fn as_ref(&self) -> &LineEventHandle { + &self.evented.get_ref().handle + } +} diff --git a/src/lib.rs b/src/lib.rs index 5556d9e..0fc6f7c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -89,6 +89,12 @@ extern crate bitflags; extern crate libc; #[macro_use] extern crate nix; +#[cfg(feature = "async-tokio")] +extern crate futures; +#[cfg(feature = "async-tokio")] +extern crate mio; +#[cfg(feature = "async-tokio")] +extern crate tokio; use std::cmp::min; use std::ffi::CStr; @@ -101,9 +107,13 @@ use std::ptr; use std::slice; use std::sync::Arc; +#[cfg(feature = "async-tokio")] +mod async_tokio; pub mod errors; mod ffi; +#[cfg(feature = "async-tokio")] +pub use crate::async_tokio::AsyncLineEventHandle; use errors::*; unsafe fn rstr_lcpy(dst: *mut libc::c_char, src: &str, length: usize) { @@ -193,7 +203,7 @@ impl Chip { /// Open the GPIO Chip at the provided path (e.g. `/dev/gpiochip`) pub fn new>(path: P) -> Result { let f = File::open(path.as_ref())?; - let mut info: ffi::gpiochip_info = unsafe { mem::uninitialized() }; + let mut info: ffi::gpiochip_info = unsafe { mem::MaybeUninit::uninit().assume_init() }; ffi::gpio_get_chipinfo_ioctl(f.as_raw_fd(), &mut info)?; Ok(Chip { @@ -328,12 +338,12 @@ pub struct LineInfo { consumer: Option, } -/// Line Request Flags -/// -/// Maps to kernel [`GPIOHANDLE_REQUEST_*`] flags. -/// -/// [`GPIOHANDLE_REQUEST_*`]: https://elixir.bootlin.com/linux/v4.9.127/source/include/uapi/linux/gpio.h#L58 bitflags! { + /// Line Request Flags + /// + /// Maps to kernel [`GPIOHANDLE_REQUEST_*`] flags. + /// + /// [`GPIOHANDLE_REQUEST_*`]: https://elixir.bootlin.com/linux/v4.9.127/source/include/uapi/linux/gpio.h#L58 pub struct LineRequestFlags: u32 { const INPUT = (1 << 0); const OUTPUT = (1 << 1); @@ -343,12 +353,12 @@ bitflags! { } } -/// Event request flags -/// -/// Maps to kernel [`GPIOEVENT_REQEST_*`] flags. -/// -/// [`GPIOEVENT_REQUEST_*`]: https://elixir.bootlin.com/linux/v4.9.127/source/include/uapi/linux/gpio.h#L109 bitflags! { + /// Event request flags + /// + /// Maps to kernel [`GPIOEVENT_REQEST_*`] flags. + /// + /// [`GPIOEVENT_REQUEST_*`]: https://elixir.bootlin.com/linux/v4.9.127/source/include/uapi/linux/gpio.h#L109 pub struct EventRequestFlags: u32 { const RISING_EDGE = (1 << 0); const FALLING_EDGE = (1 << 1); @@ -356,12 +366,12 @@ bitflags! { } } -/// Informational Flags -/// -/// Maps to kernel [`GPIOLINE_FLAG_*`] flags. -/// -/// [`GPIOLINE_FLAG_*`]: https://elixir.bootlin.com/linux/v4.9.127/source/include/uapi/linux/gpio.h#L29 bitflags! { + /// Informational Flags + /// + /// Maps to kernel [`GPIOLINE_FLAG_*`] flags. + /// + /// [`GPIOLINE_FLAG_*`]: https://elixir.bootlin.com/linux/v4.9.127/source/include/uapi/linux/gpio.h#L29 pub struct LineFlags: u32 { const KERNEL = (1 << 0); const IS_OUT = (1 << 1); @@ -550,6 +560,17 @@ impl Line { file: unsafe { File::from_raw_fd(request.fd) }, }) } + + #[cfg(feature = "async-tokio")] + pub fn async_events( + &self, + handle_flags: LineRequestFlags, + event_flags: EventRequestFlags, + consumer: &str, + ) -> Result { + let events = self.events(handle_flags, event_flags, consumer)?; + Ok(AsyncLineEventHandle::new(events)?) + } } impl LineInfo { @@ -929,21 +950,10 @@ impl LineEventHandle { /// kernel for the line which matches the subscription criteria /// specified in the `event_flags` when the handle was created. pub fn get_event(&self) -> Result { - let mut data: ffi::gpioevent_data = unsafe { mem::zeroed() }; - let mut data_as_buf = unsafe { - slice::from_raw_parts_mut( - &mut data as *mut ffi::gpioevent_data as *mut u8, - mem::size_of::(), - ) - }; - let bytes_read = - nix::unistd::read(self.file.as_raw_fd(), &mut data_as_buf).map_err(event_err)?; - - if bytes_read != mem::size_of::() { - let e = nix::Error::Sys(nix::errno::Errno::EIO); - Err(event_err(e)) - } else { - Ok(LineEvent(data)) + match self.read_event() { + Ok(Some(event)) => Ok(event), + Ok(None) => Err(event_err(nix::Error::Sys(nix::errno::Errno::EIO))), + Err(e) => Err(event_err(e)), } } @@ -963,6 +973,28 @@ impl LineEventHandle { pub fn line(&self) -> &Line { &self.line } + + /// Helper function which returns the line event if a complete event was read, Ok(None) if not + /// enough data was read or the error returned by `read()`. + /// + /// This function allows access to the raw `nix::Error` as required, for example, to theck + /// whether read() returned -EAGAIN. + pub(crate) fn read_event(&self) -> std::result::Result, nix::Error> { + let mut data: ffi::gpioevent_data = unsafe { mem::zeroed() }; + let mut data_as_buf = unsafe { + slice::from_raw_parts_mut( + &mut data as *mut ffi::gpioevent_data as *mut u8, + mem::size_of::(), + ) + }; + let bytes_read = nix::unistd::read(self.file.as_raw_fd(), &mut data_as_buf)?; + + if bytes_read != mem::size_of::() { + Ok(None) + } else { + Ok(Some(LineEvent(data))) + } + } } impl AsRawFd for LineEventHandle { @@ -976,21 +1008,9 @@ impl Iterator for LineEventHandle { type Item = Result; fn next(&mut self) -> Option> { - let mut data: ffi::gpioevent_data = unsafe { mem::zeroed() }; - let mut data_as_buf = unsafe { - slice::from_raw_parts_mut( - &mut data as *mut ffi::gpioevent_data as *mut u8, - mem::size_of::(), - ) - }; - match nix::unistd::read(self.file.as_raw_fd(), &mut data_as_buf) { - Ok(bytes_read) => { - if bytes_read != mem::size_of::() { - None - } else { - Some(Ok(LineEvent(data))) - } - } + match self.read_event() { + Ok(None) => None, + Ok(Some(event)) => Some(Ok(event)), Err(e) => Some(Err(event_err(e))), } }