summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorInna Palant <ipalant@google.com>2023-12-13 17:47:48 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2023-12-13 17:47:48 +0000
commitfed9200e15f3b6f97ff3833541b93e3783102c0b (patch)
tree38fd7ef0bb7bf2d042d193f5b068739d8939afed
parentfd16ecddb0cd94528dfe2f52d89838b97d7ad45c (diff)
parenta1085581829b1900971d98b69200ea3c58ac8292 (diff)
downloadgpio-cdev-android14-qpr2-s2-release.tar.gz
Original change: undetermined Change-Id: Iee968fce23029e652ef5be21daec5f96940faa4f Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r--.cargo_vcs_info.json6
-rw-r--r--.github/CODEOWNERS1
-rw-r--r--.github/workflows/ci.yml117
-rw-r--r--.gitignore4
-rw-r--r--Android.bp25
-rw-r--r--CHANGELOG.md69
-rw-r--r--CODE_OF_CONDUCT.md37
-rw-r--r--Cargo.toml101
-rw-r--r--Cargo.toml.orig40
-rw-r--r--LICENSE227
-rw-r--r--LICENSE-APACHE201
-rw-r--r--LICENSE-MIT23
-rw-r--r--METADATA20
-rw-r--r--MODULE_LICENSE_APACHE20
-rw-r--r--MODULE_LICENSE_MIT0
-rw-r--r--OWNERS7
-rw-r--r--README.md235
-rw-r--r--cargo_embargo.json3
-rw-r--r--examples/async_tokio.rs44
-rw-r--r--examples/blinky.rs54
-rw-r--r--examples/driveoutput.rs50
-rw-r--r--examples/gpioevents.rs42
-rw-r--r--examples/lsgpio.rs74
-rw-r--r--examples/monitor.rs85
-rw-r--r--examples/multioutput.rs58
-rw-r--r--examples/multiread.rs38
-rw-r--r--examples/readall.rs36
-rw-r--r--examples/readinput.rs37
-rw-r--r--examples/tit_for_tat.rs64
-rw-r--r--src/async_tokio.rs102
-rw-r--r--src/errors.rs96
-rw-r--r--src/ffi.rs107
-rw-r--r--src/lib.rs1041
33 files changed, 3044 insertions, 0 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
new file mode 100644
index 0000000..b12e5c6
--- /dev/null
+++ b/.cargo_vcs_info.json
@@ -0,0 +1,6 @@
+{
+ "git": {
+ "sha1": "cca34240738273f6666e6b84237391fabbc7fdfd"
+ },
+ "path_in_vcs": ""
+} \ No newline at end of file
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 0000000..2f02147
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1 @@
+* rust-embedded/embedded-linux
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..569cd2d
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,117 @@
+on:
+ push: # Run CI for all branches except GitHub merge queue tmp branches
+ branches-ignore:
+ - "gh-readonly-queue/**"
+ pull_request: # Run CI for PRs on any branch
+ merge_group: # Run CI for the GitHub merge queue
+
+name: Build
+
+env:
+ RUSTFLAGS: '--deny warnings'
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ rust: [stable]
+ TARGET:
+ - aarch64-unknown-linux-gnu
+ - aarch64-unknown-linux-musl
+ - arm-unknown-linux-gnueabi
+ - arm-unknown-linux-gnueabihf
+ - armv7-unknown-linux-gnueabihf
+ - i686-unknown-linux-gnu
+ - i686-unknown-linux-musl
+ # - loongarch64-unknown-linux-gnu
+ - powerpc-unknown-linux-gnu
+ # - powerpc64-unknown-linux-gnu
+ - powerpc64le-unknown-linux-gnu
+ - riscv64gc-unknown-linux-gnu
+ - s390x-unknown-linux-gnu
+ - x86_64-unknown-linux-gnu
+ - x86_64-unknown-linux-musl
+
+ include:
+ # MSRV
+ - rust: 1.65.0
+ TARGET: x86_64-unknown-linux-gnu
+
+ # Test nightly but don't fail
+ - rust: nightly
+ TARGET: x86_64-unknown-linux-gnu
+ experimental: true
+
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions-rs/toolchain@v1
+ with:
+ profile: minimal
+ toolchain: ${{ matrix.rust }}
+ target: ${{ matrix.TARGET }}
+ override: true
+
+ - name: Build
+ uses: actions-rs/cargo@v1
+ with:
+ command: build
+ args: --target=${{ matrix.TARGET }}
+
+ - name: Build all features
+ uses: actions-rs/cargo@v1
+ with:
+ command: build
+ args: --target=${{ matrix.TARGET }} --all-features
+
+ - name: Test
+ uses: actions-rs/cargo@v1
+ with:
+ use-cross: true
+ command: test
+ args: --target=${{ matrix.TARGET }}
+
+ - name: Test all features
+ uses: actions-rs/cargo@v1
+ with:
+ use-cross: true
+ command: test
+ args: --target=${{ matrix.TARGET }} --all-features
+
+ checks:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions-rs/toolchain@v1
+ with:
+ profile: minimal
+ toolchain: stable
+ components: rustfmt
+
+ - name: Doc
+ uses: actions-rs/cargo@v1
+ with:
+ command: doc
+
+ - name: Formatting
+ uses: actions-rs/cargo@v1
+ with:
+ command: fmt
+ args: --all -- --check
+
+ clippy:
+ runs-on: ubuntu-latest
+ env:
+ RUSTFLAGS: '--allow warnings'
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions-rs/toolchain@v1
+ with:
+ profile: minimal
+ toolchain: 1.65.0
+ components: clippy
+
+ - uses: actions-rs/clippy-check@v1
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..143b1ca
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+
+/target/
+**/*.rs.bk
+Cargo.lock
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..d0fc934
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,25 @@
+// This file is generated by cargo_embargo.
+// Do not modify this file as changes will be overridden on upgrade.
+
+// TODO: Add license.
+rust_library {
+ name: "libgpio_cdev",
+ host_supported: true,
+ crate_name: "gpio_cdev",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.6.0",
+ srcs: ["src/lib.rs"],
+ edition: "2018",
+ features: ["default"],
+ rustlibs: [
+ "libbitflags",
+ "liblibc",
+ "libnix",
+ ],
+ apex_available: [
+ "//apex_available:platform",
+ "//apex_available:anyapex",
+ ],
+ product_available: true,
+ vendor_available: true,
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..01d2c7c
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,69 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic
+Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+
+## [v0.6.0] - 2023-09-11
+
+- Updated nix to version `0.27`.
+- Updated bitflags to version `2.4`.
+- MSRV is now 1.65.0.
+
+## [v0.5.1] - 2021-11-22
+
+- Updated nix to version `0.23`.
+- MSRV is now 1.59.0.
+
+## [v0.5.0] - 2021-09-21
+
+- Update Tokio to 1.x. [#55](https://github.com/rust-embedded/gpio-cdev/pull/55).
+- Fix lsgpio example to output gpio line flags.
+- Add `is_empty()` function for `Lines` struct.
+- Add common trait implementations for public structures.
+- Breaking change of `LineEventHandle::get_event()` which now expects `&mut self`.
+- MSRV is now 1.46.0.
+- Updated `nix` to version `0.22`.
+- Updated `quicli` to version `0.4`.
+- Updated `bitflags` to version `1.3`.
+
+
+## [v0.4.0] - 2020-08-01
+
+- Removed pub "errors" module. Error now exposed at top level.
+- MSRV is now 1.39.0
+- Add support behind a feature flag for reading events from a line as a Stream via tokio. [#35](https://github.com/rust-embedded/gpio-cdev/pull/35).
+
+## [v0.3.0] - 2020-02-10
+
+Refactored Errors:
+- Removed the `error-chain` dependency.
+- Errors are now implemented "manually" with `ErrorKind` and `IoctlKind` enums.
+- The encompassing `Error` type implements the `std::error::Error` trait.
+
+## v0.2.0 - 2018-12-12
+
+Adds the ability to create a collection of lines from a single chip and read or write those lines simultaneously with a single stystem call.
+
+- A new `Lines` object (plural) was added. It is a collection of individual `Line` objects on a single `Chip` which can be read or written simultaneously with a single system call.
+- A `Line` now just contains the reference to the Chip and the offset number. No system call is incurred when one is created.
+- Information about an individual line is now represented by a separate `LineInfo` struct which can be obtained from the function `Line::info()`. This incurs a system call to retrieve the information.
+- Creating a `Line` can't fail unless the caller specifies an offset that is out of range of the chip.
+- The `LineIterator` can not fail since it checks the offset range. So now its item is just a `Line`, and not `Result<Line>`.
+- There was no longer a need for `Line::refresh()` so it was removed.
+- Since a `Line` object is trivial to create, it is now OK to have `Lines` be a simple collection of `Line` structs.
+
+## v0.1.0 - 2018-09-28
+
+- Initial release of the library with basic operations centered around operating
+ on a single line at a time.
+
+[Unreleased]: https://github.com/rust-embedded/gpio-cdev/compare/0.6.0...HEAD
+[v0.6.0]: https://github.com/rust-embedded/gpio-cdev/compare/0.5.1...0.6.0
+[v0.5.1]: https://github.com/rust-embedded/gpio-cdev/compare/0.5.0...0.5.1
+[v0.5.0]: https://github.com/rust-embedded/gpio-cdev/compare/0.4.0...0.5.0
+[v0.4.0]: https://github.com/rust-embedded/gpio-cdev/compare/0.3.0...0.4.0
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..88be6d9
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,37 @@
+# The Rust Code of Conduct
+
+## Conduct
+
+**Contact**: [Embedded Linux Team][team]
+
+* We are committed to providing a friendly, safe and welcoming environment for all, regardless of level of experience, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, nationality, or other similar characteristic.
+* On IRC, please avoid using overtly sexual nicknames or other nicknames that might detract from a friendly, safe and welcoming environment for all.
+* Please be kind and courteous. There's no need to be mean or rude.
+* Respect that people have differences of opinion and that every design or implementation choice carries a trade-off and numerous costs. There is seldom a right answer.
+* Please keep unstructured critique to a minimum. If you have solid ideas you want to experiment with, make a fork and see how it works.
+* We will exclude you from interaction if you insult, demean or harass anyone. That is not welcome behavior. We interpret the term "harassment" as including the definition in the [Citizen Code of Conduct](http://citizencodeofconduct.org/); if you have any lack of clarity about what might be included in that concept, please read their definition. In particular, we don't tolerate behavior that excludes people in socially marginalized groups.
+* Private harassment is also unacceptable. No matter who you are, if you feel you have been or are being harassed or made uncomfortable by a community member, please contact one of the channel ops or any of the [Embedded Linux Team][team] immediately. Whether you're a regular contributor or a newcomer, we care about making this community a safe place for you and we've got your back.
+* Likewise any spamming, trolling, flaming, baiting or other attention-stealing behavior is not welcome.
+
+## Moderation
+
+These are the policies for upholding our community's standards of conduct.
+
+1. Remarks that violate the Rust standards of conduct, including hateful, hurtful, oppressive, or exclusionary remarks, are not allowed. (Cursing is allowed, but never targeting another user, and never in a hateful manner.)
+2. Remarks that moderators find inappropriate, whether listed in the code of conduct or not, are also not allowed.
+3. Moderators will first respond to such remarks with a warning.
+4. If the warning is unheeded, the user will be "kicked," i.e., kicked out of the communication channel to cool off.
+5. If the user comes back and continues to make trouble, they will be banned, i.e., indefinitely excluded.
+6. Moderators may choose at their discretion to un-ban the user if it was a first offense and they offer the offended party a genuine apology.
+7. If a moderator bans someone and you think it was unjustified, please take it up with that moderator, or with a different moderator, **in private**. Complaints about bans in-channel are not allowed.
+8. Moderators are held to a higher standard than other community members. If a moderator creates an inappropriate situation, they should expect less leeway than others.
+
+In the Rust community we strive to go the extra step to look out for each other. Don't just aim to be technically unimpeachable, try to be your best self. In particular, avoid flirting with offensive or sensitive issues, particularly if they're off-topic; this all too often leads to unnecessary fights, hurt feelings, and damaged trust; worse, it can drive people away from the community entirely.
+
+And if someone takes issue with something you said or did, resist the urge to be defensive. Just stop doing what it was they complained about and apologize. Even if you feel you were misinterpreted or unfairly accused, chances are good there was something you could've communicated better — remember that it's your responsibility to make your fellow Rustaceans comfortable. Everyone wants to get along and we are all here first and foremost because we want to talk about cool technology. You will find that people will be eager to assume good intent and forgive as long as you earn their trust.
+
+The enforcement policies listed above apply to all official embedded WG venues; including official IRC channels (#rust-embedded); GitHub repositories under rust-embedded; and all forums under rust-embedded.org (forum.rust-embedded.org).
+
+*Adapted from the [Node.js Policy on Trolling](http://blog.izs.me/post/30036893703/policy-on-trolling) as well as the [Contributor Covenant v1.3.0](https://www.contributor-covenant.org/version/1/3/0/).*
+
+[team]: https://github.com/rust-embedded/wg#the-embedded-linux-team
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..48924a7
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,101 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2018"
+name = "gpio-cdev"
+version = "0.6.0"
+authors = [
+ "Paul Osborne <osbpau@gmail.com>",
+ "Frank Pagliughi <fpagliughi@mindspring.com>",
+]
+description = "Linux GPIO Character Device Support (/dev/gpiochipN)"
+homepage = "https://github.com/rust-embedded/gpio-cdev"
+readme = "README.md"
+keywords = [
+ "linux",
+ "gpio",
+ "gpiochip",
+ "embedded",
+]
+categories = [
+ "embedded",
+ "hardware-support",
+ "os",
+ "os::unix-apis",
+]
+license = "MIT OR Apache-2.0"
+repository = "https://github.com/rust-embedded/gpio-cdev"
+
+[package.metadata.docs.rs]
+all-features = true
+rustdoc-args = [
+ "--cfg",
+ "docsrs",
+]
+
+[[example]]
+name = "async_tokio"
+required-features = ["async-tokio"]
+
+[dependencies.bitflags]
+version = "2.4"
+
+[dependencies.futures]
+version = "0.3"
+optional = true
+
+[dependencies.libc]
+version = "0.2"
+
+[dependencies.nix]
+version = "0.27"
+features = ["ioctl"]
+
+[dependencies.tokio]
+version = "1"
+features = [
+ "io-std",
+ "net",
+]
+optional = true
+
+[dev-dependencies.anyhow]
+version = "1.0"
+
+[dev-dependencies.nix]
+version = "0.27"
+features = [
+ "ioctl",
+ "poll",
+]
+
+[dev-dependencies.quicli]
+version = "0.4"
+
+[dev-dependencies.structopt]
+version = "0.3"
+
+[dev-dependencies.tokio]
+version = "1"
+features = [
+ "io-std",
+ "rt-multi-thread",
+ "macros",
+ "net",
+]
+
+[features]
+async-tokio = [
+ "tokio",
+ "futures",
+]
+default = []
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
new file mode 100644
index 0000000..c7d44a9
--- /dev/null
+++ b/Cargo.toml.orig
@@ -0,0 +1,40 @@
+[package]
+name = "gpio-cdev"
+version = "0.6.0"
+authors = ["Paul Osborne <osbpau@gmail.com>", "Frank Pagliughi <fpagliughi@mindspring.com>"]
+description = "Linux GPIO Character Device Support (/dev/gpiochipN)"
+homepage = "https://github.com/rust-embedded/gpio-cdev"
+repository = "https://github.com/rust-embedded/gpio-cdev"
+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"]
+
+[[example]]
+name = "async_tokio"
+required-features = ["async-tokio"]
+
+[dependencies]
+bitflags = "2.4"
+libc = "0.2"
+nix = { version = "0.27", features = ["ioctl"] }
+tokio = { version = "1", features = ["io-std", "net"], optional = true }
+futures = { version = "0.3", optional = true }
+
+[dev-dependencies]
+quicli = "0.4"
+structopt = "0.3"
+anyhow = "1.0"
+tokio = { version = "1", features = ["io-std", "rt-multi-thread", "macros", "net"] }
+nix = { version = "0.27", features = ["ioctl", "poll"] }
+
+[package.metadata.docs.rs]
+# To build locally:
+# RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features --no-deps --open
+all-features = true
+rustdoc-args = ["--cfg", "docsrs"]
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..1c30e24
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,227 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+-----
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/LICENSE-APACHE b/LICENSE-APACHE
new file mode 100644
index 0000000..16fe87b
--- /dev/null
+++ b/LICENSE-APACHE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/LICENSE-MIT b/LICENSE-MIT
new file mode 100644
index 0000000..31aa793
--- /dev/null
+++ b/LICENSE-MIT
@@ -0,0 +1,23 @@
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..7587c47
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,20 @@
+name: "gpio-cdev"
+description: "Linux GPIO Character Device Support (/dev/gpiochipN)"
+third_party {
+ identifier {
+ type: "crates.io"
+ value: "https://crates.io/crates/gpio-cdev"
+ }
+ identifier {
+ type: "Archive"
+ value: "https://static.crates.io/crates/gpio-cdev/gpio-cdev-0.6.0.crate"
+ }
+ version: "0.6.0"
+ # Dual-licensed, using the least restrictive per go/thirdpartylicenses#same.
+ license_type: NOTICE
+ last_upgrade_date {
+ year: 2023
+ month: 11
+ day: 29
+ }
+}
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_APACHE2
diff --git a/MODULE_LICENSE_MIT b/MODULE_LICENSE_MIT
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_MIT
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..cb73f7f
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,7 @@
+# Bug component: 688011
+include platform/prebuilts/rust:main:/OWNERS
+
+# Android microXR
+afoxley@google.com
+aharp@google.com
+jrreinhart@google.com
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5efdcef
--- /dev/null
+++ b/README.md
@@ -0,0 +1,235 @@
+# gpio-cdev
+
+[![Build Status](https://github.com/rust-embedded/gpio-cdev/workflows/Build/badge.svg)](https://github.com/rust-embedded/gpio-cdev/actions)
+[![Version](https://img.shields.io/crates/v/gpio-cdev.svg)](https://crates.io/crates/gpio-cdev)
+[![License](https://img.shields.io/crates/l/gpio-cdev.svg)](https://github.com/rust-embedded/gpio-cdev/blob/master/README.md#license)
+
+- [API Documentation](https://docs.rs/gpio-cdev)
+
+rust-gpio-cdev is a Rust library/crate providing access to [GPIO character device
+ABI](https://www.kernel.org/doc/Documentation/ABI/testing/gpio-cdev). This API,
+stabilized with Linux v4.4, deprecates the legacy sysfs interface to GPIOs that is
+planned to be removed from the upstream kernel after
+year 2020 (which is coming up quickly).
+
+Use of this API is encouraged over the sysfs API used by this crate's
+predecessor [sysfs_gpio](https://crates.io/crates/sysfs_gpio) if you don't need
+to target older kernels. For more information on differences see [Sysfs GPIO vs
+GPIO Character Device](#sysfs-gpio-vs-gpio-character-device).
+
+## Installation
+
+Add the following to your `Cargo.toml`
+
+```
+[dependencies]
+gpio-cdev = "0.4"
+```
+
+Note that the following features are available:
+
+* `async-tokio`: Adds a Stream interface for consuming GPIO events in async code
+ within a tokio runtime.
+
+## Examples
+
+There are several additional examples available in the [examples
+directory](https://github.com/rust-embedded/rust-gpio-cdev/tree/master/examples).
+
+### Read State
+
+```rust
+use gpio_cdev::{Chip, LineRequestFlags};
+
+// Read the state of GPIO4 on a raspberry pi. /dev/gpiochip0
+// maps to the driver for the SoC (builtin) GPIO controller.
+let mut chip = Chip::new("/dev/gpiochip0")?;
+let handle = chip
+ .get_line(4)?
+ .request(LineRequestFlags::INPUT, 0, "read-input")?;
+for _ in 1..4 {
+ println!("Value: {:?}", handle.get_value()?);
+}
+```
+
+### Mirror State (Read/Write)
+
+```rust
+use gpio_cdev::{Chip, LineRequestFlags, EventRequestFlags, EventType};
+
+// Lines are offset within gpiochip0; see docs for more info on chips/lines
+//
+// This function will synchronously follow the state of one line
+// on gpiochip0 and mirror its state on another line. With this you
+// could, for instance, control the state of an LED with a button
+// if hooked up to the right pins on a raspberry pi.
+fn mirror_gpio(inputline: u32, outputline: u32) -> Result<(), gpio_cdev::Error> {
+ let mut chip = Chip::new("/dev/gpiochip0")?;
+ let input = chip.get_line(inputline)?;
+ let output = chip.get_line(outputline)?;
+ let output_handle = output.request(LineRequestFlags::OUTPUT, 0, "mirror-gpio")?;
+ for event in input.events(
+ LineRequestFlags::INPUT,
+ EventRequestFlags::BOTH_EDGES,
+ "mirror-gpio",
+ )? {
+ let evt = event?;
+ println!("{:?}", evt);
+ match evt.event_type() {
+ EventType::RisingEdge => {
+ output_handle.set_value(1)?;
+ }
+ EventType::FallingEdge => {
+ output_handle.set_value(0)?;
+ }
+ }
+ }
+
+ Ok(())
+}
+```
+
+### Async Usage
+
+Note that this requires the addition of the `async-tokio` feature.
+
+```rust
+use futures::stream::StreamExt;
+use gpio_cdev::{Chip, AsyncLineEventHandle};
+
+async fn gpiomon(chip: String, line: u32) -> gpio_cdev::Result<()> {
+ 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",
+ )?)?;
+
+ while let Some(event) = events.next().await {
+ let event = event?;
+ println!("GPIO Event: {:?}", event);
+ }
+
+ Ok(())
+}
+```
+
+## Sysfs GPIO vs GPIO Character Device
+
+Compared to the sysfs gpio interface (as made available by the sysfs_gpio crate)
+the character device has several advantages and critical design differences
+(some of which are driving the deprecation in the kernel).
+
+Since many people are familiar with the sysfs interface (which is easily
+accessible via basic commands in the shell) and few people are familiar with the
+GPIO character device, an exploration of the two and key differences here may
+prove useful.
+
+### Getting Access to a GPIO
+
+In the Linux kernel, individual GPIOs are exposed via drivers that on probe register
+themselves as GPIO chips with the gpio subsystem. Each of these chips provides
+access to a set of GPIOs. At present, when this chip is registered a global
+base number is assigned to this driver. The comments from the linux kernel
+[`gpio_chip_add_data`](https://elixir.bootlin.com/linux/v4.9.85/source/drivers/gpio/gpiolib.c#L1087)
+sum up the situation nicely when assigning the a base number to a GPIO chip
+on registration.
+
+ /*
+ * TODO: this allocates a Linux GPIO number base in the global
+ * GPIO numberspace for this chip. In the long run we want to
+ * get *rid* of this numberspace and use only descriptors, but
+ * it may be a pipe dream. It will not happen before we get rid
+ * of the sysfs interface anyways.
+ */
+
+The entire sysfs interface to GPIO is based around offsets from the base number
+assigned to a GPIO chip. The base number is completely dependent on the order
+in which the chip was registered with the subsystem and the number of GPIOs that
+each of the previous chips registered. The only reason this is usable at all is
+that most GPIOs are accessed via SoC hardware that is registered consistently
+during boot. It's not great; in fact, it's not even good.
+
+The GPIO character device ABI provides access to GPIOs owned by a GPIO chip via
+a bus device, `/sys/bus/gpiochipN` (or `/dev/gpiochipN`). Within a chip, the
+programmer will still need to know some details about how to access the GPIO but
+things are generally sane. Figuring out which bus device is the desired GPIO
+chip can be done by iterating over all that are present and/or setting up
+appropriate udev rules. One good example of this is the [`lsgpio` utility in
+the kernel source](https://github.com/torvalds/linux/blob/master/tools/gpio/lsgpio.c).
+
+In sysfs each GPIO within a chip would be exported and used individually. The
+GPIO character device allows for one or more GPIOs (referenced via offsets) to
+be read, written, configured, and monitored via a "linehandle" fd that is
+created dynamically on request.
+
+### "Exporting" a GPIO
+
+Using the sysfs API, one would write the global GPIO number to the "export" file
+to perform further operations using new files on the filesystem. Using the
+gpiochip character device, a handle for performing operations on one or more
+GPIO offsets within a chip are available via a "linehandle" fd created using the
+`GPIO_GET_LINEHANDLE_IOCTL`. A consequence of this is that a line will remember
+its state only for as long as the fd is open; the line's state will be reset
+once the fd is closed.
+
+When a linehandle is requested, additional information is also included about
+how the individual GPIOs will be used (input, output, as-is, active-low, open
+drain, open source, etc). Multiple lines can be grouped together in a single
+request but they must all be configured the same way if being used in that way.
+See `struct gpioevent_request`.
+
+### Reading/Writing GPIOs
+
+Via sysfs, GPIOs could be read/written using the value file. For GPIO character
+devices, the `GPIOHANDLE_GET_LINE_VALUES_IOCTL` and
+`GPIOHANDLE_SET_LINE_VALUES_IOCTL` may be used to get/set the state of one or
+more offsets within the chip.
+
+### Input Events
+
+Via sysfs, one could setup things up using the trigger file to notify userspace
+(by polling on the value file) of a single event based on how things were setup.
+With GPIO character devices, one can setup a `gpio_eventrequest` that will create
+a new anonymous file (fd provided) for event notifications on a lines within a
+gpiochip. Contrary to sysfs gpio events, the event file will queue multiple events
+and include with the event (best effort) nanosecond-precision timing and an
+identifier with event type.
+
+With this information one could more reasonably consider interpreting a basic
+digital signal from userspace (with rising and falling edges) from userspace
+using the queueing with timing information captured in the kernel. Previously, one
+would need to quickly handle the event notification, make another system call
+to the value file to see the state, etc. which had far too many variables involved
+to be considered reliable.
+
+## Minimum Supported Rust Version (MSRV)
+
+This crate is guaranteed to compile on stable Rust 1.65.0 and up. It *might*
+compile with older versions but that may change in any new patch release.
+
+## License
+
+Licensed under either of
+
+- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
+ http://www.apache.org/licenses/LICENSE-2.0)
+- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
+
+at your option.
+
+### Contribution
+
+Unless you explicitly state otherwise, any contribution intentionally submitted
+for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
+dual licensed as above, without any additional terms or conditions.
+
+## Code of Conduct
+
+Contribution to this crate is organized under the terms of the [Rust Code of
+Conduct][CoC], the maintainer of this crate, the [Embedded Linux Team][team], promises
+to intervene to uphold that code of conduct.
+
+[CoC]: CODE_OF_CONDUCT.md
+[team]: https://github.com/rust-embedded/wg#the-embedded-linux-team
diff --git a/cargo_embargo.json b/cargo_embargo.json
new file mode 100644
index 0000000..cb908d7
--- /dev/null
+++ b/cargo_embargo.json
@@ -0,0 +1,3 @@
+{
+ "run_cargo": false
+}
diff --git a/examples/async_tokio.rs b/examples/async_tokio.rs
new file mode 100644
index 0000000..6fd90cd
--- /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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use futures::stream::StreamExt;
+use gpio_cdev::{AsyncLineEventHandle, Chip, EventRequestFlags, LineRequestFlags};
+use structopt::StructOpt;
+
+#[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<(), gpio_cdev::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/examples/blinky.rs b/examples/blinky.rs
new file mode 100644
index 0000000..8578648
--- /dev/null
+++ b/examples/blinky.rs
@@ -0,0 +1,54 @@
+// Copyright (c) 2018 The rust-gpio-cdev Project Developers.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use gpio_cdev::{Chip, LineRequestFlags};
+use quicli::prelude::*;
+use std::thread::sleep;
+use std::time::{Duration, Instant};
+use structopt::StructOpt;
+
+#[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,
+ /// Period in milliseconds
+ period_ms: u64,
+ /// Duration over which to blink in milliseconds
+ duration_ms: u64,
+}
+
+fn do_main(args: Cli) -> std::result::Result<(), gpio_cdev::Error> {
+ let mut chip = Chip::new(args.chip)?;
+
+ // NOTE: we set the default value to the desired state so
+ // setting it separately is not required
+ let handle = chip
+ .get_line(args.line)?
+ .request(LineRequestFlags::OUTPUT, 1, "blinky")?;
+
+ let duration = Duration::from_millis(args.duration_ms);
+ let start_time = Instant::now();
+ while start_time.elapsed() < duration {
+ sleep(Duration::from_millis(args.period_ms));
+ handle.set_value(0)?;
+ sleep(Duration::from_millis(args.period_ms));
+ handle.set_value(1)?;
+ }
+
+ Ok(())
+}
+
+fn main() -> CliResult {
+ let args = Cli::from_args();
+ do_main(args).or_else(|e| {
+ error!("{:?}", e);
+ Ok(())
+ })
+}
diff --git a/examples/driveoutput.rs b/examples/driveoutput.rs
new file mode 100644
index 0000000..1e2be61
--- /dev/null
+++ b/examples/driveoutput.rs
@@ -0,0 +1,50 @@
+// Copyright (c) 2018 The rust-gpio-cdev Project Developers.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use gpio_cdev::{Chip, LineRequestFlags};
+use quicli::prelude::*;
+use structopt::StructOpt;
+
+#[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,
+ /// The value to write
+ value: u8,
+}
+
+fn do_main(args: Cli) -> std::result::Result<(), gpio_cdev::Error> {
+ let mut chip = Chip::new(args.chip)?;
+
+ // NOTE: we set the default value to the desired state so
+ // setting it separately is not required. The LineHandle
+ // instance that is returned by request must be owned by a
+ // variable for the duration of the time that the line will
+ // be used. If the instance is not assigned to a variable,
+ // then the LineHandle will be immediately dropped after
+ // request returns and the pin will appear to do nothing.
+ let _handle =
+ chip.get_line(args.line)?
+ .request(LineRequestFlags::OUTPUT, args.value, "driveoutput")?;
+
+ println!("Output being driven... Enter to exit");
+ let mut buf = String::new();
+ ::std::io::stdin().read_line(&mut buf)?;
+
+ Ok(())
+}
+
+fn main() -> CliResult {
+ let args = Cli::from_args();
+ do_main(args).or_else(|e| {
+ error!("{:?}", e);
+ Ok(())
+ })
+}
diff --git a/examples/gpioevents.rs b/examples/gpioevents.rs
new file mode 100644
index 0000000..3f02877
--- /dev/null
+++ b/examples/gpioevents.rs
@@ -0,0 +1,42 @@
+// Copyright (c) 2018 The rust-gpio-cdev Project Developers.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use gpio_cdev::{Chip, EventRequestFlags, LineRequestFlags};
+use quicli::prelude::*;
+use structopt::StructOpt;
+
+#[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,
+}
+
+fn do_main(args: Cli) -> std::result::Result<(), gpio_cdev::Error> {
+ let mut chip = Chip::new(args.chip)?;
+ let line = chip.get_line(args.line)?;
+
+ for event in line.events(
+ LineRequestFlags::INPUT,
+ EventRequestFlags::BOTH_EDGES,
+ "gpioevents",
+ )? {
+ println!("{:?}", event?);
+ }
+
+ Ok(())
+}
+
+fn main() -> CliResult {
+ let args = Cli::from_args();
+ do_main(args).or_else(|e| {
+ error!("{:?}", e);
+ Ok(())
+ })
+}
diff --git a/examples/lsgpio.rs b/examples/lsgpio.rs
new file mode 100644
index 0000000..414aedb
--- /dev/null
+++ b/examples/lsgpio.rs
@@ -0,0 +1,74 @@
+// Copyright (c) 2018 The rust-gpio-cdev Project Developers.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//! Clone of functionality of linux/tools/gpio/lsgpio.c
+
+use gpio_cdev::*;
+
+fn main() {
+ let chip_iterator = match chips() {
+ Ok(chips) => chips,
+ Err(e) => {
+ println!("Failed to get chip iterator: {:?}", e);
+ return;
+ }
+ };
+
+ for chip in chip_iterator {
+ if let Ok(chip) = chip {
+ println!(
+ "GPIO chip: {}, \"{}\", \"{}\", {} GPIO Lines",
+ chip.path().to_string_lossy(),
+ chip.name(),
+ chip.label(),
+ chip.num_lines()
+ );
+ for line in chip.lines() {
+ match line.info() {
+ Ok(info) => {
+ let mut flags = vec![];
+
+ if info.is_kernel() {
+ flags.push("kernel");
+ }
+
+ if info.direction() == LineDirection::Out {
+ flags.push("output");
+ }
+
+ if info.is_active_low() {
+ flags.push("active-low");
+ }
+ if info.is_open_drain() {
+ flags.push("open-drain");
+ }
+ if info.is_open_source() {
+ flags.push("open-source");
+ }
+
+ let usage = if !flags.is_empty() {
+ format!("[{}]", flags.join(" "))
+ } else {
+ "".to_owned()
+ };
+
+ println!(
+ "\tline {lineno:>3}: {name} {consumer} {usage}",
+ lineno = info.line().offset(),
+ name = info.name().unwrap_or("unused"),
+ consumer = info.consumer().unwrap_or("unused"),
+ usage = usage,
+ );
+ }
+ Err(e) => println!("\tError getting line info: {:?}", e),
+ }
+ }
+ println!();
+ }
+ }
+}
diff --git a/examples/monitor.rs b/examples/monitor.rs
new file mode 100644
index 0000000..7925907
--- /dev/null
+++ b/examples/monitor.rs
@@ -0,0 +1,85 @@
+// Copyright (c) 2018 The rust-gpio-cdev Project Developers.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use gpio_cdev::*;
+use nix::poll::*;
+use quicli::prelude::*;
+use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd};
+use structopt::StructOpt;
+
+type PollEventFlags = nix::poll::PollFlags;
+
+#[derive(Debug, StructOpt)]
+struct Cli {
+ /// The gpiochip device (e.g. /dev/gpiochip0)
+ chip: String,
+ /// The offset of the GPIO lines for the provided chip
+ lines: Vec<u32>,
+}
+
+fn do_main(args: Cli) -> anyhow::Result<()> {
+ let mut chip = Chip::new(args.chip)?;
+
+ // Get event handles for each line to monitor.
+ let mut evt_handles: Vec<LineEventHandle> = args
+ .lines
+ .into_iter()
+ .map(|off| {
+ let line = chip.get_line(off).unwrap();
+ line.events(
+ LineRequestFlags::INPUT,
+ EventRequestFlags::BOTH_EDGES,
+ "monitor",
+ )
+ .unwrap()
+ })
+ .collect();
+
+ // Create a vector of file descriptors for polling
+ let ownedfd: Vec<OwnedFd> = evt_handles
+ .iter()
+ .map(|h| unsafe { OwnedFd::from_raw_fd(h.as_raw_fd()) })
+ .collect();
+ let mut pollfds: Vec<PollFd> = ownedfd
+ .iter()
+ .map(|fd| PollFd::new(fd, PollEventFlags::POLLIN | PollEventFlags::POLLPRI))
+ .collect();
+
+ loop {
+ // poll for an event on any of the lines
+ if poll(&mut pollfds, -1)? == 0 {
+ println!("Timeout?!?");
+ } else {
+ for i in 0..pollfds.len() {
+ if let Some(revts) = pollfds[i].revents() {
+ let h = &mut evt_handles[i];
+ if revts.contains(PollEventFlags::POLLIN) {
+ let event = h.get_event()?;
+ println!("[{}] {:?}", h.line().offset(), event);
+
+ // You can figure out the new level from the event,
+ // but this shows that you can use the event handle
+ // to read the value of the bit.
+ let val = h.get_value()?;
+ println!(" {}", val);
+ } else if revts.contains(PollEventFlags::POLLPRI) {
+ println!("[{}] Got a POLLPRI", h.line().offset());
+ }
+ }
+ }
+ }
+ }
+}
+
+fn main() -> CliResult {
+ let args = Cli::from_args();
+ do_main(args).or_else(|e| {
+ error!("{:?}", e);
+ Ok(())
+ })
+}
diff --git a/examples/multioutput.rs b/examples/multioutput.rs
new file mode 100644
index 0000000..4f0acae
--- /dev/null
+++ b/examples/multioutput.rs
@@ -0,0 +1,58 @@
+// Copyright (c) 2018 The rust-gpio-cdev Project Developers.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use gpio_cdev::{Chip, LineRequestFlags};
+use quicli::prelude::*;
+use structopt::StructOpt;
+
+#[derive(Debug, StructOpt)]
+struct Cli {
+ /// The gpiochip device (e.g. /dev/gpiochip0)
+ chip: String,
+ /// The offset and value of each GPIO line for the provided chip
+ /// in the form "off=<0|1>"
+ line_values: Vec<String>,
+}
+
+// Use like:
+// muiltioutput /dev/gpiochip0 0=1 1=1 2=0 3=1 4=0
+//
+// to set lines 0, 1, & 3 high
+// 2 & 4 low
+//
+fn do_main(args: Cli) -> std::result::Result<(), gpio_cdev::Error> {
+ let mut chip = Chip::new(args.chip)?;
+ let mut offsets = Vec::new();
+ let mut values = Vec::new();
+
+ for arg in args.line_values {
+ let lv: Vec<&str> = arg.split("=").collect();
+ offsets.push(lv[0].parse::<u32>().unwrap());
+ values.push(lv[1].parse::<u8>().unwrap());
+ }
+
+ // NOTE: we set the default values to the desired states so
+ // setting them separately is not required
+ let _handle =
+ chip.get_lines(&offsets)?
+ .request(LineRequestFlags::OUTPUT, &values, "multioutput")?;
+
+ println!("Output lines being driven... Enter to exit");
+ let mut buf = String::new();
+ ::std::io::stdin().read_line(&mut buf)?;
+
+ Ok(())
+}
+
+fn main() -> CliResult {
+ let args = Cli::from_args();
+ do_main(args).or_else(|e| {
+ error!("{:?}", e);
+ Ok(())
+ })
+}
diff --git a/examples/multiread.rs b/examples/multiread.rs
new file mode 100644
index 0000000..f79e027
--- /dev/null
+++ b/examples/multiread.rs
@@ -0,0 +1,38 @@
+// Copyright (c) 2018 The rust-gpio-cdev Project Developers.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use gpio_cdev::{Chip, LineRequestFlags};
+use quicli::prelude::*;
+use structopt::StructOpt;
+
+#[derive(Debug, StructOpt)]
+struct Cli {
+ /// The gpiochip device (e.g. /dev/gpiochip0)
+ chip: String,
+ /// The offset of the GPIO lines for the provided chip
+ lines: Vec<u32>,
+}
+
+fn do_main(args: Cli) -> std::result::Result<(), gpio_cdev::Error> {
+ let mut chip = Chip::new(args.chip)?;
+ let ini_vals = vec![0; args.lines.len()];
+ let handle =
+ chip.get_lines(&args.lines)?
+ .request(LineRequestFlags::INPUT, &ini_vals, "multiread")?;
+ println!("Values: {:?}", handle.get_values()?);
+
+ Ok(())
+}
+
+fn main() -> CliResult {
+ let args = Cli::from_args();
+ do_main(args).or_else(|e| {
+ error!("{:?}", e);
+ Ok(())
+ })
+}
diff --git a/examples/readall.rs b/examples/readall.rs
new file mode 100644
index 0000000..dc9ec15
--- /dev/null
+++ b/examples/readall.rs
@@ -0,0 +1,36 @@
+// Copyright (c) 2018 The rust-gpio-cdev Project Developers.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use gpio_cdev::{Chip, LineRequestFlags};
+use quicli::prelude::*;
+use structopt::StructOpt;
+
+#[derive(Debug, StructOpt)]
+struct Cli {
+ /// The gpiochip device (e.g. /dev/gpiochip0)
+ chip: String,
+}
+
+fn do_main(args: Cli) -> std::result::Result<(), gpio_cdev::Error> {
+ let mut chip = Chip::new(args.chip)?;
+ let ini_vals = vec![0; chip.num_lines() as usize];
+ let handle = chip
+ .get_all_lines()?
+ .request(LineRequestFlags::INPUT, &ini_vals, "readall")?;
+ println!("Values: {:?}", handle.get_values()?);
+
+ Ok(())
+}
+
+fn main() -> CliResult {
+ let args = Cli::from_args();
+ do_main(args).or_else(|e| {
+ error!("{:?}", e);
+ Ok(())
+ })
+}
diff --git a/examples/readinput.rs b/examples/readinput.rs
new file mode 100644
index 0000000..5a61993
--- /dev/null
+++ b/examples/readinput.rs
@@ -0,0 +1,37 @@
+// Copyright (c) 2018 The rust-gpio-cdev Project Developers.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use gpio_cdev::{Chip, LineRequestFlags};
+use quicli::prelude::*;
+use structopt::StructOpt;
+
+#[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,
+}
+
+fn do_main(args: Cli) -> std::result::Result<(), gpio_cdev::Error> {
+ let mut chip = Chip::new(args.chip)?;
+ let handle = chip
+ .get_line(args.line)?
+ .request(LineRequestFlags::INPUT, 0, "readinput")?;
+ println!("Value: {:?}", handle.get_value()?);
+
+ Ok(())
+}
+
+fn main() -> CliResult {
+ let args = Cli::from_args();
+ do_main(args).or_else(|e| {
+ error!("{:?}", e);
+ Ok(())
+ })
+}
diff --git a/examples/tit_for_tat.rs b/examples/tit_for_tat.rs
new file mode 100644
index 0000000..c22768f
--- /dev/null
+++ b/examples/tit_for_tat.rs
@@ -0,0 +1,64 @@
+// Copyright (c) 2018 The rust-gpio-cdev Project Developers.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use gpio_cdev::{Chip, EventRequestFlags, EventType, LineRequestFlags};
+use quicli::prelude::*;
+use std::thread::sleep;
+use std::time::Duration;
+use structopt::StructOpt;
+
+#[derive(Debug, StructOpt)]
+struct Cli {
+ /// The gpiochip device (e.g. /dev/gpiochip0)
+ chip: String,
+ /// The offset of the GPIO input line for the provided chip
+ inputline: u32,
+ /// The offset of the GPIO output line for the provided chip
+ outputline: u32,
+ /// Sleep time after each actuation in milliseconds
+ sleeptime: u64,
+}
+
+fn do_main(args: Cli) -> std::result::Result<(), gpio_cdev::Error> {
+ let mut chip = Chip::new(args.chip)?;
+ let input = chip.get_line(args.inputline)?;
+ let output = chip.get_line(args.outputline)?;
+ let output_handle = output.request(LineRequestFlags::OUTPUT, 0, "tit_for_tat")?;
+
+ // To show off the buffering characteristics of the new interface we introduce a delay
+ // after each change is handled. When we fall behind, we will "replay" the input
+ // events
+ for event in input.events(
+ LineRequestFlags::INPUT,
+ EventRequestFlags::BOTH_EDGES,
+ "tit_for_tat",
+ )? {
+ let evt = event?;
+ println!("{:?}", evt);
+ match evt.event_type() {
+ EventType::RisingEdge => {
+ output_handle.set_value(1)?;
+ sleep(Duration::from_millis(args.sleeptime));
+ }
+ EventType::FallingEdge => {
+ output_handle.set_value(0)?;
+ sleep(Duration::from_millis(args.sleeptime));
+ }
+ }
+ }
+
+ Ok(())
+}
+
+fn main() -> CliResult {
+ let args = Cli::from_args();
+ do_main(args).or_else(|e| {
+ error!("{:?}", e);
+ Ok(())
+ })
+}
diff --git a/src/async_tokio.rs b/src/async_tokio.rs
new file mode 100644
index 0000000..327272c
--- /dev/null
+++ b/src/async_tokio.rs
@@ -0,0 +1,102 @@
+// Copyright (c) 2018 The rust-gpio-cdev Project Developers.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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 tokio::io::unix::{AsyncFd, TryIoError};
+
+use std::os::unix::io::AsRawFd;
+use std::pin::Pin;
+
+use super::event_err;
+use super::{LineEvent, LineEventHandle, Result};
+
+/// 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
+/// use futures::stream::StreamExt;
+/// use gpio_cdev::{AsyncLineEventHandle, Chip, EventRequestFlags, LineRequestFlags};
+///
+/// async fn print_events(line: u32) -> Result<(), gpio_cdev::Error> {
+/// 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 {
+ asyncfd: AsyncFd<LineEventHandle>,
+}
+
+impl AsyncLineEventHandle {
+ /// Wraps the specified `LineEventHandle`.
+ ///
+ /// # Arguments
+ ///
+ /// * `handle` - handle to be wrapped.
+ pub fn new(handle: LineEventHandle) -> Result<AsyncLineEventHandle> {
+ // 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 {
+ asyncfd: AsyncFd::new(handle)?,
+ })
+ }
+}
+
+impl Stream for AsyncLineEventHandle {
+ type Item = Result<LineEvent>;
+
+ fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
+ loop {
+ let mut guard = ready!(self.asyncfd.poll_read_ready_mut(cx))?;
+ match guard.try_io(|inner| inner.get_mut().read_event()) {
+ Err(TryIoError { .. }) => {
+ // Continue
+ }
+ Ok(Ok(Some(event))) => return Poll::Ready(Some(Ok(event))),
+ Ok(Ok(None)) => return Poll::Ready(Some(Err(event_err(nix::errno::Errno::EIO)))),
+ Ok(Err(err)) => return Poll::Ready(Some(Err(err.into()))),
+ }
+ }
+ }
+}
+
+impl AsRef<LineEventHandle> for AsyncLineEventHandle {
+ fn as_ref(&self) -> &LineEventHandle {
+ self.asyncfd.get_ref()
+ }
+}
diff --git a/src/errors.rs b/src/errors.rs
new file mode 100644
index 0000000..3081016
--- /dev/null
+++ b/src/errors.rs
@@ -0,0 +1,96 @@
+//! This module is deprecated and types are exported from the top-level of the crate
+//!
+//! In futures versions of the crate, this module will no longer be included in the crate.
+
+use crate::IoctlKind;
+use std::error::Error as StdError;
+use std::fmt;
+use std::io::Error as IOError;
+
+pub(crate) type Result<T> = std::result::Result<T, Error>;
+
+#[derive(Debug)]
+pub struct Error {
+ kind: ErrorKind,
+}
+
+#[derive(Debug)]
+pub enum ErrorKind {
+ Event(nix::Error),
+ Io(IOError),
+ Ioctl { kind: IoctlKind, cause: nix::Error },
+ InvalidRequest(usize, usize),
+ Offset(u32),
+}
+
+pub(crate) fn ioctl_err(kind: IoctlKind, cause: nix::Error) -> Error {
+ Error {
+ kind: ErrorKind::Ioctl { kind, cause },
+ }
+}
+
+pub(crate) fn invalid_err(n_lines: usize, n_values: usize) -> Error {
+ Error {
+ kind: ErrorKind::InvalidRequest(n_lines, n_values),
+ }
+}
+
+pub(crate) fn offset_err(offset: u32) -> Error {
+ Error {
+ kind: ErrorKind::Offset(offset),
+ }
+}
+
+pub(crate) fn event_err(err: nix::Error) -> Error {
+ Error {
+ kind: ErrorKind::Event(err),
+ }
+}
+
+impl fmt::Display for IoctlKind {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ IoctlKind::ChipInfo => write!(f, "get chip info"),
+ IoctlKind::LineInfo => write!(f, "get line info"),
+ IoctlKind::LineHandle => write!(f, "get line handle"),
+ IoctlKind::LineEvent => write!(f, "get line event "),
+ IoctlKind::GetLine => write!(f, "get line value"),
+ IoctlKind::SetLine => write!(f, "set line value"),
+ }
+ }
+}
+
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match &self.kind {
+ ErrorKind::Event(err) => write!(f, "Failed to read event: {}", err),
+ ErrorKind::Io(err) => err.fmt(f),
+ ErrorKind::Ioctl { cause, kind } => write!(f, "Ioctl to {} failed: {}", kind, cause),
+ ErrorKind::InvalidRequest(n_lines, n_values) => write!(
+ f,
+ "Invalid request: {} values requested to be set but only {} lines are open",
+ n_values, n_lines
+ ),
+ ErrorKind::Offset(offset) => write!(f, "Offset {} is out of range", offset),
+ }
+ }
+}
+
+impl StdError for Error {
+ fn source(&self) -> Option<&(dyn StdError + 'static)> {
+ match &self.kind {
+ ErrorKind::Event(err) => Some(err),
+ ErrorKind::Io(err) => Some(err),
+ ErrorKind::Ioctl { kind: _, cause } => Some(cause),
+ _ => None,
+ }
+ }
+}
+
+impl From<IOError> for Error {
+ fn from(err: IOError) -> Self {
+ Self {
+ kind: ErrorKind::Io(err),
+ }
+ }
+}
diff --git a/src/ffi.rs b/src/ffi.rs
new file mode 100644
index 0000000..ceb8f72
--- /dev/null
+++ b/src/ffi.rs
@@ -0,0 +1,107 @@
+// Copyright (c) 2018 The rust-gpio-cdev Project Developers.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::IoctlKind;
+
+pub const GPIOHANDLES_MAX: usize = 64;
+
+// struct gpiochip_info
+#[repr(C)]
+pub struct gpiochip_info {
+ pub name: [libc::c_char; 32],
+ pub label: [libc::c_char; 32],
+ pub lines: u32,
+}
+
+#[repr(C)]
+pub struct gpioline_info {
+ pub line_offset: u32,
+ pub flags: u32,
+ pub name: [libc::c_char; 32],
+ pub consumer: [libc::c_char; 32],
+}
+
+#[repr(C)]
+pub struct gpiohandle_request {
+ pub lineoffsets: [u32; GPIOHANDLES_MAX],
+ pub flags: u32,
+ pub default_values: [u8; GPIOHANDLES_MAX],
+ pub consumer_label: [libc::c_char; 32],
+ pub lines: u32,
+ pub fd: libc::c_int,
+}
+
+#[repr(C)]
+pub struct gpiohandle_data {
+ pub values: [u8; GPIOHANDLES_MAX],
+}
+
+#[repr(C)]
+pub struct gpioevent_request {
+ pub lineoffset: u32,
+ pub handleflags: u32,
+ pub eventflags: u32,
+ pub consumer_label: [libc::c_char; 32],
+ pub fd: libc::c_int,
+}
+
+#[repr(C)]
+pub struct gpioevent_data {
+ pub timestamp: u64,
+ pub id: u32,
+}
+
+macro_rules! wrap_ioctl {
+ ($ioctl_macro:ident!($name:ident, $ioty:expr, $nr:expr, $ty:ident), $ioctl_error_type:expr) => {
+ mod $name {
+ $ioctl_macro!($name, $ioty, $nr, super::$ty);
+ }
+
+ pub(crate) fn $name(fd: libc::c_int, data: &mut $ty) -> crate::errors::Result<libc::c_int> {
+ unsafe {
+ $name::$name(fd, data).map_err(|e| crate::errors::ioctl_err($ioctl_error_type, e))
+ }
+ }
+ };
+}
+
+wrap_ioctl!(
+ ioctl_read!(gpio_get_chipinfo_ioctl, 0xB4, 0x01, gpiochip_info),
+ IoctlKind::ChipInfo
+);
+wrap_ioctl!(
+ ioctl_readwrite!(gpio_get_lineinfo_ioctl, 0xB4, 0x02, gpioline_info),
+ IoctlKind::LineInfo
+);
+wrap_ioctl!(
+ ioctl_readwrite!(gpio_get_linehandle_ioctl, 0xB4, 0x03, gpiohandle_request),
+ IoctlKind::LineHandle
+);
+wrap_ioctl!(
+ ioctl_readwrite!(gpio_get_lineevent_ioctl, 0xB4, 0x04, gpioevent_request),
+ IoctlKind::LineEvent
+);
+
+wrap_ioctl!(
+ ioctl_readwrite!(
+ gpiohandle_get_line_values_ioctl,
+ 0xB4,
+ 0x08,
+ gpiohandle_data
+ ),
+ IoctlKind::GetLine
+);
+wrap_ioctl!(
+ ioctl_readwrite!(
+ gpiohandle_set_line_values_ioctl,
+ 0xB4,
+ 0x09,
+ gpiohandle_data
+ ),
+ IoctlKind::SetLine
+);
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..b8b6506
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,1041 @@
+// Copyright (c) 2018 The rust-gpio-cdev Project Developers.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//! The `gpio-cdev` crate provides access to the [GPIO character device
+//! ABI](https://www.kernel.org/doc/Documentation/ABI/testing/gpio-cdev). This API,
+//! stabilized with Linux v4.4, deprecates the legacy sysfs interface to GPIOs that is
+//! planned to be removed from the upstream kernel after
+//! year 2020 (which is coming up quickly).
+//!
+//! This crate attempts to wrap this interface in a moderately direction fashion
+//! while retaining safety and using Rust idioms (where doing so could be mapped
+//! to the underlying abstraction without significant overhead or loss of
+//! functionality).
+//!
+//! For additional context for why the kernel is moving from the sysfs API to the
+//! character device API, please see the main [README on Github].
+//!
+//! # Examples
+//!
+//! The following example reads the state of a GPIO line/pin and writes the matching
+//! state to another line/pin.
+//!
+//! ```no_run
+//! use gpio_cdev::{Chip, LineRequestFlags, EventRequestFlags, EventType};
+//!
+//! // Lines are offset within gpiochip0; see docs for more info on chips/lines
+//! fn mirror_gpio(inputline: u32, outputline: u32) -> Result<(), gpio_cdev::Error> {
+//! let mut chip = Chip::new("/dev/gpiochip0")?;
+//! let input = chip.get_line(inputline)?;
+//! let output = chip.get_line(outputline)?;
+//! let output_handle = output.request(LineRequestFlags::OUTPUT, 0, "mirror-gpio")?;
+//! for event in input.events(
+//! LineRequestFlags::INPUT,
+//! EventRequestFlags::BOTH_EDGES,
+//! "mirror-gpio",
+//! )? {
+//! let evt = event?;
+//! println!("{:?}", evt);
+//! match evt.event_type() {
+//! EventType::RisingEdge => {
+//! output_handle.set_value(1)?;
+//! }
+//! EventType::FallingEdge => {
+//! output_handle.set_value(0)?;
+//! }
+//! }
+//! }
+//!
+//! Ok(())
+//! }
+//!
+//! # fn main() -> Result<(), gpio_cdev::Error> {
+//! # mirror_gpio(0, 1)
+//! # }
+//! ```
+//!
+//! To get the state of a GPIO Line on a given chip:
+//!
+//! ```no_run
+//! use gpio_cdev::{Chip, LineRequestFlags};
+//!
+//! # fn main() -> Result<(), gpio_cdev::Error> {
+//! // Read the state of GPIO4 on a raspberry pi. /dev/gpiochip0
+//! // maps to the driver for the SoC (builtin) GPIO controller.
+//! // The LineHandle returned by request must be assigned to a
+//! // variable (in this case the variable handle) to ensure that
+//! // the corresponding file descriptor is not closed.
+//! let mut chip = Chip::new("/dev/gpiochip0")?;
+//! let handle = chip
+//! .get_line(4)?
+//! .request(LineRequestFlags::INPUT, 0, "read-input")?;
+//! for _ in 1..4 {
+//! println!("Value: {:?}", handle.get_value()?);
+//! }
+//! # Ok(()) }
+//! ```
+//!
+//! [README on Github]: https://github.com/rust-embedded/rust-gpio-cdev
+
+#![cfg_attr(docsrs, feature(doc_cfg))]
+
+#[macro_use]
+extern crate bitflags;
+#[macro_use]
+extern crate nix;
+
+use std::cmp::min;
+use std::ffi::CStr;
+use std::fs::{read_dir, File, ReadDir};
+use std::io::Read;
+use std::mem;
+use std::ops::Index;
+use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, RawFd};
+use std::path::{Path, PathBuf};
+use std::ptr;
+use std::slice;
+use std::sync::Arc;
+
+#[cfg(feature = "async-tokio")]
+#[cfg_attr(docsrs, doc(cfg(feature = "async-tokio")))]
+mod async_tokio;
+pub mod errors; // pub portion is deprecated
+mod ffi;
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum IoctlKind {
+ ChipInfo,
+ LineInfo,
+ LineHandle,
+ LineEvent,
+ GetLine,
+ SetLine,
+}
+
+#[cfg(feature = "async-tokio")]
+#[cfg_attr(docsrs, doc(cfg(feature = "async-tokio")))]
+pub use crate::async_tokio::AsyncLineEventHandle;
+pub use errors::*;
+
+unsafe fn rstr_lcpy(dst: *mut libc::c_char, src: &str, length: usize) {
+ let copylen = min(src.len() + 1, length);
+ ptr::copy_nonoverlapping(src.as_bytes().as_ptr().cast(), dst, copylen - 1);
+ slice::from_raw_parts_mut(dst, length)[copylen - 1] = 0;
+}
+
+#[derive(Debug)]
+struct InnerChip {
+ pub path: PathBuf,
+ pub file: File,
+ pub name: String,
+ pub label: String,
+ pub lines: u32,
+}
+
+/// A GPIO Chip maps to the actual device driver instance in hardware that
+/// one interacts with to interact with individual GPIOs. Often these chips
+/// map to IP chunks on an SoC but could also be enumerated within the kernel
+/// via something like a PCI or USB bus.
+///
+/// The Linux kernel itself enumerates GPIO character devices at two paths:
+/// 1. `/dev/gpiochipN`
+/// 2. `/sys/bus/gpiochipN`
+///
+/// It is best not to assume that a device will always be enumerated in the
+/// same order (especially if it is connected via a bus). In order to reliably
+/// find the correct chip, there are a few approaches that one could reasonably
+/// take:
+///
+/// 1. Create a udev rule that will match attributes of the device and
+/// setup a symlink to the device.
+/// 2. Iterate over all available chips using the [`chips()`] call to find the
+/// device with matching criteria.
+/// 3. For simple cases, just using the enumerated path is fine (demo work). This
+/// is discouraged for production.
+///
+/// [`chips()`]: fn.chips.html
+#[derive(Debug)]
+pub struct Chip {
+ inner: Arc<InnerChip>,
+}
+
+/// Iterator over chips
+#[derive(Debug)]
+pub struct ChipIterator {
+ readdir: ReadDir,
+}
+
+impl Iterator for ChipIterator {
+ type Item = Result<Chip>;
+
+ fn next(&mut self) -> Option<Result<Chip>> {
+ for entry in &mut self.readdir {
+ match entry {
+ Ok(entry) => {
+ if entry
+ .path()
+ .as_path()
+ .to_string_lossy()
+ .contains("gpiochip")
+ {
+ return Some(Chip::new(entry.path()));
+ }
+ }
+ Err(e) => {
+ return Some(Err(e.into()));
+ }
+ }
+ }
+
+ None
+ }
+}
+
+/// Iterate over all GPIO chips currently present on this system
+pub fn chips() -> Result<ChipIterator> {
+ Ok(ChipIterator {
+ readdir: read_dir("/dev")?,
+ })
+}
+
+impl Chip {
+ /// Open the GPIO Chip at the provided path (e.g. `/dev/gpiochip<N>`)
+ pub fn new<P: AsRef<Path>>(path: P) -> Result<Self> {
+ let f = File::open(path.as_ref())?;
+ let mut info: ffi::gpiochip_info = unsafe { mem::zeroed() };
+ ffi::gpio_get_chipinfo_ioctl(f.as_raw_fd(), &mut info)?;
+
+ Ok(Self {
+ inner: Arc::new(InnerChip {
+ file: f,
+ path: path.as_ref().to_path_buf(),
+ name: unsafe {
+ CStr::from_ptr(info.name.as_ptr())
+ .to_string_lossy()
+ .into_owned()
+ },
+ label: unsafe {
+ CStr::from_ptr(info.label.as_ptr())
+ .to_string_lossy()
+ .into_owned()
+ },
+ lines: info.lines,
+ }),
+ })
+ }
+
+ /// Get the fs path of this character device (e.g. `/dev/gpiochipN`)
+ pub fn path(&self) -> &Path {
+ self.inner.path.as_path()
+ }
+
+ /// The name of the device driving this GPIO chip in the kernel
+ pub fn name(&self) -> &str {
+ self.inner.name.as_str()
+ }
+
+ /// A functional name for this GPIO chip, such as a product number. Might
+ /// be an empty string.
+ ///
+ /// As an example, the SoC GPIO chip on a Raspberry Pi is "pinctrl-bcm2835"
+ pub fn label(&self) -> &str {
+ self.inner.label.as_str()
+ }
+
+ /// The number of lines/pins indexable through this chip
+ ///
+ /// Not all of these may be usable depending on how the hardware is
+ /// configured/muxed.
+ pub fn num_lines(&self) -> u32 {
+ self.inner.lines
+ }
+
+ /// Get a handle to the GPIO line at a given offset
+ ///
+ /// The actual physical line corresponding to a given offset
+ /// is completely dependent on how the driver/hardware for
+ /// the chip works as well as the associated board layout.
+ ///
+ /// For a device like the NXP i.mx6 SoC GPIO controller there
+ /// are several banks of GPIOs with each bank containing 32
+ /// GPIOs. For this hardware and driver something like
+ /// `GPIO2_5` would map to offset 37.
+ pub fn get_line(&mut self, offset: u32) -> Result<Line> {
+ Line::new(self.inner.clone(), offset)
+ }
+
+ /// Get a handle to multiple GPIO line at a given offsets
+ ///
+ /// The group of lines can be manipulated simultaneously.
+ pub fn get_lines(&mut self, offsets: &[u32]) -> Result<Lines> {
+ Lines::new(self.inner.clone(), offsets)
+ }
+
+ /// Get a handle to all the GPIO lines on the chip
+ ///
+ /// The group of lines can be manipulated simultaneously.
+ pub fn get_all_lines(&mut self) -> Result<Lines> {
+ let offsets: Vec<u32> = (0..self.num_lines()).collect();
+ self.get_lines(&offsets)
+ }
+
+ /// Get an interator over all lines that can be potentially access for this
+ /// chip.
+ pub fn lines(&self) -> LineIterator {
+ LineIterator {
+ chip: self.inner.clone(),
+ idx: 0,
+ }
+ }
+}
+
+/// Iterator over GPIO Lines for a given chip.
+#[derive(Debug)]
+pub struct LineIterator {
+ chip: Arc<InnerChip>,
+ idx: u32,
+}
+
+impl Iterator for LineIterator {
+ type Item = Line;
+
+ fn next(&mut self) -> Option<Line> {
+ if self.idx < self.chip.lines {
+ let idx = self.idx;
+ self.idx += 1;
+ // Since we checked the index, we know this will be Ok
+ Some(Line::new(self.chip.clone(), idx).unwrap())
+ } else {
+ None
+ }
+ }
+}
+
+/// Access to a specific GPIO Line
+///
+/// GPIO Lines must be obtained through a parent [`Chip`] and
+/// represent an actual GPIO pin/line accessible via that chip.
+/// Not all accessible lines for a given chip may actually
+/// map to hardware depending on how the board is setup
+/// in the kernel.
+///
+#[derive(Debug, Clone)]
+pub struct Line {
+ chip: Arc<InnerChip>,
+ offset: u32,
+}
+
+/// Information about a specific GPIO Line
+///
+/// Wraps kernel [`struct gpioline_info`].
+///
+/// [`struct gpioline_info`]: https://elixir.bootlin.com/linux/v4.9.127/source/include/uapi/linux/gpio.h#L36
+#[derive(Debug)]
+pub struct LineInfo {
+ line: Line,
+ flags: LineFlags,
+ name: Option<String>,
+ consumer: Option<String>,
+}
+
+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
+ #[derive(Debug, Clone)]
+ pub struct LineRequestFlags: u32 {
+ const INPUT = (1 << 0);
+ const OUTPUT = (1 << 1);
+ const ACTIVE_LOW = (1 << 2);
+ const OPEN_DRAIN = (1 << 3);
+ const OPEN_SOURCE = (1 << 4);
+ }
+}
+
+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);
+ const BOTH_EDGES = Self::RISING_EDGE.bits() | Self::FALLING_EDGE.bits();
+ }
+}
+
+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
+ #[derive(Debug)]
+ pub struct LineFlags: u32 {
+ const KERNEL = (1 << 0);
+ const IS_OUT = (1 << 1);
+ const ACTIVE_LOW = (1 << 2);
+ const OPEN_DRAIN = (1 << 3);
+ const OPEN_SOURCE = (1 << 4);
+ }
+}
+
+/// In or Out
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum LineDirection {
+ In,
+ Out,
+}
+
+unsafe fn cstrbuf_to_string(buf: &[libc::c_char]) -> Option<String> {
+ if buf[0] == 0 {
+ None
+ } else {
+ Some(CStr::from_ptr(buf.as_ptr()).to_string_lossy().into_owned())
+ }
+}
+
+impl Line {
+ fn new(chip: Arc<InnerChip>, offset: u32) -> Result<Self> {
+ if offset >= chip.lines {
+ return Err(offset_err(offset));
+ }
+ Ok(Self { chip, offset })
+ }
+
+ /// Get info about the line from the kernel.
+ pub fn info(&self) -> Result<LineInfo> {
+ let mut line_info = ffi::gpioline_info {
+ line_offset: self.offset,
+ flags: 0,
+ name: [0; 32],
+ consumer: [0; 32],
+ };
+ ffi::gpio_get_lineinfo_ioctl(self.chip.file.as_raw_fd(), &mut line_info)?;
+
+ Ok(LineInfo {
+ line: self.clone(),
+ flags: LineFlags::from_bits_truncate(line_info.flags),
+ name: unsafe { cstrbuf_to_string(&line_info.name[..]) },
+ consumer: unsafe { cstrbuf_to_string(&line_info.consumer[..]) },
+ })
+ }
+
+ /// Offset of this line within its parent chip
+ pub fn offset(&self) -> u32 {
+ self.offset
+ }
+
+ /// Get a handle to this chip's parent
+ pub fn chip(&self) -> Chip {
+ Chip {
+ inner: self.chip.clone(),
+ }
+ }
+
+ /// Request access to interact with this line from the kernel
+ ///
+ /// This is similar to the "export" operation present in the sysfs
+ /// API with the key difference that we are also able to configure
+ /// the GPIO with `flags` to specify how the line will be used
+ /// at the time of request.
+ ///
+ /// For an output, the `default` parameter specifies the value
+ /// the line should have when it is configured as an output. The
+ /// `consumer` string should describe the process consuming the
+ /// line (this will be truncated to 31 characters if too long).
+ ///
+ /// # Errors
+ ///
+ /// The main source of errors here is if the kernel returns an
+ /// error to the ioctl performing the request here. This will
+ /// result in an [`Error`] being returned with [`ErrorKind::Ioctl`].
+ ///
+ /// One possible cause for an error here would be if the line is
+ /// already in use. One can check for this prior to making the
+ /// request using [`is_kernel`].
+ ///
+ /// [`Error`]: errors/struct.Error.html
+ /// [`ErrorKind::Ioctl`]: errors/enum.ErrorKind.html#variant.Ioctl
+ /// [`is_kernel`]: struct.Line.html#method.is_kernel
+ pub fn request(
+ &self,
+ flags: LineRequestFlags,
+ default: u8,
+ consumer: &str,
+ ) -> Result<LineHandle> {
+ // prepare the request; the kernel consumes some of these values and will
+ // set the fd for us.
+ let mut request = ffi::gpiohandle_request {
+ lineoffsets: unsafe { mem::zeroed() },
+ flags: flags.bits(),
+ default_values: unsafe { mem::zeroed() },
+ consumer_label: unsafe { mem::zeroed() },
+ lines: 1,
+ fd: 0,
+ };
+ request.lineoffsets[0] = self.offset;
+ request.default_values[0] = default;
+ unsafe {
+ rstr_lcpy(
+ request.consumer_label[..].as_mut_ptr(),
+ consumer,
+ request.consumer_label.len(),
+ );
+ }
+ ffi::gpio_get_linehandle_ioctl(self.chip.file.as_raw_fd(), &mut request)?;
+ Ok(LineHandle {
+ line: self.clone(),
+ flags,
+ file: unsafe { File::from_raw_fd(request.fd) },
+ })
+ }
+
+ /// Get an event handle that can be used as a blocking iterator over
+ /// the events (state changes) for this Line
+ ///
+ /// When used as an iterator, it blocks while there is not another event
+ /// available from the kernel for this line matching the subscription
+ /// criteria specified in the `event_flags`. The line will be configured
+ /// with the specified `handle_flags` and `consumer` label.
+ ///
+ /// Note that as compared with the sysfs interface, the character
+ /// device interface maintains a queue of events in the kernel so
+ /// events may happen (e.g. a line changing state faster than can
+ /// be picked up in userspace in real-time). These events will be
+ /// returned on the iterator in order with the event containing the
+ /// associated timestamp attached with high precision within the
+ /// kernel (from an ISR for most drivers).
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// # fn main() -> Result<(), gpio_cdev::Error> {
+ /// use gpio_cdev::{Chip, LineRequestFlags, EventRequestFlags};
+ /// use std::io;
+ ///
+ /// let mut chip = Chip::new("/dev/gpiochip0")?;
+ /// let input = chip.get_line(0)?;
+ ///
+ /// // Show all state changes for this line forever
+ /// for event in input.events(
+ /// LineRequestFlags::INPUT,
+ /// EventRequestFlags::BOTH_EDGES,
+ /// "rust-gpio"
+ /// )? {
+ /// println!("{:?}", event?);
+ /// }
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn events(
+ &self,
+ handle_flags: LineRequestFlags,
+ event_flags: EventRequestFlags,
+ consumer: &str,
+ ) -> Result<LineEventHandle> {
+ let mut request = ffi::gpioevent_request {
+ lineoffset: self.offset,
+ handleflags: handle_flags.bits(),
+ eventflags: event_flags.bits(),
+ consumer_label: unsafe { mem::zeroed() },
+ fd: 0,
+ };
+
+ unsafe {
+ rstr_lcpy(
+ request.consumer_label[..].as_mut_ptr(),
+ consumer,
+ request.consumer_label.len(),
+ );
+ }
+ ffi::gpio_get_lineevent_ioctl(self.chip.file.as_raw_fd(), &mut request)?;
+
+ Ok(LineEventHandle {
+ line: self.clone(),
+ file: unsafe { File::from_raw_fd(request.fd) },
+ })
+ }
+
+ #[cfg(feature = "async-tokio")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "async-tokio")))]
+ pub fn async_events(
+ &self,
+ handle_flags: LineRequestFlags,
+ event_flags: EventRequestFlags,
+ consumer: &str,
+ ) -> Result<AsyncLineEventHandle> {
+ let events = self.events(handle_flags, event_flags, consumer)?;
+ AsyncLineEventHandle::new(events)
+ }
+}
+
+impl LineInfo {
+ /// Get a handle to the line that this info represents
+ pub fn line(&self) -> &Line {
+ &self.line
+ }
+
+ /// Name assigned to this chip if assigned
+ pub fn name(&self) -> Option<&str> {
+ self.name.as_deref()
+ }
+
+ /// The name of this GPIO line, such as the output pin of the line on the
+ /// chip, a rail or a pin header name on a board, as specified by the gpio
+ /// chip.
+ pub fn consumer(&self) -> Option<&str> {
+ self.consumer.as_deref()
+ }
+
+ /// Get the direction of this GPIO if configured
+ ///
+ /// Lines are considered to be inputs if not explicitly
+ /// marked as outputs in the line info flags by the kernel.
+ pub fn direction(&self) -> LineDirection {
+ if self.flags.contains(LineFlags::IS_OUT) {
+ LineDirection::Out
+ } else {
+ LineDirection::In
+ }
+ }
+
+ /// True if the any flags for the device are set (input or output)
+ pub fn is_used(&self) -> bool {
+ !self.flags.is_empty()
+ }
+
+ /// True if this line is being used by something else in the kernel
+ ///
+ /// If another driver or subsystem in the kernel is using the line
+ /// then it cannot be used via the cdev interface. See [relevant kernel code].
+ ///
+ /// [relevant kernel code]: https://elixir.bootlin.com/linux/v4.9.127/source/drivers/gpio/gpiolib.c#L938
+ pub fn is_kernel(&self) -> bool {
+ self.flags.contains(LineFlags::KERNEL)
+ }
+
+ /// True if this line is marked as active low in the kernel
+ pub fn is_active_low(&self) -> bool {
+ self.flags.contains(LineFlags::ACTIVE_LOW)
+ }
+
+ /// True if this line is marked as open drain in the kernel
+ pub fn is_open_drain(&self) -> bool {
+ self.flags.contains(LineFlags::OPEN_DRAIN)
+ }
+
+ /// True if this line is marked as open source in the kernel
+ pub fn is_open_source(&self) -> bool {
+ self.flags.contains(LineFlags::OPEN_SOURCE)
+ }
+}
+
+/// Handle for interacting with a "requested" line
+///
+/// In order for userspace to read/write the value of a GPIO
+/// it must be requested from the chip using [`Line::request`].
+/// On success, the kernel creates an anonymous file descriptor
+/// for interacting with the requested line. This structure
+/// is the go-between for callers and that file descriptor.
+///
+/// [`Line::request`]: struct.Line.html#method.request
+#[derive(Debug)]
+pub struct LineHandle {
+ line: Line,
+ flags: LineRequestFlags,
+ file: File,
+}
+
+impl LineHandle {
+ /// Request the current state of this Line from the kernel
+ ///
+ /// This call is expected to succeed for both input and output
+ /// lines. It should be noted, however, that some drivers may
+ /// not be able to give any useful information when the value
+ /// is requested for an output line.
+ ///
+ /// This value should be 0 or 1 which a "1" representing that
+ /// the line is active. Usually this means that the line is
+ /// at logic-level high but it could mean the opposite if the
+ /// line has been marked as being `ACTIVE_LOW`.
+ pub fn get_value(&self) -> Result<u8> {
+ let mut data: ffi::gpiohandle_data = unsafe { mem::zeroed() };
+ ffi::gpiohandle_get_line_values_ioctl(self.file.as_raw_fd(), &mut data)?;
+ Ok(data.values[0])
+ }
+
+ /// Request that the line be driven to the specified value
+ ///
+ /// The value should be 0 or 1 with 1 representing a request
+ /// to make the line "active". Usually "active" means
+ /// logic level high unless the line has been marked as `ACTIVE_LOW`.
+ ///
+ /// Calling `set_value` on a line that is not an output will
+ /// likely result in an error (from the kernel).
+ pub fn set_value(&self, value: u8) -> Result<()> {
+ let mut data: ffi::gpiohandle_data = unsafe { mem::zeroed() };
+ data.values[0] = value;
+ ffi::gpiohandle_set_line_values_ioctl(self.file.as_raw_fd(), &mut data)?;
+ Ok(())
+ }
+
+ /// Get the Line information associated with this handle.
+ pub fn line(&self) -> &Line {
+ &self.line
+ }
+
+ /// Get the flags with which this handle was created
+ pub fn flags(&self) -> LineRequestFlags {
+ self.flags.clone()
+ }
+}
+
+impl AsRawFd for LineHandle {
+ /// Gets the raw file descriptor for the `LineHandle`.
+ fn as_raw_fd(&self) -> RawFd {
+ self.file.as_raw_fd()
+ }
+}
+
+/// A collection of lines that can be accesses simultaneously
+///
+/// This is a collection of lines, all from the same GPIO chip that can
+/// all be accessed simultaneously
+#[derive(Debug)]
+pub struct Lines {
+ lines: Vec<Line>,
+}
+
+impl Lines {
+ fn new(chip: Arc<InnerChip>, offsets: &[u32]) -> Result<Self> {
+ let res: Result<Vec<Line>> = offsets
+ .iter()
+ .map(|off| Line::new(chip.clone(), *off))
+ .collect();
+ let lines = res?;
+ Ok(Self { lines })
+ }
+
+ /// Get a handle to the parent chip for the lines
+ pub fn chip(&self) -> Chip {
+ self.lines[0].chip()
+ }
+
+ /// Get the number of lines in the collection
+ pub fn is_empty(&self) -> bool {
+ self.lines.is_empty()
+ }
+
+ /// Get the number of lines in the collection
+ pub fn len(&self) -> usize {
+ self.lines.len()
+ }
+
+ /// Request access to interact with these lines from the kernel
+ ///
+ /// This is similar to the "export" operation present in the sysfs
+ /// API with the key difference that we are also able to configure
+ /// the GPIO with `flags` to specify how the line will be used
+ /// at the time of request.
+ ///
+ /// For an output, the `default` parameter specifies the value
+ /// each line should have when it is configured as an output. The
+ /// `consumer` string should describe the process consuming the
+ /// line (this will be truncated to 31 characters if too long).
+ ///
+ /// # Errors
+ ///
+ /// The main source of errors here is if the kernel returns an
+ /// error to the ioctl performing the request here. This will
+ /// result in an [`Error`] being returned with [`ErrorKind::Ioctl`].
+ ///
+ /// One possible cause for an error here would be if the lines are
+ /// already in use. One can check for this prior to making the
+ /// request using [`is_kernel`].
+ ///
+ /// [`Error`]: errors/struct.Error.html
+ /// [`ErrorKind::Ioctl`]: errors/enum.ErrorKind.html#variant.Ioctl
+ /// [`is_kernel`]: struct.Line.html#method.is_kernel
+ pub fn request(
+ &self,
+ flags: LineRequestFlags,
+ default: &[u8],
+ consumer: &str,
+ ) -> Result<MultiLineHandle> {
+ let n = self.lines.len();
+ if default.len() != n {
+ return Err(invalid_err(n, default.len()));
+ }
+ // prepare the request; the kernel consumes some of these values and will
+ // set the fd for us.
+ let mut request = ffi::gpiohandle_request {
+ lineoffsets: unsafe { mem::zeroed() },
+ flags: flags.bits(),
+ default_values: unsafe { mem::zeroed() },
+ consumer_label: unsafe { mem::zeroed() },
+ lines: n as u32,
+ fd: 0,
+ };
+ #[allow(clippy::needless_range_loop)] // clippy does not understand this loop correctly
+ for i in 0..n {
+ request.lineoffsets[i] = self.lines[i].offset();
+ request.default_values[i] = default[i];
+ }
+ unsafe {
+ rstr_lcpy(
+ request.consumer_label[..].as_mut_ptr(),
+ consumer,
+ request.consumer_label.len(),
+ );
+ }
+ ffi::gpio_get_linehandle_ioctl(self.lines[0].chip().inner.file.as_raw_fd(), &mut request)?;
+ let lines = self.lines.clone();
+ Ok(MultiLineHandle {
+ lines: Self { lines },
+ file: unsafe { File::from_raw_fd(request.fd) },
+ })
+ }
+}
+
+impl Index<usize> for Lines {
+ type Output = Line;
+
+ fn index(&self, i: usize) -> &Line {
+ &self.lines[i]
+ }
+}
+
+/// Handle for interacting with a "requested" line
+///
+/// In order for userspace to read/write the value of a GPIO
+/// it must be requested from the chip using [`Line::request`].
+/// On success, the kernel creates an anonymous file descriptor
+/// for interacting with the requested line. This structure
+/// is the go-between for callers and that file descriptor.
+///
+/// [`Line::request`]: struct.Line.html#method.request
+#[derive(Debug)]
+pub struct MultiLineHandle {
+ lines: Lines,
+ file: File,
+}
+
+impl MultiLineHandle {
+ /// Request the current state of this Line from the kernel
+ ///
+ /// This call is expected to succeed for both input and output
+ /// lines. It should be noted, however, that some drivers may
+ /// not be able to give any useful information when the value
+ /// is requested for an output line.
+ ///
+ /// This value should be 0 or 1 which a "1" representing that
+ /// the line is active. Usually this means that the line is
+ /// at logic-level high but it could mean the opposite if the
+ /// line has been marked as being `ACTIVE_LOW`.
+ pub fn get_values(&self) -> Result<Vec<u8>> {
+ let mut data: ffi::gpiohandle_data = unsafe { mem::zeroed() };
+ ffi::gpiohandle_get_line_values_ioctl(self.file.as_raw_fd(), &mut data)?;
+ let n = self.num_lines();
+ let values: Vec<u8> = (0..n).map(|i| data.values[i]).collect();
+ Ok(values)
+ }
+
+ /// Request that the line be driven to the specified value
+ ///
+ /// The value should be 0 or 1 with 1 representing a request
+ /// to make the line "active". Usually "active" means
+ /// logic level high unless the line has been marked as `ACTIVE_LOW`.
+ ///
+ /// Calling `set_value` on a line that is not an output will
+ /// likely result in an error (from the kernel).
+ pub fn set_values(&self, values: &[u8]) -> Result<()> {
+ let n = self.num_lines();
+ if values.len() != n {
+ return Err(invalid_err(n, values.len()));
+ }
+ let mut data: ffi::gpiohandle_data = unsafe { mem::zeroed() };
+ data.values[..n].clone_from_slice(&values[..n]);
+ ffi::gpiohandle_set_line_values_ioctl(self.file.as_raw_fd(), &mut data)?;
+ Ok(())
+ }
+
+ /// Get the number of lines associated with this handle
+ pub fn num_lines(&self) -> usize {
+ self.lines.len()
+ }
+
+ /// Get the Line information associated with this handle.
+ pub fn lines(&self) -> &Lines {
+ &self.lines
+ }
+}
+
+impl AsRawFd for MultiLineHandle {
+ /// Gets the raw file descriptor for the `LineHandle`.
+ fn as_raw_fd(&self) -> RawFd {
+ self.file.as_raw_fd()
+ }
+}
+
+/// Did the Line rise (go active) or fall (go inactive)?
+///
+/// Maps to kernel [`GPIOEVENT_EVENT_*`] definitions.
+///
+/// [`GPIOEVENT_EVENT_*`]: https://elixir.bootlin.com/linux/v4.9.127/source/include/uapi/linux/gpio.h#L136
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum EventType {
+ RisingEdge,
+ FallingEdge,
+}
+
+/// Information about a change to the state of a Line
+///
+/// Wraps kernel [`struct gpioevent_data`].
+///
+/// [`struct gpioevent_data`]: https://elixir.bootlin.com/linux/v4.9.127/source/include/uapi/linux/gpio.h#L142
+pub struct LineEvent(ffi::gpioevent_data);
+
+impl std::fmt::Debug for LineEvent {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(
+ f,
+ "LineEvent {{ timestamp: {:?}, event_type: {:?} }}",
+ self.timestamp(),
+ self.event_type()
+ )
+ }
+}
+
+impl LineEvent {
+ /// Best estimate of event occurrence time, in nanoseconds
+ ///
+ /// In most cases, the timestamp for the event is captured
+ /// in an interrupt handler so it should be very accurate.
+ ///
+ /// The nanosecond timestamp value should are captured
+ /// using the `CLOCK_MONOTONIC` offsets in the kernel and
+ /// should be compared against `CLOCK_MONOTONIC` values.
+ /// Note that kernel versions prior to 5.7 used
+ /// `CLOCK_REALTIME` offsets instead.
+ pub fn timestamp(&self) -> u64 {
+ self.0.timestamp
+ }
+
+ /// Was this a rising or a falling edge?
+ pub fn event_type(&self) -> EventType {
+ if self.0.id == 0x01 {
+ EventType::RisingEdge
+ } else {
+ EventType::FallingEdge
+ }
+ }
+}
+
+/// Handle for retrieving events from the kernel for a line
+///
+/// In order for userspace to retrieve incoming events on a GPIO,
+/// an event handle must be requested from the chip using
+/// [`Line::events`].
+/// On success, the kernel creates an anonymous file descriptor
+/// for reading events. This structure is the go-between for callers
+/// and that file descriptor.
+///
+/// [`Line::events`]: struct.Line.html#method.events
+#[derive(Debug)]
+pub struct LineEventHandle {
+ line: Line,
+ file: File,
+}
+
+impl LineEventHandle {
+ /// Retrieve the next event from the kernel for this line
+ ///
+ /// This blocks while there is not another event available from the
+ /// kernel for the line which matches the subscription criteria
+ /// specified in the `event_flags` when the handle was created.
+ pub fn get_event(&mut self) -> Result<LineEvent> {
+ match self.read_event() {
+ Ok(Some(event)) => Ok(event),
+ Ok(None) => Err(event_err(nix::errno::Errno::EIO)),
+ Err(e) => Err(e.into()),
+ }
+ }
+
+ /// Request the current state of this Line from the kernel
+ ///
+ /// This value should be 0 or 1 which a "1" representing that
+ /// the line is active. Usually this means that the line is
+ /// at logic-level high but it could mean the opposite if the
+ /// line has been marked as being `ACTIVE_LOW`.
+ pub fn get_value(&self) -> Result<u8> {
+ let mut data: ffi::gpiohandle_data = unsafe { mem::zeroed() };
+ ffi::gpiohandle_get_line_values_ioctl(self.file.as_raw_fd(), &mut data)?;
+ Ok(data.values[0])
+ }
+
+ /// Get the Line information associated with this handle.
+ pub fn line(&self) -> &Line {
+ &self.line
+ }
+
+ pub fn file(&self) -> &File {
+ &self.file
+ }
+
+ pub fn file2(&mut self) -> &File {
+ &self.file
+ }
+
+ /// 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()`.
+ pub(crate) fn read_event(&mut self) -> std::io::Result<Option<LineEvent>> {
+ let mut data: ffi::gpioevent_data = unsafe { mem::zeroed() };
+ let data_as_buf = unsafe {
+ slice::from_raw_parts_mut(
+ (&mut data as *mut ffi::gpioevent_data).cast(),
+ mem::size_of::<ffi::gpioevent_data>(),
+ )
+ };
+ let bytes_read = self.file.read(data_as_buf)?;
+ if bytes_read == mem::size_of::<ffi::gpioevent_data>() {
+ Ok(Some(LineEvent(data)))
+ } else {
+ Ok(None)
+ }
+ }
+}
+
+impl AsRawFd for LineEventHandle {
+ /// Gets the raw file descriptor for the `LineEventHandle`.
+ fn as_raw_fd(&self) -> RawFd {
+ self.file.as_raw_fd()
+ }
+}
+
+impl AsFd for LineEventHandle {
+ /// Gets the raw file descriptor for the `LineEventHandle`.
+ fn as_fd(&self) -> BorrowedFd<'_> {
+ self.file.as_fd()
+ }
+}
+
+impl Iterator for LineEventHandle {
+ type Item = Result<LineEvent>;
+
+ fn next(&mut self) -> Option<Result<LineEvent>> {
+ match self.read_event() {
+ Ok(None) => None,
+ Ok(Some(event)) => Some(Ok(event)),
+ Err(e) => Some(Err(e.into())),
+ }
+ }
+}