--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+
+*.o
+*.lo
+*.la
+doc
+*.pc
+*.tar.gz
+*.patch
+*.swp
+tags
+
+# autotools stuff
+.deps/
+.libs/
+Doxyfile
+Makefile
+Makefile.in
+aclocal.m4
+autom4te.cache/
+autostuff/
+config.h
+config.h.in
+config.h.in~
+config.log
+config.status
+configure
+configure~
+libtool
+*-libtool
+m4/
+stamp-h1
+
+# profiling
+*.gcda
+*.gcno
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Kent Gibson <warthog618@gmail.com>
+
+#
+# This file is part of libgpiod.
+#
+# Read the Docs configuration file
+# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
+
+version: 2
+
+build:
+ os: ubuntu-22.04
+ tools:
+ python: "3.11"
+ # doxygen is available by default, but just in case.
+ # others are definitely missing.
+ apt_packages:
+ - autoconf
+ - autoconf-archive
+ - libtool
+ - m4
+ - doxygen
+ - graphviz
+
+sphinx:
+ configuration: sphinx/conf.py
--- /dev/null
+libgpiod is provided under:
+
+ SPDX-License-Identifier: LGPL-2.1-or-later
+
+Being under the terms of the GNU Lesser General Public License version 2.1
+or any later version according with:
+
+ LICENSES/LGPL-2.1-or-later.txt
+
+gpio-tools, test suites and examples are provided under:
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+
+Being under the terms of the GNU General Public License version 2.0 or any
+later version according with:
+
+ LICENSES/GPL-2.0-or-later.txt
+
+The Linux Kernel uAPI headers are provided under:
+
+ SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note
+
+Being under the terms of the GNU General Public License version 2 only,
+according with:
+
+ LICENSES/GPL-2.0-only.txt
+
+With an explicit syscall exception, as stated at:
+
+ LICENSES/Linux-syscall-note.txt
+
+libgpiod text files are provided under:
+
+ SPDX-License-Identifier: CC-BY-SA-4.0
+
+Being under the terms of the Creative Commons Attribution-ShareAlike 4.0
+International License according with:
+
+ LICENSES/CC-BY-SA-4.0.txt
+
+All contributions to libgpiod are subject to this COPYING file.
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+
+# libgpiod doxygen configuration
+
+# General configuration
+PROJECT_NAME = libgpiod
+PROJECT_NUMBER = @VERSION_STR@
+OUTPUT_DIRECTORY = doc
+OUTPUT_LANGUAGE = English
+EXTRACT_ALL = NO
+EXTRACT_PRIVATE = NO
+EXTRACT_STATIC = YES
+HIDE_UNDOC_MEMBERS = NO
+HIDE_UNDOC_CLASSES = NO
+BRIEF_MEMBER_DESC = YES
+REPEAT_BRIEF = YES
+ALWAYS_DETAILED_SEC = NO
+FULL_PATH_NAMES = NO
+STRIP_FROM_PATH = @top_srcdir@
+INTERNAL_DOCS = NO
+STRIP_CODE_COMMENTS = YES
+CASE_SENSE_NAMES = YES
+SHORT_NAMES = NO
+HIDE_SCOPE_NAMES = NO
+VERBATIM_HEADERS = YES
+SHOW_INCLUDE_FILES = YES
+JAVADOC_AUTOBRIEF = YES
+INHERIT_DOCS = YES
+INLINE_INFO = YES
+SORT_MEMBER_DOCS = YES
+DISTRIBUTE_GROUP_DOC = NO
+TAB_SIZE = 8
+GENERATE_TODOLIST = YES
+GENERATE_TESTLIST = YES
+GENERATE_BUGLIST = YES
+ALIASES =
+ENABLED_SECTIONS =
+MAX_INITIALIZER_LINES = 30
+OPTIMIZE_OUTPUT_FOR_C = YES
+SHOW_USED_FILES = YES
+QUIET = YES
+WARNINGS = YES
+WARN_IF_UNDOCUMENTED = YES
+WARN_FORMAT =
+WARN_LOGFILE =
+INPUT = @top_srcdir@/include/gpiod.h \
+ @top_srcdir@/bindings/cxx/gpiod.hpp \
+ @top_srcdir@/bindings/cxx/gpiodcxx/
+SOURCE_BROWSER = YES
+INLINE_SOURCES = NO
+REFERENCED_BY_RELATION = YES
+REFERENCES_RELATION = YES
+ALPHABETICAL_INDEX = NO
+IGNORE_PREFIX =
+SEARCHENGINE = NO
+ENABLE_PREPROCESSING = YES
+
+# HTML output
+GENERATE_HTML = YES
+HTML_OUTPUT =
+HTML_HEADER =
+HTML_FOOTER =
+HTML_STYLESHEET =
+GENERATE_HTMLHELP = NO
+GENERATE_CHI = NO
+BINARY_TOC = NO
+TOC_EXPAND = NO
+DISABLE_INDEX = NO
+ENUM_VALUES_PER_LINE = 4
+GENERATE_TREEVIEW = NO
+TREEVIEW_WIDTH = 250
+
+# LaTeX output
+GENERATE_LATEX = NO
+LATEX_OUTPUT =
+COMPACT_LATEX = NO
+PAPER_TYPE = a4
+EXTRA_PACKAGES =
+LATEX_HEADER =
+PDF_HYPERLINKS = NO
+USE_PDFLATEX = NO
+LATEX_BATCHMODE = NO
+
+# RTF output
+GENERATE_RTF = NO
+RTF_OUTPUT =
+COMPACT_RTF = NO
+RTF_HYPERLINKS = NO
+RTF_STYLESHEET_FILE =
+RTF_EXTENSIONS_FILE =
+
+# Man page output
+GENERATE_MAN = YES
+MAN_OUTPUT = man
+MAN_EXTENSION = .3
+MAN_LINKS = YES
+
+# XML output
+GENERATE_XML = YES
+
+# External references
+TAGFILES =
+GENERATE_TAGFILE =
+ALLEXTERNALS = NO
--- /dev/null
+ 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.
--- /dev/null
+BSD 3-Clause License
+
+Copyright (c) [year], [fullname]
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--- /dev/null
+Attribution-ShareAlike 4.0 International
+
+=======================================================================
+
+Creative Commons Corporation ("Creative Commons") is not a law firm and
+does not provide legal services or legal advice. Distribution of
+Creative Commons public licenses does not create a lawyer-client or
+other relationship. Creative Commons makes its licenses and related
+information available on an "as-is" basis. Creative Commons gives no
+warranties regarding its licenses, any material licensed under their
+terms and conditions, or any related information. Creative Commons
+disclaims all liability for damages resulting from their use to the
+fullest extent possible.
+
+Using Creative Commons Public Licenses
+
+Creative Commons public licenses provide a standard set of terms and
+conditions that creators and other rights holders may use to share
+original works of authorship and other material subject to copyright
+and certain other rights specified in the public license below. The
+following considerations are for informational purposes only, are not
+exhaustive, and do not form part of our licenses.
+
+ Considerations for licensors: Our public licenses are
+ intended for use by those authorized to give the public
+ permission to use material in ways otherwise restricted by
+ copyright and certain other rights. Our licenses are
+ irrevocable. Licensors should read and understand the terms
+ and conditions of the license they choose before applying it.
+ Licensors should also secure all rights necessary before
+ applying our licenses so that the public can reuse the
+ material as expected. Licensors should clearly mark any
+ material not subject to the license. This includes other CC-
+ licensed material, or material used under an exception or
+ limitation to copyright. More considerations for licensors:
+ wiki.creativecommons.org/Considerations_for_licensors
+
+ Considerations for the public: By using one of our public
+ licenses, a licensor grants the public permission to use the
+ licensed material under specified terms and conditions. If
+ the licensor's permission is not necessary for any reason--for
+ example, because of any applicable exception or limitation to
+ copyright--then that use is not regulated by the license. Our
+ licenses grant only permissions under copyright and certain
+ other rights that a licensor has authority to grant. Use of
+ the licensed material may still be restricted for other
+ reasons, including because others have copyright or other
+ rights in the material. A licensor may make special requests,
+ such as asking that all changes be marked or described.
+ Although not required by our licenses, you are encouraged to
+ respect those requests where reasonable. More_considerations
+ for the public:
+ wiki.creativecommons.org/Considerations_for_licensees
+
+=======================================================================
+
+Creative Commons Attribution-ShareAlike 4.0 International Public
+License
+
+By exercising the Licensed Rights (defined below), You accept and agree
+to be bound by the terms and conditions of this Creative Commons
+Attribution-ShareAlike 4.0 International Public License ("Public
+License"). To the extent this Public License may be interpreted as a
+contract, You are granted the Licensed Rights in consideration of Your
+acceptance of these terms and conditions, and the Licensor grants You
+such rights in consideration of benefits the Licensor receives from
+making the Licensed Material available under these terms and
+conditions.
+
+
+Section 1 -- Definitions.
+
+ a. Adapted Material means material subject to Copyright and Similar
+ Rights that is derived from or based upon the Licensed Material
+ and in which the Licensed Material is translated, altered,
+ arranged, transformed, or otherwise modified in a manner requiring
+ permission under the Copyright and Similar Rights held by the
+ Licensor. For purposes of this Public License, where the Licensed
+ Material is a musical work, performance, or sound recording,
+ Adapted Material is always produced where the Licensed Material is
+ synched in timed relation with a moving image.
+
+ b. Adapter's License means the license You apply to Your Copyright
+ and Similar Rights in Your contributions to Adapted Material in
+ accordance with the terms and conditions of this Public License.
+
+ c. BY-SA Compatible License means a license listed at
+ creativecommons.org/compatiblelicenses, approved by Creative
+ Commons as essentially the equivalent of this Public License.
+
+ d. Copyright and Similar Rights means copyright and/or similar rights
+ closely related to copyright including, without limitation,
+ performance, broadcast, sound recording, and Sui Generis Database
+ Rights, without regard to how the rights are labeled or
+ categorized. For purposes of this Public License, the rights
+ specified in Section 2(b)(1)-(2) are not Copyright and Similar
+ Rights.
+
+ e. Effective Technological Measures means those measures that, in the
+ absence of proper authority, may not be circumvented under laws
+ fulfilling obligations under Article 11 of the WIPO Copyright
+ Treaty adopted on December 20, 1996, and/or similar international
+ agreements.
+
+ f. Exceptions and Limitations means fair use, fair dealing, and/or
+ any other exception or limitation to Copyright and Similar Rights
+ that applies to Your use of the Licensed Material.
+
+ g. License Elements means the license attributes listed in the name
+ of a Creative Commons Public License. The License Elements of this
+ Public License are Attribution and ShareAlike.
+
+ h. Licensed Material means the artistic or literary work, database,
+ or other material to which the Licensor applied this Public
+ License.
+
+ i. Licensed Rights means the rights granted to You subject to the
+ terms and conditions of this Public License, which are limited to
+ all Copyright and Similar Rights that apply to Your use of the
+ Licensed Material and that the Licensor has authority to license.
+
+ j. Licensor means the individual(s) or entity(ies) granting rights
+ under this Public License.
+
+ k. Share means to provide material to the public by any means or
+ process that requires permission under the Licensed Rights, such
+ as reproduction, public display, public performance, distribution,
+ dissemination, communication, or importation, and to make material
+ available to the public including in ways that members of the
+ public may access the material from a place and at a time
+ individually chosen by them.
+
+ l. Sui Generis Database Rights means rights other than copyright
+ resulting from Directive 96/9/EC of the European Parliament and of
+ the Council of 11 March 1996 on the legal protection of databases,
+ as amended and/or succeeded, as well as other essentially
+ equivalent rights anywhere in the world.
+
+ m. You means the individual or entity exercising the Licensed Rights
+ under this Public License. Your has a corresponding meaning.
+
+
+Section 2 -- Scope.
+
+ a. License grant.
+
+ 1. Subject to the terms and conditions of this Public License,
+ the Licensor hereby grants You a worldwide, royalty-free,
+ non-sublicensable, non-exclusive, irrevocable license to
+ exercise the Licensed Rights in the Licensed Material to:
+
+ a. reproduce and Share the Licensed Material, in whole or
+ in part; and
+
+ b. produce, reproduce, and Share Adapted Material.
+
+ 2. Exceptions and Limitations. For the avoidance of doubt, where
+ Exceptions and Limitations apply to Your use, this Public
+ License does not apply, and You do not need to comply with
+ its terms and conditions.
+
+ 3. Term. The term of this Public License is specified in Section
+ 6(a).
+
+ 4. Media and formats; technical modifications allowed. The
+ Licensor authorizes You to exercise the Licensed Rights in
+ all media and formats whether now known or hereafter created,
+ and to make technical modifications necessary to do so. The
+ Licensor waives and/or agrees not to assert any right or
+ authority to forbid You from making technical modifications
+ necessary to exercise the Licensed Rights, including
+ technical modifications necessary to circumvent Effective
+ Technological Measures. For purposes of this Public License,
+ simply making modifications authorized by this Section 2(a)
+ (4) never produces Adapted Material.
+
+ 5. Downstream recipients.
+
+ a. Offer from the Licensor -- Licensed Material. Every
+ recipient of the Licensed Material automatically
+ receives an offer from the Licensor to exercise the
+ Licensed Rights under the terms and conditions of this
+ Public License.
+
+ b. Additional offer from the Licensor -- Adapted Material.
+ Every recipient of Adapted Material from You
+ automatically receives an offer from the Licensor to
+ exercise the Licensed Rights in the Adapted Material
+ under the conditions of the Adapter's License You apply.
+
+ c. No downstream restrictions. You may not offer or impose
+ any additional or different terms or conditions on, or
+ apply any Effective Technological Measures to, the
+ Licensed Material if doing so restricts exercise of the
+ Licensed Rights by any recipient of the Licensed
+ Material.
+
+ 6. No endorsement. Nothing in this Public License constitutes or
+ may be construed as permission to assert or imply that You
+ are, or that Your use of the Licensed Material is, connected
+ with, or sponsored, endorsed, or granted official status by,
+ the Licensor or others designated to receive attribution as
+ provided in Section 3(a)(1)(A)(i).
+
+ b. Other rights.
+
+ 1. Moral rights, such as the right of integrity, are not
+ licensed under this Public License, nor are publicity,
+ privacy, and/or other similar personality rights; however, to
+ the extent possible, the Licensor waives and/or agrees not to
+ assert any such rights held by the Licensor to the limited
+ extent necessary to allow You to exercise the Licensed
+ Rights, but not otherwise.
+
+ 2. Patent and trademark rights are not licensed under this
+ Public License.
+
+ 3. To the extent possible, the Licensor waives any right to
+ collect royalties from You for the exercise of the Licensed
+ Rights, whether directly or through a collecting society
+ under any voluntary or waivable statutory or compulsory
+ licensing scheme. In all other cases the Licensor expressly
+ reserves any right to collect such royalties.
+
+
+Section 3 -- License Conditions.
+
+Your exercise of the Licensed Rights is expressly made subject to the
+following conditions.
+
+ a. Attribution.
+
+ 1. If You Share the Licensed Material (including in modified
+ form), You must:
+
+ a. retain the following if it is supplied by the Licensor
+ with the Licensed Material:
+
+ i. identification of the creator(s) of the Licensed
+ Material and any others designated to receive
+ attribution, in any reasonable manner requested by
+ the Licensor (including by pseudonym if
+ designated);
+
+ ii. a copyright notice;
+
+ iii. a notice that refers to this Public License;
+
+ iv. a notice that refers to the disclaimer of
+ warranties;
+
+ v. a URI or hyperlink to the Licensed Material to the
+ extent reasonably practicable;
+
+ b. indicate if You modified the Licensed Material and
+ retain an indication of any previous modifications; and
+
+ c. indicate the Licensed Material is licensed under this
+ Public License, and include the text of, or the URI or
+ hyperlink to, this Public License.
+
+ 2. You may satisfy the conditions in Section 3(a)(1) in any
+ reasonable manner based on the medium, means, and context in
+ which You Share the Licensed Material. For example, it may be
+ reasonable to satisfy the conditions by providing a URI or
+ hyperlink to a resource that includes the required
+ information.
+
+ 3. If requested by the Licensor, You must remove any of the
+ information required by Section 3(a)(1)(A) to the extent
+ reasonably practicable.
+
+ b. ShareAlike.
+
+ In addition to the conditions in Section 3(a), if You Share
+ Adapted Material You produce, the following conditions also apply.
+
+ 1. The Adapter's License You apply must be a Creative Commons
+ license with the same License Elements, this version or
+ later, or a BY-SA Compatible License.
+
+ 2. You must include the text of, or the URI or hyperlink to, the
+ Adapter's License You apply. You may satisfy this condition
+ in any reasonable manner based on the medium, means, and
+ context in which You Share Adapted Material.
+
+ 3. You may not offer or impose any additional or different terms
+ or conditions on, or apply any Effective Technological
+ Measures to, Adapted Material that restrict exercise of the
+ rights granted under the Adapter's License You apply.
+
+
+Section 4 -- Sui Generis Database Rights.
+
+Where the Licensed Rights include Sui Generis Database Rights that
+apply to Your use of the Licensed Material:
+
+ a. for the avoidance of doubt, Section 2(a)(1) grants You the right
+ to extract, reuse, reproduce, and Share all or a substantial
+ portion of the contents of the database;
+
+ b. if You include all or a substantial portion of the database
+ contents in a database in which You have Sui Generis Database
+ Rights, then the database in which You have Sui Generis Database
+ Rights (but not its individual contents) is Adapted Material,
+
+ including for purposes of Section 3(b); and
+ c. You must comply with the conditions in Section 3(a) if You Share
+ all or a substantial portion of the contents of the database.
+
+For the avoidance of doubt, this Section 4 supplements and does not
+replace Your obligations under this Public License where the Licensed
+Rights include other Copyright and Similar Rights.
+
+
+Section 5 -- Disclaimer of Warranties and Limitation of Liability.
+
+ a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
+ EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
+ AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
+ ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
+ IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
+ WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
+ ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
+ KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
+ ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
+
+ b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
+ TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
+ NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
+ INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
+ COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
+ USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
+ ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
+ DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
+ IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
+
+ c. The disclaimer of warranties and limitation of liability provided
+ above shall be interpreted in a manner that, to the extent
+ possible, most closely approximates an absolute disclaimer and
+ waiver of all liability.
+
+
+Section 6 -- Term and Termination.
+
+ a. This Public License applies for the term of the Copyright and
+ Similar Rights licensed here. However, if You fail to comply with
+ this Public License, then Your rights under this Public License
+ terminate automatically.
+
+ b. Where Your right to use the Licensed Material has terminated under
+ Section 6(a), it reinstates:
+
+ 1. automatically as of the date the violation is cured, provided
+ it is cured within 30 days of Your discovery of the
+ violation; or
+
+ 2. upon express reinstatement by the Licensor.
+
+ For the avoidance of doubt, this Section 6(b) does not affect any
+ right the Licensor may have to seek remedies for Your violations
+ of this Public License.
+
+ c. For the avoidance of doubt, the Licensor may also offer the
+ Licensed Material under separate terms or conditions or stop
+ distributing the Licensed Material at any time; however, doing so
+ will not terminate this Public License.
+
+ d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
+ License.
+
+
+Section 7 -- Other Terms and Conditions.
+
+ a. The Licensor shall not be bound by any additional or different
+ terms or conditions communicated by You unless expressly agreed.
+
+ b. Any arrangements, understandings, or agreements regarding the
+ Licensed Material not stated herein are separate from and
+ independent of the terms and conditions of this Public License.
+
+
+Section 8 -- Interpretation.
+
+ a. For the avoidance of doubt, this Public License does not, and
+ shall not be interpreted to, reduce, limit, restrict, or impose
+ conditions on any use of the Licensed Material that could lawfully
+ be made without permission under this Public License.
+
+ b. To the extent possible, if any provision of this Public License is
+ deemed unenforceable, it shall be automatically reformed to the
+ minimum extent necessary to make it enforceable. If the provision
+ cannot be reformed, it shall be severed from this Public License
+ without affecting the enforceability of the remaining terms and
+ conditions.
+
+ c. No term or condition of this Public License will be waived and no
+ failure to comply consented to unless expressly agreed to by the
+ Licensor.
+
+ d. Nothing in this Public License constitutes or may be interpreted
+ as a limitation upon, or waiver of, any privileges and immunities
+ that apply to the Licensor or You, including from the legal
+ processes of any jurisdiction or authority.
+
+
+=======================================================================
+
+Creative Commons is not a party to its public
+licenses. Notwithstanding, Creative Commons may elect to apply one of
+its public licenses to material it publishes and in those instances
+will be considered the “Licensor.” The text of the Creative Commons
+public licenses is dedicated to the public domain under the CC0 Public
+Domain Dedication. Except for the limited purpose of indicating that
+material is shared under a Creative Commons public license or as
+otherwise permitted by the Creative Commons policies published at
+creativecommons.org/policies, Creative Commons does not authorize the
+use of the trademark "Creative Commons" or any other trademark or logo
+of Creative Commons without its prior written consent including,
+without limitation, in connection with any unauthorized modifications
+to any of its public licenses or any other arrangements,
+understandings, or agreements concerning use of licensed material. For
+the avoidance of doubt, this paragraph does not form part of the
+public licenses.
+
+Creative Commons may be contacted at creativecommons.org.
--- /dev/null
+Creative Commons Legal Code
+
+CC0 1.0 Universal
+
+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
+ LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
+ ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
+ INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
+ REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
+ PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
+ THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
+ HEREUNDER.
+
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator
+and subsequent owner(s) (each and all, an "owner") of an original work of
+authorship and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for
+the purpose of contributing to a commons of creative, cultural and
+scientific works ("Commons") that the public can reliably and without fear
+of later claims of infringement build upon, modify, incorporate in other
+works, reuse and redistribute as freely as possible in any form whatsoever
+and for any purposes, including without limitation commercial purposes.
+These owners may contribute to the Commons to promote the ideal of a free
+culture and the further production of creative, cultural and scientific
+works, or to gain reputation or greater distribution for their Work in
+part through the use and efforts of others.
+
+For these and/or other purposes and motivations, and without any
+expectation of additional consideration or compensation, the person
+associating CC0 with a Work (the "Affirmer"), to the extent that he or she
+is an owner of Copyright and Related Rights in the Work, voluntarily
+elects to apply CC0 to the Work and publicly distribute the Work under its
+terms, with knowledge of his or her Copyright and Related Rights in the
+Work and the meaning and intended legal effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+protected by copyright and related or neighboring rights ("Copyright and
+Related Rights"). Copyright and Related Rights include, but are not
+limited to, the following:
+
+ i. the right to reproduce, adapt, distribute, perform, display,
+ communicate, and translate a Work;
+ ii. moral rights retained by the original author(s) and/or performer(s);
+iii. publicity and privacy rights pertaining to a person's image or
+ likeness depicted in a Work;
+ iv. rights protecting against unfair competition in regards to a Work,
+ subject to the limitations in paragraph 4(a), below;
+ v. rights protecting the extraction, dissemination, use and reuse of data
+ in a Work;
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+ European Parliament and of the Council of 11 March 1996 on the legal
+ protection of databases, and under any national implementation
+ thereof, including any amended or successor version of such
+ directive); and
+vii. other similar, equivalent or corresponding rights throughout the
+ world based on applicable law or treaty, and any national
+ implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention
+of, applicable law, Affirmer hereby overtly, fully, permanently,
+irrevocably and unconditionally waives, abandons, and surrenders all of
+Affirmer's Copyright and Related Rights and associated claims and causes
+of action, whether now known or unknown (including existing as well as
+future claims and causes of action), in the Work (i) in all territories
+worldwide, (ii) for the maximum duration provided by applicable law or
+treaty (including future time extensions), (iii) in any current or future
+medium and for any number of copies, and (iv) for any purpose whatsoever,
+including without limitation commercial, advertising or promotional
+purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
+member of the public at large and to the detriment of Affirmer's heirs and
+successors, fully intending that such Waiver shall not be subject to
+revocation, rescission, cancellation, termination, or any other legal or
+equitable action to disrupt the quiet enjoyment of the Work by the public
+as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason
+be judged legally invalid or ineffective under applicable law, then the
+Waiver shall be preserved to the maximum extent permitted taking into
+account Affirmer's express Statement of Purpose. In addition, to the
+extent the Waiver is so judged Affirmer hereby grants to each affected
+person a royalty-free, non transferable, non sublicensable, non exclusive,
+irrevocable and unconditional license to exercise Affirmer's Copyright and
+Related Rights in the Work (i) in all territories worldwide, (ii) for the
+maximum duration provided by applicable law or treaty (including future
+time extensions), (iii) in any current or future medium and for any number
+of copies, and (iv) for any purpose whatsoever, including without
+limitation commercial, advertising or promotional purposes (the
+"License"). The License shall be deemed effective as of the date CC0 was
+applied by Affirmer to the Work. Should any part of the License for any
+reason be judged legally invalid or ineffective under applicable law, such
+partial invalidity or ineffectiveness shall not invalidate the remainder
+of the License, and in such case Affirmer hereby affirms that he or she
+will not (i) exercise any of his or her remaining Copyright and Related
+Rights in the Work or (ii) assert any associated claims and causes of
+action with respect to the Work, in either case contrary to Affirmer's
+express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+ surrendered, licensed or otherwise affected by this document.
+ b. Affirmer offers the Work as-is and makes no representations or
+ warranties of any kind concerning the Work, express, implied,
+ statutory or otherwise, including without limitation warranties of
+ title, merchantability, fitness for a particular purpose, non
+ infringement, or the absence of latent or other defects, accuracy, or
+ the present or absence of errors, whether or not discoverable, all to
+ the greatest extent permissible under applicable law.
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+ that may apply to the Work or any use thereof, including without
+ limitation any person's Copyright and Related Rights in the Work.
+ Further, Affirmer disclaims responsibility for obtaining any necessary
+ consents, permissions or other rights required for any use of the
+ Work.
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+ party to this document and has no duty or obligation with respect to
+ this CC0 or use of the Work.
--- /dev/null
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
--- /dev/null
+GPL-2.0-only.txt
\ No newline at end of file
--- /dev/null
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
--- /dev/null
+SPDX-Exception-Identifier: Linux-syscall-note
+SPDX-URL: https://spdx.org/licenses/Linux-syscall-note.html
+SPDX-Licenses: GPL-2.0, GPL-2.0+, GPL-1.0+, LGPL-2.0, LGPL-2.0+, LGPL-2.1, LGPL-2.1+, GPL-2.0-only, GPL-2.0-or-later
+Usage-Guide:
+ This exception is used together with one of the above SPDX-Licenses
+ to mark user space API (uapi) header files so they can be included
+ into non GPL compliant user space application code.
+ To use this exception add it with the keyword WITH to one of the
+ identifiers in the SPDX-Licenses tag:
+ SPDX-License-Identifier: <SPDX-License> WITH Linux-syscall-note
+License-Text:
+
+ NOTE! This copyright does *not* cover user programs that use kernel
+ services by normal system calls - this is merely considered normal use
+ of the kernel, and does *not* fall under the heading of "derived work".
+ Also note that the GPL below is copyrighted by the Free Software
+ Foundation, but the instance of code that it refers to (the Linux
+ kernel) is copyrighted by me and others who actually wrote it.
+
+ Also note that the only valid version of the GPL as far as the kernel
+ is concerned is _this_ particular version of the license (ie v2, not
+ v2.2 or v3.x or whatever), unless explicitly otherwise stated.
+
+ Linus Torvalds
+
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+
+ACLOCAL_AMFLAGS = -I m4
+AUTOMAKE_OPTIONS = foreign
+SUBDIRS = include lib contrib
+
+EXTRA_DIST = \
+ LICENSES/GPL-2.0-or-later.txt \
+ LICENSES/Apache-2.0.txt \
+ LICENSES/LGPL-2.1-or-later.txt \
+ LICENSES/CC-BY-SA-4.0.txt \
+ LICENSES/CC0-1.0.txt \
+ LICENSES/GPL-2.0-only.txt \
+ LICENSES/Linux-syscall-note.txt \
+ LICENSES/BSD-3-Clause.txt
+
+if WITH_EXAMPLES
+
+SUBDIRS += examples
+
+endif
+
+if WITH_TOOLS
+
+SUBDIRS += tools man
+
+endif
+
+if WITH_TESTS
+
+SUBDIRS += tests
+
+endif
+
+# Build bindings after core tests. When building tests for bindings, we need
+# libgpiosim to be already present.
+SUBDIRS += bindings
+
+if HAS_DOXYGEN
+
+doc: Doxyfile
+ $(AM_V_GEN)doxygen Doxyfile
+.PHONY: doc
+
+clean-local:
+ rm -rf doc
+
+EXTRA_DIST += Doxyfile
+
+endif
--- /dev/null
+# SPDX-License-Identifier: CC-BY-SA-4.0
+# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+# SPDX-FileCopyrightText: 2023 Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
+
+libgpiod v2.1.3
+===============
+
+Bug fixes:
+- fix C++ tests with recent kernels which introduced stricter reconfigure
+ behavior
+- fix a use-after-free bug in python bindings
+- fix passing the event clock property to line requests in python bindings
+- fix a memory leak in tools
+- make sure the string buffers in line-info and chip-info are big enough to not
+ truncate the strings they hold below the size accepted by the kernel
+
+libgpiod v2.1.2
+===============
+
+Bug fixes:
+- fix C++ bindings build using slibtool
+- accept the new style automatic GPIO chip labels from gpio-sim in bash tests
+
+Misc:
+- relicense C++ bindings under LGPL-2.1-or-later
+
+libgpiod v2.1.1
+===============
+
+Bug fixes:
+- remove buggy and unnecessary flags sanitization from line-config
+- fix python bindings installation with Makefile build
+- sanitize the return values of GPIO ioctl()s which in some cases may be
+ erroneously positive
+
+libgpiod v2.1
+=============
+
+New features:
+- port tests to shunit2
+- add a sample Android build file
+- add code examples to the core library and replace existing reimplementations
+ of gpio-tools with dedicated examples in bindings
+- don't install test executables
+- add idle-timeout option to gpiomon and gpionotify
+- provide gpiod_line_request_get_chip_name() and respective wrappers in
+ bindings
+
+Improvements:
+- add more tests for various corner-cases and improve coverage
+- documentation improvements
+- drop dependencies that make it impossible to build libgpiod with bionic libc
+- remove dead code
+- use AM_V_GEN where applicable in build
+
+Bug fixes:
+- fix a segfault in the GLib wrapper around libgpiosim
+- fix a race condition in libgpiosim
+- remove an implicit dependency on ncurses in gpio-tools tests
+- make value toggling in gpio-tools tests more reliable by removing hard-coded
+ sleeps in favor of actively waiting for value changes
+- sanitize the arguments in gpiod_line_config_set_output_values()
+- make the chip file descriptor blocking in the core library so that calls to
+ gpiod_chip_read_info_event() behave as advertised in the docs
+- fix setting the event clock type in gpiomon
+- fix the regex pattern for version strings
+- fix some test cases
+- drop profiling flags from tests' Makefile
+- don't use the same chip from different threads
+
+libgpiod v2.0
+=============
+
+This is a major release that breaks compatiblity with the v1.6.x series. The
+entire data model has been overhauled in order to make using the library more
+intuitive and less cumbersome, while also making the code future-proof and
+extensible. Please refer to the documentation for details.
+
+New features:
+- rework the entire API: core C library as well as C++ and Python bindings
+- rework the command-line tools in order to make them more line-name-oriented
+- drop gpiofind as tools can now resolve line names on their own
+- add the interactive mode to gpioset
+- add Rust bindings
+- make tests work with the gpio-sim kernel module
+
+libgpiod v1.6
+=============
+
+New features:
+- add a standardized '__version__' module attribute in Python bindings
+- print the bias flags info (if set) in gpioinfo
+
+Improvements:
+- remove unnecessary indirection in free_dirs() in iterator code
+- put all ABI versions next to one another in configure.ac
+- improve std namespace resolution in C++ bindings
+- add more checks for non-standard functions in configure.ac
+- various code size improvements
+- enforce gnu89 C standard in makefiles
+- many documentation improvements
+- unduplicate signalfd() handling in tools
+- fix a forward declaration for line_event in C++ bindings
+
+Bug fixes:
+- relax is_gpiochip_cdev() for symbolic links
+- make gpiod_line_get_value_bulk() work for bulks of lines requested for
+ events, not only those requested for values
+- fix regex patterns for timestamps in gpiomon test cases
+- remove leftover asserts from tests
+- fix unit conversion in event timestamp calculation in C++ bindings
+- fix reading subset of available events in core library
+
+libgpiod v1.5
+=============
+
+New features:
+- switched to using the GLib testing framework for core library tests and BATS
+ (Bash Automated Testing System) for command-line tools
+- used Catch2 C++ testing framework to implement a proper test-suite for C++
+ bindings while also reusing the API provided by libgpiomockup
+- used Python's unittest package to implement a proper test suite for Python
+ bindings and reused libgpiockup again
+- provided line::update() and Line.update() routines for C++ and Python
+ bindings respectively allowing to update the line info from bindings as well
+- added support for bias flags which are a new functionality first available in
+ linux v5.5; subsequently the library now requires v5.5 kernel headers to
+ build; the new flags are supported in the core library, C++ and Python
+ bindings as well as the command-line tools
+- added support for the new SET_CONFIG ioctl(): this too is a new functionality
+ added in linux v5.5; both features have been implemented in the library by
+ Kent Gibson
+- added routines for reading multiple line events at once to the core library,
+ C++ and Python bindings
+
+Improvements:
+- constified function arguments where applicable in libgpiomockup
+- fixed the name of the test exeucutable displayed at build time
+- improved the function pointer casting in Python bindings to avoid warnings
+ emitted by GCC8
+- switched to using the KERNEL_VERSION() macro in tests instead of handcoded
+ version parsing
+- improved the setup ordering in tests (setup libgpiomockup before checking
+ the kernel version
+- add 'extern "c"' to the libgpiomockup header to make it usable from C++
+- add chip index validation to libgpiomockup functions
+- check if the debugfs directory used by libgpiomockup is writable before
+ using it to set the pull of dummy lines
+- add several new test cases
+- improved Python example programs (made gpiomon's output similar to the
+ original tool, make gpioset wait for an ENTER pres by default)
+- fixed the major:minor number comparison between the device and sysfs
+- deprecated the gpiod_line_needs_update() function and removed the logic
+ behind it from the library
+- shrank the Python bindings a bit by directly returning the value from
+ PyErr_SetFromErrno()
+- dropped noexcept from methods which can throw in C++ bindings
+- switched to initializing the bitset with integers instead of strings in C++
+ bindings
+- allowed gpiod_line_set_value_bulk() to accept null pointers
+- when building Python bindings: check for the existence of python-config
+- improved the readability of help text messages for command-line tools
+- reworked the .gitignore file: added libtool scripts generated during
+ cross-compilation and split the main .gitignore into several fine-grained
+ files
+- fixed several misspellings
+- other minor tweaks and improvements
+
+Bug fixes:
+- fixed memory leaks in libgpiomockup
+- fixed memory leaks in the testing framework
+- fixed a segfault in error path in tests
+- make gpioinfo show lines claimed by the kernel as used even if they have no
+ named consumer
+- fixed the test cases validating the '--active-low' switch in gpiomon and
+ the GPIOHANDLE_REQUEST_ACTIVE_LOW flag in the core library after a fix
+ for incorrect behavior was merged in linux v5.2.7
+- stopped failing at init-time of libgpiomockup if gpio-mockup is already
+ loaded
+- added a missing throw keyword in error path in C++ bindings
+- fixed a segfault in Python bindings when calling Line.request() without
+ the consumer argument
+
+libgpiod v1.4
+=============
+
+New features:
+- updated the testing framework to work with linux v5.1 in which the debugfs
+ interface of the GPIO testing module changed in a backward incompatible way
+- factored out the code controlling the GPIO testing module into a separate
+ shared library that may be reused by future testing executables for different
+ language bindings
+- removed the --enable-install-tests build option and the make check target as
+ they were redundant, subsequently tests are now installed as a normal program
+ whenever they're enabled with --enable-tests
+
+Improvements:
+- removed unnecessary std::move calls from C++ bindings
+- added the explicit keyword to bool() operators in C++ bindings
+
+Bug fixes:
+- fix out of source build of man pages
+
+libgpiod v1.3
+=============
+
+New features:
+- the gpio-tools now have automatically generated (using help2man) man pages
+ that are bundled with the release tarball
+- support a singular 'default_val' argument in Line.request() in python
+ bindings
+- the test executable can now be installed to the bindir along with the
+ gpio-tools and the testing framework will look for the binaries in standard
+ locations if it's not run from the top source directory
+- gpiomon now supports line buffered output
+
+Improvements:
+- tweaks to the C API documentation
+- treewide unification of the naming of local variables
+- extended helptest in gpioset (explanation of the way the character device
+ works aimed at reducing user confusion when a GPIO line reverts to its
+ default value after gpioset exits)
+- the source directories have been rearranged and the src/ directory was
+ dropped, lib/ and tools/ now live in the top source directory
+- minor coding style fixes in python bindings, ctxless functions and tools
+- automatically generated documentation is now removed by 'make clean'
+- all Makefiles now use top_builddir instead of relative paths
+- code shrink in configure.ac
+- add a brief section about API documentation to README
+
+Bug fixes:
+- fix a segfault causing bug in C++ bindings
+- make bitset_cmp::operator() const as this is required by C++17
+- ignore 'remove' events from udev in the testing framework
+- don't segfault on num_lines = 0 in ctxless functions
+
+libgpiod v1.2
+=============
+
+New features:
+- new contextless event monitor that should replace the previous event loop
+ which caused problems on hardware that doesn't allow to watch both rising
+ and falling edge events
+- port gpiomon to the new event monitor
+- deprecate event loop routines
+
+Improvements:
+- many minor improvements and tweaks in the python module
+- new test cases for python bindings
+- add much more detailed documentation for python bindings
+- coding style improvements in gpio-tools
+- remove unicode characters from build scripts
+- improve the help text messages in gpio-tools
+- make gpiod_chip_open() and its variants verify that we're really trying to
+ open a character device associated with a GPIO chip
+
+Bug fixes:
+- fix memory leaks in python bindings
+- fix a memory corruption bug in python bindings
+- fix the default_vals argument in line request implementation in python
+ bindings
+- fix a compilation warning in python bindings
+- fix gpiod_Chip_find_lines() for nonexistent lines (python bindings)
+- add a missing include in C++ bindings examples
+- correctly display the version string in gpio-tools
+
+libgpiod v1.1
+=============
+
+New features:
+- add object-oriented C++ bindings
+- add object-oriented Python3 bindings
+- add several new helpers to the C API
+
+Improvements:
+- start using separate versioning schemes for API and ABI
+- use SPDX license identifiers and remove LGPL boilerplate
+- check for unexpanded macros in configure.ac
+
+Bug fixes:
+- include Doxyfile in the release tarball
+- fix the implicit-fallthrough warnings
+- make tests work together with gpio-mockup post v4.16 linux kernel
+- use reference counting for line file descriptors
+- correctly handle POLLNVAL when polling for events
+- fix the copyright notice in tools
+
+libgpiod v1.0
+=============
+
+NOTE: This is a major release - it breaks the API compatibility with
+ the 0.x.y series.
+
+New features:
+- remove custom error handling in favor of errnos
+- merge the two separate interfaces for event requests and regular line
+ requests
+- redesign the simple API
+- change the prefix of the high-level API from 'simple' to 'ctxless' (for
+ contextless) which better reflects its purpose
+- redesign the iterator API
+- make use of prefixes more consistent
+- rename symbols all over the place
+- various minor tweaks
+- add support for pkg-config
+
+Improvements:
+- add a bunch of helpers for line requests
+- split the library code into multiple source files by functionality
+- re-enable a test case previously broken by a bug in the kernel
+
+Bug fixes:
+- correctly handle signal interrupts when polling in gpiod_simple_event_loop()
+- fix the linking order when building with static libraries
+- pass the correct consumer string to gpiod_simple_get_value_multiple() in
+ gpioget
+- fix a line test case: don't use open-drain or open-source flags for input
+ mode
+- fix the flags passed to ar in order to supress a build warning
+- set the last error code in gpiod_chip_open_by_label() to ENOENT if a chip
+ can't be found
+- fix checking the kernel version in the test suite
+- fix various coding style issues
+- initialize the active low variable in gpiomon
+
+libgpiod v0.3
+=============
+
+New features:
+- gpiomon can now watch multiple lines at the same time and supports custom
+ output formats which can be specified using the --format argument
+- testing framework can now test external programs: test cases for gpio-tools
+ have been added
+
+Improvements:
+- improve error messages
+- improve README examples
+- configure script improvements
+
+Bug fixes:
+- use correct UAPI flags when requesting line events
+- capitalize 'GPIO' in error messages in gpioset, gpioget & gpiomon
+- tweak the error message on invalid arguments in gpiofind
+- don't ignore superfluous arguments and fix the displayed name for falling
+ edge events in gpiomon
+
+libgpiod v0.2
+=============
+
+New features:
+- relicensed under LGPLv2.1
+- implemented a unit testing framework together with a comprehensive
+ set of test cases
+- added a non-closing variant of the gpiochip iterator and foreach
+ macro [by Clemens Gruber]
+- added gpiod_chip_open_by_label()
+
+Improvements:
+- Makefiles & build commands have been reworked [by Thierry Reding]
+- documentation updates
+- code shrinkage here and there
+- coding style fixes
+- removed all designated initializers from the header for better standards
+ compliance
+
+Bug fixes:
+- fix the return value of gpiod_simple_event_loop()
+- don't try to process docs if doxygen is not installed
+- pass the O_CLOEXEC flag to open() when opening the GPIO chip device file
+- include <poll.h> instead of <sys/poll.h> in gpioset
+- fix a formatting issue in gpioinfo for chips with >100 GPIO lines
+- fix a bug when requesting both-edges event notifications
+- fix short options in gpiomon (short opt for --silent was missing)
+- correct the kernel headers requirements in README
+- include <time.h> for struct timespec
+- include <poll.h> instead of <sys/poll.h>
+- detect the version of strerror_r()
+
+libgpiod v0.1
+=============
+
+First version of libgpiod.
+
+It's currently possible to:
+- get and set the values of multiple GPIO lines with a single function call
+- monitor a GPIO line for events
+- enumerate all GPIO chips present in the system
+- enumerate all GPIO lines exposed by a chip
+- extract information about GPIO chips (label, name, number of lines)
+- extract information about GPIO lines (name, flags, state, user)
+
+Tools provided with the library are:
+- gpioget - read values from GPIO lines
+- gpioset - set values of GPIO lines
+- gpiodetect - list GPIO chips
+- gpioinfo - print info about GPIO lines exposed by a chip
+- gpiomon - monitor a GPIO line for events
+- gpiofind - find a GPIO line by name
--- /dev/null
+# SPDX-License-Identifier: CC-BY-SA-4.0
+# SPDX-FileCopyrightText: 2017-2023 Bartosz Golaszewski <brgl@bgdev.pl>
+
+libgpiod
+========
+
+ libgpiod - C library and tools for interacting with the linux GPIO
+ character device (gpiod stands for GPIO device)
+
+Since linux 4.8 the GPIO sysfs interface is deprecated. User space should use
+the character device instead. This library encapsulates the ioctl calls and
+data structures behind a straightforward API.
+
+RATIONALE
+---------
+
+The new character device interface guarantees all allocated resources are
+freed after closing the device file descriptor and adds several new features
+that are not present in the obsolete sysfs interface (like event polling,
+setting/reading multiple values at once or open-source and open-drain GPIOs).
+
+Unfortunately interacting with the linux device file can no longer be done
+using only standard command-line tools. This is the reason for creating a
+library encapsulating the cumbersome, ioctl-based kernel-userspace interaction
+in a set of convenient functions and opaque data structures.
+
+Additionally this project contains a set of command-line tools that should
+allow an easy conversion of user scripts to using the character device.
+
+BUILDING
+--------
+
+This is a pretty standard autotools project. The core C library does not have
+any external dependencies other than the standard C library with GNU extensions.
+
+The command-line tools optionally depend on libedit for the interactive feature.
+
+To build the project (including command-line utilities) run:
+
+ ./autogen.sh --enable-tools=yes --prefix=<install path>
+ make
+ make install
+
+The autogen script will execute ./configure and pass all the command-line
+arguments to it.
+
+If building from release tarballs, the configure script is already provided and
+there's no need to invoke autogen.sh.
+
+For all configure features, see: ./configure --help.
+
+TOOLS
+-----
+
+There are currently six command-line tools available:
+
+* gpiodetect - list all gpiochips present on the system, their names, labels
+ and number of GPIO lines
+
+* gpioinfo - list lines, their gpiochip, offset, name, and direction, and
+ if in use then the consumer name and any other configured
+ attributes, such as active state, bias, drive, edge detection
+ and debounce period
+
+* gpioget - read values of specified GPIO lines
+
+* gpioset - set values of specified GPIO lines, holding the lines until the
+ process is killed or otherwise exits
+
+* gpiomon - wait for edge events on GPIO lines, specify which edges to watch
+ for, how many events to process before exiting, or if the events
+ should be reported to the console
+
+* gpionotify - wait for changed to the info for GPIO lines, specify which
+ changes to watch for, how many events to process before exiting,
+ or if the events should be reported to the console
+
+Examples:
+
+ (using a Raspberry Pi 4B)
+
+ # Detect the available gpiochips.
+ $ gpiodetect
+ gpiochip0 [pinctrl-bcm2711] (58 lines)
+ gpiochip1 [raspberrypi-exp-gpio] (8 lines)
+
+ # Read the info for all the lines on a gpiochip.
+ $ gpioinfo -c 1
+ gpiochip1 - 8 lines:
+ line 0: "BT_ON" output
+ line 1: "WL_ON" output
+ line 2: "PWR_LED_OFF" output active-low consumer="led1"
+ line 3: "GLOBAL_RESET" output
+ line 4: "VDD_SD_IO_SEL" output consumer="vdd-sd-io"
+ line 5: "CAM_GPIO" output consumer="cam1_regulator"
+ line 6: "SD_PWR_ON" output consumer="sd_vcc_reg"
+ line 7: "SD_OC_N" input
+
+ # Read the info for particular lines.
+ $ ./gpioinfo PWR_LED_OFF STATUS_LED_G_CLK GLOBAL_RESET
+ gpiochip0 42 "STATUS_LED_G_CLK" output consumer="led0"
+ gpiochip1 2 "PWR_LED_OFF" output active-low consumer="led1"
+ gpiochip1 3 "GLOBAL_RESET" output
+
+ # Read the value of a single GPIO line by name.
+ $ gpioget RXD1
+ "RXD1"=active
+
+ # Read the value of a single GPIO line by chip and offset.
+ $ gpioget -c 0 15
+ "15"=active
+
+ # Read the value of a single GPIO line as a numeric value.
+ $ gpioget --numeric RXD1
+ 1
+
+ # Read two values at the same time. Set the active state of the lines
+ # to low and without quoted names.
+ $ gpioget --active-low --unquoted GPIO23 GPIO24
+ GPIO23=active GPIO24=active
+
+ # Set the value of a line and hold the line until killed.
+ $ gpioset GPIO23=1
+
+ # Set values of two lines, then daemonize and hold the lines.
+ $ gpioset --daemonize GPIO23=1 GPIO24=0
+
+ # Set the value of a single line, hold it for 20ms, then exit.
+ $ gpioset --hold-period 20ms -t0 GPIO23=1
+
+ # Blink an LED on GPIO22 at 1Hz
+ $ gpioset -t500ms GPIO22=1
+
+ # Blink an LED on GPIO22 at 1Hz with a 20% duty cycle
+ $ gpioset -t200ms,800ms GPIO22=1
+
+ # Set some lines interactively (requires --enable-gpioset-interative)
+ $ gpioset --interactive --unquoted GPIO23=inactive GPIO24=active
+ gpioset> get
+ GPIO23=inactive GPIO24=active
+ gpioset> toggle
+ gpioset> get
+ GPIO23=active GPIO24=inactive
+ gpioset> set GPIO24=1
+ gpioset> get
+ GPIO23=active GPIO24=active
+ gpioset> toggle
+ gpioset> get
+ GPIO23=inactive GPIO24=inactive
+ gpioset> toggle GPIO23
+ gpioset> get
+ GPIO23=active GPIO24=inactive
+ gpioset> exit
+
+ # Wait for three rising edge events on a single GPIO line, then exit.
+ $ gpiomon --num-events=3 --edges=rising GPIO22
+ 10002.907638045 rising "GPIO22"
+ 10037.132562259 rising "GPIO22"
+ 10047.179790748 rising "GPIO22"
+
+ # Wait for three edge events on a single GPIO line, with time in local time
+ # and with unquoted line name, then exit.
+ $ gpiomon --num-events=3 --edges=both --localtime --unquoted GPIO22
+ 2022-11-15T10:36:59.109615508 rising GPIO22
+ 2022-11-15T10:36:59.129681898 falling GPIO22
+ 2022-11-15T10:36:59.698971886 rising GPIO22
+
+ # Wait for falling edge events with a custom output format.
+ $ gpiomon --format="%e %c %o %l %S" --edges=falling -c gpiochip0 22
+ 2 gpiochip0 22 GPIO22 10946.693481859
+ 2 gpiochip0 22 GPIO22 10947.025347604
+ 2 gpiochip0 22 GPIO22 10947.283716669
+ 2 gpiochip0 22 GPIO22 10947.570109430
+ ...
+
+ # Block until an edge event occurs. Don't print anything.
+ $ gpiomon --num-events=1 --quiet GPIO22
+
+ # Monitor multiple lines, exit after the first edge event.
+ $ gpiomon --quiet --num-events=1 GPIO5 GPIO6 GPIO12 GPIO17
+
+ # Monitor a line for changes to info.
+ $ gpionotify GPIO23
+ 11571.816473718 requested "GPIO23"
+ 11571.816535124 released "GPIO23"
+ 11572.722894029 requested "GPIO23"
+ 11572.722932843 released "GPIO23"
+ 11573.222998598 requested "GPIO23"
+ ...
+
+ # Monitor a line for requests, reporting UTC time and unquoted line name.
+ $ gpionotify --utc --unquoted GPIO23
+ 2022-11-15T03:05:23.807090687Z requested GPIO23
+ 2022-11-15T03:05:23.807151390Z released GPIO23
+ 2022-11-15T03:05:24.784984280Z requested GPIO23
+ 2022-11-15T03:05:24.785023873Z released GPIO23
+ ...
+
+ # Monitor multiple lines, exit after the first is requested.
+ $ gpionotify --quiet --num-events=1 --event=requested GPIO5 GPIO6 GPIO12 GPIO17
+
+ # Block until a line is released.
+ $ gpionotify --quiet --num-events=1 --event=released GPIO6
+
+BINDINGS
+--------
+
+High-level, object-oriented bindings for C++, python3 and Rust are provided.
+They can be enabled by passing --enable-bindings-cxx, --enable-bindings-python
+and --enable-bindings-rust arguments respectively to configure.
+
+C++ bindings require C++11 support and autoconf-archive collection if building
+from git.
+
+Python bindings require python3 support and libpython development files. Care
+must be taken when cross-compiling python bindings: users usually must specify
+the PYTHON_CPPFLAGS and PYTHON_LIBS variables in order to point the build
+system to the correct locations. During native builds, the configure script
+can auto-detect the location of the development files.
+
+Rust bindings require cargo support. When building the Rust bindings along the
+C library using make, they will be automatically configured to build against the
+build results of the C library.
+
+TESTING
+-------
+
+A comprehensive testing framework is included with the library and can be
+used to test both the core library code as well as the kernel-to-user-space
+interface.
+
+The minimum kernel version required to run the tests can be checked in the
+tests/gpiod-test.c source file (it's subject to change if new features are
+added to the kernel). The tests work together with the gpio-sim kernel module
+which must either be built-in or available for loading using kmod. A helper
+library - libgpiosim - is included to enable straightforward interaction with
+the module.
+
+To build the testing executable add the '--enable-tests' option when running
+the configure script. If enabled, the tests will be installed next to
+gpio-tools.
+
+As opposed to standard autotools projects, libgpiod doesn't execute any tests
+when invoking 'make check'. Instead the user must run them manually with
+superuser privileges.
+
+The testing framework uses the GLib unit testing library so development package
+for GLib must be installed.
+
+The gpio-tools programs can be tested separately using the gpio-tools-test.bash
+script. It requires shunit2[1] to run and assumes that the tested executables are
+in the same directory as the script.
+
+C++, Rust and Python bindings also include their own test-suites. All three
+reuse the libgpiosim library to avoid code duplication when interacting with
+gpio-sim.
+
+Python test-suite uses the standard unittest package. C++ tests use an external
+testing framework - Catch2 - which must be installed in the system. Rust
+bindings use the standard tests module layout and the #[test] attribute.
+
+DOCUMENTATION
+-------------
+
+All API symbols exposed by the core C API and C++ bindings are documented with
+doxygen markup blocks. Doxygen documentation can be generated by executing
+'make doc' given that the doxygen executable is available in the system.
+
+Python bindings contain help strings that can be accessed with the help
+builtin.
+
+Rust bindings use rustdoc.
+
+Man pages for command-line programs are generated automatically if gpio-tools
+were selected and help2man is available in the system.
+
+CONTRIBUTING
+------------
+
+Contributions are welcome - please send questions, patches and bug reports
+to the linux-gpio mailing list[2] by e-mailing to linux-gpio@vger.kernel.org
+(add the [libgpiod] prefix to the e-mail subject line).
+Note that the mailing list quietly drops HTML formatted e-mail, so be sure
+to send plain text[3].
+
+Code submissions should stick to the linux kernel coding style[4] and
+follow the kernel patch submission process[5] as applied to the libgpiod
+source tree.
+
+The mailing list archive[6] contains all the historical mails to the list,
+and is the place to check to ensure your e-mail has been received.
+Search for "libgpiod" to filter the list down to relevant messages.
+Those also provide examples of the expected formatting.
+Allow some time for your e-mail to propagate to the list before retrying,
+particularly if there are no e-mails in the list more recent than yours.
+
+[1] https://github.com/kward/shunit2
+[2] http://vger.kernel.org/vger-lists.html#linux-gpio
+[3] https://docs.kernel.org/process/email-clients.html
+[4] https://docs.kernel.org/process/coding-style.html
+[5] https://docs.kernel.org/process/submitting-patches.html
+[6] https://lore.kernel.org/linux-gpio/
--- /dev/null
+# SPDX-License-Identifier: CC-BY-SA-4.0
+# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+
+TODO list for libgpiod
+
+==========
+
+This document contains the list of things I'd like to have in libgpiod before
+declaring it "mostly feature-complete". If anyone wants to help, this can
+serve as the starting point.
+
+==========
+
+* implement dbus API for controlling GPIOs
+
+A common complaint from users about gpioset is that the state of a line is not
+retained once the program exits. While this is precisely the way linux
+character devices work, it's understandable that most users will want some
+centralized way of controlling GPIOs - similar to how sysfs worked.
+
+One of the possible solutions is a DBus API. We need a daemon exposing chips
+and lines as dbus objects and allowing to control and inspect lines using
+dbus methods and monitor them using signals.
+
+As of writing of this document some of the work has already been done and the
+skeleton of the dbus daemon written in C using GLib has already been developed
+and is partially functional.
+
+----------
+
+* implement a simple daemon for controlling GPIOs in C together with a client
+ program
+
+This is by far the lowest priority task. Similarly as with the dbus daemon:
+the goal is to provide a centralized agent controlling GPIOs with a simple
+interface consisting of a command line client communicating with the server
+over unix sockets.
+
+In this case however the goal is to have as few dependencies as possible. This
+is because for some small systems dbus is overkill. Since we won't be using any
+standardized protocol, it will take much more effort to implement it correctly.
+
+----------
+
+* improve gpioset --interactive tab completion
+
+The existing tab completion uses libedit's readline emulation layer which
+has a few limitations, including not being able to correctly handle quoted
+line names and being disabled when stdin/stdout are not a tty (which makes
+testing with gpio-tools-test.bash using coproc problematic).
+
+One approach that could address both these problems is to bypass the readline
+emulation and use the libedit API (histedit.h) directly.
--- /dev/null
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+# SPDX-FileCopyrightText: 2017 Thierry Reding <treding@nvidia.com>
+
+srcdir=`dirname $0`
+test -z "$srcdir" && srcdir=.
+
+ORIGDIR=`pwd`
+cd "$srcdir"
+
+autoreconf --force --install --verbose || exit 1
+cd $ORIGDIR || exit $?
+
+if test -z "$NOCONFIGURE"; then
+ exec "$srcdir"/configure "$@"
+fi
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+
+SUBDIRS = .
+
+if WITH_BINDINGS_CXX
+
+SUBDIRS += cxx
+
+endif
+
+if WITH_BINDINGS_PYTHON
+
+SUBDIRS += python
+
+endif
+
+if WITH_BINDINGS_RUST
+
+SUBDIRS += rust
+
+endif
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+
+lib_LTLIBRARIES = libgpiodcxx.la
+libgpiodcxx_la_SOURCES = \
+ chip.cpp \
+ chip-info.cpp \
+ edge-event-buffer.cpp \
+ edge-event.cpp \
+ exception.cpp \
+ info-event.cpp \
+ internal.cpp \
+ internal.hpp \
+ line.cpp \
+ line-config.cpp \
+ line-info.cpp \
+ line-request.cpp \
+ line-settings.cpp \
+ misc.cpp \
+ request-builder.cpp \
+ request-config.cpp
+
+libgpiodcxx_la_CXXFLAGS = -Wall -Wextra -g -std=gnu++17
+libgpiodcxx_la_CXXFLAGS += -fvisibility=hidden -I$(top_srcdir)/include/
+libgpiodcxx_la_CXXFLAGS += $(PROFILING_CFLAGS)
+libgpiodcxx_la_LDFLAGS = -version-info $(subst .,:,$(ABI_CXX_VERSION))
+libgpiodcxx_la_LDFLAGS += $(PROFILING_LDFLAGS)
+libgpiodcxx_la_LIBADD = $(top_builddir)/lib/libgpiod.la
+
+include_HEADERS = gpiod.hpp
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = libgpiodcxx.pc
+
+SUBDIRS = gpiodcxx .
+
+if WITH_TESTS
+
+SUBDIRS += tests
+
+endif
+
+if WITH_EXAMPLES
+
+SUBDIRS += examples
+
+endif
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <ostream>
+#include <utility>
+
+#include "internal.hpp"
+
+namespace gpiod {
+
+void chip_info::impl::set_info_ptr(chip_info_ptr& new_info)
+{
+ this->info = ::std::move(new_info);
+}
+
+GPIOD_CXX_API chip_info::chip_info()
+ : _m_priv(new impl)
+{
+
+}
+
+GPIOD_CXX_API chip_info::chip_info(const chip_info& other)
+ : _m_priv(other._m_priv)
+{
+
+}
+
+GPIOD_CXX_API chip_info::chip_info(chip_info&& other) noexcept
+ : _m_priv(::std::move(other._m_priv))
+{
+
+}
+
+GPIOD_CXX_API chip_info::~chip_info()
+{
+
+}
+
+GPIOD_CXX_API chip_info& chip_info::operator=(const chip_info& other)
+{
+ this->_m_priv = other._m_priv;
+
+ return *this;
+}
+
+GPIOD_CXX_API chip_info& chip_info::operator=(chip_info&& other) noexcept
+{
+ this->_m_priv = ::std::move(other._m_priv);
+
+ return *this;
+}
+
+GPIOD_CXX_API ::std::string chip_info::name() const noexcept
+{
+ return ::gpiod_chip_info_get_name(this->_m_priv->info.get());
+}
+
+GPIOD_CXX_API ::std::string chip_info::label() const noexcept
+{
+ return ::gpiod_chip_info_get_label(this->_m_priv->info.get());
+}
+
+GPIOD_CXX_API ::std::size_t chip_info::num_lines() const noexcept
+{
+ return ::gpiod_chip_info_get_num_lines(this->_m_priv->info.get());
+}
+
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const chip_info& info)
+{
+ out << "gpiod::chip_info(name=\"" << info.name() <<
+ "\", label=\"" << info.label() <<
+ "\", num_lines=" << info.num_lines() << ")";
+
+ return out;
+}
+
+} /* namespace gpiod */
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <ostream>
+#include <utility>
+
+#include "internal.hpp"
+
+namespace gpiod {
+
+namespace {
+
+chip_ptr open_chip(const ::std::filesystem::path& path)
+{
+ chip_ptr chip(::gpiod_chip_open(path.c_str()));
+ if (!chip)
+ throw_from_errno("unable to open the GPIO device " + path.string());
+
+ return chip;
+}
+
+} /* namespace */
+
+chip::impl::impl(const ::std::filesystem::path& path)
+ : chip(open_chip(path))
+{
+
+}
+
+void chip::impl::throw_if_closed() const
+{
+ if (!this->chip)
+ throw chip_closed("GPIO chip has been closed");
+}
+
+GPIOD_CXX_API chip::chip(const ::std::filesystem::path& path)
+ : _m_priv(new impl(path))
+{
+
+}
+
+chip::chip(const chip& other)
+ : _m_priv(other._m_priv)
+{
+
+}
+
+GPIOD_CXX_API chip::chip(chip&& other) noexcept
+ : _m_priv(::std::move(other._m_priv))
+{
+
+}
+
+GPIOD_CXX_API chip::~chip()
+{
+
+}
+
+GPIOD_CXX_API chip& chip::operator=(chip&& other) noexcept
+{
+ this->_m_priv = ::std::move(other._m_priv);
+
+ return *this;
+}
+
+GPIOD_CXX_API chip::operator bool() const noexcept
+{
+ return this->_m_priv->chip.get() != nullptr;
+}
+
+GPIOD_CXX_API void chip::close()
+{
+ this->_m_priv->throw_if_closed();
+
+ this->_m_priv->chip.reset();
+}
+
+GPIOD_CXX_API ::std::filesystem::path chip::path() const
+{
+ this->_m_priv->throw_if_closed();
+
+ return ::gpiod_chip_get_path(this->_m_priv->chip.get());
+}
+
+GPIOD_CXX_API chip_info chip::get_info() const
+{
+ this->_m_priv->throw_if_closed();
+
+ chip_info_ptr info(::gpiod_chip_get_info(this->_m_priv->chip.get()));
+ if (!info)
+ throw_from_errno("failed to retrieve GPIO chip info");
+
+ chip_info ret;
+
+ ret._m_priv->set_info_ptr(info);
+
+ return ret;
+}
+
+GPIOD_CXX_API line_info chip::get_line_info(line::offset offset) const
+{
+ this->_m_priv->throw_if_closed();
+
+ line_info_ptr info(::gpiod_chip_get_line_info(this->_m_priv->chip.get(), offset));
+ if (!info)
+ throw_from_errno("unable to retrieve GPIO line info");
+
+ line_info ret;
+
+ ret._m_priv->set_info_ptr(info);
+
+ return ret;
+}
+
+GPIOD_CXX_API line_info chip::watch_line_info(line::offset offset) const
+{
+ this->_m_priv->throw_if_closed();
+
+ line_info_ptr info(::gpiod_chip_watch_line_info(this->_m_priv->chip.get(), offset));
+ if (!info)
+ throw_from_errno("unable to start watching GPIO line info changes");
+
+ line_info ret;
+
+ ret._m_priv->set_info_ptr(info);
+
+ return ret;
+}
+
+GPIOD_CXX_API void chip::unwatch_line_info(line::offset offset) const
+{
+ this->_m_priv->throw_if_closed();
+
+ int ret = ::gpiod_chip_unwatch_line_info(this->_m_priv->chip.get(), offset);
+ if (ret)
+ throw_from_errno("unable to unwatch line status changes");
+}
+
+GPIOD_CXX_API int chip::fd() const
+{
+ this->_m_priv->throw_if_closed();
+
+ return ::gpiod_chip_get_fd(this->_m_priv->chip.get());
+}
+
+GPIOD_CXX_API bool chip::wait_info_event(const ::std::chrono::nanoseconds& timeout) const
+{
+ this->_m_priv->throw_if_closed();
+
+ int ret = ::gpiod_chip_wait_info_event(this->_m_priv->chip.get(), timeout.count());
+ if (ret < 0)
+ throw_from_errno("error waiting for info events");
+
+ return ret;
+}
+
+GPIOD_CXX_API info_event chip::read_info_event() const
+{
+ this->_m_priv->throw_if_closed();
+
+ info_event_ptr event(gpiod_chip_read_info_event(this->_m_priv->chip.get()));
+ if (!event)
+ throw_from_errno("error reading the line info event_handle");
+
+ info_event ret;
+ ret._m_priv->set_info_event_ptr(event);
+
+ return ret;
+}
+
+GPIOD_CXX_API int chip::get_line_offset_from_name(const ::std::string& name) const
+{
+ this->_m_priv->throw_if_closed();
+
+ int ret = ::gpiod_chip_get_line_offset_from_name(this->_m_priv->chip.get(), name.c_str());
+ if (ret < 0) {
+ if (errno == ENOENT)
+ return -1;
+
+ throw_from_errno("error looking up line by name");
+ }
+
+ return ret;
+}
+
+GPIOD_CXX_API request_builder chip::prepare_request()
+{
+ return request_builder(*this);
+}
+
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const chip& chip)
+{
+ if (!chip)
+ out << "gpiod::chip(closed)";
+ else
+ out << "gpiod::chip(path=" << chip.path() <<
+ ", info=" << chip.get_info() << ")";
+
+ return out;
+}
+
+} /* namespace gpiod */
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <iterator>
+#include <ostream>
+#include <utility>
+
+#include "internal.hpp"
+
+namespace gpiod {
+
+namespace {
+
+edge_event_buffer_ptr make_edge_event_buffer(unsigned int capacity)
+{
+ edge_event_buffer_ptr buffer(::gpiod_edge_event_buffer_new(capacity));
+ if (!buffer)
+ throw_from_errno("unable to allocate the edge event buffer");
+
+ return buffer;
+}
+
+} /* namespace */
+
+edge_event_buffer::impl::impl(unsigned int capacity)
+ : buffer(make_edge_event_buffer(capacity)),
+ events()
+{
+ events.reserve(capacity);
+
+ for (unsigned int i = 0; i < capacity; i++) {
+ events.push_back(edge_event());
+ events.back()._m_priv.reset(new edge_event::impl_external);
+ }
+}
+
+int edge_event_buffer::impl::read_events(const line_request_ptr& request, unsigned int max_events)
+{
+ int ret = ::gpiod_line_request_read_edge_events(request.get(),
+ this->buffer.get(), max_events);
+ if (ret < 0)
+ throw_from_errno("error reading edge events from file descriptor");
+
+ for (int i = 0; i < ret; i++) {
+ ::gpiod_edge_event* event = ::gpiod_edge_event_buffer_get_event(this->buffer.get(), i);
+
+ dynamic_cast<edge_event::impl_external&>(*this->events[i]._m_priv).event = event;
+ }
+
+ return ret;
+}
+
+GPIOD_CXX_API edge_event_buffer::edge_event_buffer(::std::size_t capacity)
+ : _m_priv(new impl(capacity))
+{
+
+}
+
+GPIOD_CXX_API edge_event_buffer::edge_event_buffer(edge_event_buffer&& other) noexcept
+ : _m_priv(::std::move(other._m_priv))
+{
+
+}
+
+GPIOD_CXX_API edge_event_buffer::~edge_event_buffer()
+{
+
+}
+
+GPIOD_CXX_API edge_event_buffer& edge_event_buffer::operator=(edge_event_buffer&& other) noexcept
+{
+ this->_m_priv = ::std::move(other._m_priv);
+
+ return *this;
+}
+
+GPIOD_CXX_API const edge_event& edge_event_buffer::get_event(unsigned int index) const
+{
+ return this->_m_priv->events.at(index);
+}
+
+GPIOD_CXX_API ::std::size_t edge_event_buffer::num_events() const
+{
+ return ::gpiod_edge_event_buffer_get_num_events(this->_m_priv->buffer.get());
+}
+
+GPIOD_CXX_API ::std::size_t edge_event_buffer::capacity() const noexcept
+{
+ return ::gpiod_edge_event_buffer_get_capacity(this->_m_priv->buffer.get());
+}
+
+GPIOD_CXX_API edge_event_buffer::const_iterator edge_event_buffer::begin() const noexcept
+{
+ return this->_m_priv->events.begin();
+}
+
+GPIOD_CXX_API edge_event_buffer::const_iterator edge_event_buffer::end() const noexcept
+{
+ return this->_m_priv->events.begin() + this->num_events();
+}
+
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const edge_event_buffer& buf)
+{
+ out << "gpiod::edge_event_buffer(num_events=" << buf.num_events() <<
+ ", capacity=" << buf.capacity() <<
+ ", events=[";
+
+ ::std::copy(buf.begin(), ::std::prev(buf.end()),
+ ::std::ostream_iterator<edge_event>(out, ", "));
+ out << *(::std::prev(buf.end()));
+
+ out << "])";
+
+ return out;
+}
+
+} /* namespace gpiod */
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <map>
+#include <ostream>
+#include <utility>
+
+#include "internal.hpp"
+
+namespace gpiod {
+
+namespace {
+
+const ::std::map<int, edge_event::event_type> event_type_mapping = {
+ { GPIOD_EDGE_EVENT_RISING_EDGE, edge_event::event_type::RISING_EDGE },
+ { GPIOD_EDGE_EVENT_FALLING_EDGE, edge_event::event_type::FALLING_EDGE },
+};
+
+const ::std::map<edge_event::event_type, ::std::string> event_type_names = {
+ { edge_event::event_type::RISING_EDGE, "RISING_EDGE" },
+ { edge_event::event_type::FALLING_EDGE, "FALLING_EDGE" },
+};
+
+} /* namespace */
+
+::gpiod_edge_event* edge_event::impl_managed::get_event_ptr() const noexcept
+{
+ return this->event.get();
+}
+
+::std::shared_ptr<edge_event::impl>
+edge_event::impl_managed::copy(const ::std::shared_ptr<impl>& self) const
+{
+ return self;
+}
+
+edge_event::impl_external::impl_external()
+ : impl(),
+ event(nullptr)
+{
+
+}
+
+::gpiod_edge_event* edge_event::impl_external::get_event_ptr() const noexcept
+{
+ return this->event;
+}
+
+::std::shared_ptr<edge_event::impl>
+edge_event::impl_external::copy([[maybe_unused]] const ::std::shared_ptr<impl>& self) const
+{
+ ::std::shared_ptr<impl> ret(new impl_managed);
+ impl_managed& managed = dynamic_cast<impl_managed&>(*ret);
+
+ managed.event.reset(::gpiod_edge_event_copy(this->event));
+ if (!managed.event)
+ throw_from_errno("unable to copy the edge event object");
+
+ return ret;
+}
+
+edge_event::edge_event()
+ : _m_priv()
+{
+
+}
+
+GPIOD_CXX_API edge_event::edge_event(const edge_event& other)
+ : _m_priv(other._m_priv->copy(other._m_priv))
+{
+
+}
+
+GPIOD_CXX_API edge_event::edge_event(edge_event&& other) noexcept
+ : _m_priv(::std::move(other._m_priv))
+{
+
+}
+
+GPIOD_CXX_API edge_event::~edge_event()
+{
+
+}
+
+GPIOD_CXX_API edge_event& edge_event::operator=(const edge_event& other)
+{
+ this->_m_priv = other._m_priv->copy(other._m_priv);
+
+ return *this;
+}
+
+GPIOD_CXX_API edge_event& edge_event::operator=(edge_event&& other) noexcept
+{
+ this->_m_priv = ::std::move(other._m_priv);
+
+ return *this;
+}
+
+GPIOD_CXX_API edge_event::event_type edge_event::type() const
+{
+ int evtype = ::gpiod_edge_event_get_event_type(this->_m_priv->get_event_ptr());
+
+ return get_mapped_value(evtype, event_type_mapping);
+}
+
+GPIOD_CXX_API timestamp edge_event::timestamp_ns() const noexcept
+{
+ return ::gpiod_edge_event_get_timestamp_ns(this->_m_priv->get_event_ptr());
+}
+
+GPIOD_CXX_API line::offset edge_event::line_offset() const noexcept
+{
+ return ::gpiod_edge_event_get_line_offset(this->_m_priv->get_event_ptr());
+}
+
+GPIOD_CXX_API unsigned long edge_event::global_seqno() const noexcept
+{
+ return ::gpiod_edge_event_get_global_seqno(this->_m_priv->get_event_ptr());
+}
+
+GPIOD_CXX_API unsigned long edge_event::line_seqno() const noexcept
+{
+ return ::gpiod_edge_event_get_line_seqno(this->_m_priv->get_event_ptr());
+}
+
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const edge_event& event)
+{
+ out << "gpiod::edge_event(type='" << event_type_names.at(event.type()) <<
+ "', timestamp=" << event.timestamp_ns() <<
+ ", line_offset=" << event.line_offset() <<
+ ", global_seqno=" << event.global_seqno() <<
+ ", line_seqno=" << event.line_seqno() << ")";
+
+ return out;
+}
+
+} /* namespace gpiod */
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+
+async_watch_line_value
+find_line_by_name
+get_chip_info
+get_line_info
+get_line_value
+get_multiple_line_values
+reconfigure_input_to_output
+toggle_line_value
+toggle_multiple_line_values
+watch_line_info
+watch_line_rising
+watch_line_value
+watch_multiple_line_values
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+
+AM_CXXFLAGS = -I$(top_srcdir)/bindings/cxx/ -I$(top_srcdir)/include
+AM_CXXFLAGS += -Wall -Wextra -g -std=gnu++17
+LDADD = $(top_builddir)/bindings/cxx/libgpiodcxx.la
+
+noinst_PROGRAMS = \
+ async_watch_line_value \
+ find_line_by_name \
+ get_chip_info \
+ get_line_info \
+ get_line_value \
+ get_multiple_line_values \
+ reconfigure_input_to_output \
+ toggle_line_value \
+ toggle_multiple_line_values \
+ watch_line_info \
+ watch_line_rising \
+ watch_line_value \
+ watch_multiple_line_values
+
+async_watch_line_value_SOURCES = async_watch_line_value.cpp
+
+find_line_by_name_SOURCES = find_line_by_name.cpp
+
+get_chip_info_SOURCES = get_chip_info.cpp
+
+get_line_info_SOURCES = get_line_info.cpp
+
+get_line_value_SOURCES = get_line_value.cpp
+
+get_multiple_line_values_SOURCES = get_multiple_line_values.cpp
+
+reconfigure_input_to_output_SOURCES = reconfigure_input_to_output.cpp
+
+toggle_line_value_SOURCES = toggle_line_value.cpp
+
+toggle_multiple_line_values_SOURCES = toggle_multiple_line_values.cpp
+
+watch_line_info_SOURCES = watch_line_info.cpp
+
+watch_line_value_SOURCES = watch_line_value.cpp
+
+watch_line_rising_SOURCES = watch_line_rising.cpp
+
+watch_multiple_line_values_SOURCES = watch_multiple_line_values.cpp
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+/* Minimal example of asynchronously watching for edges on a single line. */
+
+#include <cstdlib>
+#include <cstring>
+#include <filesystem>
+#include <gpiod.hpp>
+#include <iomanip>
+#include <iostream>
+#include <poll.h>
+
+namespace {
+
+/* Example configuration - customize to suit your situation. */
+const ::std::filesystem::path chip_path("/dev/gpiochip0");
+const ::gpiod::line::offset line_offset = 5;
+
+const char* edge_event_type_str(const ::gpiod::edge_event &event)
+{
+ switch (event.type()) {
+ case ::gpiod::edge_event::event_type::RISING_EDGE:
+ return "Rising ";
+ case ::gpiod::edge_event::event_type::FALLING_EDGE:
+ return "Falling";
+ default:
+ return "Unknown";
+ }
+}
+
+} /* namespace */
+
+int main()
+{
+ /*
+ * Assume a button connecting the pin to ground, so pull it up and
+ * provide some debounce.
+ */
+ auto request =
+ ::gpiod::chip(chip_path)
+ .prepare_request()
+ .set_consumer("async-watch-line-value")
+ .add_line_settings(
+ line_offset,
+ ::gpiod::line_settings()
+ .set_direction(
+ ::gpiod::line::direction::INPUT)
+ .set_edge_detection(
+ ::gpiod::line::edge::BOTH)
+ .set_bias(::gpiod::line::bias::PULL_UP)
+ .set_debounce_period(
+ std::chrono::milliseconds(10)))
+ .do_request();
+
+ /*
+ * A larger buffer is an optimisation for reading bursts of events from
+ * the kernel, but that is not necessary in this case, so 1 is fine.
+ */
+ ::gpiod::edge_event_buffer buffer(1);
+
+ struct pollfd pollfd;
+ pollfd.fd = request.fd();
+ pollfd.events = POLLIN;
+
+ for (;;) {
+ /*
+ * Other fds could be registered with the poll and be handled
+ * separately using the pollfd.revents after poll()
+ */
+ auto ret = poll(&pollfd, 1, -1);
+ if (ret == -1) {
+ ::std::cerr << "error waiting for edge events: "
+ << strerror(errno) << ::std::endl;
+
+ return EXIT_FAILURE;
+ }
+
+ request.read_edge_events(buffer);
+
+ for (const auto& event : buffer)
+ ::std::cout << "offset: " << event.line_offset()
+ << " type: " << ::std::setw(7)
+ << ::std::left << edge_event_type_str(event)
+ << " event #" << event.line_seqno()
+ << ::std::endl;
+ }
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+/* Minimal example of finding a line with the given name. */
+
+#include <cstdlib>
+#include <filesystem>
+#include <gpiod.hpp>
+#include <iostream>
+
+namespace {
+
+/* Example configuration - customize to suit your situation */
+const ::std::string line_name = "GPIO19";
+
+} /* namespace */
+
+int main()
+{
+ /*
+ * Names are not guaranteed unique, so this finds the first line with
+ * the given name.
+ */
+ for (const auto &entry :
+ ::std::filesystem::directory_iterator("/dev/")) {
+ if (::gpiod::is_gpiochip_device(entry.path())) {
+ ::gpiod::chip chip(entry.path());
+
+ auto offset = chip.get_line_offset_from_name(line_name);
+ if (offset >= 0) {
+ ::std::cout << line_name << ": "
+ << chip.get_info().name() << " "
+ << offset << ::std::endl;
+ return EXIT_SUCCESS;
+ }
+ }
+ }
+ ::std::cout << "line '" << line_name << "' not found" << ::std::endl;
+
+ return EXIT_FAILURE;
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+/* Minimal example of reading the info for a chip. */
+
+#include <cstdlib>
+#include <filesystem>
+#include <gpiod.hpp>
+#include <iostream>
+
+namespace {
+
+/* Example configuration - customize to suit your situation */
+const ::std::filesystem::path chip_path("/dev/gpiochip0");
+
+} /* namespace */
+
+int main()
+{
+ ::gpiod::chip chip(chip_path);
+ auto info = chip.get_info();
+
+ ::std::cout << info.name() << " [" << info.label() << "] ("
+ << info.num_lines() << " lines)" << ::std::endl;
+
+ return EXIT_SUCCESS;
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+/* Minimal example of reading the info for a line. */
+
+#include <cstdlib>
+#include <filesystem>
+#include <gpiod.hpp>
+#include <iomanip>
+#include <iostream>
+
+namespace {
+
+/* Example configuration - customize to suit your situation */
+const ::std::filesystem::path chip_path("/dev/gpiochip0");
+const ::gpiod::line::offset line_offset = 3;
+
+} /* namespace */
+
+int main()
+{
+ auto chip = ::gpiod::chip(chip_path);
+ auto info = chip.get_line_info(line_offset);
+
+ ::std::cout << "line " << ::std::setw(3) << info.offset() << ": "
+ << ::std::setw(12)
+ << (info.name().empty() ? "unnamed" : info.name()) << " "
+ << ::std::setw(12)
+ << (info.consumer().empty() ? "unused" : info.consumer())
+ << " " << ::std::setw(8)
+ << (info.direction() == ::gpiod::line::direction::INPUT ?
+ "input" :
+ "output")
+ << " " << ::std::setw(10)
+ << (info.active_low() ? "active-low" : "active-high")
+ << ::std::endl;
+
+ return EXIT_SUCCESS;
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+/* Minimal example of reading a single line. */
+
+#include <cstdlib>
+#include <filesystem>
+#include <gpiod.hpp>
+#include <iostream>
+
+namespace {
+
+/* Example configuration - customize to suit your situation */
+const ::std::filesystem::path chip_path("/dev/gpiochip0");
+const ::gpiod::line::offset line_offset = 5;
+
+} /* namespace */
+
+int main()
+{
+ auto request = ::gpiod::chip(chip_path)
+ .prepare_request()
+ .set_consumer("get-line-value")
+ .add_line_settings(
+ line_offset,
+ ::gpiod::line_settings().set_direction(
+ ::gpiod::line::direction::INPUT))
+ .do_request();
+
+ ::std::cout << line_offset << "="
+ << (request.get_value(line_offset) ==
+ ::gpiod::line::value::ACTIVE ?
+ "Active" :
+ "Inactive")
+ << ::std::endl;
+
+ return EXIT_SUCCESS;
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+/* Minimal example of reading multiple lines. */
+
+#include <cstdlib>
+#include <gpiod.hpp>
+#include <iostream>
+
+namespace {
+
+/* Example configuration - customize to suit your situation */
+const ::std::filesystem::path chip_path("/dev/gpiochip0");
+const ::gpiod::line::offsets line_offsets = { 5, 3, 7 };
+
+} /* namespace */
+
+int main()
+{
+ auto request = ::gpiod::chip(chip_path)
+ .prepare_request()
+ .set_consumer("get-multiple-line-values")
+ .add_line_settings(
+ line_offsets,
+ ::gpiod::line_settings().set_direction(
+ ::gpiod::line::direction::INPUT))
+ .do_request();
+
+ auto values = request.get_values();
+
+ for (size_t i = 0; i < line_offsets.size(); i++)
+ ::std::cout << line_offsets[i] << "="
+ << (values[i] == ::gpiod::line::value::ACTIVE ?
+ "Active" :
+ "Inactive")
+ << ' ';
+ ::std::cout << ::std::endl;
+
+ return EXIT_SUCCESS;
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+/*
+ * Example of a bi-directional line requested as input and then switched
+ * to output.
+ */
+
+#include <cstdlib>
+#include <filesystem>
+#include <gpiod.hpp>
+#include <iostream>
+
+namespace {
+
+/* Example configuration - customize to suit your situation */
+const ::std::filesystem::path chip_path("/dev/gpiochip0");
+const ::gpiod::line::offset line_offset = 5;
+
+} /* namespace */
+
+int main()
+{
+ /* request the line initially as an input */
+ auto request = ::gpiod::chip(chip_path)
+ .prepare_request()
+ .set_consumer("reconfigure-input-to-output")
+ .add_line_settings(
+ line_offset,
+ ::gpiod::line_settings().set_direction(
+ ::gpiod::line::direction::INPUT))
+ .do_request();
+
+ /* read the current line value */
+ ::std::cout << line_offset << "="
+ << (request.get_value(line_offset) ==
+ ::gpiod::line::value::ACTIVE ?
+ "Active" :
+ "Inactive")
+ << " (input)" << ::std::endl;
+
+ /* switch the line to an output and drive it low */
+ request.reconfigure_lines(::gpiod::line_config().add_line_settings(
+ line_offset,
+ ::gpiod::line_settings()
+ .set_direction(::gpiod::line::direction::OUTPUT)
+ .set_output_value(::gpiod::line::value::INACTIVE)));
+
+ /* report the current driven value */
+ ::std::cout << line_offset << "="
+ << (request.get_value(line_offset) ==
+ ::gpiod::line::value::ACTIVE ?
+ "Active" :
+ "Inactive")
+ << " (output)" << ::std::endl;
+
+ return EXIT_SUCCESS;
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+/* Minimal example of toggling a single line. */
+
+#include <cstdlib>
+#include <chrono>
+#include <filesystem>
+#include <gpiod.hpp>
+#include <iostream>
+#include <thread>
+
+namespace {
+
+/* Example configuration - customize to suit your situation. */
+const ::std::filesystem::path chip_path("/dev/gpiochip0");
+const ::gpiod::line::offset line_offset = 5;
+
+::gpiod::line::value toggle_value(::gpiod::line::value v)
+{
+ return (v == ::gpiod::line::value::ACTIVE) ?
+ ::gpiod::line::value::INACTIVE :
+ ::gpiod::line::value::ACTIVE;
+}
+
+} /* namespace */
+
+int main()
+{
+ ::gpiod::line::value value = ::gpiod::line::value::ACTIVE;
+
+ auto request =
+ ::gpiod::chip(chip_path)
+ .prepare_request()
+ .set_consumer("toggle-line-value")
+ .add_line_settings(
+ line_offset,
+ ::gpiod::line_settings().set_direction(
+ ::gpiod::line::direction::OUTPUT))
+ .do_request();
+
+ for (;;) {
+ ::std::cout << line_offset << "=" << value << ::std::endl;
+
+ std::this_thread::sleep_for(std::chrono::seconds(1));
+ value = toggle_value(value);
+ request.set_value(line_offset, value);
+ }
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+/* Minimal example of toggling multiple lines. */
+
+#include <cstdlib>
+#include <gpiod.hpp>
+#include <iostream>
+#include <thread>
+
+namespace {
+
+/* Example configuration - customize to suit your situation */
+const ::std::filesystem::path chip_path("/dev/gpiochip0");
+const ::gpiod::line::offsets line_offsets = { 5, 3, 7 };
+
+::gpiod::line::value toggle_value(::gpiod::line::value v)
+{
+ return (v == ::gpiod::line::value::ACTIVE) ?
+ ::gpiod::line::value::INACTIVE :
+ ::gpiod::line::value::ACTIVE;
+}
+
+void toggle_values(::gpiod::line::values &values)
+{
+ for (size_t i = 0; i < values.size(); i++)
+ values[i] = toggle_value(values[i]);
+}
+
+void print_values(::gpiod::line::offsets const &offsets,
+ ::gpiod::line::values const &values)
+{
+ for (size_t i = 0; i < offsets.size(); i++)
+ ::std::cout << offsets[i] << "=" << values[i] << ' ';
+ ::std::cout << ::std::endl;
+}
+
+} /* namespace */
+
+int main()
+{
+ ::gpiod::line::values values = { ::gpiod::line::value::ACTIVE,
+ ::gpiod::line::value::ACTIVE,
+ ::gpiod::line::value::INACTIVE };
+
+ auto request =
+ ::gpiod::chip(chip_path)
+ .prepare_request()
+ .set_consumer("toggle-multiple-line-values")
+ .add_line_settings(
+ line_offsets,
+ ::gpiod::line_settings().set_direction(
+ ::gpiod::line::direction::OUTPUT))
+ .set_output_values(values)
+ .do_request();
+
+ for (;;) {
+ print_values(line_offsets, values);
+ std::this_thread::sleep_for(std::chrono::seconds(1));
+ toggle_values(values);
+ request.set_values(line_offsets, values);
+ }
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+/* Minimal example of watching for requests on particular lines. */
+
+#include <cstdlib>
+#include <gpiod.hpp>
+#include <iomanip>
+#include <iostream>
+
+namespace {
+
+/* Example configuration - customize to suit your situation. */
+const ::std::filesystem::path chip_path("/dev/gpiochip0");
+const ::gpiod::line::offsets line_offsets = { 5, 3, 7 };
+
+const char *event_type(const ::gpiod::info_event &event)
+{
+ switch (event.type()) {
+ case ::gpiod::info_event::event_type::LINE_REQUESTED:
+ return "Requested";
+ case ::gpiod::info_event::event_type::LINE_RELEASED:
+ return "Released";
+ case ::gpiod::info_event::event_type::LINE_CONFIG_CHANGED:
+ return "Reconfig";
+ default:
+ return "Unknown";
+ }
+}
+
+} /* namespace */
+
+int main()
+{
+ ::gpiod::chip chip(chip_path);
+
+ for (auto offset :line_offsets)
+ chip.watch_line_info(offset);
+
+ for (;;) {
+ /* Blocks until at least one event is available */
+ auto event = chip.read_info_event();
+ ::std::cout << "line: " << event.get_line_info().offset() << " "
+ << ::std::setw(9) << ::std::left
+ << event_type(event) << " "
+ << event.timestamp_ns() / 1000000000 << "."
+ << event.timestamp_ns() % 1000000000 << ::std::endl;
+ }
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+/* Minimal example of watching for rising edges on a single line. */
+
+#include <cstdlib>
+#include <filesystem>
+#include <gpiod.hpp>
+#include <iomanip>
+#include <iostream>
+
+namespace {
+
+/* Example configuration - customize to suit your situation. */
+const ::std::filesystem::path chip_path("/dev/gpiochip0");
+const ::gpiod::line::offset line_offset = 5;
+
+const char *edge_event_type_str(const ::gpiod::edge_event &event)
+{
+ switch (event.type()) {
+ case ::gpiod::edge_event::event_type::RISING_EDGE:
+ return "Rising";
+ case ::gpiod::edge_event::event_type::FALLING_EDGE:
+ return "Falling";
+ default:
+ return "Unknown";
+ }
+}
+
+} /* namespace */
+
+int main()
+{
+ auto request =
+ ::gpiod::chip(chip_path)
+ .prepare_request()
+ .set_consumer("watch-line-value")
+ .add_line_settings(
+ line_offset,
+ ::gpiod::line_settings()
+ .set_direction(
+ ::gpiod::line::direction::INPUT)
+ .set_edge_detection(
+ ::gpiod::line::edge::RISING)
+ )
+ .do_request();
+
+ /*
+ * A larger buffer is an optimisation for reading bursts of events from
+ * the kernel, but that is not necessary in this case, so 1 is fine.
+ */
+ ::gpiod::edge_event_buffer buffer(1);
+
+ for (;;) {
+ /* Blocks until at least one event is available. */
+ request.read_edge_events(buffer);
+
+ for (const auto &event : buffer)
+ ::std::cout << "line: " << event.line_offset()
+ << " type: " << ::std::setw(7) << ::std::left << edge_event_type_str(event)
+ << " event #" << event.line_seqno()
+ << ::std::endl;
+ }
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+/* Minimal example of watching for edges on a single line. */
+
+#include <cstdlib>
+#include <filesystem>
+#include <gpiod.hpp>
+#include <iomanip>
+#include <iostream>
+
+namespace {
+
+/* Example configuration - customize to suit your situation. */
+const ::std::filesystem::path chip_path("/dev/gpiochip0");
+const ::gpiod::line::offset line_offset = 5;
+
+const char *edge_event_type_str(const ::gpiod::edge_event &event)
+{
+ switch (event.type()) {
+ case ::gpiod::edge_event::event_type::RISING_EDGE:
+ return "Rising";
+ case ::gpiod::edge_event::event_type::FALLING_EDGE:
+ return "Falling";
+ default:
+ return "Unknown";
+ }
+}
+
+} /* namespace */
+
+int main()
+{
+ /*
+ * Assume a button connecting the pin to ground, so pull it up and
+ * provide some debounce.
+ */
+ auto request =
+ ::gpiod::chip(chip_path)
+ .prepare_request()
+ .set_consumer("watch-line-value")
+ .add_line_settings(
+ line_offset,
+ ::gpiod::line_settings()
+ .set_direction(
+ ::gpiod::line::direction::INPUT)
+ .set_edge_detection(
+ ::gpiod::line::edge::BOTH)
+ .set_bias(::gpiod::line::bias::PULL_UP)
+ .set_debounce_period(
+ std::chrono::milliseconds(10)))
+ .do_request();
+
+ /*
+ * A larger buffer is an optimisation for reading bursts of events from
+ * the kernel, but that is not necessary in this case, so 1 is fine.
+ */
+ ::gpiod::edge_event_buffer buffer(1);
+
+ for (;;) {
+ /* Blocks until at least one event is available. */
+ request.read_edge_events(buffer);
+
+ for (const auto &event : buffer)
+ ::std::cout << "line: " << event.line_offset()
+ << " type: " << ::std::setw(7)
+ << ::std::left << edge_event_type_str(event)
+ << " event #" << event.line_seqno()
+ << ::std::endl;
+ }
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+/* Minimal example of watching for edges on multiple lines. */
+
+#include <cstdlib>
+#include <gpiod.hpp>
+#include <iomanip>
+#include <iostream>
+
+namespace {
+
+/* Example configuration - customize to suit your situation */
+const ::std::filesystem::path chip_path("/dev/gpiochip0");
+const ::gpiod::line::offsets line_offsets = { 5, 3, 7 };
+
+const char *edge_event_type_str(const ::gpiod::edge_event &event)
+{
+ switch (event.type()) {
+ case ::gpiod::edge_event::event_type::RISING_EDGE:
+ return "Rising";
+ case ::gpiod::edge_event::event_type::FALLING_EDGE:
+ return "Falling";
+ default:
+ return "Unknown";
+ }
+}
+
+} /* namespace */
+
+int main()
+{
+ auto request =
+ ::gpiod::chip(chip_path)
+ .prepare_request()
+ .set_consumer("watch-multiple-line-values")
+ .add_line_settings(
+ line_offsets,
+ ::gpiod::line_settings()
+ .set_direction(
+ ::gpiod::line::direction::INPUT)
+ .set_edge_detection(
+ ::gpiod::line::edge::BOTH))
+ .do_request();
+
+ ::gpiod::edge_event_buffer buffer;
+
+ for (;;) {
+ /* Blocks until at leat one event available */
+ request.read_edge_events(buffer);
+
+ for (const auto &event : buffer)
+ ::std::cout << "offset: " << event.line_offset()
+ << " type: " << ::std::setw(7)
+ << ::std::left << edge_event_type_str(event)
+ << " event #" << event.global_seqno()
+ << " line event #" << event.line_seqno()
+ << ::std::endl;
+ }
+}
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include "internal.hpp"
+
+namespace gpiod {
+
+GPIOD_CXX_API chip_closed::chip_closed(const ::std::string& what)
+ : ::std::logic_error(what)
+{
+
+}
+
+GPIOD_CXX_API chip_closed::chip_closed(const chip_closed& other) noexcept
+ : ::std::logic_error(other)
+{
+
+}
+
+GPIOD_CXX_API chip_closed::chip_closed(chip_closed&& other) noexcept
+ : ::std::logic_error(other)
+{
+
+}
+
+GPIOD_CXX_API chip_closed& chip_closed::operator=(const chip_closed& other) noexcept
+{
+ ::std::logic_error::operator=(other);
+
+ return *this;
+}
+
+GPIOD_CXX_API chip_closed& chip_closed::operator=(chip_closed&& other) noexcept
+{
+ ::std::logic_error::operator=(other);
+
+ return *this;
+}
+
+GPIOD_CXX_API chip_closed::~chip_closed()
+{
+
+}
+
+GPIOD_CXX_API request_released::request_released(const ::std::string& what)
+ : ::std::logic_error(what)
+{
+
+}
+
+GPIOD_CXX_API request_released::request_released(const request_released& other) noexcept
+ : ::std::logic_error(other)
+{
+
+}
+
+GPIOD_CXX_API request_released::request_released(request_released&& other) noexcept
+ : ::std::logic_error(other)
+{
+
+}
+
+GPIOD_CXX_API request_released& request_released::operator=(const request_released& other) noexcept
+{
+ ::std::logic_error::operator=(other);
+
+ return *this;
+}
+
+GPIOD_CXX_API request_released& request_released::operator=(request_released&& other) noexcept
+{
+ ::std::logic_error::operator=(other);
+
+ return *this;
+}
+
+GPIOD_CXX_API request_released::~request_released()
+{
+
+}
+
+GPIOD_CXX_API bad_mapping::bad_mapping(const ::std::string& what)
+ : ::std::runtime_error(what)
+{
+
+}
+
+GPIOD_CXX_API bad_mapping::bad_mapping(const bad_mapping& other) noexcept
+ : ::std::runtime_error(other)
+{
+
+}
+
+GPIOD_CXX_API bad_mapping::bad_mapping(bad_mapping&& other) noexcept
+ : ::std::runtime_error(other)
+{
+
+}
+
+GPIOD_CXX_API bad_mapping& bad_mapping::operator=(const bad_mapping& other) noexcept
+{
+ ::std::runtime_error::operator=(other);
+
+ return *this;
+}
+
+GPIOD_CXX_API bad_mapping& bad_mapping::operator=(bad_mapping&& other) noexcept
+{
+ ::std::runtime_error::operator=(other);
+
+ return *this;
+}
+
+GPIOD_CXX_API bad_mapping::~bad_mapping()
+{
+
+}
+
+} /* namespace gpiod */
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+/**
+ * @file gpiod.hpp
+ */
+
+#ifndef __LIBGPIOD_GPIOD_CXX_HPP__
+#define __LIBGPIOD_GPIOD_CXX_HPP__
+
+/**
+ * @defgroup gpiod_cxx C++ bindings
+ *
+ * C++ bindings for libgpiod.
+ */
+
+/**
+ * @cond
+ */
+
+/*
+ * We don't make this symbol private because it needs to be accessible by
+ * the declarations in exception.hpp in order to expose the symbols of classes
+ * inheriting from standard exceptions.
+ */
+#define GPIOD_CXX_API __attribute__((visibility("default")))
+
+/**
+ * @endcond
+ */
+
+#define __LIBGPIOD_GPIOD_CXX_INSIDE__
+#include "gpiodcxx/chip.hpp"
+#include "gpiodcxx/chip-info.hpp"
+#include "gpiodcxx/edge-event.hpp"
+#include "gpiodcxx/edge-event-buffer.hpp"
+#include "gpiodcxx/exception.hpp"
+#include "gpiodcxx/info-event.hpp"
+#include "gpiodcxx/line.hpp"
+#include "gpiodcxx/line-config.hpp"
+#include "gpiodcxx/line-info.hpp"
+#include "gpiodcxx/line-request.hpp"
+#include "gpiodcxx/line-settings.hpp"
+#include "gpiodcxx/request-builder.hpp"
+#include "gpiodcxx/request-config.hpp"
+#undef __LIBGPIOD_GPIOD_CXX_INSIDE__
+
+#endif /* __LIBGPIOD_GPIOD_CXX_HPP__ */
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
+
+otherincludedir = $(includedir)/gpiodcxx
+otherinclude_HEADERS = \
+ chip.hpp \
+ chip-info.hpp \
+ edge-event-buffer.hpp \
+ edge-event.hpp \
+ exception.hpp \
+ info-event.hpp \
+ line.hpp \
+ line-config.hpp \
+ line-info.hpp \
+ line-request.hpp \
+ line-settings.hpp \
+ misc.hpp \
+ request-builder.hpp \
+ request-config.hpp \
+ timestamp.hpp
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/* SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+/**
+ * @file chip-info.hpp
+ */
+
+#ifndef __LIBGPIOD_CXX_CHIP_INFO_HPP__
+#define __LIBGPIOD_CXX_CHIP_INFO_HPP__
+
+#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
+#error "Only gpiod.hpp can be included directly."
+#endif
+
+#include <memory>
+#include <ostream>
+
+namespace gpiod {
+
+class chip;
+
+/**
+ * @ingroup gpiod_cxx
+ * @{
+ */
+
+/**
+ * @brief Represents an immutable snapshot of GPIO chip information.
+ */
+class chip_info final
+{
+public:
+
+ /**
+ * @brief Copy constructor.
+ * @param other Object to copy.
+ */
+ chip_info(const chip_info& other);
+
+ /**
+ * @brief Move constructor.
+ * @param other Object to move.
+ */
+ chip_info(chip_info&& other) noexcept;
+
+ ~chip_info();
+
+ /**
+ * @brief Assignment operator.
+ * @param other Object to copy.
+ * @return Reference to self.
+ */
+ chip_info& operator=(const chip_info& other);
+
+ /**
+ * @brief Move assignment operator.
+ * @param other Object to move.
+ * @return Reference to self.
+ */
+ chip_info& operator=(chip_info&& other) noexcept;
+
+ /**
+ * @brief Get the name of this GPIO chip.
+ * @return String containing the chip name.
+ */
+ ::std::string name() const noexcept;
+
+ /**
+ * @brief Get the label of this GPIO chip.
+ * @return String containing the chip name.
+ */
+ ::std::string label() const noexcept;
+
+ /**
+ * @brief Return the number of lines exposed by this chip.
+ * @return Number of lines.
+ */
+ ::std::size_t num_lines() const noexcept;
+
+private:
+
+ chip_info();
+
+ struct impl;
+
+ ::std::shared_ptr<impl> _m_priv;
+
+ friend chip;
+};
+
+/**
+ * @brief Stream insertion operator for GPIO chip objects.
+ * @param out Output stream to write to.
+ * @param chip GPIO chip to insert into the output stream.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, const chip_info& chip);
+
+/**
+ * @}
+ */
+
+} /* namespace gpiod */
+
+#endif /* __LIBGPIOD_CXX_CHIP_INFO_HPP__ */
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+/**
+ * @file chip.hpp
+ */
+
+#ifndef __LIBGPIOD_CXX_CHIP_HPP__
+#define __LIBGPIOD_CXX_CHIP_HPP__
+
+#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
+#error "Only gpiod.hpp can be included directly."
+#endif
+
+#include <chrono>
+#include <cstddef>
+#include <iostream>
+#include <filesystem>
+#include <memory>
+
+#include "line.hpp"
+
+namespace gpiod {
+
+class chip_info;
+class info_event;
+class line_config;
+class line_info;
+class line_request;
+class request_builder;
+class request_config;
+
+/**
+ * @ingroup gpiod_cxx
+ * @{
+ */
+
+/**
+ * @brief Represents a GPIO chip.
+ */
+class chip final
+{
+public:
+
+ /**
+ * @brief Instantiates a new chip object by opening the device file
+ * indicated by the \p path argument.
+ * @param path Path to the device file to open.
+ */
+ explicit chip(const ::std::filesystem::path& path);
+
+ /**
+ * @brief Move constructor.
+ * @param other Object to move.
+ */
+ chip(chip&& other) noexcept;
+
+ ~chip();
+
+ chip& operator=(const chip& other) = delete;
+
+ /**
+ * @brief Move assignment operator.
+ * @param other Object to move.
+ * @return Reference to self.
+ */
+ chip& operator=(chip&& other) noexcept;
+
+ /**
+ * @brief Check if this object is valid.
+ * @return True if this object's methods can be used, false otherwise.
+ * False usually means the chip was closed. If the user calls
+ * any of the methods of this class on an object for which this
+ * operator returned false, a logic_error will be thrown.
+ */
+ explicit operator bool() const noexcept;
+
+ /**
+ * @brief Close the GPIO chip device file and free associated resources.
+ * @note The chip object can live after calling this method but any of
+ * the chip's mutators will throw a logic_error exception.
+ */
+ void close();
+
+ /**
+ * @brief Get the filesystem path that was used to open this GPIO chip.
+ * @return Path to the underlying character device file.
+ */
+ ::std::filesystem::path path() const;
+
+ /**
+ * @brief Get information about the chip.
+ * @return New chip_info object.
+ */
+ chip_info get_info() const;
+
+ /**
+ * @brief Retrieve the current snapshot of line information for a
+ * single line.
+ * @param offset Offset of the line to get the info for.
+ * @return New ::gpiod::line_info object.
+ */
+ line_info get_line_info(line::offset offset) const;
+
+ /**
+ * @brief Wrapper around ::gpiod::chip::get_line_info that retrieves
+ * the line info and starts watching the line for changes.
+ * @param offset Offset of the line to get the info for.
+ * @return New ::gpiod::line_info object.
+ */
+ line_info watch_line_info(line::offset offset) const;
+
+ /**
+ * @brief Stop watching the line at given offset for info events.
+ * @param offset Offset of the line to get the info for.
+ */
+ void unwatch_line_info(line::offset offset) const;
+
+ /**
+ * @brief Get the file descriptor associated with this chip.
+ * @return File descriptor number.
+ */
+ int fd() const;
+
+ /**
+ * @brief Wait for line status events on any of the watched lines
+ * exposed by this chip.
+ * @param timeout Wait time limit in nanoseconds. If set to 0, the
+ * function returns immediatelly. If set to a negative
+ * number, the function blocks indefinitely until an
+ * event becomes available.
+ * @return True if at least one event is ready to be read. False if the
+ * wait timed out.
+ */
+ bool wait_info_event(const ::std::chrono::nanoseconds& timeout) const;
+
+ /**
+ * @brief Read a single line status change event from this chip.
+ * @return New info_event object.
+ */
+ info_event read_info_event() const;
+
+ /**
+ * @brief Map a GPIO line's name to its offset within the chip.
+ * @param name Name of the GPIO line to map.
+ * @return Offset of the line within the chip or -1 if the line with
+ * given name is not exposed by this chip.
+ */
+ int get_line_offset_from_name(const ::std::string& name) const;
+
+ /**
+ * @brief Create a request_builder associated with this chip.
+ * @return New request_builder object.
+ */
+ request_builder prepare_request();
+
+private:
+
+ struct impl;
+
+ ::std::shared_ptr<impl> _m_priv;
+
+ chip(const chip& other);
+
+ friend request_builder;
+};
+
+/**
+ * @brief Stream insertion operator for GPIO chip objects.
+ * @param out Output stream to write to.
+ * @param chip GPIO chip to insert into the output stream.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, const chip& chip);
+
+/**
+ * @}
+ */
+
+} /* namespace gpiod */
+
+#endif /* __LIBGPIOD_CXX_CHIP_HPP__ */
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+/**
+ * @file edge-event-buffer.hpp
+ */
+
+#ifndef __LIBGPIOD_CXX_EDGE_EVENT_BUFFER_HPP__
+#define __LIBGPIOD_CXX_EDGE_EVENT_BUFFER_HPP__
+
+#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
+#error "Only gpiod.hpp can be included directly."
+#endif
+
+#include <cstddef>
+#include <iostream>
+#include <memory>
+#include <vector>
+
+namespace gpiod {
+
+class edge_event;
+class line_request;
+
+/**
+ * @ingroup gpiod_cxx
+ * @{
+ */
+
+/**
+ * @brief Object into which edge events are read for better performance.
+ *
+ * The edge_event_buffer allows reading edge_event objects into an existing
+ * buffer which improves the performance by avoiding needless memory
+ * allocations.
+ */
+class edge_event_buffer final
+{
+public:
+
+ /**
+ * @brief Constant iterator for iterating over edge events stored in
+ * the buffer.
+ */
+ using const_iterator = ::std::vector<edge_event>::const_iterator;
+
+ /**
+ * @brief Constructor. Creates a new edge event buffer with given
+ * capacity.
+ * @param capacity Capacity of the new buffer.
+ */
+ explicit edge_event_buffer(::std::size_t capacity = 64);
+
+ edge_event_buffer(const edge_event_buffer& other) = delete;
+
+ /**
+ * @brief Move constructor.
+ * @param other Object to move.
+ */
+ edge_event_buffer(edge_event_buffer&& other) noexcept;
+
+ ~edge_event_buffer();
+
+ edge_event_buffer& operator=(const edge_event_buffer& other) = delete;
+
+ /**
+ * @brief Move assignment operator.
+ * @param other Object to move.
+ * @return Reference to self.
+ */
+ edge_event_buffer& operator=(edge_event_buffer&& other) noexcept;
+
+ /**
+ * @brief Get the constant reference to the edge event at given index.
+ * @param index Index of the event in the buffer.
+ * @return Constant reference to the edge event.
+ */
+ const edge_event& get_event(unsigned int index) const;
+
+ /**
+ * @brief Get the number of edge events currently stored in the buffer.
+ * @return Number of edge events in the buffer.
+ */
+ ::std::size_t num_events() const;
+
+ /**
+ * @brief Maximum capacity of the buffer.
+ * @return Buffer capacity.
+ */
+ ::std::size_t capacity() const noexcept;
+
+ /**
+ * @brief Get a constant iterator to the first edge event currently
+ * stored in the buffer.
+ * @return Constant iterator to the first element.
+ */
+ const_iterator begin() const noexcept;
+
+ /**
+ * @brief Get a constant iterator to the element after the last edge
+ * event in the buffer.
+ * @return Constant iterator to the element after the last edge event.
+ */
+ const_iterator end() const noexcept;
+
+private:
+
+ struct impl;
+
+ ::std::unique_ptr<impl> _m_priv;
+
+ friend line_request;
+};
+
+/**
+ * @brief Stream insertion operator for GPIO edge event buffer objects.
+ * @param out Output stream to write to.
+ * @param buf GPIO edge event buffer object to insert into the output stream.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, const edge_event_buffer& buf);
+
+/**
+ * @}
+ */
+
+} /* namespace gpiod */
+
+#endif /* __LIBGPIOD_CXX_EDGE_EVENT_BUFFER_HPP__ */
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+/**
+ * @file edge-event.hpp
+ */
+
+#ifndef __LIBGPIOD_CXX_EDGE_EVENT_HPP__
+#define __LIBGPIOD_CXX_EDGE_EVENT_HPP__
+
+#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
+#error "Only gpiod.hpp can be included directly."
+#endif
+
+#include <cstdint>
+#include <iostream>
+#include <memory>
+
+#include "timestamp.hpp"
+
+namespace gpiod {
+
+class edge_event_buffer;
+
+/**
+ * @ingroup gpiod_cxx
+ * @{
+ */
+
+/**
+ * @brief Immutable object containing data about a single edge event.
+ */
+class edge_event final
+{
+public:
+
+ /**
+ * @brief Edge event types.
+ */
+ enum class event_type
+ {
+ RISING_EDGE = 1,
+ /**< This is a rising edge event. */
+ FALLING_EDGE,
+ /**< This is falling edge event. */
+ };
+
+ /**
+ * @brief Copy constructor.
+ * @param other Object to copy.
+ */
+ edge_event(const edge_event& other);
+
+ /**
+ * @brief Move constructor.
+ * @param other Object to move.
+ */
+ edge_event(edge_event&& other) noexcept;
+
+ ~edge_event();
+
+ /**
+ * @brief Copy assignment operator.
+ * @param other Object to copy.
+ * @return Reference to self.
+ */
+ edge_event& operator=(const edge_event& other);
+
+ /**
+ * @brief Move assignment operator.
+ * @param other Object to move.
+ * @return Reference to self.
+ */
+ edge_event& operator=(edge_event&& other) noexcept;
+
+ /**
+ * @brief Retrieve the event type.
+ * @return Event type (rising or falling edge).
+ */
+ event_type type() const;
+
+ /**
+ * @brief Retrieve the event time-stamp.
+ * @return Time-stamp in nanoseconds as registered by the kernel using
+ * the configured edge event clock.
+ */
+ timestamp timestamp_ns() const noexcept;
+
+ /**
+ * @brief Read the offset of the line on which this event was
+ * registered.
+ * @return Line offset.
+ */
+ line::offset line_offset() const noexcept;
+
+ /**
+ * @brief Get the global sequence number of this event.
+ * @return Sequence number of the event relative to all lines in the
+ * associated line request.
+ */
+ unsigned long global_seqno() const noexcept;
+
+ /**
+ * @brief Get the event sequence number specific to the concerned line.
+ * @return Sequence number of the event relative to this line within
+ * the lifetime of the associated line request.
+ */
+ unsigned long line_seqno() const noexcept;
+
+private:
+
+ edge_event();
+
+ struct impl;
+ struct impl_managed;
+ struct impl_external;
+
+ ::std::shared_ptr<impl> _m_priv;
+
+ friend edge_event_buffer;
+};
+
+/**
+ * @brief Stream insertion operator for edge events.
+ * @param out Output stream to write to.
+ * @param event Edge event to insert into the output stream.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, const edge_event& event);
+
+/**
+ * @}
+ */
+
+} /* namespace gpiod */
+
+#endif /* __LIBGPIOD_CXX_EDGE_EVENT_HPP__ */
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+/**
+ * @file exception.hpp
+ */
+
+#ifndef __LIBGPIOD_CXX_EXCEPTION_HPP__
+#define __LIBGPIOD_CXX_EXCEPTION_HPP__
+
+#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
+#error "Only gpiod.hpp can be included directly."
+#endif
+
+#include <stdexcept>
+#include <string>
+
+namespace gpiod {
+
+/**
+ * @ingroup gpiod_cxx
+ * @{
+ */
+
+/**
+ * @brief Exception thrown when an already closed chip is used.
+ */
+class GPIOD_CXX_API chip_closed final : public ::std::logic_error
+{
+public:
+
+ /**
+ * @brief Constructor.
+ * @param what Human readable reason for error.
+ */
+ explicit chip_closed(const ::std::string& what);
+
+ /**
+ * @brief Copy constructor.
+ * @param other Object to copy from.
+ */
+ chip_closed(const chip_closed& other) noexcept;
+
+ /**
+ * @brief Move constructor.
+ * @param other Object to move.
+ */
+ chip_closed(chip_closed&& other) noexcept;
+
+ /**
+ * @brief Assignment operator.
+ * @param other Object to copy from.
+ * @return Reference to self.
+ */
+ chip_closed& operator=(const chip_closed& other) noexcept;
+
+ /**
+ * @brief Move assignment operator.
+ * @param other Object to move.
+ * @return Reference to self.
+ */
+ chip_closed& operator=(chip_closed&& other) noexcept;
+
+ ~chip_closed();
+};
+
+/**
+ * @brief Exception thrown when an already released line request is used.
+ */
+class GPIOD_CXX_API request_released final : public ::std::logic_error
+{
+public:
+
+ /**
+ * @brief Constructor.
+ * @param what Human readable reason for error.
+ */
+ explicit request_released(const ::std::string& what);
+
+ /**
+ * @brief Copy constructor.
+ * @param other Object to copy from.
+ */
+ request_released(const request_released& other) noexcept;
+
+ /**
+ * @brief Move constructor.
+ * @param other Object to move.
+ */
+ request_released(request_released&& other) noexcept;
+
+ /**
+ * @brief Assignment operator.
+ * @param other Object to copy from.
+ * @return Reference to self.
+ */
+ request_released& operator=(const request_released& other) noexcept;
+
+ /**
+ * @brief Move assignment operator.
+ * @param other Object to move.
+ * @return Reference to self.
+ */
+ request_released& operator=(request_released&& other) noexcept;
+
+ ~request_released();
+};
+
+/**
+ * @brief Exception thrown when the core C library returns an invalid value
+ * for any of the line_info properties.
+ */
+class GPIOD_CXX_API bad_mapping final : public ::std::runtime_error
+{
+public:
+
+ /**
+ * @brief Constructor.
+ * @param what Human readable reason for error.
+ */
+ explicit bad_mapping(const ::std::string& what);
+
+ /**
+ * @brief Copy constructor.
+ * @param other Object to copy from.
+ */
+ bad_mapping(const bad_mapping& other) noexcept;
+
+ /**
+ * @brief Move constructor.
+ * @param other Object to move.
+ */
+ bad_mapping(bad_mapping&& other) noexcept;
+
+ /**
+ * @brief Assignment operator.
+ * @param other Object to copy from.
+ * @return Reference to self.
+ */
+ bad_mapping& operator=(const bad_mapping& other) noexcept;
+
+ /**
+ * @brief Move assignment operator.
+ * @param other Object to move.
+ * @return Reference to self.
+ */
+ bad_mapping& operator=(bad_mapping&& other) noexcept;
+
+ ~bad_mapping();
+};
+
+/**
+ * @}
+ */
+
+} /* namespace gpiod */
+
+#endif /* __LIBGPIOD_CXX_EXCEPTION_HPP__ */
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+/**
+ * @file gpiod.h
+ */
+
+#ifndef __LIBGPIOD_CXX_INFO_EVENT_HPP__
+#define __LIBGPIOD_CXX_INFO_EVENT_HPP__
+
+#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
+#error "Only gpiod.hpp can be included directly."
+#endif
+
+#include <cstdint>
+#include <iostream>
+#include <memory>
+
+#include "timestamp.hpp"
+
+namespace gpiod {
+
+class chip;
+class line_info;
+
+/**
+ * @ingroup gpiod_cxx
+ * @{
+ */
+
+/**
+ * @brief Immutable object containing data about a single line info event.
+ */
+class info_event final
+{
+public:
+
+ /**
+ * @brief Types of info events.
+ */
+ enum class event_type
+ {
+ LINE_REQUESTED = 1,
+ /**< Line has been requested. */
+ LINE_RELEASED,
+ /**< Previously requested line has been released. */
+ LINE_CONFIG_CHANGED,
+ /**< Line configuration has changed. */
+ };
+
+ /**
+ * @brief Copy constructor.
+ * @param other Object to copy.
+ */
+ info_event(const info_event& other);
+
+ /**
+ * @brief Move constructor.
+ * @param other Object to move.
+ */
+ info_event(info_event&& other) noexcept;
+
+ ~info_event();
+
+ /**
+ * @brief Copy assignment operator.
+ * @param other Object to copy.
+ * @return Reference to self.
+ */
+ info_event& operator=(const info_event& other);
+
+ /**
+ * @brief Move assignment operator.
+ * @param other Object to move.
+ * @return Reference to self.
+ */
+ info_event& operator=(info_event&& other) noexcept;
+
+ /**
+ * @brief Type of this event.
+ * @return Event type.
+ */
+ event_type type() const;
+
+ /**
+ * @brief Timestamp of the event as returned by the kernel.
+ * @return Timestamp as a 64-bit unsigned integer.
+ */
+ ::std::uint64_t timestamp_ns() const noexcept;
+
+ /**
+ * @brief Get the new line information.
+ * @return Constant reference to the line info object containing the
+ * line data as read at the time of the info event.
+ */
+ const line_info& get_line_info() const noexcept;
+
+private:
+
+ info_event();
+
+ struct impl;
+
+ ::std::shared_ptr<impl> _m_priv;
+
+ friend chip;
+};
+
+/**
+ * @brief Stream insertion operator for info events.
+ * @param out Output stream to write to.
+ * @param event GPIO line info event to insert into the output stream.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, const info_event& event);
+
+/**
+ * @}
+ */
+
+} /* namespace gpiod */
+
+#endif /* __LIBGPIOD_CXX_INFO_EVENT_HPP__ */
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+/**
+ * @file line-config.hpp
+ */
+
+#ifndef __LIBGPIOD_CXX_LINE_CONFIG_HPP__
+#define __LIBGPIOD_CXX_LINE_CONFIG_HPP__
+
+#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
+#error "Only gpiod.hpp can be included directly."
+#endif
+
+#include <map>
+#include <memory>
+
+namespace gpiod {
+
+class chip;
+class line_request;
+class line_settings;
+
+/**
+ * @ingroup gpiod_cxx
+ * @{
+ */
+
+/**
+ * @brief Contains a set of line config options used in line requests and
+ * reconfiguration.
+ */
+class line_config final
+{
+public:
+
+
+ line_config();
+
+ line_config(const line_config& other) = delete;
+
+ /**
+ * @brief Move constructor.
+ * @param other Object to move.
+ */
+ line_config(line_config&& other) noexcept;
+
+ ~line_config();
+
+ /**
+ * @brief Move assignment operator.
+ * @param other Object to move.
+ * @return Reference to self.
+ */
+ line_config& operator=(line_config&& other) noexcept;
+
+ /**
+ * @brief Reset the line config object.
+ * @return Reference to self.
+ */
+ line_config& reset() noexcept;
+
+ /**
+ * @brief Add line settings for a single offset.
+ * @param offset Offset for which to add settings.
+ * @param settings Line settings to add.
+ * @return Reference to self.
+ */
+ line_config& add_line_settings(line::offset offset, const line_settings& settings);
+
+ /**
+ * @brief Add line settings for a set of offsets.
+ * @param offsets Offsets for which to add settings.
+ * @param settings Line settings to add.
+ * @return Reference to self.
+ */
+ line_config& add_line_settings(const line::offsets& offsets, const line_settings& settings);
+
+ /**
+ * @brief Set output values for a number of lines.
+ * @param values Buffer containing the output values.
+ * @return Reference to self.
+ */
+ line_config& set_output_values(const line::values& values);
+
+ /**
+ * @brief Get a mapping of offsets to line settings stored by this
+ * object.
+ * @return Map in which keys represent line offsets and values are
+ * the settings corresponding with them.
+ */
+ ::std::map<line::offset, line_settings> get_line_settings() const;
+
+private:
+
+ struct impl;
+
+ ::std::shared_ptr<impl> _m_priv;
+
+ line_config& operator=(const line_config& other);
+
+ friend line_request;
+ friend request_builder;
+};
+
+/**
+ * @brief Stream insertion operator for GPIO line config objects.
+ * @param out Output stream to write to.
+ * @param config Line config object to insert into the output stream.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, const line_config& config);
+
+/**
+ * @}
+ */
+
+} /* namespace gpiod */
+
+#endif /* __LIBGPIOD_CXX_LINE_CONFIG_HPP__ */
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+/**
+ * @file line-info.hpp
+ */
+
+#ifndef __LIBGPIOD_CXX_LINE_INFO_HPP__
+#define __LIBGPIOD_CXX_LINE_INFO_HPP__
+
+#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
+#error "Only gpiod.hpp can be included directly."
+#endif
+
+#include <chrono>
+#include <iostream>
+#include <memory>
+#include <string>
+
+namespace gpiod {
+
+class chip;
+class info_event;
+
+/**
+ * @ingroup gpiod_cxx
+ * @{
+ */
+
+/**
+ * @brief Contains an immutable snapshot of the line's state at the
+ * time when the object of this class was instantiated.
+ */
+class line_info final
+{
+public:
+
+ /**
+ * @brief Copy constructor.
+ * @param other Object to copy.
+ */
+ line_info(const line_info& other) noexcept;
+
+ /**
+ * @brief Move constructor.
+ * @param other Object to move.
+ */
+ line_info(line_info&& other) noexcept;
+
+ ~line_info();
+
+ /**
+ * @brief Copy assignment operator.
+ * @param other Object to copy.
+ * @return Reference to self.
+ */
+ line_info& operator=(const line_info& other) noexcept;
+
+ /**
+ * @brief Move assignment operator.
+ * @param other Object to move.
+ * @return Reference to self.
+ */
+ line_info& operator=(line_info&& other) noexcept;
+
+ /**
+ * @brief Get the hardware offset of the line.
+ * @return Offset of the line within the parent chip.
+ */
+ line::offset offset() const noexcept;
+
+ /**
+ * @brief Get the GPIO line name.
+ * @return Name of the GPIO line as it is represented in the kernel.
+ * This routine returns an empty string if the line is unnamed.
+ */
+ ::std::string name() const noexcept;
+
+ /**
+ * @brief Check if the line is currently in use.
+ * @return True if the line is in use, false otherwise.
+ *
+ * The user space can't know exactly why a line is busy. It may have
+ * been requested by another process or hogged by the kernel. It only
+ * matters that the line is used and we can't request it.
+ */
+ bool used() const noexcept;
+
+ /**
+ * @brief Read the GPIO line consumer name.
+ * @return Name of the GPIO consumer name as it is represented in the
+ * kernel. This routine returns an empty string if the line is
+ * not used.
+ */
+ ::std::string consumer() const noexcept;
+
+ /**
+ * @brief Read the GPIO line direction setting.
+ * @return Returns DIRECTION_INPUT or DIRECTION_OUTPUT.
+ */
+ line::direction direction() const;
+
+ /**
+ * @brief Read the current edge detection setting of this line.
+ * @return Returns EDGE_NONE, EDGE_RISING, EDGE_FALLING or EDGE_BOTH.
+ */
+ line::edge edge_detection() const;
+
+ /**
+ * @brief Read the GPIO line bias setting.
+ * @return Returns BIAS_PULL_UP, BIAS_PULL_DOWN, BIAS_DISABLE or
+ * BIAS_UNKNOWN.
+ */
+ line::bias bias() const;
+
+ /**
+ * @brief Read the GPIO line drive setting.
+ * @return Returns DRIVE_PUSH_PULL, DRIVE_OPEN_DRAIN or
+ * DRIVE_OPEN_SOURCE.
+ */
+ line::drive drive() const;
+
+ /**
+ * @brief Check if the signal of this line is inverted.
+ * @return True if this line is "active-low", false otherwise.
+ */
+ bool active_low() const noexcept;
+
+ /**
+ * @brief Check if this line is debounced (either by hardware or by the
+ * kernel software debouncer).
+ * @return True if the line is debounced, false otherwise.
+ */
+ bool debounced() const noexcept;
+
+ /**
+ * @brief Read the current debounce period in microseconds.
+ * @return Current debounce period in microseconds, 0 if the line is
+ * not debounced.
+ */
+ ::std::chrono::microseconds debounce_period() const noexcept;
+
+ /**
+ * @brief Read the current event clock setting used for edge event
+ * timestamps.
+ * @return Returns MONOTONIC, REALTIME or HTE.
+ */
+ line::clock event_clock() const;
+
+private:
+
+ line_info();
+
+ struct impl;
+
+ ::std::shared_ptr<impl> _m_priv;
+
+ friend chip;
+ friend info_event;
+};
+
+/**
+ * @brief Stream insertion operator for GPIO line info objects.
+ * @param out Output stream to write to.
+ * @param info GPIO line info object to insert into the output stream.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, const line_info& info);
+
+/**
+ * @}
+ */
+
+} /* namespace gpiod */
+
+#endif /* __LIBGPIOD_CXX_LINE_INFO_HPP__ */
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+/**
+ * @file line-request.hpp
+ */
+
+#ifndef __LIBGPIOD_CXX_LINE_REQUEST_HPP__
+#define __LIBGPIOD_CXX_LINE_REQUEST_HPP__
+
+#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
+#error "Only gpiod.hpp can be included directly."
+#endif
+
+#include <chrono>
+#include <cstddef>
+#include <iostream>
+#include <memory>
+
+#include "misc.hpp"
+
+namespace gpiod {
+
+class chip;
+class edge_event;
+class edge_event_buffer;
+class line_config;
+
+/**
+ * @ingroup gpiod_cxx
+ * @{
+ */
+
+/**
+ * @brief Stores the context of a set of requested GPIO lines.
+ */
+class line_request final
+{
+public:
+
+ line_request(const line_request& other) = delete;
+
+ /**
+ * @brief Move constructor.
+ * @param other Object to move.
+ */
+ line_request(line_request&& other) noexcept;
+
+ ~line_request();
+
+ line_request& operator=(const line_request& other) = delete;
+
+ /**
+ * @brief Move assignment operator.
+ * @param other Object to move.
+ * @return Reference to self.
+ */
+ line_request& operator=(line_request&& other) noexcept;
+
+ /**
+ * @brief Check if this object is valid.
+ * @return True if this object's methods can be used, false otherwise.
+ * False usually means the request was released. If the user
+ * calls any of the methods of this class on an object for
+ * which this operator returned false, a logic_error will be
+ * thrown.
+ */
+ explicit operator bool() const noexcept;
+
+ /**
+ * @brief Release the GPIO chip and free all associated resources.
+ * @note The object can still be used after this method is called but
+ * using any of the mutators will result in throwing
+ * a logic_error exception.
+ */
+ void release();
+
+ /**
+ * @brief Get the name of the chip this request was made on.
+ * @return Name to the GPIO chip.
+ */
+ ::std::string chip_name() const;
+
+ /**
+ * @brief Get the number of requested lines.
+ * @return Number of lines in this request.
+ */
+ ::std::size_t num_lines() const;
+
+ /**
+ * @brief Get the list of offsets of requested lines.
+ * @return List of hardware offsets of the lines in this request.
+ */
+ line::offsets offsets() const;
+
+ /**
+ * @brief Get the value of a single requested line.
+ * @param offset Offset of the line to read within the chip.
+ * @return Current line value.
+ */
+ line::value get_value(line::offset offset);
+
+ /**
+ * @brief Get the values of a subset of requested lines.
+ * @param offsets Vector of line offsets
+ * @return Vector of lines values with indexes of values corresponding
+ * to those of the offsets.
+ */
+ line::values get_values(const line::offsets& offsets);
+
+ /**
+ * @brief Get the values of all requested lines.
+ * @return List of read values.
+ */
+ line::values get_values();
+
+ /**
+ * @brief Get the values of a subset of requested lines into a vector
+ * supplied by the caller.
+ * @param offsets Vector of line offsets.
+ * @param values Vector for storing the values. Its size must be at
+ * least that of the offsets vector. The indexes of read
+ * values will correspond with those in the offsets
+ * vector.
+ */
+ void get_values(const line::offsets& offsets, line::values& values);
+
+ /**
+ * @brief Get the values of all requested lines.
+ * @param values Array in which the values will be stored. Must hold
+ * at least the number of elements returned by
+ * line_request::num_lines.
+ */
+ void get_values(line::values& values);
+
+ /**
+ * @brief Set the value of a single requested line.
+ * @param offset Offset of the line to set within the chip.
+ * @param value New line value.
+ * @return Reference to self.
+ */
+ line_request& set_value(line::offset offset, line::value value);
+
+ /**
+ * @brief Set the values of a subset of requested lines.
+ * @param values Vector containing a set of offset->value mappings.
+ * @return Reference to self.
+ */
+ line_request& set_values(const line::value_mappings& values);
+
+ /**
+ * @brief Set the values of a subset of requested lines.
+ * @param offsets Vector containing the offsets of lines to set.
+ * @param values Vector containing new values with indexes
+ * corresponding with those in the offsets vector.
+ * @return Reference to self.
+ */
+ line_request& set_values(const line::offsets& offsets, const line::values& values);
+
+ /**
+ * @brief Set the values of all requested lines.
+ * @param values Array of new line values. The size must be equal to
+ * the value returned by line_request::num_lines.
+ * @return Reference to self.
+ */
+ line_request& set_values(const line::values& values);
+
+ /**
+ * @brief Apply new config options to requested lines.
+ * @param config New configuration.
+ * @return Reference to self.
+ */
+ line_request& reconfigure_lines(const line_config& config);
+
+ /**
+ * @brief Get the file descriptor number associated with this line
+ * request.
+ * @return File descriptor number.
+ */
+ int fd() const;
+
+ /**
+ * @brief Wait for edge events on any of the lines requested with edge
+ * detection enabled.
+ * @param timeout Wait time limit in nanoseconds. If set to 0, the
+ * function returns immediatelly. If set to a negative
+ * number, the function blocks indefinitely until an
+ * event becomes available.
+ * @return True if at least one event is ready to be read. False if the
+ * wait timed out.
+ */
+ bool wait_edge_events(const ::std::chrono::nanoseconds& timeout) const;
+
+ /**
+ * @brief Read a number of edge events from this request up to the
+ * maximum capacity of the buffer.
+ * @param buffer Edge event buffer to read events into.
+ * @return Number of events read.
+ */
+ ::std::size_t read_edge_events(edge_event_buffer& buffer);
+
+ /**
+ * @brief Read a number of edge events from this request.
+ * @param buffer Edge event buffer to read events into.
+ * @param max_events Maximum number of events to read. Limited by the
+ * capacity of the buffer.
+ * @return Number of events read.
+ */
+ ::std::size_t read_edge_events(edge_event_buffer& buffer, ::std::size_t max_events);
+
+private:
+
+ line_request();
+
+ struct impl;
+
+ ::std::unique_ptr<impl> _m_priv;
+
+ friend request_builder;
+};
+
+/**
+ * @brief Stream insertion operator for line requests.
+ * @param out Output stream to write to.
+ * @param request Line request object to insert into the output stream.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, const line_request& request);
+
+/**
+ * @}
+ */
+
+} /* namespace gpiod */
+
+#endif /* __LIBGPIOD_CXX_LINE_REQUEST_HPP__ */
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/* SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+/**
+ * @file request-config.hpp
+ */
+
+#ifndef __LIBGPIOD_CXX_LINE_SETTINGS_HPP__
+#define __LIBGPIOD_CXX_LINE_SETTINGS_HPP__
+
+#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
+#error "Only gpiod.hpp can be included directly."
+#endif
+
+#include <chrono>
+#include <memory>
+
+#include "line.hpp"
+
+namespace gpiod {
+
+class line_config;
+
+/**
+ * @ingroup gpiod_cxx
+ * @{
+ */
+
+/**
+ * @brief Stores GPIO line settings.
+ */
+class line_settings final
+{
+public:
+
+ /**
+ * @brief Initializes the line_settings object with default values.
+ */
+ line_settings();
+
+ /**
+ * @brief Copy constructor.
+ * @param other Object to copy.
+ */
+ line_settings(const line_settings& other);
+
+ /**
+ * @brief Move constructor.
+ * @param other Object to move.
+ */
+ line_settings(line_settings&& other) noexcept;
+
+ ~line_settings();
+
+ /**
+ * @brief Copy assignment operator.
+ * @param other Object to copy.
+ * @return Reference to self.
+ */
+ line_settings& operator=(const line_settings& other);
+
+ /**
+ * @brief Move assignment operator.
+ * @param other Object to move.
+ * @return Reference to self.
+ */
+ line_settings& operator=(line_settings&& other);
+
+ /**
+ * @brief Reset the line settings to default values.
+ * @return Reference to self.
+ */
+ line_settings& reset() noexcept;
+
+ /**
+ * @brief Set direction.
+ * @param direction New direction.
+ * @return Reference to self.
+ */
+ line_settings& set_direction(line::direction direction);
+
+ /**
+ * @brief Get direction.
+ * @return Current direction setting.
+ */
+ line::direction direction() const;
+
+ /**
+ * @brief Set edge detection.
+ * @param edge New edge detection setting.
+ * @return Reference to self.
+ */
+ line_settings& set_edge_detection(line::edge edge);
+
+ /**
+ * @brief Get edge detection.
+ * @return Current edge detection setting.
+ */
+ line::edge edge_detection() const;
+
+ /**
+ * @brief Set bias setting.
+ * @param bias New bias.
+ * @return Reference to self.
+ */
+ line_settings& set_bias(line::bias bias);
+
+ /**
+ * @brief Get bias setting.
+ * @return Current bias.
+ */
+ line::bias bias() const;
+
+ /**
+ * @brief Set drive setting.
+ * @param drive New drive.
+ * @return Reference to self.
+ */
+ line_settings& set_drive(line::drive drive);
+
+ /**
+ * @brief Get drive setting.
+ * @return Current drive.
+ */
+ line::drive drive() const;
+
+ /**
+ * @brief Set the active-low setting.
+ * @param active_low New active-low setting.
+ * @return Reference to self.
+ */
+ line_settings& set_active_low(bool active_low);
+
+ /**
+ * @brief Get the active-low setting.
+ * @return Current active-low setting.
+ */
+ bool active_low() const noexcept;
+
+ /**
+ * @brief Set debounce period.
+ * @param period New debounce period in microseconds.
+ * @return Reference to self.
+ */
+ line_settings& set_debounce_period(const ::std::chrono::microseconds& period);
+
+ /**
+ * @brief Get debounce period.
+ * @return Current debounce period.
+ */
+ ::std::chrono::microseconds debounce_period() const noexcept;
+
+ /**
+ * @brief Set the event clock to use for edge event timestamps.
+ * @param event_clock Clock to use.
+ * @return Reference to self.
+ */
+ line_settings& set_event_clock(line::clock event_clock);
+
+ /**
+ * @brief Get the event clock used for edge event timestamps.
+ * @return Current event clock type.
+ */
+ line::clock event_clock() const;
+
+ /**
+ * @brief Set the output value.
+ * @param value New output value.
+ * @return Reference to self.
+ */
+ line_settings& set_output_value(line::value value);
+
+ /**
+ * @brief Get the output value.
+ * @return Current output value.
+ */
+ line::value output_value() const;
+
+private:
+
+ struct impl;
+
+ ::std::unique_ptr<impl> _m_priv;
+
+ friend line_config;
+};
+
+/**
+ * @brief Stream insertion operator for line settings.
+ * @param out Output stream to write to.
+ * @param settings Line settings object to insert into the output stream.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, const line_settings& settings);
+
+/**
+ * @}
+ */
+
+} /* namespace gpiod */
+
+#endif /* __LIBGPIOD_CXX_LINE_SETTINGS_HPP__ */
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+/**
+ * @file line.hpp
+ */
+
+#ifndef __LIBGPIOD_CXX_LINE_HPP__
+#define __LIBGPIOD_CXX_LINE_HPP__
+
+#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
+#error "Only gpiod.hpp can be included directly."
+#endif
+
+#include <ostream>
+#include <utility>
+#include <vector>
+
+namespace gpiod {
+
+/**
+ * @brief Namespace containing various type definitions for GPIO lines.
+ */
+namespace line {
+
+/**
+ * @ingroup gpiod_cxx
+ * @{
+ */
+
+/**
+ * @brief Wrapper around unsigned int for representing line offsets.
+ */
+class offset
+{
+public:
+ /**
+ * @brief Constructor with implicit conversion from unsigned int.
+ * @param off Line offset.
+ */
+ offset(unsigned int off = 0) : _m_offset(off) { }
+
+ /**
+ * @brief Copy constructor.
+ * @param other Object to copy.
+ */
+ offset(const offset& other) = default;
+
+ /**
+ * @brief Move constructor.
+ * @param other Object to move.
+ */
+ offset(offset&& other) = default;
+
+ ~offset() = default;
+
+ /**
+ * @brief Assignment operator.
+ * @param other Object to copy.
+ * @return Reference to self.
+ */
+ offset& operator=(const offset& other) = default;
+
+ /**
+ * @brief Move assignment operator.
+ * @param other Object to move.
+ * @return Reference to self.
+ */
+ offset& operator=(offset&& other) noexcept = default;
+
+ /**
+ * @brief Conversion operator to `unsigned int`.
+ */
+ operator unsigned int() const noexcept
+ {
+ return this->_m_offset;
+ }
+
+private:
+ unsigned int _m_offset;
+};
+
+/**
+ * @brief Logical line states.
+ */
+enum class value
+{
+ INACTIVE = 0,
+ /**< Line is inactive. */
+ ACTIVE = 1,
+ /**< Line is active. */
+};
+
+/**
+ * @brief Direction settings.
+ */
+enum class direction
+{
+ AS_IS = 1,
+ /**< Request the line(s), but don't change current direction. */
+ INPUT,
+ /**< Direction is input - we're reading the state of a GPIO line. */
+ OUTPUT,
+ /**< Direction is output - we're driving the GPIO line. */
+};
+
+/**
+ * @brief Edge detection settings.
+ */
+enum class edge
+{
+ NONE = 1,
+ /**< Line edge detection is disabled. */
+ RISING,
+ /**< Line detects rising edge events. */
+ FALLING,
+ /**< Line detect falling edge events. */
+ BOTH,
+ /**< Line detects both rising and falling edge events. */
+};
+
+/**
+ * @brief Internal bias settings.
+ */
+enum class bias
+{
+ AS_IS = 1,
+ /**< Don't change the bias setting when applying line config. */
+ UNKNOWN,
+ /**< The internal bias state is unknown. */
+ DISABLED,
+ /**< The internal bias is disabled. */
+ PULL_UP,
+ /**< The internal pull-up bias is enabled. */
+ PULL_DOWN,
+ /**< The internal pull-down bias is enabled. */
+};
+
+/**
+ * @brief Drive settings.
+ */
+enum class drive
+{
+ PUSH_PULL = 1,
+ /**< Drive setting is push-pull. */
+ OPEN_DRAIN,
+ /**< Line output is open-drain. */
+ OPEN_SOURCE,
+ /**< Line output is open-source. */
+};
+
+/**
+ * @brief Event clock settings.
+ */
+enum class clock
+{
+ MONOTONIC = 1,
+ /**< Line uses the monotonic clock for edge event timestamps. */
+ REALTIME,
+ /**< Line uses the realtime clock for edge event timestamps. */
+ HTE,
+ /*<< Line uses the hardware timestamp engine for event timestamps. */
+};
+
+/**
+ * @brief Vector of line offsets.
+ */
+using offsets = ::std::vector<offset>;
+
+/**
+ * @brief Vector of line values.
+ */
+using values = ::std::vector<value>;
+
+/**
+ * @brief Represents a mapping of a line offset to line logical state.
+ */
+using value_mapping = ::std::pair<offset, value>;
+
+/**
+ * @brief Vector of offset->value mappings. Each mapping is defined as a pair
+ * of an unsigned and signed integers.
+ */
+using value_mappings = ::std::vector<value_mapping>;
+
+/**
+ * @brief Stream insertion operator for logical line values.
+ * @param out Output stream.
+ * @param val Value to insert into the output stream in a human-readable form.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, value val);
+
+/**
+ * @brief Stream insertion operator for direction values.
+ * @param out Output stream.
+ * @param dir Value to insert into the output stream in a human-readable form.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, direction dir);
+
+/**
+ * @brief Stream insertion operator for edge detection values.
+ * @param out Output stream.
+ * @param edge Value to insert into the output stream in a human-readable form.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, edge edge);
+
+/**
+ * @brief Stream insertion operator for bias values.
+ * @param out Output stream.
+ * @param bias Value to insert into the output stream in a human-readable form.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, bias bias);
+
+/**
+ * @brief Stream insertion operator for drive values.
+ * @param out Output stream.
+ * @param drive Value to insert into the output stream in a human-readable form.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, drive drive);
+
+/**
+ * @brief Stream insertion operator for event clock values.
+ * @param out Output stream.
+ * @param clock Value to insert into the output stream in a human-readable form.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, clock clock);
+
+/**
+ * @brief Stream insertion operator for the list of output values.
+ * @param out Output stream.
+ * @param vals Object to insert into the output stream in a human-readable form.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, const values& vals);
+
+/**
+ * @brief Stream insertion operator for the list of line offsets.
+ * @param out Output stream.
+ * @param offs Object to insert into the output stream in a human-readable form.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, const offsets& offs);
+
+/**
+ * @brief Stream insertion operator for the offset-to-value mapping.
+ * @param out Output stream.
+ * @param mapping Value to insert into the output stream in a human-readable
+ * form.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, const value_mapping& mapping);
+
+/**
+ * @brief Stream insertion operator for the list of offset-to-value mappings.
+ * @param out Output stream.
+ * @param mappings Object to insert into the output stream in a human-readable
+ * form.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, const value_mappings& mappings);
+
+/**
+ * @}
+ */
+
+} /* namespace line */
+
+} /* namespace gpiod */
+
+#endif /* __LIBGPIOD_CXX_LINE_HPP__ */
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/* SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+/**
+ * @file misc.hpp
+ */
+
+#ifndef __LIBGPIOD_CXX_MISC_HPP__
+#define __LIBGPIOD_CXX_MISC_HPP__
+
+#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
+#error "Only gpiod.hpp can be included directly."
+#endif
+
+#include <string>
+
+namespace gpiod {
+
+/**
+ * @ingroup gpiod_cxx
+ * @{
+ */
+
+/**
+ * @brief Check if the file pointed to by path is a GPIO chip character device.
+ * @param path Path to check.
+ * @return True if the file exists and is a GPIO chip character device or a
+ * symbolic link to it.
+ */
+bool is_gpiochip_device(const ::std::filesystem::path& path);
+
+/**
+ * @brief Get the human readable version string for libgpiod API
+ * @return String containing the library version.
+ */
+const ::std::string& api_version();
+
+/**
+ * @}
+ */
+
+} /* namespace gpiod */
+
+#endif /* __LIBGPIOD_CXX_MISC_HPP__ */
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/* SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+/**
+ * @file request-builder.hpp
+ */
+
+#ifndef __LIBGPIOD_CXX_REQUEST_BUILDER_HPP__
+#define __LIBGPIOD_CXX_REQUEST_BUILDER_HPP__
+
+#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
+#error "Only gpiod.hpp can be included directly."
+#endif
+
+#include <memory>
+#include <ostream>
+
+namespace gpiod {
+
+class chip;
+class line_config;
+class line_request;
+class request_config;
+
+/**
+ * @ingroup gpiod_cxx
+ * @{
+ */
+
+/**
+ * @brief Intermediate object storing the configuration for a line request.
+ */
+class request_builder final
+{
+public:
+
+ request_builder(const request_builder& other) = delete;
+
+ /**
+ * @brief Move constructor.
+ * @param other Object to be moved.
+ */
+ request_builder(request_builder&& other) noexcept;
+
+ ~request_builder();
+
+ request_builder& operator=(const request_builder& other) = delete;
+
+ /**
+ * @brief Move assignment operator.
+ * @param other Object to be moved.
+ * @return Reference to self.
+ */
+ request_builder& operator=(request_builder&& other) noexcept;
+
+ /**
+ * @brief Set the request config for the request.
+ * @param req_cfg Request config to use.
+ * @return Reference to self.
+ */
+ request_builder& set_request_config(request_config& req_cfg);
+
+ /**
+ * @brief Get the current request config.
+ * @return Const reference to the current request config stored by this
+ * object.
+ */
+ const request_config& get_request_config() const noexcept;
+
+ /**
+ * @brief Set consumer in the request config stored by this object.
+ * @param consumer New consumer string.
+ * @return Reference to self.
+ */
+ request_builder& set_consumer(const ::std::string& consumer) noexcept;
+
+ /**
+ * @brief Set the event buffer size in the request config stored by
+ * this object.
+ * @param event_buffer_size New event buffer size.
+ * @return Reference to self.
+ */
+ request_builder& set_event_buffer_size(::std::size_t event_buffer_size) noexcept;
+
+ /**
+ * @brief Set the line config for this request.
+ * @param line_cfg Line config to use.
+ * @return Reference to self.
+ */
+ request_builder& set_line_config(line_config &line_cfg);
+
+ /**
+ * @brief Get the current line config.
+ * @return Const reference to the current line config stored by this
+ * object.
+ */
+ const line_config& get_line_config() const noexcept;
+
+ /**
+ * @brief Add line settings to the line config stored by this object
+ * for a single offset.
+ * @param offset Offset for which to add settings.
+ * @param settings Line settings to use.
+ * @return Reference to self.
+ */
+ request_builder& add_line_settings(line::offset offset, const line_settings& settings);
+
+ /**
+ * @brief Add line settings to the line config stored by this object
+ * for a set of offsets.
+ * @param offsets Offsets for which to add settings.
+ * @param settings Settings to add.
+ * @return Reference to self.
+ */
+ request_builder& add_line_settings(const line::offsets& offsets, const line_settings& settings);
+
+ /**
+ * @brief Set output values for a number of lines in the line config
+ * stored by this object.
+ * @param values Buffer containing the output values.
+ * @return Reference to self.
+ */
+ request_builder& set_output_values(const line::values& values);
+
+ /**
+ * @brief Make the line request.
+ * @return New line_request object.
+ */
+ line_request do_request();
+
+private:
+
+ struct impl;
+
+ request_builder(chip& chip);
+
+ ::std::unique_ptr<impl> _m_priv;
+
+ friend chip;
+ friend ::std::ostream& operator<<(::std::ostream& out, const request_builder& builder);
+};
+
+/**
+ * @brief Stream insertion operator for GPIO request builder objects.
+ * @param out Output stream to write to.
+ * @param builder Request builder object to insert into the output stream.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, const request_builder& builder);
+
+/**
+ * @}
+ */
+
+} /* namespace gpiod */
+
+#endif /* __LIBGPIOD_CXX_REQUEST_BUILDER_HPP__ */
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+/**
+ * @file request-config.hpp
+ */
+
+#ifndef __LIBGPIOD_CXX_REQUEST_CONFIG_HPP__
+#define __LIBGPIOD_CXX_REQUEST_CONFIG_HPP__
+
+#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
+#error "Only gpiod.hpp can be included directly."
+#endif
+
+#include <cstddef>
+#include <iostream>
+#include <memory>
+#include <string>
+
+#include "line.hpp"
+
+namespace gpiod {
+
+class chip;
+
+/**
+ * @ingroup gpiod_cxx
+ * @{
+ */
+
+/**
+ * @brief Stores a set of options passed to the kernel when making a line
+ * request.
+ */
+class request_config final
+{
+public:
+
+ /**
+ * @brief Constructor.
+ */
+ request_config();
+
+ request_config(const request_config& other) = delete;
+
+ /**
+ * @brief Move constructor.
+ * @param other Object to move.
+ */
+ request_config(request_config&& other) noexcept;
+
+ ~request_config();
+
+ /**
+ * @brief Move assignment operator.
+ * @param other Object to move.
+ * @return Reference to self.
+ */
+ request_config& operator=(request_config&& other) noexcept;
+
+ /**
+ * @brief Set the consumer name.
+ * @param consumer New consumer name.
+ * @return Reference to self.
+ */
+ request_config& set_consumer(const ::std::string& consumer) noexcept;
+
+ /**
+ * @brief Get the consumer name.
+ * @return Currently configured consumer name. May be an empty string.
+ */
+ ::std::string consumer() const noexcept;
+
+ /**
+ * @brief Set the size of the kernel event buffer.
+ * @param event_buffer_size New event buffer size.
+ * @return Reference to self.
+ * @note The kernel may adjust the value if it's too high. If set to 0,
+ * the default value will be used.
+ */
+ request_config& set_event_buffer_size(::std::size_t event_buffer_size) noexcept;
+
+ /**
+ * @brief Get the edge event buffer size from this request config.
+ * @return Current edge event buffer size setting.
+ */
+ ::std::size_t event_buffer_size() const noexcept;
+
+private:
+
+ struct impl;
+
+ ::std::shared_ptr<impl> _m_priv;
+
+ request_config& operator=(const request_config& other);
+
+ friend request_builder;
+};
+
+/**
+ * @brief Stream insertion operator for request_config objects.
+ * @param out Output stream to write to.
+ * @param config request_config to insert into the output stream.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, const request_config& config);
+
+/**
+ * @}
+ */
+
+} /* namespace gpiod */
+
+#endif /* __LIBGPIOD_CXX_REQUEST_CONFIG_HPP__ */
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/* SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+/**
+ * @file timestamp.hpp
+ */
+
+#ifndef __LIBGPIOD_CXX_TIMESTAMP_HPP__
+#define __LIBGPIOD_CXX_TIMESTAMP_HPP__
+
+#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
+#error "Only gpiod.hpp can be included directly."
+#endif
+
+#include <chrono>
+#include <cstdint>
+
+namespace gpiod {
+
+/**
+ * @ingroup gpiod_cxx
+ * @{
+ */
+
+/**
+ * @brief Stores the edge and info event timestamps as returned by the kernel
+ * and allows to convert them to std::chrono::time_point.
+ */
+class timestamp final
+{
+public:
+
+ /**
+ * @brief Monotonic time_point.
+ */
+ using time_point_monotonic = ::std::chrono::time_point<::std::chrono::steady_clock>;
+
+ /**
+ * @brief Real-time time_point.
+ */
+ using time_point_realtime = ::std::chrono::time_point<::std::chrono::system_clock,
+ ::std::chrono::nanoseconds>;
+
+ /**
+ * @brief Constructor with implicit conversion from `uint64_t`.
+ * @param ns Timestamp in nanoseconds.
+ */
+ timestamp(::std::uint64_t ns) : _m_ns(ns) { }
+
+ /**
+ * @brief Copy constructor.
+ * @param other Object to copy.
+ */
+ timestamp(const timestamp& other) noexcept = default;
+
+ /**
+ * @brief Move constructor.
+ * @param other Object to move.
+ */
+ timestamp(timestamp&& other) noexcept = default;
+
+ /**
+ * @brief Assignment operator.
+ * @param other Object to copy.
+ * @return Reference to self.
+ */
+ timestamp& operator=(const timestamp& other) noexcept = default;
+
+ /**
+ * @brief Move assignment operator.
+ * @param other Object to move.
+ * @return Reference to self.
+ */
+ timestamp& operator=(timestamp&& other) noexcept = default;
+
+ ~timestamp() = default;
+
+ /**
+ * @brief Conversion operator to `std::uint64_t`.
+ */
+ operator ::std::uint64_t() noexcept
+ {
+ return this->ns();
+ }
+
+ /**
+ * @brief Get the timestamp in nanoseconds.
+ * @return Timestamp in nanoseconds.
+ */
+ ::std::uint64_t ns() const noexcept
+ {
+ return this->_m_ns;
+ }
+
+ /**
+ * @brief Convert the timestamp to a monotonic time_point.
+ * @return time_point associated with the steady clock.
+ */
+ time_point_monotonic to_time_point_monotonic() const
+ {
+ return time_point_monotonic(::std::chrono::nanoseconds(this->ns()));
+ }
+
+ /**
+ * @brief Convert the timestamp to a real-time time_point.
+ * @return time_point associated with the system clock.
+ */
+ time_point_realtime to_time_point_realtime() const
+ {
+ return time_point_realtime(::std::chrono::nanoseconds(this->ns()));
+ }
+
+private:
+ ::std::uint64_t _m_ns;
+};
+
+/**
+ * @}
+ */
+
+} /* namespace gpiod */
+
+#endif /* __LIBGPIOD_CXX_TIMESTAMP_HPP__ */
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <map>
+#include <ostream>
+
+#include "internal.hpp"
+
+namespace gpiod {
+
+namespace {
+
+const ::std::map<int, info_event::event_type> event_type_mapping = {
+ { GPIOD_INFO_EVENT_LINE_REQUESTED, info_event::event_type::LINE_REQUESTED },
+ { GPIOD_INFO_EVENT_LINE_RELEASED, info_event::event_type::LINE_RELEASED },
+ { GPIOD_INFO_EVENT_LINE_CONFIG_CHANGED, info_event::event_type::LINE_CONFIG_CHANGED },
+};
+
+const ::std::map<info_event::event_type, ::std::string> event_type_names = {
+ { info_event::event_type::LINE_REQUESTED, "LINE_REQUESTED" },
+ { info_event::event_type::LINE_RELEASED, "LINE_RELEASED" },
+ { info_event::event_type::LINE_CONFIG_CHANGED, "LINE_CONFIG_CHANGED" },
+};
+
+} /* namespace */
+
+void info_event::impl::set_info_event_ptr(info_event_ptr& new_event)
+{
+ ::gpiod_line_info* info = ::gpiod_info_event_get_line_info(new_event.get());
+
+ line_info_ptr copy(::gpiod_line_info_copy(info));
+ if (!copy)
+ throw_from_errno("unable to copy the line info object");
+
+ this->event = ::std::move(new_event);
+ this->info._m_priv->set_info_ptr(copy);
+}
+
+info_event::info_event()
+ : _m_priv(new impl)
+{
+
+}
+
+GPIOD_CXX_API info_event::info_event(const info_event& other)
+ : _m_priv(other._m_priv)
+{
+
+}
+
+GPIOD_CXX_API info_event::info_event(info_event&& other) noexcept
+ : _m_priv(::std::move(other._m_priv))
+{
+
+}
+
+GPIOD_CXX_API info_event::~info_event()
+{
+
+}
+
+GPIOD_CXX_API info_event& info_event::operator=(const info_event& other)
+{
+ this->_m_priv = other._m_priv;
+
+ return *this;
+}
+
+GPIOD_CXX_API info_event& info_event::operator=(info_event&& other) noexcept
+{
+ this->_m_priv = ::std::move(other._m_priv);
+
+ return *this;
+}
+
+GPIOD_CXX_API info_event::event_type info_event::type() const
+{
+ int type = ::gpiod_info_event_get_event_type(this->_m_priv->event.get());
+
+ return get_mapped_value(type, event_type_mapping);
+}
+
+GPIOD_CXX_API ::std::uint64_t info_event::timestamp_ns() const noexcept
+{
+ return ::gpiod_info_event_get_timestamp_ns(this->_m_priv->event.get());
+}
+
+GPIOD_CXX_API const line_info& info_event::get_line_info() const noexcept
+{
+ return this->_m_priv->info;
+}
+
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const info_event& event)
+{
+ out << "gpiod::info_event(event_type='" << event_type_names.at(event.type()) <<
+ "', timestamp=" << event.timestamp_ns() <<
+ ", line_info=" << event.get_line_info() <<
+ ")";
+
+ return out;
+}
+
+} /* namespace gpiod */
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <map>
+#include <stdexcept>
+#include <system_error>
+
+#include "internal.hpp"
+
+namespace gpiod {
+
+void throw_from_errno(const ::std::string& what)
+{
+ switch (errno) {
+ case EINVAL:
+ throw ::std::invalid_argument(what);
+ case E2BIG:
+ throw ::std::length_error(what);
+ case ENOMEM:
+ throw ::std::bad_alloc();
+ case EDOM:
+ throw ::std::domain_error(what);
+ default:
+ throw ::std::system_error(errno, ::std::system_category(), what);
+ }
+}
+
+} /* namespace gpiod */
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+#ifndef __LIBGPIOD_CXX_INTERNAL_HPP__
+#define __LIBGPIOD_CXX_INTERNAL_HPP__
+
+#include <gpiod.h>
+#include <map>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "gpiod.hpp"
+
+namespace gpiod {
+
+template<class cxx_enum_type, class c_enum_type>
+cxx_enum_type get_mapped_value(c_enum_type value,
+ const ::std::map<c_enum_type, cxx_enum_type>& mapping)
+{
+ try {
+ return mapping.at(value);
+ } catch (const ::std::out_of_range& err) {
+ /* FIXME Demangle the name. */
+ throw bad_mapping(::std::string("invalid value for ") +
+ typeid(cxx_enum_type).name());
+ }
+}
+
+void throw_from_errno(const ::std::string& what);
+::gpiod_line_value map_output_value(line::value value);
+
+template<class T, void F(T*)> struct deleter
+{
+ void operator()(T* ptr)
+ {
+ F(ptr);
+ }
+};
+
+using chip_deleter = deleter<::gpiod_chip, ::gpiod_chip_close>;
+using chip_info_deleter = deleter<::gpiod_chip_info, ::gpiod_chip_info_free>;
+using line_info_deleter = deleter<::gpiod_line_info, ::gpiod_line_info_free>;
+using info_event_deleter = deleter<::gpiod_info_event, ::gpiod_info_event_free>;
+using line_settings_deleter = deleter<::gpiod_line_settings, ::gpiod_line_settings_free>;
+using line_config_deleter = deleter<::gpiod_line_config, ::gpiod_line_config_free>;
+using request_config_deleter = deleter<::gpiod_request_config, ::gpiod_request_config_free>;
+using line_request_deleter = deleter<::gpiod_line_request, ::gpiod_line_request_release>;
+using edge_event_deleter = deleter<::gpiod_edge_event, ::gpiod_edge_event_free>;
+using edge_event_buffer_deleter = deleter<::gpiod_edge_event_buffer,
+ ::gpiod_edge_event_buffer_free>;
+
+using chip_ptr = ::std::unique_ptr<::gpiod_chip, chip_deleter>;
+using chip_info_ptr = ::std::unique_ptr<::gpiod_chip_info, chip_info_deleter>;
+using line_info_ptr = ::std::unique_ptr<::gpiod_line_info, line_info_deleter>;
+using info_event_ptr = ::std::unique_ptr<::gpiod_info_event, info_event_deleter>;
+using line_settings_ptr = ::std::unique_ptr<::gpiod_line_settings, line_settings_deleter>;
+using line_config_ptr = ::std::unique_ptr<::gpiod_line_config, line_config_deleter>;
+using request_config_ptr = ::std::unique_ptr<::gpiod_request_config, request_config_deleter>;
+using line_request_ptr = ::std::unique_ptr<::gpiod_line_request, line_request_deleter>;
+using edge_event_ptr = ::std::unique_ptr<::gpiod_edge_event, edge_event_deleter>;
+using edge_event_buffer_ptr = ::std::unique_ptr<::gpiod_edge_event_buffer,
+ edge_event_buffer_deleter>;
+
+struct chip::impl
+{
+ impl(const ::std::filesystem::path& path);
+ impl(const impl& other) = delete;
+ impl(impl&& other) = delete;
+ impl& operator=(const impl& other) = delete;
+ impl& operator=(impl&& other) = delete;
+
+ void throw_if_closed() const;
+
+ chip_ptr chip;
+};
+
+struct chip_info::impl
+{
+ impl() = default;
+ impl(const impl& other) = delete;
+ impl(impl&& other) = delete;
+ impl& operator=(const impl& other) = delete;
+ impl& operator=(impl&& other) = delete;
+
+ void set_info_ptr(chip_info_ptr& new_info);
+
+ chip_info_ptr info;
+};
+
+struct line_info::impl
+{
+ impl() = default;
+ impl(const impl& other) = delete;
+ impl(impl&& other) = delete;
+ impl& operator=(const impl& other) = delete;
+ impl& operator=(impl&& other) = delete;
+
+ void set_info_ptr(line_info_ptr& new_info);
+
+ line_info_ptr info;
+};
+
+struct info_event::impl
+{
+ impl() = default;
+ impl(const impl& other) = delete;
+ impl(impl&& other) = delete;
+ impl& operator=(const impl& other) = delete;
+ impl& operator=(impl&& other) = delete;
+
+ void set_info_event_ptr(info_event_ptr& new_event);
+
+ info_event_ptr event;
+ line_info info;
+};
+
+struct line_settings::impl
+{
+ impl();
+ impl(const impl& other);
+ impl(impl&& other) = delete;
+ impl& operator=(const impl& other) = delete;
+ impl& operator=(impl&& other) = delete;
+
+ line_settings_ptr settings;
+};
+
+struct line_config::impl
+{
+ impl();
+ impl(const impl& other) = delete;
+ impl(impl&& other) = delete;
+ impl& operator=(const impl& other) = delete;
+ impl& operator=(impl&& other) = delete;
+
+ line_config_ptr config;
+};
+
+struct request_config::impl
+{
+ impl();
+ impl(const impl& other) = delete;
+ impl(impl&& other) = delete;
+ impl& operator=(const impl& other) = delete;
+ impl& operator=(impl&& other) = delete;
+
+ request_config_ptr config;
+};
+
+struct line_request::impl
+{
+ impl() = default;
+ impl(const impl& other) = delete;
+ impl(impl&& other) = delete;
+ impl& operator=(const impl& other) = delete;
+ impl& operator=(impl&& other) = delete;
+
+ void throw_if_released() const;
+ void set_request_ptr(line_request_ptr& ptr);
+ void fill_offset_buf(const line::offsets& offsets);
+
+ line_request_ptr request;
+
+ /*
+ * Used when reading/setting the line values in order to avoid
+ * allocating a new buffer on every call. We're not doing it for
+ * offsets in the line & request config structures because they don't
+ * require high performance unlike the set/get value calls.
+ */
+ ::std::vector<unsigned int> offset_buf;
+};
+
+struct edge_event::impl
+{
+ impl() = default;
+ impl(const impl& other) = delete;
+ impl(impl&& other) = delete;
+ virtual ~impl() = default;
+ impl& operator=(const impl& other) = delete;
+ impl& operator=(impl&& other) = delete;
+
+ virtual ::gpiod_edge_event* get_event_ptr() const noexcept = 0;
+ virtual ::std::shared_ptr<impl> copy(const ::std::shared_ptr<impl>& self) const = 0;
+};
+
+struct edge_event::impl_managed final : public edge_event::impl
+{
+ impl_managed() = default;
+ impl_managed(const impl_managed& other) = delete;
+ impl_managed(impl_managed&& other) = delete;
+ ~impl_managed() = default;
+ impl_managed& operator=(const impl_managed& other) = delete;
+ impl_managed& operator=(impl_managed&& other) = delete;
+
+ ::gpiod_edge_event* get_event_ptr() const noexcept override;
+ ::std::shared_ptr<impl> copy(const ::std::shared_ptr<impl>& self) const override;
+
+ edge_event_ptr event;
+};
+
+struct edge_event::impl_external final : public edge_event::impl
+{
+ impl_external();
+ impl_external(const impl_external& other) = delete;
+ impl_external(impl_external&& other) = delete;
+ ~impl_external() = default;
+ impl_external& operator=(const impl_external& other) = delete;
+ impl_external& operator=(impl_external&& other) = delete;
+
+ ::gpiod_edge_event* get_event_ptr() const noexcept override;
+ ::std::shared_ptr<impl> copy(const ::std::shared_ptr<impl>& self) const override;
+
+ ::gpiod_edge_event *event;
+};
+
+struct edge_event_buffer::impl
+{
+ impl(unsigned int capacity);
+ impl(const impl& other) = delete;
+ impl(impl&& other) = delete;
+ impl& operator=(const impl& other) = delete;
+ impl& operator=(impl&& other) = delete;
+
+ int read_events(const line_request_ptr& request, unsigned int max_events);
+
+ edge_event_buffer_ptr buffer;
+ ::std::vector<edge_event> events;
+};
+
+} /* namespace gpiod */
+
+#endif /* __LIBGPIOD_CXX_INTERNAL_HPP__ */
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libgpiodcxx
+Description: C++ bindings for libgpiod
+URL: @PACKAGE_URL@
+Version: @PACKAGE_VERSION@
+Libs: -L${libdir} -lgpiodcxx
+Cflags: -I${includedir}
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <cstdlib>
+#include <iterator>
+#include <ostream>
+#include <sstream>
+#include <utility>
+#include <vector>
+
+#include "internal.hpp"
+
+namespace gpiod {
+
+namespace {
+
+line_config_ptr make_line_config()
+{
+ line_config_ptr config(::gpiod_line_config_new());
+ if (!config)
+ throw_from_errno("Unable to allocate the line config object");
+
+ return config;
+}
+
+} /* namespace */
+
+line_config::impl::impl()
+ : config(make_line_config())
+{
+
+}
+
+GPIOD_CXX_API line_config::line_config()
+ : _m_priv(new impl)
+{
+
+}
+
+GPIOD_CXX_API line_config::line_config(line_config&& other) noexcept
+ : _m_priv(::std::move(other._m_priv))
+{
+
+}
+
+GPIOD_CXX_API line_config::~line_config()
+{
+
+}
+
+line_config& line_config::operator=(const line_config& other)
+{
+ this->_m_priv = other._m_priv;
+
+ return *this;
+}
+
+GPIOD_CXX_API line_config& line_config::operator=(line_config&& other) noexcept
+{
+ this->_m_priv = ::std::move(other._m_priv);
+
+ return *this;
+}
+
+GPIOD_CXX_API line_config& line_config::reset() noexcept
+{
+ ::gpiod_line_config_reset(this->_m_priv->config.get());
+
+ return *this;
+}
+
+GPIOD_CXX_API line_config& line_config::add_line_settings(line::offset offset,
+ const line_settings& settings)
+{
+ return this->add_line_settings(line::offsets({offset}), settings);
+}
+
+GPIOD_CXX_API line_config& line_config::add_line_settings(const line::offsets& offsets,
+ const line_settings& settings)
+{
+ ::std::vector<unsigned int> raw_offsets(offsets.size());
+
+ for (unsigned int i = 0; i < offsets.size(); i++)
+ raw_offsets[i] = offsets[i];
+
+ auto ret = ::gpiod_line_config_add_line_settings(this->_m_priv->config.get(),
+ raw_offsets.data(), raw_offsets.size(),
+ settings._m_priv->settings.get());
+ if (ret)
+ throw_from_errno("unable to add line settings");
+
+ return *this;
+}
+
+GPIOD_CXX_API line_config& line_config::set_output_values(const line::values& values)
+{
+ ::std::vector<::gpiod_line_value> mapped_values(values.size());
+
+ for (unsigned int i = 0; i < values.size(); i++)
+ mapped_values[i] = map_output_value(values[i]);
+
+ auto ret = ::gpiod_line_config_set_output_values(this->_m_priv->config.get(),
+ mapped_values.data(), mapped_values.size());
+ if (ret)
+ throw_from_errno("unable to set output values");
+
+ return *this;
+}
+
+GPIOD_CXX_API ::std::map<line::offset, line_settings> line_config::get_line_settings() const
+{
+ ::std::size_t num_offsets = ::gpiod_line_config_get_num_configured_offsets(
+ this->_m_priv->config.get());
+ ::std::map<line::offset, line_settings> settings_map;
+ ::std::vector<unsigned int> offsets(num_offsets);
+
+ if (num_offsets == 0)
+ return settings_map;
+
+ ::gpiod_line_config_get_configured_offsets(this->_m_priv->config.get(),
+ offsets.data(), num_offsets);
+
+ for (size_t i = 0; i < num_offsets; i++) {
+ line_settings settings;
+
+ settings._m_priv->settings.reset(::gpiod_line_config_get_line_settings(
+ this->_m_priv->config.get(),
+ offsets[i]));
+ if (!settings._m_priv->settings)
+ throw_from_errno("unable to retrieve line settings");
+
+ settings_map[offsets[i]] = ::std::move(settings);
+ }
+
+ return settings_map;
+}
+
+GPIOD_CXX_API ::std::ostream&
+operator<<(::std::ostream& out, const line_config& config)
+{
+ auto settings_map = config.get_line_settings();
+ ::std::vector<::std::string> vec;
+
+ out << "gpiod::line_config(num_settings=" << settings_map.size();
+
+ if (settings_map.size() == 0) {
+ out << ")";
+ return out;
+ }
+
+ for (const auto& [offset, settings]: settings_map) {
+ ::std::stringstream str;
+
+ str << offset << ": " << settings;
+ vec.push_back(str.str());
+ }
+
+ out << ", settings=[";
+ ::std::copy(vec.begin(), ::std::prev(vec.end()),
+ ::std::ostream_iterator<::std::string>(out, ", "));
+ out << vec.back();
+ out << "])";
+
+ return out;
+}
+
+} /* namespace gpiod */
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <map>
+#include <ostream>
+#include <utility>
+
+#include "internal.hpp"
+
+namespace gpiod {
+
+namespace {
+
+const ::std::map<int, line::direction> direction_mapping = {
+ { GPIOD_LINE_DIRECTION_INPUT, line::direction::INPUT },
+ { GPIOD_LINE_DIRECTION_OUTPUT, line::direction::OUTPUT },
+};
+
+const ::std::map<int, line::bias> bias_mapping = {
+ { GPIOD_LINE_BIAS_UNKNOWN, line::bias::UNKNOWN },
+ { GPIOD_LINE_BIAS_DISABLED, line::bias::DISABLED },
+ { GPIOD_LINE_BIAS_PULL_UP, line::bias::PULL_UP },
+ { GPIOD_LINE_BIAS_PULL_DOWN, line::bias::PULL_DOWN },
+};
+
+const ::std::map<int, line::drive> drive_mapping = {
+ { GPIOD_LINE_DRIVE_PUSH_PULL, line::drive::PUSH_PULL },
+ { GPIOD_LINE_DRIVE_OPEN_DRAIN, line::drive::OPEN_DRAIN },
+ { GPIOD_LINE_DRIVE_OPEN_SOURCE, line::drive::OPEN_SOURCE },
+};
+
+const ::std::map<int, line::edge> edge_mapping = {
+ { GPIOD_LINE_EDGE_NONE, line::edge::NONE },
+ { GPIOD_LINE_EDGE_RISING, line::edge::RISING },
+ { GPIOD_LINE_EDGE_FALLING, line::edge::FALLING },
+ { GPIOD_LINE_EDGE_BOTH, line::edge::BOTH },
+};
+
+const ::std::map<int, line::clock> clock_mapping = {
+ { GPIOD_LINE_CLOCK_MONOTONIC, line::clock::MONOTONIC },
+ { GPIOD_LINE_CLOCK_REALTIME, line::clock::REALTIME },
+ { GPIOD_LINE_CLOCK_HTE, line::clock::HTE },
+};
+
+} /* namespace */
+
+void line_info::impl::set_info_ptr(line_info_ptr& new_info)
+{
+ this->info = ::std::move(new_info);
+}
+
+line_info::line_info()
+ : _m_priv(new impl)
+{
+
+}
+
+GPIOD_CXX_API line_info::line_info(const line_info& other) noexcept
+ : _m_priv(other._m_priv)
+{
+
+}
+
+GPIOD_CXX_API line_info::line_info(line_info&& other) noexcept
+ : _m_priv(::std::move(other._m_priv))
+{
+
+}
+
+GPIOD_CXX_API line_info::~line_info()
+{
+
+}
+
+GPIOD_CXX_API line_info& line_info::operator=(const line_info& other) noexcept
+{
+ this->_m_priv = other._m_priv;
+
+ return *this;
+}
+
+GPIOD_CXX_API line_info& line_info::operator=(line_info&& other) noexcept
+{
+ this->_m_priv = ::std::move(other._m_priv);
+
+ return *this;
+}
+
+GPIOD_CXX_API line::offset line_info::offset() const noexcept
+{
+ return ::gpiod_line_info_get_offset(this->_m_priv->info.get());
+}
+
+GPIOD_CXX_API ::std::string line_info::name() const noexcept
+{
+ const char* name = ::gpiod_line_info_get_name(this->_m_priv->info.get());
+
+ return name ?: "";
+}
+
+GPIOD_CXX_API bool line_info::used() const noexcept
+{
+ return ::gpiod_line_info_is_used(this->_m_priv->info.get());
+}
+
+GPIOD_CXX_API ::std::string line_info::consumer() const noexcept
+{
+ const char* consumer = ::gpiod_line_info_get_consumer(this->_m_priv->info.get());
+
+ return consumer ?: "";
+}
+
+GPIOD_CXX_API line::direction line_info::direction() const
+{
+ int direction = ::gpiod_line_info_get_direction(this->_m_priv->info.get());
+
+ return get_mapped_value(direction, direction_mapping);
+}
+
+GPIOD_CXX_API bool line_info::active_low() const noexcept
+{
+ return ::gpiod_line_info_is_active_low(this->_m_priv->info.get());
+}
+
+GPIOD_CXX_API line::bias line_info::bias() const
+{
+ int bias = ::gpiod_line_info_get_bias(this->_m_priv->info.get());
+
+ return bias_mapping.at(bias);
+}
+
+GPIOD_CXX_API line::drive line_info::drive() const
+{
+ int drive = ::gpiod_line_info_get_drive(this->_m_priv->info.get());
+
+ return drive_mapping.at(drive);
+}
+
+GPIOD_CXX_API line::edge line_info::edge_detection() const
+{
+ int edge = ::gpiod_line_info_get_edge_detection(this->_m_priv->info.get());
+
+ return edge_mapping.at(edge);
+}
+
+GPIOD_CXX_API line::clock line_info::event_clock() const
+{
+ int clock = ::gpiod_line_info_get_event_clock(this->_m_priv->info.get());
+
+ return clock_mapping.at(clock);
+}
+
+GPIOD_CXX_API bool line_info::debounced() const noexcept
+{
+ return ::gpiod_line_info_is_debounced(this->_m_priv->info.get());
+}
+
+GPIOD_CXX_API ::std::chrono::microseconds line_info::debounce_period() const noexcept
+{
+ return ::std::chrono::microseconds(
+ ::gpiod_line_info_get_debounce_period_us(this->_m_priv->info.get()));
+}
+
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const line_info& info)
+{
+ ::std::string name, consumer;
+
+ name = info.name().empty() ? "unnamed" : ::std::string("'") + info.name() + "'";
+ consumer = info.consumer().empty() ? "unused" : ::std::string("'") + info.name() + "'";
+
+ out << "gpiod::line_info(offset=" << info.offset() <<
+ ", name=" << name <<
+ ", used=" << ::std::boolalpha << info.used() <<
+ ", consumer=" << consumer <<
+ ", direction=" << info.direction() <<
+ ", active_low=" << ::std::boolalpha << info.active_low() <<
+ ", bias=" << info.bias() <<
+ ", drive=" << info.drive() <<
+ ", edge_detection=" << info.edge_detection() <<
+ ", event_clock=" << info.event_clock() <<
+ ", debounced=" << ::std::boolalpha << info.debounced();
+
+ if (info.debounced())
+ out << ", debounce_period=" << info.debounce_period().count() << "us";
+
+ out << ")";
+
+ return out;
+}
+
+} /* namespace gpiod */
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <iterator>
+#include <ostream>
+#include <utility>
+
+#include "internal.hpp"
+
+namespace gpiod {
+
+void line_request::impl::throw_if_released() const
+{
+ if (!this->request)
+ throw request_released("GPIO lines have been released");
+}
+
+void line_request::impl::set_request_ptr(line_request_ptr& ptr)
+{
+ this->request = ::std::move(ptr);
+ this->offset_buf.resize(::gpiod_line_request_get_num_requested_lines(this->request.get()));
+}
+
+void line_request::impl::fill_offset_buf(const line::offsets& offsets)
+{
+ for (unsigned int i = 0; i < offsets.size(); i++)
+ this->offset_buf[i] = offsets[i];
+}
+
+line_request::line_request()
+ : _m_priv(new impl)
+{
+
+}
+
+GPIOD_CXX_API line_request::line_request(line_request&& other) noexcept
+ : _m_priv(::std::move(other._m_priv))
+{
+
+}
+
+GPIOD_CXX_API line_request::~line_request()
+{
+
+}
+
+GPIOD_CXX_API line_request& line_request::operator=(line_request&& other) noexcept
+{
+ this->_m_priv = ::std::move(other._m_priv);
+
+ return *this;
+}
+
+GPIOD_CXX_API line_request::operator bool() const noexcept
+{
+ return this->_m_priv->request.get() != nullptr;
+}
+
+GPIOD_CXX_API void line_request::release()
+{
+ this->_m_priv->throw_if_released();
+
+ this->_m_priv->request.reset();
+}
+
+GPIOD_CXX_API ::std::string line_request::chip_name() const
+{
+ this->_m_priv->throw_if_released();
+
+ return ::gpiod_line_request_get_chip_name(this->_m_priv->request.get());
+}
+
+GPIOD_CXX_API ::std::size_t line_request::num_lines() const
+{
+ this->_m_priv->throw_if_released();
+
+ return ::gpiod_line_request_get_num_requested_lines(this->_m_priv->request.get());
+}
+
+GPIOD_CXX_API line::offsets line_request::offsets() const
+{
+ this->_m_priv->throw_if_released();
+
+ auto num_lines = this->num_lines();
+ ::std::vector<unsigned int> buf(num_lines);
+ line::offsets offsets(num_lines);
+
+ ::gpiod_line_request_get_requested_offsets(this->_m_priv->request.get(), buf.data(), buf.size());
+
+ for (unsigned int i = 0; i < num_lines; i++)
+ offsets[i] = buf[i];
+
+ return offsets;
+}
+
+GPIOD_CXX_API line::value line_request::get_value(line::offset offset)
+{
+ return this->get_values({ offset }).front();
+}
+
+GPIOD_CXX_API line::values
+line_request::get_values(const line::offsets& offsets)
+{
+ line::values vals(offsets.size());
+
+ this->get_values(offsets, vals);
+
+ return vals;
+}
+
+GPIOD_CXX_API line::values line_request::get_values()
+{
+ return this->get_values(this->offsets());
+}
+
+GPIOD_CXX_API void line_request::get_values(const line::offsets& offsets, line::values& values)
+{
+ this->_m_priv->throw_if_released();
+
+ if (offsets.size() != values.size())
+ throw ::std::invalid_argument("values must have the same size as the offsets");
+
+ this->_m_priv->fill_offset_buf(offsets);
+
+ int ret = ::gpiod_line_request_get_values_subset(
+ this->_m_priv->request.get(),
+ offsets.size(), this->_m_priv->offset_buf.data(),
+ reinterpret_cast<::gpiod_line_value*>(values.data()));
+ if (ret)
+ throw_from_errno("unable to retrieve line values");
+}
+
+GPIOD_CXX_API void line_request::get_values(line::values& values)
+{
+ this->get_values(this->offsets(), values);
+}
+
+GPIOD_CXX_API line_request&
+line_request::line_request::set_value(line::offset offset, line::value value)
+{
+ return this->set_values({ offset }, { value });
+}
+
+GPIOD_CXX_API line_request&
+line_request::set_values(const line::value_mappings& values)
+{
+ line::offsets offsets(values.size());
+ line::values vals(values.size());
+
+ for (unsigned int i = 0; i < values.size(); i++) {
+ offsets[i] = values[i].first;
+ vals[i] = values[i].second;
+ }
+
+ return this->set_values(offsets, vals);
+}
+
+GPIOD_CXX_API line_request& line_request::set_values(const line::offsets& offsets,
+ const line::values& values)
+{
+ this->_m_priv->throw_if_released();
+
+ if (offsets.size() != values.size())
+ throw ::std::invalid_argument("values must have the same size as the offsets");
+
+ this->_m_priv->fill_offset_buf(offsets);
+
+ int ret = ::gpiod_line_request_set_values_subset(
+ this->_m_priv->request.get(),
+ offsets.size(), this->_m_priv->offset_buf.data(),
+ reinterpret_cast<const ::gpiod_line_value*>(values.data()));
+ if (ret)
+ throw_from_errno("unable to set line values");
+
+ return *this;
+}
+
+GPIOD_CXX_API line_request& line_request::set_values(const line::values& values)
+{
+ return this->set_values(this->offsets(), values);
+}
+
+GPIOD_CXX_API line_request& line_request::reconfigure_lines(const line_config& config)
+{
+ this->_m_priv->throw_if_released();
+
+ int ret = ::gpiod_line_request_reconfigure_lines(this->_m_priv->request.get(),
+ config._m_priv->config.get());
+ if (ret)
+ throw_from_errno("unable to reconfigure GPIO lines");
+
+ return *this;
+}
+
+GPIOD_CXX_API int line_request::fd() const
+{
+ this->_m_priv->throw_if_released();
+
+ return ::gpiod_line_request_get_fd(this->_m_priv->request.get());
+}
+
+GPIOD_CXX_API bool line_request::wait_edge_events(const ::std::chrono::nanoseconds& timeout) const
+{
+ this->_m_priv->throw_if_released();
+
+ int ret = ::gpiod_line_request_wait_edge_events(this->_m_priv->request.get(),
+ timeout.count());
+ if (ret < 0)
+ throw_from_errno("error waiting for edge events");
+
+ return ret;
+}
+
+GPIOD_CXX_API ::std::size_t line_request::read_edge_events(edge_event_buffer& buffer)
+{
+ return this->read_edge_events(buffer, buffer.capacity());
+}
+
+GPIOD_CXX_API ::std::size_t
+line_request::read_edge_events(edge_event_buffer& buffer, ::std::size_t max_events)
+{
+ this->_m_priv->throw_if_released();
+
+ return buffer._m_priv->read_events(this->_m_priv->request, max_events);
+}
+
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const line_request& request)
+{
+ if (!request)
+ out << "gpiod::line_request(released)";
+ else
+ out << "gpiod::line_request(chip=\"" << request.chip_name() <<
+ "\", num_lines=" << request.num_lines() <<
+ ", line_offsets=" << request.offsets() <<
+ ", fd=" << request.fd() <<
+ ")";
+
+ return out;
+}
+
+} /* namespace gpiod */
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <map>
+#include <ostream>
+
+#include "internal.hpp"
+
+namespace gpiod {
+
+namespace {
+
+template<class cxx_enum_type, class c_enum_type>
+::std::map<c_enum_type, cxx_enum_type>
+make_reverse_maping(const ::std::map<cxx_enum_type, c_enum_type>& mapping)
+{
+ ::std::map<c_enum_type, cxx_enum_type> ret;
+
+ for (const auto &item: mapping)
+ ret[item.second] = item.first;
+
+ return ret;
+}
+
+const ::std::map<line::direction, ::gpiod_line_direction> direction_mapping = {
+ { line::direction::AS_IS, GPIOD_LINE_DIRECTION_AS_IS },
+ { line::direction::INPUT, GPIOD_LINE_DIRECTION_INPUT },
+ { line::direction::OUTPUT, GPIOD_LINE_DIRECTION_OUTPUT },
+};
+
+const ::std::map<::gpiod_line_direction, line::direction>
+reverse_direction_mapping = make_reverse_maping(direction_mapping);
+
+const ::std::map<line::edge, ::gpiod_line_edge> edge_mapping = {
+ { line::edge::NONE, GPIOD_LINE_EDGE_NONE },
+ { line::edge::FALLING, GPIOD_LINE_EDGE_FALLING },
+ { line::edge::RISING, GPIOD_LINE_EDGE_RISING },
+ { line::edge::BOTH, GPIOD_LINE_EDGE_BOTH },
+};
+
+const ::std::map<::gpiod_line_edge, line::edge>
+reverse_edge_mapping = make_reverse_maping(edge_mapping);
+
+const ::std::map<line::bias, ::gpiod_line_bias> bias_mapping = {
+ { line::bias::AS_IS, GPIOD_LINE_BIAS_AS_IS },
+ { line::bias::DISABLED, GPIOD_LINE_BIAS_DISABLED },
+ { line::bias::PULL_UP, GPIOD_LINE_BIAS_PULL_UP },
+ { line::bias::PULL_DOWN, GPIOD_LINE_BIAS_PULL_DOWN },
+};
+
+const ::std::map<::gpiod_line_bias, line::bias>
+reverse_bias_mapping = make_reverse_maping(bias_mapping);
+
+const ::std::map<line::drive, ::gpiod_line_drive> drive_mapping = {
+ { line::drive::PUSH_PULL, GPIOD_LINE_DRIVE_PUSH_PULL },
+ { line::drive::OPEN_DRAIN, GPIOD_LINE_DRIVE_OPEN_DRAIN },
+ { line::drive::OPEN_SOURCE, GPIOD_LINE_DRIVE_OPEN_SOURCE },
+};
+
+const ::std::map<::gpiod_line_drive, line::drive>
+reverse_drive_mapping = make_reverse_maping(drive_mapping);
+
+const ::std::map<line::clock, ::gpiod_line_clock> clock_mapping = {
+ { line::clock::MONOTONIC, GPIOD_LINE_CLOCK_MONOTONIC },
+ { line::clock::REALTIME, GPIOD_LINE_CLOCK_REALTIME },
+ { line::clock::HTE, GPIOD_LINE_CLOCK_HTE },
+};
+
+const ::std::map<::gpiod_line_clock, line::clock>
+reverse_clock_mapping = make_reverse_maping(clock_mapping);
+
+const ::std::map<line::value, ::gpiod_line_value> value_mapping = {
+ { line::value::INACTIVE, GPIOD_LINE_VALUE_INACTIVE },
+ { line::value::ACTIVE, GPIOD_LINE_VALUE_ACTIVE },
+};
+
+const ::std::map<::gpiod_line_value, line::value>
+reverse_value_mapping = make_reverse_maping(value_mapping);
+
+line_settings_ptr make_line_settings()
+{
+ line_settings_ptr settings(::gpiod_line_settings_new());
+ if (!settings)
+ throw_from_errno("Unable to allocate the line settings object");
+
+ return settings;
+}
+
+line_settings_ptr copy_line_settings(const line_settings_ptr& ptr)
+{
+ line_settings_ptr settings(::gpiod_line_settings_copy(ptr.get()));
+ if (!settings)
+ throw_from_errno("Unable to copy the line settings object");
+
+ return settings;
+}
+
+template<class cxx_enum_type, class c_enum_type>
+c_enum_type do_map_value(cxx_enum_type value, const ::std::map<cxx_enum_type, c_enum_type>& mapping)
+try {
+ return get_mapped_value(value, mapping);
+} catch (const bad_mapping& ex) {
+ throw ::std::invalid_argument(ex.what());
+}
+
+template<class cxx_enum_type, class c_enum_type, int set_func(::gpiod_line_settings*, c_enum_type)>
+void set_mapped_prop(::gpiod_line_settings* settings, cxx_enum_type value,
+ const ::std::map<cxx_enum_type, c_enum_type>& mapping)
+{
+ c_enum_type mapped_val = do_map_value(value, mapping);
+
+ auto ret = set_func(settings, mapped_val);
+ if (ret)
+ throw_from_errno("unable to set property");
+}
+
+template<class cxx_enum_type, class c_enum_type, c_enum_type get_func(::gpiod_line_settings*)>
+cxx_enum_type get_mapped_prop(::gpiod_line_settings* settings,
+ const ::std::map<c_enum_type, cxx_enum_type>& mapping)
+{
+ auto mapped_val = get_func(settings);
+
+ return get_mapped_value(mapped_val, mapping);
+}
+
+} /* namespace */
+
+::gpiod_line_value map_output_value(line::value value)
+{
+ return do_map_value(value, value_mapping);
+}
+
+line_settings::impl::impl()
+ : settings(make_line_settings())
+{
+
+}
+
+line_settings::impl::impl(const impl& other)
+ : settings(copy_line_settings(other.settings))
+{
+
+}
+
+GPIOD_CXX_API line_settings::line_settings()
+ : _m_priv(new impl)
+{
+
+}
+
+GPIOD_CXX_API line_settings::line_settings(const line_settings& other)
+ : _m_priv(new impl(*other._m_priv))
+{
+
+}
+
+GPIOD_CXX_API line_settings::line_settings(line_settings&& other) noexcept
+ : _m_priv(::std::move(other._m_priv))
+{
+
+}
+
+GPIOD_CXX_API line_settings::~line_settings()
+{
+
+}
+
+GPIOD_CXX_API line_settings& line_settings::operator=(const line_settings& other)
+{
+ this->_m_priv.reset(new impl(*other._m_priv));
+
+ return *this;
+}
+
+GPIOD_CXX_API line_settings& line_settings::operator=(line_settings&& other)
+{
+ this->_m_priv = ::std::move(other._m_priv);
+
+ return *this;
+}
+
+GPIOD_CXX_API line_settings& line_settings::reset() noexcept
+{
+ ::gpiod_line_settings_reset(this->_m_priv->settings.get());
+
+ return *this;
+}
+
+GPIOD_CXX_API line_settings& line_settings::set_direction(line::direction direction)
+{
+ set_mapped_prop<line::direction, ::gpiod_line_direction,
+ ::gpiod_line_settings_set_direction>(this->_m_priv->settings.get(),
+ direction, direction_mapping);
+
+ return *this;
+}
+
+GPIOD_CXX_API line::direction line_settings::direction() const
+{
+ return get_mapped_prop<line::direction, ::gpiod_line_direction,
+ ::gpiod_line_settings_get_direction>(
+ this->_m_priv->settings.get(),
+ reverse_direction_mapping);
+}
+
+GPIOD_CXX_API line_settings& line_settings::set_edge_detection(line::edge edge)
+{
+ set_mapped_prop<line::edge, ::gpiod_line_edge,
+ ::gpiod_line_settings_set_edge_detection>(this->_m_priv->settings.get(),
+ edge, edge_mapping);
+
+ return *this;
+}
+
+GPIOD_CXX_API line::edge line_settings::edge_detection() const
+{
+ return get_mapped_prop<line::edge, ::gpiod_line_edge,
+ ::gpiod_line_settings_get_edge_detection>(
+ this->_m_priv->settings.get(),
+ reverse_edge_mapping);
+}
+
+GPIOD_CXX_API line_settings& line_settings::set_bias(line::bias bias)
+{
+ set_mapped_prop<line::bias, ::gpiod_line_bias,
+ ::gpiod_line_settings_set_bias>(this->_m_priv->settings.get(),
+ bias, bias_mapping);
+
+ return *this;
+}
+
+GPIOD_CXX_API line::bias line_settings::bias() const
+{
+ return get_mapped_prop<line::bias, ::gpiod_line_bias,
+ ::gpiod_line_settings_get_bias>(this->_m_priv->settings.get(),
+ reverse_bias_mapping);
+}
+
+GPIOD_CXX_API line_settings& line_settings::set_drive(line::drive drive)
+{
+ set_mapped_prop<line::drive, ::gpiod_line_drive,
+ ::gpiod_line_settings_set_drive>(this->_m_priv->settings.get(),
+ drive, drive_mapping);
+
+ return *this;
+}
+
+GPIOD_CXX_API line::drive line_settings::drive() const
+{
+ return get_mapped_prop<line::drive, ::gpiod_line_drive,
+ ::gpiod_line_settings_get_drive>(this->_m_priv->settings.get(),
+ reverse_drive_mapping);
+}
+
+GPIOD_CXX_API line_settings& line_settings::set_active_low(bool active_low)
+{
+ ::gpiod_line_settings_set_active_low(this->_m_priv->settings.get(), active_low);
+
+ return *this;
+}
+
+GPIOD_CXX_API bool line_settings::active_low() const noexcept
+{
+ return ::gpiod_line_settings_get_active_low(this->_m_priv->settings.get());
+}
+
+GPIOD_CXX_API line_settings&
+line_settings::set_debounce_period(const ::std::chrono::microseconds& period)
+{
+ ::gpiod_line_settings_set_debounce_period_us(this->_m_priv->settings.get(), period.count());
+
+ return *this;
+}
+
+GPIOD_CXX_API ::std::chrono::microseconds line_settings::debounce_period() const noexcept
+{
+ return ::std::chrono::microseconds(
+ ::gpiod_line_settings_get_debounce_period_us(this->_m_priv->settings.get()));
+}
+
+GPIOD_CXX_API line_settings& line_settings::set_event_clock(line::clock event_clock)
+{
+ set_mapped_prop<line::clock, ::gpiod_line_clock,
+ ::gpiod_line_settings_set_event_clock>(this->_m_priv->settings.get(),
+ event_clock, clock_mapping);
+
+ return *this;
+}
+
+GPIOD_CXX_API line::clock line_settings::event_clock() const
+{
+ return get_mapped_prop<line::clock, ::gpiod_line_clock,
+ ::gpiod_line_settings_get_event_clock>(
+ this->_m_priv->settings.get(),
+ reverse_clock_mapping);
+}
+
+GPIOD_CXX_API line_settings& line_settings::set_output_value(line::value value)
+{
+ set_mapped_prop<line::value, ::gpiod_line_value,
+ ::gpiod_line_settings_set_output_value>(this->_m_priv->settings.get(),
+ value, value_mapping);
+
+ return *this;
+}
+
+GPIOD_CXX_API line::value line_settings::output_value() const
+{
+ return get_mapped_prop<line::value, ::gpiod_line_value,
+ ::gpiod_line_settings_get_output_value>(
+ this->_m_priv->settings.get(),
+ reverse_value_mapping);
+}
+
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const line_settings& settings)
+{
+ out << "gpiod::line_settings(direction=" << settings.direction() <<
+ ", edge_detection=" << settings.edge_detection() <<
+ ", bias=" << settings.bias() <<
+ ", drive=" << settings.drive() <<
+ ", " << (settings.active_low() ? "active-low" : "active-high") <<
+ ", debounce_period=" << settings.debounce_period().count() <<
+ ", event_clock=" << settings.event_clock() <<
+ ", output_value=" << settings.output_value() <<
+ ")";
+
+ return out;
+}
+
+} /* namespace gpiod */
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <iterator>
+#include <ostream>
+
+#include "internal.hpp"
+
+namespace gpiod {
+namespace line {
+
+namespace {
+
+const ::std::map<line::value, ::std::string> value_names = {
+ { line::value::INACTIVE, "INACTIVE" },
+ { line::value::ACTIVE, "ACTIVE" },
+};
+
+const ::std::map<line::direction, ::std::string> direction_names = {
+ { line::direction::AS_IS, "AS_IS" },
+ { line::direction::INPUT, "INPUT" },
+ { line::direction::OUTPUT, "OUTPUT" },
+};
+
+const ::std::map<line::bias, ::std::string> bias_names = {
+ { line::bias::AS_IS, "AS_IS" },
+ { line::bias::UNKNOWN, "UNKNOWN" },
+ { line::bias::DISABLED, "DISABLED" },
+ { line::bias::PULL_UP, "PULL_UP" },
+ { line::bias::PULL_DOWN, "PULL_DOWN" },
+};
+
+const ::std::map<line::drive, ::std::string> drive_names = {
+ { line::drive::PUSH_PULL, "PUSH_PULL" },
+ { line::drive::OPEN_DRAIN, "OPEN_DRAIN" },
+ { line::drive::OPEN_SOURCE, "OPEN_SOURCE" },
+};
+
+const ::std::map<line::edge, ::std::string> edge_names = {
+ { line::edge::NONE, "NONE" },
+ { line::edge::RISING, "RISING_EDGE" },
+ { line::edge::FALLING, "FALLING_EDGE" },
+ { line::edge::BOTH, "BOTH_EDGES" },
+};
+
+const ::std::map<line::clock, ::std::string> clock_names = {
+ { line::clock::MONOTONIC, "MONOTONIC" },
+ { line::clock::REALTIME, "REALTIME" },
+ { line::clock::HTE, "HTE" },
+};
+
+} /* namespace */
+
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, line::value val)
+{
+ out << value_names.at(val);
+
+ return out;
+}
+
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, line::direction dir)
+{
+ out << direction_names.at(dir);
+
+ return out;
+}
+
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, line::edge edge)
+{
+ out << edge_names.at(edge);
+
+ return out;
+}
+
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, line::bias bias)
+{
+ out << bias_names.at(bias);
+
+ return out;
+}
+
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, line::drive drive)
+{
+ out << drive_names.at(drive);
+
+ return out;
+}
+
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, line::clock clock)
+{
+ out << clock_names.at(clock);
+
+ return out;
+}
+
+template<typename T>
+::std::ostream& insert_vector(::std::ostream& out,
+ const ::std::string& name, const ::std::vector<T>& vec)
+{
+ out << name << "(";
+ ::std::copy(vec.begin(), ::std::prev(vec.end()),
+ ::std::ostream_iterator<T>(out, ", "));
+ out << vec.back();
+ out << ")";
+
+ return out;
+}
+
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const offsets& offs)
+{
+ return insert_vector(out, "gpiod::offsets", offs);
+}
+
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const line::values& vals)
+{
+ return insert_vector(out, "gpiod::values", vals);
+}
+
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const line::value_mapping& mapping)
+{
+ out << "gpiod::value_mapping(" << mapping.first << ": " << mapping.second << ")";
+
+ return out;
+}
+
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const line::value_mappings& mappings)
+{
+ return insert_vector(out, "gpiod::value_mappings", mappings);
+}
+
+} /* namespace line */
+} /* namespace gpiod */
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include "internal.hpp"
+
+namespace gpiod {
+
+GPIOD_CXX_API bool is_gpiochip_device(const ::std::filesystem::path& path)
+{
+ return ::gpiod_is_gpiochip_device(path.c_str());
+}
+
+GPIOD_CXX_API const ::std::string& api_version()
+{
+ static const ::std::string version(::gpiod_api_version());
+
+ return version;
+}
+
+} /* namespace gpiod */
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <ostream>
+#include <utility>
+
+#include "internal.hpp"
+
+namespace gpiod {
+
+struct request_builder::impl
+{
+ impl(chip& parent)
+ : line_cfg(),
+ req_cfg(),
+ parent(parent)
+ {
+
+ }
+
+ impl(const impl& other) = delete;
+ impl(impl&& other) = delete;
+ impl& operator=(const impl& other) = delete;
+ impl& operator=(impl&& other) = delete;
+
+ line_config line_cfg;
+ request_config req_cfg;
+ chip parent;
+};
+
+GPIOD_CXX_API request_builder::request_builder(chip& chip)
+ : _m_priv(new impl(chip))
+{
+
+}
+
+GPIOD_CXX_API request_builder::request_builder(request_builder&& other) noexcept
+ : _m_priv(::std::move(other._m_priv))
+{
+
+}
+
+GPIOD_CXX_API request_builder::~request_builder()
+{
+
+}
+
+GPIOD_CXX_API request_builder& request_builder::operator=(request_builder&& other) noexcept
+{
+ this->_m_priv = ::std::move(other._m_priv);
+
+ return *this;
+}
+
+GPIOD_CXX_API request_builder& request_builder::set_request_config(request_config& req_cfg)
+{
+ this->_m_priv->req_cfg = req_cfg;
+
+ return *this;
+}
+
+GPIOD_CXX_API const request_config& request_builder::get_request_config() const noexcept
+{
+ return this->_m_priv->req_cfg;
+}
+
+GPIOD_CXX_API request_builder&
+request_builder::set_consumer(const ::std::string& consumer) noexcept
+{
+ this->_m_priv->req_cfg.set_consumer(consumer);
+
+ return *this;
+}
+
+GPIOD_CXX_API request_builder&
+request_builder::set_event_buffer_size(::std::size_t event_buffer_size) noexcept
+{
+ this->_m_priv->req_cfg.set_event_buffer_size(event_buffer_size);
+
+ return *this;
+}
+
+GPIOD_CXX_API request_builder& request_builder::set_line_config(line_config &line_cfg)
+{
+ this->_m_priv->line_cfg = line_cfg;
+
+ return *this;
+}
+
+GPIOD_CXX_API const line_config& request_builder::get_line_config() const noexcept
+{
+ return this->_m_priv->line_cfg;
+}
+
+GPIOD_CXX_API request_builder&
+request_builder::add_line_settings(line::offset offset, const line_settings& settings)
+{
+ return this->add_line_settings(line::offsets({offset}), settings);
+}
+
+GPIOD_CXX_API request_builder&
+request_builder::add_line_settings(const line::offsets& offsets, const line_settings& settings)
+{
+ this->_m_priv->line_cfg.add_line_settings(offsets, settings);
+
+ return *this;
+}
+
+GPIOD_CXX_API request_builder&
+request_builder::set_output_values(const line::values& values)
+{
+ this->_m_priv->line_cfg.set_output_values(values);
+
+ return *this;
+}
+
+GPIOD_CXX_API line_request request_builder::do_request()
+{
+ line_request_ptr request(::gpiod_chip_request_lines(
+ this->_m_priv->parent._m_priv->chip.get(),
+ this->_m_priv->req_cfg._m_priv->config.get(),
+ this->_m_priv->line_cfg._m_priv->config.get()));
+ if (!request)
+ throw_from_errno("error requesting GPIO lines");
+
+ line_request ret;
+ ret._m_priv.get()->set_request_ptr(request);
+
+ return ret;
+}
+
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const request_builder& builder)
+{
+ out << "gpiod::request_builder(request_config=" << builder._m_priv->req_cfg <<
+ ", line_config=" << builder._m_priv->line_cfg <<
+ ", parent=" << builder._m_priv->parent <<
+ ")";
+
+ return out;
+}
+
+} /* namespace gpiod */
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <ostream>
+#include <utility>
+
+#include "internal.hpp"
+
+namespace gpiod {
+
+namespace {
+
+request_config_ptr make_request_config()
+{
+ request_config_ptr config(::gpiod_request_config_new());
+ if (!config)
+ throw_from_errno("Unable to allocate the request config object");
+
+ return config;
+}
+
+} /* namespace */
+
+request_config::impl::impl()
+ : config(make_request_config())
+{
+
+}
+
+GPIOD_CXX_API request_config::request_config()
+ : _m_priv(new impl)
+{
+
+}
+
+GPIOD_CXX_API request_config::request_config(request_config&& other) noexcept
+ : _m_priv(::std::move(other._m_priv))
+{
+
+}
+
+GPIOD_CXX_API request_config::~request_config()
+{
+
+}
+
+request_config& request_config::operator=(const request_config& other)
+{
+ this->_m_priv = other._m_priv;
+
+ return *this;
+}
+
+GPIOD_CXX_API request_config& request_config::operator=(request_config&& other) noexcept
+{
+ this->_m_priv = ::std::move(other._m_priv);
+
+ return *this;
+}
+
+GPIOD_CXX_API request_config&
+request_config::set_consumer(const ::std::string& consumer) noexcept
+{
+ ::gpiod_request_config_set_consumer(this->_m_priv->config.get(), consumer.c_str());
+
+ return *this;
+}
+
+GPIOD_CXX_API ::std::string request_config::consumer() const noexcept
+{
+ const char* consumer = ::gpiod_request_config_get_consumer(this->_m_priv->config.get());
+
+ return consumer ?: "";
+}
+
+GPIOD_CXX_API request_config&
+request_config::set_event_buffer_size(::std::size_t event_buffer_size) noexcept
+{
+ ::gpiod_request_config_set_event_buffer_size(this->_m_priv->config.get(),
+ event_buffer_size);
+
+ return *this;
+}
+
+GPIOD_CXX_API ::std::size_t request_config::event_buffer_size() const noexcept
+{
+ return ::gpiod_request_config_get_event_buffer_size(this->_m_priv->config.get());
+}
+
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const request_config& config)
+{
+ ::std::string consumer;
+
+ consumer = config.consumer().empty() ? "N/A" : ::std::string("'") + config.consumer() + "'";
+
+ out << "gpiod::request_config(consumer=" << consumer <<
+ ", event_buffer_size=" << config.event_buffer_size() <<
+ ")";
+
+ return out;
+}
+
+} /* namespace gpiod */
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+
+gpiod-cxx-test
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+
+AM_CXXFLAGS = -I$(top_srcdir)/bindings/cxx/ -I$(top_srcdir)/include
+AM_CXXFLAGS += -I$(top_srcdir)/tests/gpiosim/
+AM_CXXFLAGS += -Wall -Wextra -g -std=gnu++17 $(CATCH2_CFLAGS)
+AM_LDFLAGS = -pthread
+LDADD = $(top_builddir)/bindings/cxx/libgpiodcxx.la
+LDADD += $(top_builddir)/tests/gpiosim/libgpiosim.la
+
+noinst_PROGRAMS = gpiod-cxx-test
+
+gpiod_cxx_test_SOURCES = \
+ check-kernel.cpp \
+ gpiod-cxx-test-main.cpp \
+ gpiosim.cpp \
+ gpiosim.hpp \
+ helpers.cpp \
+ helpers.hpp \
+ tests-chip.cpp \
+ tests-chip-info.cpp \
+ tests-edge-event.cpp \
+ tests-info-event.cpp \
+ tests-line.cpp \
+ tests-line-config.cpp \
+ tests-line-info.cpp \
+ tests-line-request.cpp \
+ tests-line-settings.cpp \
+ tests-misc.cpp \
+ tests-request-config.cpp
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2017-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <linux/version.h>
+#include <sys/utsname.h>
+#include <system_error>
+#include <sstream>
+
+namespace {
+
+class kernel_checker
+{
+public:
+ kernel_checker(int major, int minor, int release)
+ {
+ int curr_major, curr_minor, curr_release, curr_ver, req_ver;
+ ::std::string major_str, minor_str, release_str;
+ ::utsname un;
+ int ret;
+
+ ret = ::uname(::std::addressof(un));
+ if (ret)
+ throw ::std::system_error(errno, ::std::system_category(),
+ "unable to read the kernel version");
+
+ ::std::stringstream ver_stream(::std::string(un.release));
+ ::std::getline(ver_stream, major_str, '.');
+ ::std::getline(ver_stream, minor_str, '.');
+ ::std::getline(ver_stream, release_str, '-');
+
+ curr_major = ::std::stoi(major_str, nullptr, 0);
+ curr_minor = ::std::stoi(minor_str, nullptr, 0);
+ curr_release = ::std::stoi(release_str, nullptr, 0);
+
+ curr_ver = KERNEL_VERSION(curr_major, curr_minor, curr_release);
+ req_ver = KERNEL_VERSION(major, minor, release);
+
+ if (curr_ver < req_ver)
+ throw ::std::runtime_error("kernel release must be at least: " +
+ ::std::to_string(major) + "." +
+ ::std::to_string(minor) + "." +
+ ::std::to_string(release));
+ }
+};
+
+kernel_checker require_kernel(5, 19, 0);
+
+} /* namespace */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+
+#define CATCH_CONFIG_MAIN
+#include <catch2/catch.hpp>
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/* SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+#include <map>
+#include <system_error>
+#include <utility>
+
+#include "gpiosim.h"
+#include "gpiosim.hpp"
+
+namespace gpiosim {
+
+namespace {
+
+const ::std::map<chip::pull, gpiosim_pull> pull_mapping = {
+ { chip::pull::PULL_UP, GPIOSIM_PULL_UP },
+ { chip::pull::PULL_DOWN, GPIOSIM_PULL_DOWN },
+};
+
+const ::std::map<chip_builder::direction, gpiosim_direction> hog_dir_mapping = {
+ { chip_builder::direction::INPUT, GPIOSIM_DIRECTION_INPUT },
+ { chip_builder::direction::OUTPUT_HIGH, GPIOSIM_DIRECTION_OUTPUT_HIGH },
+ { chip_builder::direction::OUTPUT_LOW, GPIOSIM_DIRECTION_OUTPUT_LOW },
+};
+
+const ::std::map<gpiosim_value, chip::value> value_mapping = {
+ { GPIOSIM_VALUE_INACTIVE, chip::value::INACTIVE },
+ { GPIOSIM_VALUE_ACTIVE, chip::value::ACTIVE },
+};
+
+template<class gpiosim_type, void free_func(gpiosim_type*)> struct deleter
+{
+ void operator()(gpiosim_type* ptr)
+ {
+ free_func(ptr);
+ }
+};
+
+using ctx_deleter = deleter<::gpiosim_ctx, ::gpiosim_ctx_unref>;
+using dev_deleter = deleter<::gpiosim_dev, ::gpiosim_dev_unref>;
+using bank_deleter = deleter<::gpiosim_bank, ::gpiosim_bank_unref>;
+
+using ctx_ptr = ::std::unique_ptr<::gpiosim_ctx, ctx_deleter>;
+using dev_ptr = ::std::unique_ptr<::gpiosim_dev, dev_deleter>;
+using bank_ptr = ::std::unique_ptr<::gpiosim_bank, bank_deleter>;
+
+ctx_ptr sim_ctx;
+
+class sim_ctx_initializer
+{
+public:
+ sim_ctx_initializer()
+ {
+ sim_ctx.reset(gpiosim_ctx_new());
+ if (!sim_ctx)
+ throw ::std::system_error(errno, ::std::system_category(),
+ "unable to create the GPIO simulator context");
+ }
+};
+
+dev_ptr make_sim_dev()
+{
+ static sim_ctx_initializer ctx_initializer;
+
+ dev_ptr dev(::gpiosim_dev_new(sim_ctx.get()));
+ if (!dev)
+ throw ::std::system_error(errno, ::std::system_category(),
+ "failed to create a new GPIO simulator device");
+
+ return dev;
+}
+
+bank_ptr make_sim_bank(const dev_ptr& dev)
+{
+ bank_ptr bank(::gpiosim_bank_new(dev.get()));
+ if (!bank)
+ throw ::std::system_error(errno, ::std::system_category(),
+ "failed to create a new GPIO simulator bank");
+
+ return bank;
+}
+
+} /* namespace */
+
+struct chip::impl
+{
+ impl()
+ : dev(make_sim_dev()),
+ bank(make_sim_bank(this->dev))
+ {
+
+ }
+
+ impl(const impl& other) = delete;
+ impl(impl&& other) = delete;
+ ~impl() = default;
+ impl& operator=(const impl& other) = delete;
+ impl& operator=(impl&& other) = delete;
+
+ dev_ptr dev;
+ bank_ptr bank;
+};
+
+chip::chip()
+ : _m_priv(new impl)
+{
+
+}
+
+chip::chip(chip&& other)
+ : _m_priv(::std::move(other._m_priv))
+{
+
+}
+
+chip::~chip()
+{
+
+}
+
+chip& chip::operator=(chip&& other)
+{
+ this->_m_priv = ::std::move(other._m_priv);
+
+ return *this;
+}
+
+::std::filesystem::path chip::dev_path() const
+{
+ return ::gpiosim_bank_get_dev_path(this->_m_priv->bank.get());
+}
+
+::std::string chip::name() const
+{
+ return ::gpiosim_bank_get_chip_name(this->_m_priv->bank.get());
+}
+
+chip::value chip::get_value(unsigned int offset)
+{
+ auto val = ::gpiosim_bank_get_value(this->_m_priv->bank.get(), offset);
+ if (val == GPIOSIM_VALUE_ERROR)
+ throw ::std::system_error(errno, ::std::system_category(),
+ "failed to read the simulated GPIO line value");
+
+ return value_mapping.at(val);
+}
+
+void chip::set_pull(unsigned int offset, pull pull)
+{
+ auto ret = ::gpiosim_bank_set_pull(this->_m_priv->bank.get(),
+ offset, pull_mapping.at(pull));
+ if (ret)
+ throw ::std::system_error(errno, ::std::system_category(),
+ "failed to set the pull of simulated GPIO line");
+}
+
+struct chip_builder::impl
+{
+ impl()
+ : num_lines(0),
+ label(),
+ line_names(),
+ hogs()
+ {
+
+ }
+
+ ::std::size_t num_lines;
+ ::std::string label;
+ ::std::map<unsigned int, ::std::string> line_names;
+ ::std::map<unsigned int, ::std::pair<::std::string, direction>> hogs;
+};
+
+chip_builder::chip_builder()
+ : _m_priv(new impl)
+{
+
+}
+
+chip_builder::chip_builder(chip_builder&& other)
+ : _m_priv(::std::move(other._m_priv))
+{
+
+}
+
+chip_builder::~chip_builder()
+{
+
+}
+
+chip_builder& chip_builder::operator=(chip_builder&& other)
+{
+ this->_m_priv = ::std::move(other._m_priv);
+
+ return *this;
+}
+
+chip_builder& chip_builder::set_num_lines(::std::size_t num_lines)
+{
+ this->_m_priv->num_lines = num_lines;
+
+ return *this;
+}
+
+chip_builder& chip_builder::set_label(const ::std::string& label)
+{
+ this->_m_priv->label = label;
+
+ return *this;
+}
+
+chip_builder& chip_builder::set_line_name(unsigned int offset, const ::std::string& name)
+{
+ this->_m_priv->line_names[offset] = name;
+
+ return *this;
+}
+
+chip_builder& chip_builder::set_hog(unsigned int offset, const ::std::string& name, direction direction)
+{
+ this->_m_priv->hogs[offset] = { name, direction };
+
+ return *this;
+}
+
+chip chip_builder::build()
+{
+ chip sim;
+ int ret;
+
+ if (this->_m_priv->num_lines) {
+ ret = ::gpiosim_bank_set_num_lines(sim._m_priv->bank.get(),
+ this->_m_priv->num_lines);
+ if (ret)
+ throw ::std::system_error(errno, ::std::system_category(),
+ "failed to set number of lines");
+ }
+
+ if (!this->_m_priv->label.empty()) {
+ ret = ::gpiosim_bank_set_label(sim._m_priv->bank.get(),
+ this->_m_priv->label.c_str());
+ if (ret)
+ throw ::std::system_error(errno, ::std::system_category(),
+ "failed to set the chip label");
+ }
+
+ for (const auto& name: this->_m_priv->line_names) {
+ ret = ::gpiosim_bank_set_line_name(sim._m_priv->bank.get(),
+ name.first, name.second.c_str());
+ if (ret)
+ throw ::std::system_error(errno, ::std::system_category(),
+ "failed to set the line name");
+ }
+
+ for (const auto& hog: this->_m_priv->hogs) {
+ ret = ::gpiosim_bank_hog_line(sim._m_priv->bank.get(), hog.first,
+ hog.second.first.c_str(),
+ hog_dir_mapping.at(hog.second.second));
+ if (ret)
+ throw ::std::system_error(errno, ::std::system_category(),
+ "failed to hog the line");
+ }
+
+ ret = ::gpiosim_dev_enable(sim._m_priv->dev.get());
+ if (ret)
+ throw ::std::system_error(errno, ::std::system_category(),
+ "failed to enable the simulated GPIO device");
+
+ return sim;
+}
+
+chip_builder make_sim()
+{
+ return chip_builder();
+}
+
+} /* namespace gpiosim */
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/* SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+#ifndef __GPIOD_CXX_GPIOSIM_HPP__
+#define __GPIOD_CXX_GPIOSIM_HPP__
+
+#include <filesystem>
+#include <memory>
+
+namespace gpiosim {
+
+class chip_builder;
+
+class chip
+{
+public:
+ enum class pull {
+ PULL_UP = 1,
+ PULL_DOWN,
+ };
+
+ enum class value {
+ INACTIVE = 0,
+ ACTIVE = 1,
+ };
+
+ chip(const chip& other) = delete;
+ chip(chip&& other);
+ ~chip();
+
+ chip& operator=(const chip& other) = delete;
+ chip& operator=(chip&& other);
+
+ ::std::filesystem::path dev_path() const;
+ ::std::string name() const;
+
+ value get_value(unsigned int offset);
+ void set_pull(unsigned int offset, pull pull);
+
+private:
+
+ chip();
+
+ struct impl;
+
+ ::std::unique_ptr<impl> _m_priv;
+
+ friend chip_builder;
+};
+
+class chip_builder
+{
+public:
+ enum class direction {
+ INPUT = 1,
+ OUTPUT_HIGH,
+ OUTPUT_LOW,
+ };
+
+ chip_builder();
+ chip_builder(const chip_builder& other) = delete;
+ chip_builder(chip_builder&& other);
+ ~chip_builder();
+
+ chip_builder& operator=(const chip_builder& other) = delete;
+ chip_builder& operator=(chip_builder&& other);
+
+ chip_builder& set_num_lines(::std::size_t num_lines);
+ chip_builder& set_label(const ::std::string& label);
+ chip_builder& set_line_name(unsigned int offset, const ::std::string& name);
+ chip_builder& set_hog(unsigned int offset, const ::std::string& name, direction direction);
+
+ chip build();
+
+private:
+
+ struct impl;
+
+ ::std::unique_ptr<impl> _m_priv;
+};
+
+chip_builder make_sim();
+
+} /* namespace gpiosim */
+
+#endif /* __GPIOD_CXX_GPIOSIM_HPP__ */
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include "helpers.hpp"
+
+system_error_matcher::system_error_matcher(int expected_errno)
+ : _m_cond(::std::system_category().default_error_condition(expected_errno))
+{
+
+}
+
+::std::string system_error_matcher::describe() const
+{
+ return "matches: errno " + ::std::to_string(this->_m_cond.value());
+}
+
+bool system_error_matcher::match(const ::std::system_error& error) const
+{
+ return error.code().value() == this->_m_cond.value();
+}
+
+regex_matcher::regex_matcher(const ::std::string& pattern)
+ : _m_pattern(pattern),
+ _m_repr("matches: regex \"" + pattern + "\"")
+{
+
+}
+
+::std::string regex_matcher::describe() const
+{
+ return this->_m_repr;
+}
+
+bool regex_matcher::match(const ::std::string& str) const
+{
+ return ::std::regex_match(str, this->_m_pattern);
+}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+#ifndef __GPIOD_CXX_TEST_HELPERS_HPP__
+#define __GPIOD_CXX_TEST_HELPERS_HPP__
+
+#include <catch2/catch.hpp>
+#include <regex>
+#include <string>
+#include <sstream>
+#include <system_error>
+
+class system_error_matcher : public Catch::MatcherBase<::std::system_error>
+{
+public:
+ explicit system_error_matcher(int expected_errno);
+ ::std::string describe() const override;
+ bool match(const ::std::system_error& error) const override;
+
+private:
+ ::std::error_condition _m_cond;
+};
+
+class regex_matcher : public Catch::MatcherBase<::std::string>
+{
+public:
+ explicit regex_matcher(const ::std::string& pattern);
+ ::std::string describe() const override;
+ bool match(const ::std::string& str) const override;
+
+private:
+ ::std::regex _m_pattern;
+ ::std::string _m_repr;
+};
+
+template<class T> class stringify_matcher : public Catch::MatcherBase<T>
+{
+public:
+ explicit stringify_matcher(const ::std::string& expected) : _m_expected(expected)
+ {
+
+ }
+
+ ::std::string describe() const override
+ {
+ return "equals " + this->_m_expected;
+ }
+
+ bool match(const T& obj) const override
+ {
+ ::std::stringstream buf;
+
+ buf << obj;
+
+ return buf.str() == this->_m_expected;
+ }
+
+private:
+ ::std::string _m_expected;
+};
+
+#endif /* __GPIOD_CXX_TEST_HELPERS_HPP__ */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <catch2/catch.hpp>
+#include <gpiod.hpp>
+#include <sstream>
+
+#include "gpiosim.hpp"
+#include "helpers.hpp"
+
+using ::gpiosim::make_sim;
+
+namespace {
+
+TEST_CASE("chip_info properties can be read", "[chip-info][chip]")
+{
+ auto sim = make_sim()
+ .set_num_lines(8)
+ .set_label("foobar")
+ .build();
+
+ ::gpiod::chip chip(sim.dev_path());
+ auto info = chip.get_info();
+
+ SECTION("get chip name")
+ {
+ REQUIRE_THAT(info.name(), Catch::Equals(sim.name()));
+ }
+
+ SECTION("get chip label")
+ {
+ REQUIRE_THAT(info.label(), Catch::Equals("foobar"));
+ }
+
+ SECTION("get num_lines")
+ {
+ REQUIRE(info.num_lines() == 8);
+ }
+}
+
+TEST_CASE("chip_info can be copied and moved", "[chip-info]")
+{
+ auto sim = make_sim()
+ .set_num_lines(4)
+ .set_label("foobar")
+ .build();
+
+ ::gpiod::chip chip(sim.dev_path());
+ auto info = chip.get_info();
+
+ SECTION("copy constructor works")
+ {
+ auto copy(info);
+
+ REQUIRE_THAT(copy.name(), Catch::Equals(sim.name()));
+ REQUIRE_THAT(copy.label(), Catch::Equals("foobar"));
+ REQUIRE(copy.num_lines() == 4);
+
+ REQUIRE_THAT(info.name(), Catch::Equals(sim.name()));
+ REQUIRE_THAT(info.label(), Catch::Equals("foobar"));
+ REQUIRE(info.num_lines() == 4);
+ }
+
+ SECTION("assignment operator works")
+ {
+ auto copy = chip.get_info();
+
+ copy = info;
+
+ REQUIRE_THAT(copy.name(), Catch::Equals(sim.name()));
+ REQUIRE_THAT(copy.label(), Catch::Equals("foobar"));
+ REQUIRE(copy.num_lines() == 4);
+
+ REQUIRE_THAT(info.name(), Catch::Equals(sim.name()));
+ REQUIRE_THAT(info.label(), Catch::Equals("foobar"));
+ REQUIRE(info.num_lines() == 4);
+ }
+
+ SECTION("move constructor works")
+ {
+ auto moved(std::move(info));
+
+ REQUIRE_THAT(moved.name(), Catch::Equals(sim.name()));
+ REQUIRE_THAT(moved.label(), Catch::Equals("foobar"));
+ REQUIRE(moved.num_lines() == 4);
+ }
+
+ SECTION("move assignment operator works")
+ {
+ auto moved = chip.get_info();
+
+ moved = ::std::move(info);
+
+ REQUIRE_THAT(moved.name(), Catch::Equals(sim.name()));
+ REQUIRE_THAT(moved.label(), Catch::Equals("foobar"));
+ REQUIRE(moved.num_lines() == 4);
+ }
+}
+
+TEST_CASE("stream insertion operator works for chip_info", "[chip-info]")
+{
+ auto sim = make_sim()
+ .set_num_lines(4)
+ .set_label("foobar")
+ .build();
+
+ ::gpiod::chip chip(sim.dev_path());
+ auto info = chip.get_info();
+ ::std::stringstream expected;
+
+ expected << "gpiod::chip_info(name=\"" << sim.name() <<
+ "\", label=\"foobar\", num_lines=4)";
+
+ REQUIRE_THAT(info, stringify_matcher<::gpiod::chip_info>(expected.str()));
+}
+
+} /* namespace */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <catch2/catch.hpp>
+#include <gpiod.hpp>
+#include <sstream>
+#include <system_error>
+#include <utility>
+
+#include "gpiosim.hpp"
+#include "helpers.hpp"
+
+using ::gpiosim::make_sim;
+
+namespace {
+
+TEST_CASE("chip constructor works", "[chip]")
+{
+ SECTION("open an existing GPIO chip")
+ {
+ auto sim = make_sim().build();
+
+ REQUIRE_NOTHROW(::gpiod::chip(sim.dev_path()));
+ }
+
+ SECTION("opening a nonexistent file fails with ENOENT")
+ {
+ REQUIRE_THROWS_MATCHES(::gpiod::chip("/dev/nonexistent"),
+ ::std::system_error, system_error_matcher(ENOENT));
+ }
+
+ SECTION("opening a file that is not a device fails with ENOTTY")
+ {
+ REQUIRE_THROWS_MATCHES(::gpiod::chip("/tmp"),
+ ::std::system_error, system_error_matcher(ENOTTY));
+ }
+
+ SECTION("opening a non-GPIO character device fails with ENODEV")
+ {
+ REQUIRE_THROWS_MATCHES(::gpiod::chip("/dev/null"),
+ ::std::system_error, system_error_matcher(ENODEV));
+ }
+
+ SECTION("move constructor")
+ {
+ auto sim = make_sim()
+ .set_label("foobar")
+ .build();
+
+ ::gpiod::chip first(sim.dev_path());
+ REQUIRE_THAT(first.get_info().label(), Catch::Equals("foobar"));
+ ::gpiod::chip second(::std::move(first));
+ REQUIRE_THAT(second.get_info().label(), Catch::Equals("foobar"));
+ }
+}
+
+TEST_CASE("chip operators work", "[chip]")
+{
+ auto sim = make_sim()
+ .set_label("foobar")
+ .build();
+
+ ::gpiod::chip chip(sim.dev_path());
+
+ SECTION("assignment operator")
+ {
+ auto moved_sim = make_sim()
+ .set_label("moved")
+ .build();
+
+ ::gpiod::chip moved_chip(moved_sim.dev_path());
+
+ REQUIRE_THAT(chip.get_info().label(), Catch::Equals("foobar"));
+ chip = ::std::move(moved_chip);
+ REQUIRE_THAT(chip.get_info().label(), Catch::Equals("moved"));
+ }
+
+ SECTION("boolean operator")
+ {
+ REQUIRE(chip);
+ chip.close();
+ REQUIRE_FALSE(chip);
+ }
+}
+
+TEST_CASE("chip properties can be read", "[chip]")
+{
+ auto sim = make_sim()
+ .set_num_lines(8)
+ .set_label("foobar")
+ .build();
+
+ ::gpiod::chip chip(sim.dev_path());
+
+ SECTION("get device path")
+ {
+ REQUIRE_THAT(chip.path(), Catch::Equals(sim.dev_path()));
+ }
+
+ SECTION("get file descriptor")
+ {
+ REQUIRE(chip.fd() >= 0);
+ }
+}
+
+TEST_CASE("line lookup by name works", "[chip]")
+{
+ auto sim = make_sim()
+ .set_num_lines(8)
+ .set_line_name(0, "foo")
+ .set_line_name(2, "bar")
+ .set_line_name(3, "baz")
+ .set_line_name(5, "xyz")
+ .build();
+
+ ::gpiod::chip chip(sim.dev_path());
+
+ SECTION("lookup successful")
+ {
+ REQUIRE(chip.get_line_offset_from_name("baz") == 3);
+ }
+
+ SECTION("lookup failed")
+ {
+ REQUIRE(chip.get_line_offset_from_name("nonexistent") < 0);
+ }
+}
+
+TEST_CASE("line lookup: behavior for duplicate names", "[chip]")
+{
+ auto sim = make_sim()
+ .set_num_lines(8)
+ .set_line_name(0, "foo")
+ .set_line_name(2, "bar")
+ .set_line_name(3, "baz")
+ .set_line_name(5, "bar")
+ .build();
+
+ ::gpiod::chip chip(sim.dev_path());
+
+ REQUIRE(chip.get_line_offset_from_name("bar") == 2);
+}
+
+TEST_CASE("closed chip can no longer be used", "[chip]")
+{
+ auto sim = make_sim().build();
+
+ ::gpiod::chip chip(sim.dev_path());
+ chip.close();
+ REQUIRE_THROWS_AS(chip.path(), ::gpiod::chip_closed);
+}
+
+TEST_CASE("stream insertion operator works for chip", "[chip]")
+{
+ auto sim = make_sim()
+ .set_num_lines(4)
+ .set_label("foobar")
+ .build();
+
+ ::gpiod::chip chip(sim.dev_path());
+ ::std::stringstream buf;
+
+ SECTION("open chip")
+ {
+ ::std::stringstream expected;
+
+ expected << "gpiod::chip(path=" << sim.dev_path() <<
+ ", info=gpiod::chip_info(name=\"" << sim.name() <<
+ "\", label=\"foobar\", num_lines=4))";
+
+ buf << chip;
+ REQUIRE_THAT(buf.str(), Catch::Equals(expected.str()));
+ }
+
+ SECTION("closed chip")
+ {
+ chip.close();
+ REQUIRE_THAT(chip, stringify_matcher<::gpiod::chip>("gpiod::chip(closed)"));
+ }
+}
+
+} /* namespace */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <catch2/catch.hpp>
+#include <chrono>
+#include <gpiod.hpp>
+#include <sstream>
+#include <thread>
+#include <utility>
+
+#include "gpiosim.hpp"
+#include "helpers.hpp"
+
+using ::gpiosim::make_sim;
+using direction = ::gpiod::line::direction;
+using edge = ::gpiod::line::edge;
+using offsets = ::gpiod::line::offsets;
+using pull = ::gpiosim::chip::pull;
+using event_type = ::gpiod::edge_event::event_type;
+
+namespace {
+
+TEST_CASE("edge_event_buffer capacity settings work", "[edge-event]")
+{
+ SECTION("default capacity")
+ {
+ REQUIRE(::gpiod::edge_event_buffer().capacity() == 64);
+ }
+
+ SECTION("user-defined capacity")
+ {
+ REQUIRE(::gpiod::edge_event_buffer(123).capacity() == 123);
+ }
+
+ SECTION("max capacity")
+ {
+ REQUIRE(::gpiod::edge_event_buffer(16 * 64 * 2).capacity() == 1024);
+ }
+}
+
+TEST_CASE("edge_event wait timeout", "[edge-event]")
+{
+ auto sim = make_sim().build();
+ ::gpiod::chip chip(sim.dev_path());
+
+ auto request = chip.prepare_request()
+ .add_line_settings(
+ 0,
+ ::gpiod::line_settings()
+ .set_edge_detection(edge::BOTH)
+ )
+ .do_request();
+
+ REQUIRE_FALSE(request.wait_edge_events(::std::chrono::milliseconds(100)));
+}
+
+TEST_CASE("output mode and edge detection don't work together", "[edge-event]")
+{
+ auto sim = make_sim().build();
+
+ REQUIRE_THROWS_AS(
+ ::gpiod::chip(sim.dev_path())
+ .prepare_request()
+ .add_line_settings(
+ 0,
+ ::gpiod::line_settings()
+ .set_direction(direction::OUTPUT)
+ .set_edge_detection(edge::BOTH)
+ )
+ .do_request(),
+ ::std::invalid_argument
+ );
+}
+
+void trigger_falling_and_rising_edge(::gpiosim::chip& sim, unsigned int offset)
+{
+ ::std::this_thread::sleep_for(::std::chrono::milliseconds(30));
+ sim.set_pull(offset, pull::PULL_UP);
+ ::std::this_thread::sleep_for(::std::chrono::milliseconds(30));
+ sim.set_pull(offset, pull::PULL_DOWN);
+}
+
+void trigger_rising_edge_events_on_two_offsets(::gpiosim::chip& sim,
+ unsigned int off0, unsigned int off1)
+{
+ ::std::this_thread::sleep_for(::std::chrono::milliseconds(30));
+ sim.set_pull(off0, pull::PULL_UP);
+ ::std::this_thread::sleep_for(::std::chrono::milliseconds(30));
+ sim.set_pull(off1, pull::PULL_UP);
+}
+
+TEST_CASE("waiting for and reading edge events works", "[edge-event]")
+{
+ auto sim = make_sim()
+ .set_num_lines(8)
+ .build();
+
+ ::gpiod::chip chip(sim.dev_path());
+ ::gpiod::edge_event_buffer buffer;
+
+ SECTION("both edge events")
+ {
+ auto request = chip
+ .prepare_request()
+ .add_line_settings(
+ 2,
+ ::gpiod::line_settings()
+ .set_edge_detection(edge::BOTH)
+ )
+ .do_request();
+
+ ::std::uint64_t ts_rising, ts_falling;
+
+ ::std::thread thread(trigger_falling_and_rising_edge, ::std::ref(sim), 2);
+
+ REQUIRE(request.wait_edge_events(::std::chrono::seconds(1)));
+ REQUIRE(request.read_edge_events(buffer, 1) == 1);
+ REQUIRE(buffer.num_events() == 1);
+ auto event = buffer.get_event(0);
+ REQUIRE(event.type() == event_type::RISING_EDGE);
+ REQUIRE(event.line_offset() == 2);
+ ts_rising = event.timestamp_ns();
+
+ REQUIRE(request.wait_edge_events(::std::chrono::seconds(1)));
+ REQUIRE(request.read_edge_events(buffer, 1) == 1);
+ REQUIRE(buffer.num_events() == 1);
+ event = buffer.get_event(0);
+ REQUIRE(event.type() == event_type::FALLING_EDGE);
+ REQUIRE(event.line_offset() == 2);
+ ts_falling = event.timestamp_ns();
+
+ REQUIRE_FALSE(request.wait_edge_events(::std::chrono::milliseconds(100)));
+
+ thread.join();
+
+ REQUIRE(ts_falling > ts_rising);
+ }
+
+ SECTION("rising edge event")
+ {
+ auto request = chip
+ .prepare_request()
+ .add_line_settings(
+ 6,
+ ::gpiod::line_settings()
+ .set_edge_detection(edge::RISING)
+ )
+ .do_request();
+
+ ::std::thread thread(trigger_falling_and_rising_edge, ::std::ref(sim), 6);
+
+ REQUIRE(request.wait_edge_events(::std::chrono::seconds(1)));
+ REQUIRE(request.read_edge_events(buffer, 1) == 1);
+ REQUIRE(buffer.num_events() == 1);
+ auto event = buffer.get_event(0);
+ REQUIRE(event.type() == event_type::RISING_EDGE);
+ REQUIRE(event.line_offset() == 6);
+
+ REQUIRE_FALSE(request.wait_edge_events(::std::chrono::milliseconds(100)));
+
+ thread.join();
+ }
+
+ SECTION("falling edge event")
+ {
+ auto request = chip
+ .prepare_request()
+ .add_line_settings(
+ 7,
+ ::gpiod::line_settings()
+ .set_edge_detection(edge::FALLING)
+ )
+ .do_request();
+
+ ::std::thread thread(trigger_falling_and_rising_edge, ::std::ref(sim), 7);
+
+ REQUIRE(request.wait_edge_events(::std::chrono::seconds(1)));
+ REQUIRE(request.read_edge_events(buffer, 1) == 1);
+ REQUIRE(buffer.num_events() == 1);
+ auto event = buffer.get_event(0);
+ REQUIRE(event.type() == event_type::FALLING_EDGE);
+ REQUIRE(event.line_offset() == 7);
+
+ REQUIRE_FALSE(request.wait_edge_events(::std::chrono::milliseconds(100)));
+
+ thread.join();
+ }
+
+ SECTION("sequence numbers")
+ {
+ auto request = chip
+ .prepare_request()
+ .add_line_settings(
+ { 0, 1 },
+ ::gpiod::line_settings()
+ .set_edge_detection(edge::BOTH)
+ )
+ .do_request();
+
+ ::std::thread thread(trigger_rising_edge_events_on_two_offsets, ::std::ref(sim), 0, 1);
+
+ REQUIRE(request.wait_edge_events(::std::chrono::seconds(1)));
+ REQUIRE(request.read_edge_events(buffer, 1) == 1);
+ REQUIRE(buffer.num_events() == 1);
+ auto event = buffer.get_event(0);
+ REQUIRE(event.type() == event_type::RISING_EDGE);
+ REQUIRE(event.line_offset() == 0);
+ REQUIRE(event.global_seqno() == 1);
+ REQUIRE(event.line_seqno() == 1);
+
+ REQUIRE(request.wait_edge_events(::std::chrono::seconds(1)));
+ REQUIRE(request.read_edge_events(buffer, 1) == 1);
+ REQUIRE(buffer.num_events() == 1);
+ event = buffer.get_event(0);
+ REQUIRE(event.type() == event_type::RISING_EDGE);
+ REQUIRE(event.line_offset() == 1);
+ REQUIRE(event.global_seqno() == 2);
+ REQUIRE(event.line_seqno() == 1);
+
+ thread.join();
+ }
+}
+
+TEST_CASE("reading multiple events", "[edge-event]")
+{
+ auto sim = make_sim()
+ .set_num_lines(8)
+ .build();
+
+ ::gpiod::chip chip(sim.dev_path());
+
+ auto request = chip
+ .prepare_request()
+ .add_line_settings(
+ 1,
+ ::gpiod::line_settings()
+ .set_edge_detection(edge::BOTH)
+ )
+ .do_request();
+
+ unsigned long line_seqno = 1, global_seqno = 1;
+
+ sim.set_pull(1, pull::PULL_UP);
+ ::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
+ sim.set_pull(1, pull::PULL_DOWN);
+ ::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
+ sim.set_pull(1, pull::PULL_UP);
+ ::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
+
+ SECTION("read multiple events")
+ {
+ ::gpiod::edge_event_buffer buffer;
+
+ REQUIRE(request.wait_edge_events(::std::chrono::seconds(1)));
+ REQUIRE(request.read_edge_events(buffer) == 3);
+ REQUIRE(buffer.num_events() == 3);
+
+ for (const auto& event: buffer) {
+ REQUIRE(event.line_offset() == 1);
+ REQUIRE(event.line_seqno() == line_seqno++);
+ REQUIRE(event.global_seqno() == global_seqno++);
+ }
+ }
+
+ SECTION("read over capacity")
+ {
+ ::gpiod::edge_event_buffer buffer(2);
+
+ REQUIRE(request.wait_edge_events(::std::chrono::seconds(1)));
+ REQUIRE(request.read_edge_events(buffer) == 2);
+ REQUIRE(buffer.num_events() == 2);
+ }
+}
+
+TEST_CASE("edge_event_buffer can be moved", "[edge-event]")
+{
+ auto sim = make_sim()
+ .set_num_lines(2)
+ .build();
+
+ ::gpiod::chip chip(sim.dev_path());
+ ::gpiod::edge_event_buffer buffer(13);
+
+ /* Get some events into the buffer. */
+ auto request = chip
+ .prepare_request()
+ .add_line_settings(
+ 1,
+ ::gpiod::line_settings()
+ .set_edge_detection(edge::BOTH)
+ )
+ .do_request();
+
+ sim.set_pull(1, pull::PULL_UP);
+ ::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
+ sim.set_pull(1, pull::PULL_DOWN);
+ ::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
+ sim.set_pull(1, pull::PULL_UP);
+ ::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
+
+ ::std::this_thread::sleep_for(::std::chrono::milliseconds(500));
+
+ REQUIRE(request.wait_edge_events(::std::chrono::seconds(1)));
+ REQUIRE(request.read_edge_events(buffer) == 3);
+
+ SECTION("move constructor works")
+ {
+ auto moved(::std::move(buffer));
+ REQUIRE(moved.capacity() == 13);
+ REQUIRE(moved.num_events() == 3);
+ }
+
+ SECTION("move assignment operator works")
+ {
+ ::gpiod::edge_event_buffer moved;
+
+ moved = ::std::move(buffer);
+ REQUIRE(moved.capacity() == 13);
+ REQUIRE(moved.num_events() == 3);
+ }
+}
+
+TEST_CASE("edge_event can be copied and moved", "[edge-event]")
+{
+ auto sim = make_sim().build();
+ ::gpiod::chip chip(sim.dev_path());
+ ::gpiod::edge_event_buffer buffer;
+
+ auto request = chip
+ .prepare_request()
+ .add_line_settings(
+ 0,
+ ::gpiod::line_settings()
+ .set_edge_detection(edge::BOTH)
+ )
+ .do_request();
+
+ sim.set_pull(0, pull::PULL_UP);
+ ::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
+ REQUIRE(request.wait_edge_events(::std::chrono::seconds(1)));
+ REQUIRE(request.read_edge_events(buffer) == 1);
+ auto event = buffer.get_event(0);
+
+ sim.set_pull(0, pull::PULL_DOWN);
+ ::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
+ REQUIRE(request.wait_edge_events(::std::chrono::seconds(1)));
+ REQUIRE(request.read_edge_events(buffer) == 1);
+ auto copy = buffer.get_event(0);
+
+ SECTION("copy constructor works")
+ {
+ auto copy(event);
+ REQUIRE(copy.line_offset() == 0);
+ REQUIRE(copy.type() == event_type::RISING_EDGE);
+ REQUIRE(event.line_offset() == 0);
+ REQUIRE(event.type() == event_type::RISING_EDGE);
+ }
+
+ SECTION("move constructor works")
+ {
+ auto copy(::std::move(event));
+ REQUIRE(copy.line_offset() == 0);
+ REQUIRE(copy.type() == event_type::RISING_EDGE);
+ }
+
+ SECTION("assignment operator works")
+ {
+ copy = event;
+ REQUIRE(copy.line_offset() == 0);
+ REQUIRE(copy.type() == event_type::RISING_EDGE);
+ REQUIRE(event.line_offset() == 0);
+ REQUIRE(event.type() == event_type::RISING_EDGE);
+ }
+
+ SECTION("move assignment operator works")
+ {
+ copy = ::std::move(event);
+ REQUIRE(copy.line_offset() == 0);
+ REQUIRE(copy.type() == event_type::RISING_EDGE);
+ }
+}
+
+TEST_CASE("stream insertion operators work for edge_event and edge_event_buffer", "[edge-event]")
+{
+ /*
+ * This tests the stream insertion operators for both edge_event and
+ * edge_event_buffer classes.
+ */
+
+ auto sim = make_sim().build();
+ ::gpiod::chip chip(sim.dev_path());
+ ::gpiod::edge_event_buffer buffer;
+ ::std::stringstream sbuf, expected;
+
+ auto request = chip
+ .prepare_request()
+ .add_line_settings(
+ 0,
+ ::gpiod::line_settings()
+ .set_edge_detection(edge::BOTH)
+ )
+ .do_request();
+
+ sim.set_pull(0, pull::PULL_UP);
+ ::std::this_thread::sleep_for(::std::chrono::milliseconds(30));
+ sim.set_pull(0, pull::PULL_DOWN);
+ ::std::this_thread::sleep_for(::std::chrono::milliseconds(30));
+
+ REQUIRE(request.wait_edge_events(::std::chrono::seconds(1)));
+ REQUIRE(request.read_edge_events(buffer) == 2);
+
+ sbuf << buffer;
+
+ expected << "gpiod::edge_event_buffer\\(num_events=2, capacity=64, events=\\[gpiod::edge_event\\" <<
+ "(type='RISING_EDGE', timestamp=[1-9][0-9]+, line_offset=0, global_seqno=1, " <<
+ "line_seqno=1\\), gpiod::edge_event\\(type='FALLING_EDGE', timestamp=[1-9][0-9]+, " <<
+ "line_offset=0, global_seqno=2, line_seqno=2\\)\\]\\)";
+
+ REQUIRE_THAT(sbuf.str(), regex_matcher(expected.str()));
+}
+
+} /* namespace */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <catch2/catch.hpp>
+#include <chrono>
+#include <filesystem>
+#include <gpiod.hpp>
+#include <sstream>
+#include <thread>
+#include <utility>
+
+#include "gpiosim.hpp"
+#include "helpers.hpp"
+
+using ::gpiosim::make_sim;
+using direction = ::gpiod::line::direction;
+using event_type = ::gpiod::info_event::event_type;
+
+namespace {
+
+void request_reconfigure_release_line(const ::std::filesystem::path& chip_path)
+{
+ ::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
+
+ auto request = ::gpiod::chip(chip_path)
+ .prepare_request()
+ .add_line_settings(7, ::gpiod::line_settings())
+ .do_request();
+
+ ::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
+
+ request.reconfigure_lines(
+ ::gpiod::line_config()
+ .add_line_settings(
+ 7,
+ ::gpiod::line_settings()
+ .set_direction(direction::OUTPUT)
+ )
+ );
+
+ ::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
+
+ request.release();
+}
+
+TEST_CASE("Lines can be watched", "[info-event][chip]")
+{
+ auto sim = make_sim()
+ .set_num_lines(8)
+ .build();
+
+ const auto chip_path = sim.dev_path();
+
+ ::gpiod::chip chip(chip_path);
+
+ SECTION("watch_line_info() returns line info")
+ {
+ auto info = chip.watch_line_info(7);
+ REQUIRE(info.offset() == 7);
+ }
+
+ SECTION("watch_line_info() fails for offset out of range")
+ {
+ REQUIRE_THROWS_AS(chip.watch_line_info(8), ::std::invalid_argument);
+ }
+
+ SECTION("waiting for event timeout")
+ {
+ chip.watch_line_info(3);
+ REQUIRE_FALSE(chip.wait_info_event(::std::chrono::milliseconds(100)));
+ }
+
+ SECTION("request-reconfigure-release events")
+ {
+ auto info = chip.watch_line_info(7);
+ ::std::uint64_t ts_req, ts_rec, ts_rel;
+
+ REQUIRE(info.direction() == direction::INPUT);
+
+ ::std::thread thread(request_reconfigure_release_line, ::std::ref(chip_path));
+
+ REQUIRE(chip.wait_info_event(::std::chrono::seconds(1)));
+ auto event = chip.read_info_event();
+ REQUIRE(event.type() == event_type::LINE_REQUESTED);
+ REQUIRE(event.get_line_info().direction() == direction::INPUT);
+ ts_req = event.timestamp_ns();
+
+ REQUIRE(chip.wait_info_event(::std::chrono::seconds(1)));
+ event = chip.read_info_event();
+ REQUIRE(event.type() == event_type::LINE_CONFIG_CHANGED);
+ REQUIRE(event.get_line_info().direction() == direction::OUTPUT);
+ ts_rec = event.timestamp_ns();
+
+ REQUIRE(chip.wait_info_event(::std::chrono::seconds(1)));
+ event = chip.read_info_event();
+ REQUIRE(event.type() == event_type::LINE_RELEASED);
+ ts_rel = event.timestamp_ns();
+
+ /* No more events. */
+ REQUIRE_FALSE(chip.wait_info_event(::std::chrono::milliseconds(100)));
+ thread.join();
+
+ /* Check timestamps are really monotonic. */
+ REQUIRE(ts_rel > ts_rec);
+ REQUIRE(ts_rec > ts_req);
+ }
+}
+
+TEST_CASE("line info can be unwatched", "[info-event]")
+{
+ auto sim = make_sim()
+ .set_num_lines(8)
+ .build();
+
+ ::gpiod::chip chip(sim.dev_path());
+
+ auto info = chip.watch_line_info(5);
+
+ auto request = chip
+ .prepare_request()
+ .add_line_settings(5, ::gpiod::line_settings())
+ .do_request();
+
+ REQUIRE(chip.wait_info_event(::std::chrono::seconds(1)));
+ auto event = chip.read_info_event();
+ REQUIRE(event.type() == event_type::LINE_REQUESTED);
+
+ chip.unwatch_line_info(5);
+
+ request.release();
+
+ REQUIRE_FALSE(chip.wait_info_event(::std::chrono::milliseconds(100)));
+}
+
+TEST_CASE("info_event can be copied and moved", "[info-event]")
+{
+ auto sim = make_sim().build();
+ ::gpiod::chip chip(sim.dev_path());
+ ::std::stringstream buf, expected;
+
+ chip.watch_line_info(0);
+
+ auto request = chip
+ .prepare_request()
+ .add_line_settings(0, ::gpiod::line_settings())
+ .do_request();
+
+ REQUIRE(chip.wait_info_event(::std::chrono::seconds(1)));
+ auto event = chip.read_info_event();
+
+ request.release();
+
+ REQUIRE(chip.wait_info_event(::std::chrono::seconds(1)));
+ auto copy = chip.read_info_event();
+
+ SECTION("copy constructor works")
+ {
+ auto copy(event);
+
+ REQUIRE(copy.type() == event_type::LINE_REQUESTED);
+ REQUIRE(copy.get_line_info().offset() == 0);
+
+ REQUIRE(event.type() == event_type::LINE_REQUESTED);
+ REQUIRE(event.get_line_info().offset() == 0);
+ }
+
+ SECTION("assignment operator works")
+ {
+ copy = event;
+
+ REQUIRE(copy.type() == event_type::LINE_REQUESTED);
+ REQUIRE(copy.get_line_info().offset() == 0);
+
+ REQUIRE(event.type() == event_type::LINE_REQUESTED);
+ REQUIRE(event.get_line_info().offset() == 0);
+ }
+
+ SECTION("move constructor works")
+ {
+ auto copy(::std::move(event));
+
+ REQUIRE(copy.type() == event_type::LINE_REQUESTED);
+ REQUIRE(copy.get_line_info().offset() == 0);
+ }
+
+ SECTION("move assignment operator works")
+ {
+ copy = ::std::move(event);
+
+ REQUIRE(copy.type() == event_type::LINE_REQUESTED);
+ REQUIRE(copy.get_line_info().offset() == 0);
+ }
+}
+
+TEST_CASE("info_event stream insertion operator works", "[info-event][line-info]")
+{
+ /*
+ * This tests the stream insertion operator for both the info_event
+ * and line_info classes.
+ */
+
+ auto sim = make_sim().build();
+ ::gpiod::chip chip(sim.dev_path());
+ ::std::stringstream buf, expected;
+
+ chip.watch_line_info(0);
+
+ auto request = chip
+ .prepare_request()
+ .add_line_settings(0, ::gpiod::line_settings())
+ .do_request();
+
+ auto event = chip.read_info_event();
+
+ buf << event;
+
+ expected << "gpiod::info_event\\(event_type='LINE_REQUESTED', timestamp=[1-9][0-9]+, " <<
+ "line_info=gpiod::line_info\\(offset=0, name=unnamed, used=true, consumer='', " <<
+ "direction=INPUT, active_low=false, bias=UNKNOWN, drive=PUSH_PULL, " <<
+ "edge_detection=NONE, event_clock=MONOTONIC, debounced=false\\)\\)";
+
+ REQUIRE_THAT(buf.str(), regex_matcher(expected.str()));
+}
+
+} /* namespace */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <catch2/catch.hpp>
+#include <gpiod.hpp>
+
+#include "gpiosim.hpp"
+#include "helpers.hpp"
+
+using ::gpiosim::make_sim;
+using namespace ::std::chrono_literals;
+using direction = ::gpiod::line::direction;
+using drive = ::gpiod::line::drive;
+using edge = ::gpiod::line::edge;
+using simval = ::gpiosim::chip::value;
+using value = ::gpiod::line::value;
+using values = ::gpiod::line::values;
+
+namespace {
+
+TEST_CASE("line_config constructor works", "[line-config]")
+{
+ SECTION("no arguments - default values")
+ {
+ ::gpiod::line_config cfg;
+
+ REQUIRE(cfg.get_line_settings().size() == 0);
+ }
+}
+
+TEST_CASE("adding line_settings to line_config works", "[line-config][line-settings]")
+{
+ ::gpiod::line_config cfg;
+
+ cfg.add_line_settings(4,
+ ::gpiod::line_settings()
+ .set_direction(direction::INPUT)
+ .set_edge_detection(edge::RISING));
+
+ cfg.add_line_settings({7, 2},
+ ::gpiod::line_settings()
+ .set_direction(direction::OUTPUT)
+ .set_drive(drive::OPEN_DRAIN));
+
+ auto settings = cfg.get_line_settings();
+
+ REQUIRE(settings.size() == 3);
+ REQUIRE(settings.at(2).direction() == direction::OUTPUT);
+ REQUIRE(settings.at(2).drive() == drive::OPEN_DRAIN);
+ REQUIRE(settings.at(4).direction() == direction::INPUT);
+ REQUIRE(settings.at(4).edge_detection() == edge::RISING);
+ REQUIRE(settings.at(7).direction() == direction::OUTPUT);
+ REQUIRE(settings.at(7).drive() == drive::OPEN_DRAIN);
+}
+
+TEST_CASE("line_config can be reset", "[line-config]")
+{
+ ::gpiod::line_config cfg;
+
+ cfg.add_line_settings({3, 4, 7},
+ ::gpiod::line_settings()
+ .set_direction(direction::INPUT)
+ .set_edge_detection(edge::BOTH));
+
+ auto settings = cfg.get_line_settings();
+
+ REQUIRE(settings.size() == 3);
+ REQUIRE(settings.at(3).direction() == direction::INPUT);
+ REQUIRE(settings.at(3).edge_detection() == edge::BOTH);
+ REQUIRE(settings.at(4).direction() == direction::INPUT);
+ REQUIRE(settings.at(4).edge_detection() == edge::BOTH);
+ REQUIRE(settings.at(7).direction() == direction::INPUT);
+ REQUIRE(settings.at(7).edge_detection() == edge::BOTH);
+
+ cfg.reset();
+
+ REQUIRE(cfg.get_line_settings().size() == 0);
+}
+
+TEST_CASE("output values can be set globally", "[line-config]")
+{
+ const values vals = { value::ACTIVE, value::INACTIVE, value::ACTIVE, value::INACTIVE };
+
+ auto sim = make_sim()
+ .set_num_lines(4)
+ .build();
+
+ ::gpiod::line_config cfg;
+
+ SECTION("request with globally set output values")
+ {
+ cfg
+ .add_line_settings(
+ {0, 1, 2, 3},
+ ::gpiod::line_settings().set_direction(direction::OUTPUT)
+ )
+ .set_output_values(vals);
+
+ auto request = ::gpiod::chip(sim.dev_path())
+ .prepare_request()
+ .set_line_config(cfg)
+ .do_request();
+
+ REQUIRE(sim.get_value(0) == simval::ACTIVE);
+ REQUIRE(sim.get_value(1) == simval::INACTIVE);
+ REQUIRE(sim.get_value(2) == simval::ACTIVE);
+ REQUIRE(sim.get_value(3) == simval::INACTIVE);
+ }
+
+ SECTION("read back global output values")
+ {
+ cfg
+ .add_line_settings(
+ {0, 1, 2, 3},
+ ::gpiod::line_settings()
+ .set_direction(direction::OUTPUT)
+ .set_output_value(value::ACTIVE)
+ )
+ .set_output_values(vals);
+
+ auto settings = cfg.get_line_settings()[1];
+ REQUIRE(settings.output_value() == value::INACTIVE);
+ }
+}
+
+TEST_CASE("line_config stream insertion operator works", "[line-config]")
+{
+ ::gpiod::line_config cfg;
+
+ SECTION("empty config")
+ {
+ REQUIRE_THAT(cfg, stringify_matcher<::gpiod::line_config>(
+ "gpiod::line_config(num_settings=0)"));
+ }
+
+ SECTION("config with settings")
+ {
+ cfg.add_line_settings({0, 2},
+ ::gpiod::line_settings()
+ .set_direction(direction::OUTPUT)
+ .set_drive(drive::OPEN_SOURCE)
+ );
+
+ REQUIRE_THAT(cfg, stringify_matcher<::gpiod::line_config>(
+ "gpiod::line_config(num_settings=2, "
+ "settings=[0: gpiod::line_settings(direction=OUTPUT, edge_detection=NONE, "
+ "bias=AS_IS, drive=OPEN_SOURCE, active-high, debounce_period=0, "
+ "event_clock=MONOTONIC, output_value=INACTIVE), "
+ "2: gpiod::line_settings(direction=OUTPUT, edge_detection=NONE, bias=AS_IS, "
+ "drive=OPEN_SOURCE, active-high, debounce_period=0, event_clock=MONOTONIC, "
+ "output_value=INACTIVE)])"));
+ }
+}
+
+} /* namespace */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <catch2/catch.hpp>
+#include <gpiod.hpp>
+#include <string>
+
+#include "helpers.hpp"
+#include "gpiosim.hpp"
+
+using ::gpiosim::make_sim;
+using hog_dir = ::gpiosim::chip_builder::direction;
+using direction = ::gpiod::line::direction;
+using edge = ::gpiod::line::edge;
+using bias = ::gpiod::line::bias;
+using drive = ::gpiod::line::drive;
+using event_clock = ::gpiod::line::clock;
+
+using namespace ::std::chrono_literals;
+
+namespace {
+
+TEST_CASE("get_line_info() works", "[chip][line-info]")
+{
+ auto sim = make_sim()
+ .set_num_lines(8)
+ .set_line_name(0, "foobar")
+ .set_hog(0, "hog", hog_dir::OUTPUT_HIGH)
+ .build();
+
+ ::gpiod::chip chip(sim.dev_path());
+
+ SECTION("line_info can be retrieved from chip")
+ {
+ auto info = chip.get_line_info(0);
+
+ REQUIRE(info.offset() == 0);
+ REQUIRE_THAT(info.name(), Catch::Equals("foobar"));
+ REQUIRE(info.used());
+ REQUIRE_THAT(info.consumer(), Catch::Equals("hog"));
+ REQUIRE(info.direction() == ::gpiod::line::direction::OUTPUT);
+ REQUIRE_FALSE(info.active_low());
+ REQUIRE(info.bias() == ::gpiod::line::bias::UNKNOWN);
+ REQUIRE(info.drive() == ::gpiod::line::drive::PUSH_PULL);
+ REQUIRE(info.edge_detection() == ::gpiod::line::edge::NONE);
+ REQUIRE(info.event_clock() == ::gpiod::line::clock::MONOTONIC);
+ REQUIRE_FALSE(info.debounced());
+ REQUIRE(info.debounce_period() == 0us);
+ }
+
+ SECTION("offset out of range")
+ {
+ REQUIRE_THROWS_AS(chip.get_line_info(8), ::std::invalid_argument);
+ }
+}
+
+TEST_CASE("line properties can be retrieved", "[line-info]")
+{
+ auto sim = make_sim()
+ .set_num_lines(8)
+ .set_line_name(1, "foo")
+ .set_line_name(2, "bar")
+ .set_line_name(4, "baz")
+ .set_line_name(5, "xyz")
+ .set_hog(3, "hog3", hog_dir::OUTPUT_HIGH)
+ .set_hog(4, "hog4", hog_dir::OUTPUT_LOW)
+ .build();
+
+ ::gpiod::chip chip(sim.dev_path());
+
+ SECTION("basic properties")
+ {
+ auto info4 = chip.get_line_info(4);
+ auto info6 = chip.get_line_info(6);
+
+ REQUIRE(info4.offset() == 4);
+ REQUIRE_THAT(info4.name(), Catch::Equals("baz"));
+ REQUIRE(info4.used());
+ REQUIRE_THAT(info4.consumer(), Catch::Equals("hog4"));
+ REQUIRE(info4.direction() == direction::OUTPUT);
+ REQUIRE(info4.edge_detection() == edge::NONE);
+ REQUIRE_FALSE(info4.active_low());
+ REQUIRE(info4.bias() == bias::UNKNOWN);
+ REQUIRE(info4.drive() == drive::PUSH_PULL);
+ REQUIRE(info4.event_clock() == event_clock::MONOTONIC);
+ REQUIRE_FALSE(info4.debounced());
+ REQUIRE(info4.debounce_period() == 0us);
+ }
+}
+
+TEST_CASE("line_info can be copied and moved")
+{
+ auto sim = make_sim()
+ .set_num_lines(4)
+ .set_line_name(2, "foobar")
+ .build();
+
+ ::gpiod::chip chip(sim.dev_path());
+ auto info = chip.get_line_info(2);
+
+ SECTION("copy constructor works")
+ {
+ auto copy(info);
+ REQUIRE(copy.offset() == 2);
+ REQUIRE_THAT(copy.name(), Catch::Equals("foobar"));
+ /* info can still be used */
+ REQUIRE(info.offset() == 2);
+ REQUIRE_THAT(info.name(), Catch::Equals("foobar"));
+ }
+
+ SECTION("assignment operator works")
+ {
+ auto copy = chip.get_line_info(0);
+ copy = info;
+ REQUIRE(copy.offset() == 2);
+ REQUIRE_THAT(copy.name(), Catch::Equals("foobar"));
+ /* info can still be used */
+ REQUIRE(info.offset() == 2);
+ REQUIRE_THAT(info.name(), Catch::Equals("foobar"));
+ }
+
+ SECTION("move constructor works")
+ {
+ auto copy(::std::move(info));
+ REQUIRE(copy.offset() == 2);
+ REQUIRE_THAT(copy.name(), Catch::Equals("foobar"));
+ }
+
+ SECTION("move assignment operator works")
+ {
+ auto copy = chip.get_line_info(0);
+ copy = ::std::move(info);
+ REQUIRE(copy.offset() == 2);
+ REQUIRE_THAT(copy.name(), Catch::Equals("foobar"));
+ }
+}
+
+TEST_CASE("line_info stream insertion operator works")
+{
+ auto sim = make_sim()
+ .set_line_name(0, "foo")
+ .set_hog(0, "hogger", hog_dir::OUTPUT_HIGH)
+ .build();
+
+ ::gpiod::chip chip(sim.dev_path());
+
+ auto info = chip.get_line_info(0);
+
+ REQUIRE_THAT(info, stringify_matcher<::gpiod::line_info>(
+ "gpiod::line_info(offset=0, name='foo', used=true, consumer='foo', direction=OUTPUT, "
+ "active_low=false, bias=UNKNOWN, drive=PUSH_PULL, edge_detection=NONE, event_clock=MONOTONIC, debounced=false)"));
+}
+
+} /* namespace */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <catch2/catch.hpp>
+#include <gpiod.hpp>
+#include <sstream>
+#include <stdexcept>
+#include <vector>
+
+#include "gpiosim.hpp"
+#include "helpers.hpp"
+
+using ::gpiosim::make_sim;
+using offsets = ::gpiod::line::offsets;
+using values = ::gpiod::line::values;
+using direction = ::gpiod::line::direction;
+using value = ::gpiod::line::value;
+using simval = ::gpiosim::chip::value;
+using pull = ::gpiosim::chip::pull;
+
+namespace {
+
+class value_matcher : public Catch::MatcherBase<value>
+{
+public:
+ value_matcher(pull pull, bool active_low = false)
+ : _m_pull(pull),
+ _m_active_low(active_low)
+ {
+
+ }
+
+ ::std::string describe() const override
+ {
+ ::std::string repr(this->_m_pull == pull::PULL_UP ? "PULL_UP" : "PULL_DOWN");
+ ::std::string active_low = this->_m_active_low ? "(active-low) " : "";
+
+ return active_low + "corresponds with " + repr;
+ }
+
+ bool match(const value& val) const override
+ {
+ if (this->_m_active_low) {
+ if ((val == value::ACTIVE && this->_m_pull == pull::PULL_DOWN) ||
+ (val == value::INACTIVE && this->_m_pull == pull::PULL_UP))
+ return true;
+ } else {
+ if ((val == value::ACTIVE && this->_m_pull == pull::PULL_UP) ||
+ (val == value::INACTIVE && this->_m_pull == pull::PULL_DOWN))
+ return true;
+ }
+
+ return false;
+ }
+
+private:
+ pull _m_pull;
+ bool _m_active_low;
+};
+
+TEST_CASE("requesting lines behaves correctly with invalid arguments", "[line-request][chip]")
+{
+ auto sim = make_sim()
+ .set_num_lines(8)
+ .build();
+
+ ::gpiod::chip chip(sim.dev_path());
+
+ SECTION("no offsets")
+ {
+ REQUIRE_THROWS_AS(chip.prepare_request().do_request(), ::std::invalid_argument);
+ }
+
+ SECTION("duplicate offsets")
+ {
+ auto request = chip
+ .prepare_request()
+ .add_line_settings({ 2, 0, 0, 4 }, ::gpiod::line_settings())
+ .do_request();
+
+ auto offsets = request.offsets();
+
+ REQUIRE(offsets.size() == 3);
+ REQUIRE(offsets[0] == 2);
+ REQUIRE(offsets[1] == 0);
+ REQUIRE(offsets[2] == 4);
+ }
+
+ SECTION("offset out of bounds")
+ {
+ REQUIRE_THROWS_AS(chip
+ .prepare_request()
+ .add_line_settings({ 2, 0, 8, 4 }, ::gpiod::line_settings())
+ .do_request(),
+ ::std::invalid_argument
+ );
+ }
+}
+
+TEST_CASE("consumer string is set correctly", "[line-request]")
+{
+ auto sim = make_sim()
+ .set_num_lines(4)
+ .build();
+
+ ::gpiod::chip chip(sim.dev_path());
+ offsets offs({ 3, 0, 2 });
+
+ SECTION("set custom consumer")
+ {
+ auto request = chip
+ .prepare_request()
+ .add_line_settings(offs, ::gpiod::line_settings())
+ .set_consumer("foobar")
+ .do_request();
+
+ auto info = chip.get_line_info(2);
+
+ REQUIRE(info.used());
+ REQUIRE_THAT(info.consumer(), Catch::Equals("foobar"));
+ }
+
+ SECTION("empty consumer")
+ {
+ auto request = chip
+ .prepare_request()
+ .add_line_settings(2, ::gpiod::line_settings())
+ .do_request();
+
+ auto info = chip.get_line_info(2);
+
+ REQUIRE(info.used());
+ REQUIRE_THAT(info.consumer(), Catch::Equals("?"));
+ }
+}
+
+TEST_CASE("values can be read", "[line-request]")
+{
+ auto sim = make_sim()
+ .set_num_lines(8)
+ .build();
+
+ const offsets offs({ 7, 1, 0, 6, 2 });
+
+ const ::std::vector<pull> pulls({
+ pull::PULL_UP,
+ pull::PULL_UP,
+ pull::PULL_DOWN,
+ pull::PULL_UP,
+ pull::PULL_DOWN
+ });
+
+ for (unsigned int i = 0; i < offs.size(); i++)
+ sim.set_pull(offs[i], pulls[i]);
+
+ auto request = ::gpiod::chip(sim.dev_path())
+ .prepare_request()
+ .add_line_settings(
+ offs,
+ ::gpiod::line_settings()
+ .set_direction(direction::INPUT)
+ )
+ .do_request();
+
+ SECTION("get all values (returning variant)")
+ {
+ auto vals = request.get_values();
+
+ REQUIRE_THAT(vals[0], value_matcher(pull::PULL_UP));
+ REQUIRE_THAT(vals[1], value_matcher(pull::PULL_UP));
+ REQUIRE_THAT(vals[2], value_matcher(pull::PULL_DOWN));
+ REQUIRE_THAT(vals[3], value_matcher(pull::PULL_UP));
+ REQUIRE_THAT(vals[4], value_matcher(pull::PULL_DOWN));
+ }
+
+ SECTION("get all values (passed buffer variant)")
+ {
+ values vals(5);
+
+ request.get_values(vals);
+
+ REQUIRE_THAT(vals[0], value_matcher(pull::PULL_UP));
+ REQUIRE_THAT(vals[1], value_matcher(pull::PULL_UP));
+ REQUIRE_THAT(vals[2], value_matcher(pull::PULL_DOWN));
+ REQUIRE_THAT(vals[3], value_matcher(pull::PULL_UP));
+ REQUIRE_THAT(vals[4], value_matcher(pull::PULL_DOWN));
+ }
+
+ SECTION("get_values(buffer) throws for invalid buffer size")
+ {
+ values vals(4);
+ REQUIRE_THROWS_AS(request.get_values(vals), ::std::invalid_argument);
+ vals.resize(6);
+ REQUIRE_THROWS_AS(request.get_values(vals), ::std::invalid_argument);
+ }
+
+ SECTION("get a single value")
+ {
+ auto val = request.get_value(7);
+
+ REQUIRE_THAT(val, value_matcher(pull::PULL_UP));
+ }
+
+ SECTION("get a single value (active-low)")
+ {
+ request.reconfigure_lines(
+ ::gpiod::line_config()
+ .add_line_settings(
+ offs,
+ ::gpiod::line_settings()
+ .set_direction(direction::INPUT)
+ .set_active_low(true))
+ );
+
+ auto val = request.get_value(7);
+
+ REQUIRE_THAT(val, value_matcher(pull::PULL_UP, true));
+ }
+
+ SECTION("get a subset of values (returning variant)")
+ {
+ auto vals = request.get_values(offsets({ 2, 0, 6 }));
+
+ REQUIRE_THAT(vals[0], value_matcher(pull::PULL_DOWN));
+ REQUIRE_THAT(vals[1], value_matcher(pull::PULL_DOWN));
+ REQUIRE_THAT(vals[2], value_matcher(pull::PULL_UP));
+ }
+
+ SECTION("get a subset of values (passed buffer variant)")
+ {
+ values vals(3);
+
+ request.get_values(offsets({ 2, 0, 6 }), vals);
+
+ REQUIRE_THAT(vals[0], value_matcher(pull::PULL_DOWN));
+ REQUIRE_THAT(vals[1], value_matcher(pull::PULL_DOWN));
+ REQUIRE_THAT(vals[2], value_matcher(pull::PULL_UP));
+ }
+}
+
+TEST_CASE("output values can be set at request time", "[line-request]")
+{
+ auto sim = make_sim()
+ .set_num_lines(8)
+ .build();
+
+ ::gpiod::chip chip(sim.dev_path());
+ const offsets offs({ 0, 1, 3, 4 });
+
+ ::gpiod::line_settings settings;
+ settings.set_direction(direction::OUTPUT);
+ settings.set_output_value(value::ACTIVE);
+
+ ::gpiod::line_config line_cfg;
+ line_cfg.add_line_settings(offs, settings);
+
+ SECTION("default output value")
+ {
+ auto request = chip
+ .prepare_request()
+ .set_line_config(line_cfg)
+ .do_request();
+
+ for (const auto& off: offs)
+ REQUIRE(sim.get_value(off) == simval::ACTIVE);
+
+ REQUIRE(sim.get_value(2) == simval::INACTIVE);
+ }
+
+ SECTION("overridden output value")
+ {
+ settings.set_output_value(value::INACTIVE);
+ line_cfg.add_line_settings(1, settings);
+
+ auto request = chip
+ .prepare_request()
+ .set_line_config(line_cfg)
+ .do_request();
+
+ REQUIRE(sim.get_value(0) == simval::ACTIVE);
+ REQUIRE(sim.get_value(1) == simval::INACTIVE);
+ REQUIRE(sim.get_value(2) == simval::INACTIVE);
+ REQUIRE(sim.get_value(3) == simval::ACTIVE);
+ REQUIRE(sim.get_value(4) == simval::ACTIVE);
+ }
+}
+
+TEST_CASE("values can be set after requesting lines", "[line-request]")
+{
+ auto sim = make_sim()
+ .set_num_lines(8)
+ .build();
+
+ const offsets offs({ 0, 1, 3, 4 });
+
+ auto request = ::gpiod::chip(sim.dev_path())
+ .prepare_request()
+ .add_line_settings(
+ offs,
+ ::gpiod::line_settings()
+ .set_direction(direction::OUTPUT)
+ )
+ .do_request();
+
+ SECTION("set single value")
+ {
+ request.set_value(1, value::ACTIVE);
+
+ REQUIRE(sim.get_value(0) == simval::INACTIVE);
+ REQUIRE(sim.get_value(1) == simval::ACTIVE);
+ REQUIRE(sim.get_value(3) == simval::INACTIVE);
+ REQUIRE(sim.get_value(4) == simval::INACTIVE);
+ }
+
+ SECTION("set all values")
+ {
+ request.set_values({
+ value::ACTIVE,
+ value::INACTIVE,
+ value::ACTIVE,
+ value::INACTIVE
+ });
+
+ REQUIRE(sim.get_value(0) == simval::ACTIVE);
+ REQUIRE(sim.get_value(1) == simval::INACTIVE);
+ REQUIRE(sim.get_value(3) == simval::ACTIVE);
+ REQUIRE(sim.get_value(4) == simval::INACTIVE);
+ }
+
+ SECTION("set a subset of values")
+ {
+ request.set_values({ 4, 3 }, { value::ACTIVE, value::INACTIVE });
+
+ REQUIRE(sim.get_value(0) == simval::INACTIVE);
+ REQUIRE(sim.get_value(1) == simval::INACTIVE);
+ REQUIRE(sim.get_value(3) == simval::INACTIVE);
+ REQUIRE(sim.get_value(4) == simval::ACTIVE);
+ }
+
+ SECTION("set a subset of values with mappings")
+ {
+ request.set_values({
+ { 0, value::ACTIVE },
+ { 4, value::INACTIVE },
+ { 1, value::ACTIVE}
+ });
+
+ REQUIRE(sim.get_value(0) == simval::ACTIVE);
+ REQUIRE(sim.get_value(1) == simval::ACTIVE);
+ REQUIRE(sim.get_value(3) == simval::INACTIVE);
+ REQUIRE(sim.get_value(4) == simval::INACTIVE);
+ }
+}
+
+TEST_CASE("line_request can be moved", "[line-request]")
+{
+ auto sim = make_sim()
+ .set_num_lines(8)
+ .build();
+
+ ::gpiod::chip chip(sim.dev_path());
+ const offsets offs({ 3, 1, 0, 2 });
+
+ auto request = chip
+ .prepare_request()
+ .add_line_settings(
+ offs,
+ ::gpiod::line_settings()
+ )
+ .do_request();
+
+ auto fd = request.fd();
+
+ auto another = chip
+ .prepare_request()
+ .add_line_settings(6, ::gpiod::line_settings())
+ .do_request();
+
+ SECTION("move constructor works")
+ {
+ auto moved(::std::move(request));
+
+ REQUIRE(moved.fd() == fd);
+ REQUIRE_THAT(moved.offsets(), Catch::Equals(offs));
+ }
+
+ SECTION("move assignment operator works")
+ {
+ another = ::std::move(request);
+
+ REQUIRE(another.fd() == fd);
+ REQUIRE_THAT(another.offsets(), Catch::Equals(offs));
+ }
+}
+
+TEST_CASE("released request can no longer be used", "[line-request]")
+{
+ auto sim = make_sim().build();
+
+ auto request = ::gpiod::chip(sim.dev_path())
+ .prepare_request()
+ .add_line_settings(0, ::gpiod::line_settings())
+ .do_request();
+
+ request.release();
+
+ REQUIRE_THROWS_AS(request.offsets(), ::gpiod::request_released);
+}
+
+TEST_CASE("line_request survives parent chip", "[line-request][chip]")
+{
+ auto sim = make_sim().build();
+
+ sim.set_pull(0, pull::PULL_UP);
+
+ SECTION("chip is released")
+ {
+ ::gpiod::chip chip(sim.dev_path());
+
+ auto request = chip
+ .prepare_request()
+ .add_line_settings(
+ 0,
+ ::gpiod::line_settings()
+ .set_direction(direction::INPUT)
+ )
+ .do_request();
+
+ REQUIRE_THAT(request.get_value(0), value_matcher(pull::PULL_UP));
+
+ chip.close();
+
+ REQUIRE_THAT(request.get_value(0), value_matcher(pull::PULL_UP));
+ }
+
+ SECTION("chip goes out of scope")
+ {
+ /* Need to get the request object somehow. */
+ ::gpiod::chip dummy(sim.dev_path());
+ ::gpiod::line_config cfg;
+ cfg.add_line_settings(0, ::gpiod::line_settings().set_direction(direction::INPUT));
+
+ auto request = dummy
+ .prepare_request()
+ .set_line_config(cfg)
+ .do_request();
+
+ request.release();
+ dummy.close();
+
+ {
+ ::gpiod::chip chip(sim.dev_path());
+
+ request = chip
+ .prepare_request()
+ .set_line_config(cfg)
+ .do_request();
+
+ REQUIRE_THAT(request.get_value(0), value_matcher(pull::PULL_UP));
+ }
+
+ REQUIRE_THAT(request.get_value(0), value_matcher(pull::PULL_UP));
+ }
+}
+
+TEST_CASE("line_request stream insertion operator works", "[line-request]")
+{
+ auto sim = make_sim()
+ .set_num_lines(4)
+ .build();
+
+ auto chip = ::gpiod::chip(sim.dev_path());
+ auto request = chip
+ .prepare_request()
+ .add_line_settings({ 3, 1, 0, 2}, ::gpiod::line_settings())
+ .do_request();
+
+ ::std::stringstream buf, expected;
+
+ expected << "gpiod::line_request(chip=\"" << sim.name() <<
+ "\", num_lines=4, line_offsets=gpiod::offsets(3, 1, 0, 2), fd=" <<
+ request.fd() << ")";
+
+ SECTION("active request")
+ {
+ buf << request;
+
+ REQUIRE_THAT(buf.str(), Catch::Equals(expected.str()));
+ }
+
+ SECTION("request released")
+ {
+ request.release();
+
+ buf << request;
+
+ REQUIRE_THAT(buf.str(), Catch::Equals("gpiod::line_request(released)"));
+ }
+}
+
+} /* namespace */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <catch2/catch.hpp>
+#include <gpiod.hpp>
+
+#include "helpers.hpp"
+
+using value = ::gpiod::line::value;
+using direction = ::gpiod::line::direction;
+using edge = ::gpiod::line::edge;
+using bias = ::gpiod::line::bias;
+using drive = ::gpiod::line::drive;
+using clock_type = ::gpiod::line::clock;
+using value = ::gpiod::line::value;
+
+using namespace ::std::chrono_literals;
+
+namespace {
+
+TEST_CASE("line_settings constructor works", "[line-settings]")
+{
+ ::gpiod::line_settings settings;
+
+ REQUIRE(settings.direction() == direction::AS_IS);
+ REQUIRE(settings.edge_detection() == edge::NONE);
+ REQUIRE(settings.bias() == bias::AS_IS);
+ REQUIRE(settings.drive() == drive::PUSH_PULL);
+ REQUIRE_FALSE(settings.active_low());
+ REQUIRE(settings.debounce_period() == 0us);
+ REQUIRE(settings.event_clock() == clock_type::MONOTONIC);
+ REQUIRE(settings.output_value() == value::INACTIVE);
+}
+
+TEST_CASE("line_settings mutators work", "[line-settings]")
+{
+ ::gpiod::line_settings settings;
+
+ SECTION("direction")
+ {
+ settings.set_direction(direction::INPUT);
+ REQUIRE(settings.direction() == direction::INPUT);
+ settings.set_direction(direction::AS_IS);
+ REQUIRE(settings.direction() == direction::AS_IS);
+ settings.set_direction(direction::OUTPUT);
+ REQUIRE(settings.direction() == direction::OUTPUT);
+ REQUIRE_THROWS_AS(settings.set_direction(static_cast<direction>(999)),
+ ::std::invalid_argument);
+ }
+
+ SECTION("edge detection")
+ {
+ settings.set_edge_detection(edge::BOTH);
+ REQUIRE(settings.edge_detection() == edge::BOTH);
+ settings.set_edge_detection(edge::NONE);
+ REQUIRE(settings.edge_detection() == edge::NONE);
+ settings.set_edge_detection(edge::FALLING);
+ REQUIRE(settings.edge_detection() == edge::FALLING);
+ settings.set_edge_detection(edge::RISING);
+ REQUIRE(settings.edge_detection() == edge::RISING);
+ REQUIRE_THROWS_AS(settings.set_edge_detection(static_cast<edge>(999)),
+ ::std::invalid_argument);
+ }
+
+ SECTION("bias")
+ {
+ settings.set_bias(bias::DISABLED);
+ REQUIRE(settings.bias() == bias::DISABLED);
+ settings.set_bias(bias::AS_IS);
+ REQUIRE(settings.bias() == bias::AS_IS);
+ settings.set_bias(bias::PULL_DOWN);
+ REQUIRE(settings.bias() == bias::PULL_DOWN);
+ settings.set_bias(bias::PULL_UP);
+ REQUIRE(settings.bias() == bias::PULL_UP);
+ REQUIRE_THROWS_AS(settings.set_bias(static_cast<bias>(999)), ::std::invalid_argument);
+ REQUIRE_THROWS_AS(settings.set_bias(bias::UNKNOWN), ::std::invalid_argument);
+ }
+
+ SECTION("drive")
+ {
+ settings.set_drive(drive::OPEN_DRAIN);
+ REQUIRE(settings.drive() == drive::OPEN_DRAIN);
+ settings.set_drive(drive::PUSH_PULL);
+ REQUIRE(settings.drive() == drive::PUSH_PULL);
+ settings.set_drive(drive::OPEN_SOURCE);
+ REQUIRE(settings.drive() == drive::OPEN_SOURCE);
+ REQUIRE_THROWS_AS(settings.set_drive(static_cast<drive>(999)), ::std::invalid_argument);
+ }
+
+ SECTION("active-low")
+ {
+ settings.set_active_low(true);
+ REQUIRE(settings.active_low());
+ settings.set_active_low(false);
+ REQUIRE_FALSE(settings.active_low());
+ }
+
+ SECTION("debounce period")
+ {
+ settings.set_debounce_period(2000us);
+ REQUIRE(settings.debounce_period() == 2000us);
+ }
+
+ SECTION("event clock")
+ {
+ settings.set_event_clock(clock_type::REALTIME);
+ REQUIRE(settings.event_clock() == clock_type::REALTIME);
+ settings.set_event_clock(clock_type::MONOTONIC);
+ REQUIRE(settings.event_clock() == clock_type::MONOTONIC);
+ settings.set_event_clock(clock_type::HTE);
+ REQUIRE(settings.event_clock() == clock_type::HTE);
+ REQUIRE_THROWS_AS(settings.set_event_clock(static_cast<clock_type>(999)),
+ ::std::invalid_argument);
+ }
+
+ SECTION("output value")
+ {
+ settings.set_output_value(value::ACTIVE);
+ REQUIRE(settings.output_value() == value::ACTIVE);
+ settings.set_output_value(value::INACTIVE);
+ REQUIRE(settings.output_value() == value::INACTIVE);
+ REQUIRE_THROWS_AS(settings.set_output_value(static_cast<value>(999)),
+ ::std::invalid_argument);
+ }
+}
+
+TEST_CASE("line_settings can be moved and copied", "[line-settings]")
+{
+ ::gpiod::line_settings settings;
+
+ settings
+ .set_direction(direction::INPUT)
+ .set_edge_detection(edge::BOTH);
+
+ SECTION("copy constructor works")
+ {
+ auto copy(settings);
+ settings.set_direction(direction::OUTPUT);
+ settings.set_edge_detection(edge::NONE);
+ REQUIRE(copy.direction() == direction::INPUT);
+ REQUIRE(copy.edge_detection() == edge::BOTH);
+ }
+
+ SECTION("assignment operator works")
+ {
+ ::gpiod::line_settings copy;
+ copy = settings;
+ settings.set_direction(direction::OUTPUT);
+ settings.set_edge_detection(edge::NONE);
+ REQUIRE(copy.direction() == direction::INPUT);
+ REQUIRE(copy.edge_detection() == edge::BOTH);
+ }
+
+ SECTION("move constructor works")
+ {
+ auto copy(::std::move(settings));
+ REQUIRE(copy.direction() == direction::INPUT);
+ REQUIRE(copy.edge_detection() == edge::BOTH);
+ }
+
+ SECTION("move assignment operator works")
+ {
+ ::gpiod::line_settings copy;
+ copy = ::std::move(settings);
+ REQUIRE(copy.direction() == direction::INPUT);
+ REQUIRE(copy.edge_detection() == edge::BOTH);
+ }
+}
+
+TEST_CASE("line_settings stream insertion operator works", "[line-settings]")
+{
+ ::gpiod::line_settings settings;
+
+ REQUIRE_THAT(settings
+ .set_active_low(true)
+ .set_direction(direction::INPUT)
+ .set_edge_detection(edge::BOTH)
+ .set_bias(bias::PULL_DOWN)
+ .set_event_clock(clock_type::REALTIME),
+ stringify_matcher<::gpiod::line_settings>(
+ "gpiod::line_settings(direction=INPUT, edge_detection=BOTH_EDGES, "
+ "bias=PULL_DOWN, drive=PUSH_PULL, active-low, debounce_period=0, "
+ "event_clock=REALTIME, output_value=INACTIVE)"
+ )
+ );
+}
+
+} /* namespace */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <catch2/catch.hpp>
+#include <gpiod.hpp>
+
+#include "helpers.hpp"
+
+using offset = ::gpiod::line::offset;
+using value = ::gpiod::line::value;
+using direction = ::gpiod::line::direction;
+using edge = ::gpiod::line::edge;
+using bias = ::gpiod::line::bias;
+using drive = ::gpiod::line::drive;
+using clock_type = ::gpiod::line::clock;
+using offsets = ::gpiod::line::offsets;
+using values = ::gpiod::line::values;
+using value_mapping = ::gpiod::line::value_mapping;
+using value_mappings = ::gpiod::line::value_mappings;
+
+namespace {
+
+TEST_CASE("stream insertion operators for types in gpiod::line work", "[line]")
+{
+ SECTION("offset")
+ {
+ offset off = 4;
+
+ REQUIRE_THAT(off, stringify_matcher<offset>("4"));
+ }
+
+ SECTION("value")
+ {
+ auto active = value::ACTIVE;
+ auto inactive = value::INACTIVE;
+
+ REQUIRE_THAT(active, stringify_matcher<value>("ACTIVE"));
+ REQUIRE_THAT(inactive, stringify_matcher<value>("INACTIVE"));
+ }
+
+ SECTION("direction")
+ {
+ auto input = direction::INPUT;
+ auto output = direction::OUTPUT;
+ auto as_is = direction::AS_IS;
+
+ REQUIRE_THAT(input, stringify_matcher<direction>("INPUT"));
+ REQUIRE_THAT(output, stringify_matcher<direction>("OUTPUT"));
+ REQUIRE_THAT(as_is, stringify_matcher<direction>("AS_IS"));
+ }
+
+ SECTION("edge")
+ {
+ auto rising = edge::RISING;
+ auto falling = edge::FALLING;
+ auto both = edge::BOTH;
+ auto none = edge::NONE;
+
+ REQUIRE_THAT(rising, stringify_matcher<edge>("RISING_EDGE"));
+ REQUIRE_THAT(falling, stringify_matcher<edge>("FALLING_EDGE"));
+ REQUIRE_THAT(both, stringify_matcher<edge>("BOTH_EDGES"));
+ REQUIRE_THAT(none, stringify_matcher<edge>("NONE"));
+ }
+
+ SECTION("bias")
+ {
+ auto pull_up = bias::PULL_UP;
+ auto pull_down = bias::PULL_DOWN;
+ auto disabled = bias::DISABLED;
+ auto as_is = bias::AS_IS;
+ auto unknown = bias::UNKNOWN;
+
+ REQUIRE_THAT(pull_up, stringify_matcher<bias>("PULL_UP"));
+ REQUIRE_THAT(pull_down, stringify_matcher<bias>("PULL_DOWN"));
+ REQUIRE_THAT(disabled, stringify_matcher<bias>("DISABLED"));
+ REQUIRE_THAT(as_is, stringify_matcher<bias>("AS_IS"));
+ REQUIRE_THAT(unknown, stringify_matcher<bias>("UNKNOWN"));
+ }
+
+ SECTION("drive")
+ {
+ auto push_pull = drive::PUSH_PULL;
+ auto open_drain = drive::OPEN_DRAIN;
+ auto open_source = drive::OPEN_SOURCE;
+
+ REQUIRE_THAT(push_pull, stringify_matcher<drive>("PUSH_PULL"));
+ REQUIRE_THAT(open_drain, stringify_matcher<drive>("OPEN_DRAIN"));
+ REQUIRE_THAT(open_source, stringify_matcher<drive>("OPEN_SOURCE"));
+ }
+
+ SECTION("clock")
+ {
+ auto monotonic = clock_type::MONOTONIC;
+ auto realtime = clock_type::REALTIME;
+ auto hte = clock_type::HTE;
+
+ REQUIRE_THAT(monotonic, stringify_matcher<clock_type>("MONOTONIC"));
+ REQUIRE_THAT(realtime, stringify_matcher<clock_type>("REALTIME"));
+ REQUIRE_THAT(hte, stringify_matcher<clock_type>("HTE"));
+ }
+
+ SECTION("offsets")
+ {
+ offsets offs = { 2, 5, 3, 9, 8, 7 };
+
+ REQUIRE_THAT(offs, stringify_matcher<offsets>("gpiod::offsets(2, 5, 3, 9, 8, 7)"));
+ }
+
+ SECTION("values")
+ {
+ values vals = {
+ value::ACTIVE,
+ value::INACTIVE,
+ value::ACTIVE,
+ value::ACTIVE,
+ value::INACTIVE
+ };
+
+ REQUIRE_THAT(vals,
+ stringify_matcher<values>("gpiod::values(ACTIVE, INACTIVE, ACTIVE, ACTIVE, INACTIVE)"));
+ }
+
+ SECTION("value_mapping")
+ {
+ value_mapping val = { 4, value::ACTIVE };
+
+ REQUIRE_THAT(val, stringify_matcher<value_mapping>("gpiod::value_mapping(4: ACTIVE)"));
+ }
+
+ SECTION("value_mappings")
+ {
+ value_mappings vals = { { 0, value::ACTIVE }, { 4, value::INACTIVE }, { 8, value::ACTIVE } };
+
+ REQUIRE_THAT(vals, stringify_matcher<value_mappings>(
+ "gpiod::value_mappings(gpiod::value_mapping(0: ACTIVE), gpiod::value_mapping(4: INACTIVE), gpiod::value_mapping(8: ACTIVE))"));
+ }
+}
+
+} /* namespace */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <catch2/catch.hpp>
+#include <filesystem>
+#include <gpiod.hpp>
+#include <string>
+#include <regex>
+#include <unistd.h>
+
+#include "gpiosim.hpp"
+#include "helpers.hpp"
+
+using ::gpiosim::make_sim;
+
+namespace {
+
+class symlink_guard
+{
+public:
+ symlink_guard(const ::std::filesystem::path& target,
+ const ::std::filesystem::path& link)
+ : _m_link(link)
+ {
+ ::std::filesystem::create_symlink(target, this->_m_link);
+ }
+
+ ~symlink_guard()
+ {
+ ::std::filesystem::remove(this->_m_link);
+ }
+
+private:
+ ::std::filesystem::path _m_link;
+};
+
+TEST_CASE("is_gpiochip_device() works", "[misc][chip]")
+{
+ SECTION("is_gpiochip_device() returns false for /dev/null")
+ {
+ REQUIRE_FALSE(::gpiod::is_gpiochip_device("/dev/null"));
+ }
+
+ SECTION("is_gpiochip_device() returns false for nonexistent file")
+ {
+ REQUIRE_FALSE(::gpiod::is_gpiochip_device("/dev/nonexistent"));
+ }
+
+ SECTION("is_gpiochip_device() returns true for a GPIO chip")
+ {
+ auto sim = make_sim().build();
+
+ REQUIRE(::gpiod::is_gpiochip_device(sim.dev_path()));
+ }
+
+ SECTION("is_gpiochip_device() can resolve a symlink")
+ {
+ auto sim = make_sim().build();
+ ::std::string link("/tmp/gpiod-cxx-tmp-link.");
+
+ link += ::std::to_string(::getpid());
+
+ symlink_guard link_guard(sim.dev_path(), link);
+
+ REQUIRE(::gpiod::is_gpiochip_device(link));
+ }
+}
+
+TEST_CASE("api_version() returns a valid API version", "[misc]")
+{
+ SECTION("check api_version() format")
+ {
+ REQUIRE_THAT(::gpiod::api_version(),
+ regex_matcher("^\\d+\\.\\d+(\\.\\d+|\\-devel|\\-rc\\d+)?$"));
+ }
+}
+
+} /* namespace */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <catch2/catch.hpp>
+#include <cstddef>
+#include <gpiod.hpp>
+#include <string>
+#include <sstream>
+
+#include "helpers.hpp"
+
+using offsets = ::gpiod::line::offsets;
+
+namespace {
+
+TEST_CASE("request_config constructor works", "[request-config]")
+{
+ SECTION("no arguments")
+ {
+ ::gpiod::request_config cfg;
+
+ REQUIRE(cfg.consumer().empty());
+ REQUIRE(cfg.event_buffer_size() == 0);
+ }
+}
+
+TEST_CASE("request_config can be moved", "[request-config]")
+{
+ ::gpiod::request_config cfg;
+
+ cfg.set_consumer("foobar").set_event_buffer_size(64);
+
+ SECTION("move constructor works")
+ {
+ auto moved(::std::move(cfg));
+ REQUIRE_THAT(moved.consumer(), Catch::Equals("foobar"));
+ REQUIRE(moved.event_buffer_size() == 64);
+ }
+
+ SECTION("move assignment operator works")
+ {
+ ::gpiod::request_config moved;
+
+ moved = ::std::move(cfg);
+
+ REQUIRE_THAT(moved.consumer(), Catch::Equals("foobar"));
+ REQUIRE(moved.event_buffer_size() == 64);
+ }
+}
+
+TEST_CASE("request_config mutators work", "[request-config]")
+{
+ ::gpiod::request_config cfg;
+
+ SECTION("set consumer")
+ {
+ cfg.set_consumer("foobar");
+ REQUIRE_THAT(cfg.consumer(), Catch::Equals("foobar"));
+ }
+
+ SECTION("set event_buffer_size")
+ {
+ cfg.set_event_buffer_size(128);
+ REQUIRE(cfg.event_buffer_size() == 128);
+ }
+}
+
+TEST_CASE("request_config stream insertion operator works", "[request-config]")
+{
+ ::gpiod::request_config cfg;
+
+ cfg.set_consumer("foobar").set_event_buffer_size(32);
+
+ ::std::stringstream buf;
+
+ buf << cfg;
+
+ ::std::string expected("gpiod::request_config(consumer='foobar', event_buffer_size=32)");
+
+ REQUIRE_THAT(buf.str(), Catch::Equals(expected));
+}
+
+} /* namespace */
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2017-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+build/
+__pycache__/
+dist/
+*.egg-info/
+*.so
--- /dev/null
+# SPDX-License-Identifier: CC0-1.0
+# SPDX-FileCopyrightText: 2023 Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
+
+include setup.py
+include README.md
+include libgpiod-version.txt
+
+recursive-include gpiod *.py
+recursive-include tests *.py
+
+recursive-include gpiod/ext *.c
+recursive-include gpiod/ext *.h
+
+recursive-include tests/gpiosim *.c
+recursive-include tests/procname *.c
+
+recursive-include lib *.c
+recursive-include lib *.h
+recursive-include include *.h
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+EXTRA_DIST = \
+ MANIFEST.in \
+ setup.py
+
+if WITH_TESTS
+
+BUILD_TESTS = 1
+
+endif
+
+all-local:
+ GPIOD_WITH_TESTS=$(BUILD_TESTS) \
+ $(PYTHON) setup.py build_ext --inplace \
+ --include-dirs=$(top_srcdir)/include/:$(top_srcdir)/tests/gpiosim/ \
+ --library-dirs=$(top_builddir)/lib/.libs/:$(top_srcdir)/tests/gpiosim/.libs/
+
+install-exec-local:
+ GPIOD_WITH_TESTS= \
+ $(PYTHON) setup.py install --root=$(if $(DESTDIR),$(DESTDIR),/) --prefix=$(prefix)
+
+SUBDIRS = gpiod
+
+if WITH_TESTS
+
+SUBDIRS += tests
+
+endif
+
+if WITH_EXAMPLES
+
+SUBDIRS += examples
+
+endif
--- /dev/null
+# SPDX-License-Identifier: CC-BY-SA-4.0
+# SPDX-FileCopyrightText: 2023 Phil Howard <phil@gadgetoid.com>
+
+# gpiod
+
+These are the official Python bindings for [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about/).
+
+The gpiod library has been vendored into this package for your convenience and
+this version of gpiod is independent from your system package.
+
+Binary wheels are not provided. The source package requires python3-dev.
+
+## Rationale
+
+The new character device interface guarantees all allocated resources are
+freed after closing the device file descriptor and adds several new features
+that are not present in the obsolete sysfs interface (like event polling,
+setting/reading multiple values at once or open-source and open-drain GPIOs).
+
+Unfortunately interacting with the linux device file can no longer be done
+using only standard command-line tools. This is the reason for creating a
+library encapsulating the cumbersome, ioctl-based kernel-userspace interaction
+in a set of convenient functions and opaque data structures.
+
+## Breaking Changes
+
+As of v2.0.2 we have replaced the unofficial, pure-Python "gpiod". The official
+gpiod is not backwards compatible.
+
+You should ensure you specify at least v2.0.2 for the official API. Versions
+1.5.4 and prior are the deprecated, unofficial, pure-Python bindings.
+
+## Installing
+
+You will need `python3-dev`, on Debian/Ubuntu you can install this with:
+
+```
+sudo apt install python3-dev
+```
+
+And then install gpiod with:
+
+```
+pip install gpiod
+```
+
+You can optionally depend upon your system gpiod by installing with:
+
+```
+LINK_SYSTEM_LIBGPIOD=1 pip install gpiod
+```
+
+If you still need the deprecated pure-Python bindings, install with:
+
+```
+pip install gpiod==1.5.4
+```
+
+## Examples
+
+Check a GPIO chip character device exists:
+
+```python
+import gpiod
+
+gpiod.is_gpiochip_device("/dev/gpiochip0")
+
+```
+
+Get information about a GPIO chip character device:
+
+```python
+import gpiod
+
+with gpiod.Chip("/dev/gpiochip0") as chip:
+ info = chip.get_info()
+ print(f"{info.name} [{info.label}] ({info.num_lines} lines)")
+```
+
+Blink an LED, or toggling a GPIO line:
+
+```python
+import time
+
+from gpiod.line import Direction, Value
+
+LINE = 5
+
+with gpiod.request_lines(
+ "/dev/gpiochip0",
+ consumer="blink-example",
+ config={
+ LINE: gpiod.LineSettings(
+ direction=Direction.OUTPUT, output_value=Value.ACTIVE
+ )
+ },
+) as request:
+ while True:
+ request.set_value(LINE, Value.ACTIVE)
+ time.sleep(1)
+ request.set_value(LINE, Value.INACTIVE)
+ time.sleep(1)
+```
+
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+EXTRA_DIST = \
+ async_watch_line_value.py \
+ find_line_by_name.py \
+ get_chip_info.py \
+ get_line_info.py \
+ get_line_value.py \
+ get_multiple_line_values.py \
+ reconfigure_input_to_output.py \
+ toggle_line_value.py \
+ toggle_multiple_line_values.py \
+ watch_line_info.py \
+ watch_line_value.py \
+ watch_line_rising.py \
+ watch_multiple_line_values.py
--- /dev/null
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+"""Minimal example of asynchronously watching for edges on a single line."""
+
+import gpiod
+import select
+
+from datetime import timedelta
+from gpiod.line import Bias, Edge
+
+
+def edge_type_str(event):
+ if event.event_type is event.Type.RISING_EDGE:
+ return "Rising"
+ if event.event_type is event.Type.FALLING_EDGE:
+ return "Falling"
+ return "Unknown"
+
+
+def async_watch_line_value(chip_path, line_offset):
+ # Assume a button connecting the pin to ground,
+ # so pull it up and provide some debounce.
+ with gpiod.request_lines(
+ chip_path,
+ consumer="async-watch-line-value",
+ config={
+ line_offset: gpiod.LineSettings(
+ edge_detection=Edge.BOTH,
+ bias=Bias.PULL_UP,
+ debounce_period=timedelta(milliseconds=10),
+ )
+ },
+ ) as request:
+ poll = select.poll()
+ poll.register(request.fd, select.POLLIN)
+ while True:
+ # Other fds could be registered with the poll and be handled
+ # separately using the return value (fd, event) from poll()
+ poll.poll()
+ for event in request.read_edge_events():
+ print(
+ "offset: {} type: {:<7} event #{}".format(
+ event.line_offset, edge_type_str(event), event.line_seqno
+ )
+ )
+
+
+if __name__ == "__main__":
+ try:
+ async_watch_line_value("/dev/gpiochip0", 5)
+ except OSError as ex:
+ print(ex, "\nCustomise the example configuration to suit your situation")
--- /dev/null
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+"""Minimal example of finding a line with the given name."""
+
+import gpiod
+import os
+
+
+def generate_gpio_chips():
+ for entry in os.scandir("/dev/"):
+ if gpiod.is_gpiochip_device(entry.path):
+ yield entry.path
+
+
+def find_line_by_name(line_name):
+ # Names are not guaranteed unique, so this finds the first line with
+ # the given name.
+ for path in generate_gpio_chips():
+ with gpiod.Chip(path) as chip:
+ try:
+ offset = chip.line_offset_from_id(line_name)
+ print("{}: {} {}".format(line_name, chip.get_info().name, offset))
+ return
+ except OSError:
+ # An OSError is raised if the name is not found.
+ continue
+
+ print("line '{}' not found".format(line_name))
+
+
+if __name__ == "__main__":
+ try:
+ find_line_by_name("GPIO19")
+ except OSError as ex:
+ print(ex, "\nCustomise the example configuration to suit your situation")
--- /dev/null
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+"""Minimal example of reading the info for a chip."""
+
+import gpiod
+
+
+def get_chip_info(chip_path):
+ with gpiod.Chip(chip_path) as chip:
+ info = chip.get_info()
+ print("{} [{}] ({} lines)".format(info.name, info.label, info.num_lines))
+
+
+if __name__ == "__main__":
+ try:
+ get_chip_info("/dev/gpiochip0")
+ except OSError as ex:
+ print(ex, "\nCustomise the example configuration to suit your situation")
--- /dev/null
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+"""Minimal example of reading the info for a line."""
+
+import gpiod
+
+
+def get_line_info(chip_path, line_offset):
+ with gpiod.Chip(chip_path) as chip:
+ info = chip.get_line_info(line_offset)
+ is_input = info.direction == gpiod.line.Direction.INPUT
+ print(
+ "line {:>3}: {:>12} {:>12} {:>8} {:>10}".format(
+ info.offset,
+ info.name or "unnamed",
+ info.consumer or "unused",
+ "input" if is_input else "output",
+ "active-low" if info.active_low else "active-high",
+ )
+ )
+
+
+if __name__ == "__main__":
+ try:
+ get_line_info("/dev/gpiochip0", 3)
+ except OSError as ex:
+ print(ex, "\nCustomise the example configuration to suit your situation")
--- /dev/null
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+"""Minimal example of reading a single line."""
+
+import gpiod
+
+from gpiod.line import Direction
+
+
+def get_line_value(chip_path, line_offset):
+ with gpiod.request_lines(
+ chip_path,
+ consumer="get-line-value",
+ config={line_offset: gpiod.LineSettings(direction=Direction.INPUT)},
+ ) as request:
+ value = request.get_value(line_offset)
+ print("{}={}".format(line_offset, value))
+
+
+if __name__ == "__main__":
+ try:
+ get_line_value("/dev/gpiochip0", 5)
+ except OSError as ex:
+ print(ex, "\nCustomise the example configuration to suit your situation")
--- /dev/null
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+"""Minimal example of reading multiple lines."""
+
+import gpiod
+
+from gpiod.line import Direction
+
+
+def get_multiple_line_values(chip_path, line_offsets):
+ with gpiod.request_lines(
+ chip_path,
+ consumer="get-multiple-line-values",
+ config={tuple(line_offsets): gpiod.LineSettings(direction=Direction.INPUT)},
+ ) as request:
+ vals = request.get_values()
+
+ for offset, val in zip(line_offsets, vals):
+ print("{}={} ".format(offset, val), end="")
+ print()
+
+
+if __name__ == "__main__":
+ try:
+ get_multiple_line_values("/dev/gpiochip0", [5, 3, 7])
+ except OSError as ex:
+ print(ex, "\nCustomise the example configuration to suit your situation")
--- /dev/null
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+"""Example of a bi-directional line requested as input and then switched to output."""
+
+import gpiod
+
+from gpiod.line import Direction, Value
+
+
+def reconfigure_input_to_output(chip_path, line_offset):
+ # request the line initially as an input
+ with gpiod.request_lines(
+ chip_path,
+ consumer="reconfigure-input-to-output",
+ config={line_offset: gpiod.LineSettings(direction=Direction.INPUT)},
+ ) as request:
+ # read the current line value
+ value = request.get_value(line_offset)
+ print("{}={} (input)".format(line_offset, value))
+ # switch the line to an output and drive it low
+ request.reconfigure_lines(
+ config={
+ line_offset: gpiod.LineSettings(
+ direction=Direction.OUTPUT, output_value=Value.INACTIVE
+ )
+ }
+ )
+ # report the current driven value
+ value = request.get_value(line_offset)
+ print("{}={} (output)".format(line_offset, value))
+
+
+if __name__ == "__main__":
+ try:
+ reconfigure_input_to_output("/dev/gpiochip0", 5)
+ except OSError as ex:
+ print(ex, "\nCustomise the example configuration to suit your situation")
--- /dev/null
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+"""Minimal example of toggling a single line."""
+
+import gpiod
+import time
+
+from gpiod.line import Direction, Value
+
+
+def toggle_value(value):
+ if value == Value.INACTIVE:
+ return Value.ACTIVE
+ return Value.INACTIVE
+
+
+def toggle_line_value(chip_path, line_offset):
+ value_str = {Value.ACTIVE: "Active", Value.INACTIVE: "Inactive"}
+ value = Value.ACTIVE
+
+ with gpiod.request_lines(
+ chip_path,
+ consumer="toggle-line-value",
+ config={
+ line_offset: gpiod.LineSettings(
+ direction=Direction.OUTPUT, output_value=value
+ )
+ },
+ ) as request:
+ while True:
+ print("{}={}".format(line_offset, value_str[value]))
+ time.sleep(1)
+ value = toggle_value(value)
+ request.set_value(line_offset, value)
+
+
+if __name__ == "__main__":
+ try:
+ toggle_line_value("/dev/gpiochip0", 5)
+ except OSError as ex:
+ print(ex, "\nCustomise the example configuration to suit your situation")
--- /dev/null
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+"""Minimal example of toggling multiple lines."""
+
+import gpiod
+import time
+
+from gpiod.line import Direction, Value
+
+
+def toggle_value(value):
+ if value == Value.INACTIVE:
+ return Value.ACTIVE
+ return Value.INACTIVE
+
+
+def toggle_multiple_line_values(chip_path, line_values):
+ value_str = {Value.ACTIVE: "Active", Value.INACTIVE: "Inactive"}
+
+ request = gpiod.request_lines(
+ chip_path,
+ consumer="toggle-multiple-line-values",
+ config={
+ tuple(line_values.keys()): gpiod.LineSettings(direction=Direction.OUTPUT)
+ },
+ output_values=line_values,
+ )
+
+ while True:
+ print(
+ " ".join("{}={}".format(l, value_str[v]) for (l, v) in line_values.items())
+ )
+ time.sleep(1)
+ for l, v in line_values.items():
+ line_values[l] = toggle_value(v)
+ request.set_values(line_values)
+
+
+if __name__ == "__main__":
+ try:
+ toggle_multiple_line_values(
+ "/dev/gpiochip0", {5: Value.ACTIVE, 3: Value.ACTIVE, 7: Value.INACTIVE}
+ )
+ except OSError as ex:
+ print(ex, "\nCustomise the example configuration to suit your situation")
--- /dev/null
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+"""Minimal example of watching for info changes on particular lines."""
+
+import gpiod
+
+
+def watch_line_info(chip_path, line_offsets):
+ with gpiod.Chip(chip_path) as chip:
+ for offset in line_offsets:
+ chip.watch_line_info(offset)
+
+ while True:
+ print(chip.read_info_event())
+
+
+if __name__ == "__main__":
+ try:
+ watch_line_info("/dev/gpiochip0", [5, 3, 7])
+ except OSError as ex:
+ print(ex, "\nCustomise the example configuration to suit your situation")
--- /dev/null
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+"""Minimal example of watching for rising edges on a single line."""
+
+import gpiod
+
+from gpiod.line import Edge
+
+
+def watch_line_rising(chip_path, line_offset):
+ with gpiod.request_lines(
+ chip_path,
+ consumer="watch-line-rising",
+ config={line_offset: gpiod.LineSettings(edge_detection=Edge.RISING)},
+ ) as request:
+ while True:
+ # Blocks until at least one event is available
+ for event in request.read_edge_events():
+ print(
+ "line: {} type: Rising event #{}".format(
+ event.line_offset, event.line_seqno
+ )
+ )
+
+
+if __name__ == "__main__":
+ try:
+ watch_line_rising("/dev/gpiochip0", 5)
+ except OSError as ex:
+ print(ex, "\nCustomise the example configuration to suit your situation")
--- /dev/null
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+"""Minimal example of watching for edges on a single line."""
+
+import gpiod
+
+from datetime import timedelta
+from gpiod.line import Bias, Edge
+
+
+def edge_type_str(event):
+ if event.event_type is event.Type.RISING_EDGE:
+ return "Rising"
+ if event.event_type is event.Type.FALLING_EDGE:
+ return "Falling"
+ return "Unknown"
+
+
+def watch_line_value(chip_path, line_offset):
+ # Assume a button connecting the pin to ground,
+ # so pull it up and provide some debounce.
+ with gpiod.request_lines(
+ chip_path,
+ consumer="watch-line-value",
+ config={
+ line_offset: gpiod.LineSettings(
+ edge_detection=Edge.BOTH,
+ bias=Bias.PULL_UP,
+ debounce_period=timedelta(milliseconds=10),
+ )
+ },
+ ) as request:
+ while True:
+ # Blocks until at least one event is available
+ for event in request.read_edge_events():
+ print(
+ "line: {} type: {:<7} event #{}".format(
+ event.line_offset, edge_type_str(event), event.line_seqno
+ )
+ )
+
+
+if __name__ == "__main__":
+ try:
+ watch_line_value("/dev/gpiochip0", 5)
+ except OSError as ex:
+ print(ex, "\nCustomise the example configuration to suit your situation")
--- /dev/null
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+"""Minimal example of watching for edges on multiple lines."""
+
+import gpiod
+
+from gpiod.line import Edge
+
+
+def edge_type_str(event):
+ if event.event_type is event.Type.RISING_EDGE:
+ return "Rising"
+ if event.event_type is event.Type.FALLING_EDGE:
+ return "Falling"
+ return "Unknown"
+
+
+def watch_multiple_line_values(chip_path, line_offsets):
+ with gpiod.request_lines(
+ chip_path,
+ consumer="watch-multiple-line-values",
+ config={tuple(line_offsets): gpiod.LineSettings(edge_detection=Edge.BOTH)},
+ ) as request:
+ while True:
+ for event in request.read_edge_events():
+ print(
+ "offset: {} type: {:<7} event #{} line event #{}".format(
+ event.line_offset,
+ edge_type_str(event),
+ event.global_seqno,
+ event.line_seqno,
+ )
+ )
+
+
+if __name__ == "__main__":
+ try:
+ watch_multiple_line_values("/dev/gpiochip0", [5, 3, 7])
+ except OSError as ex:
+ print(ex, "\nCustomise the example configuration to suit your situation")
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+SUBDIRS = ext
+
+EXTRA_DIST = \
+ chip_info.py \
+ chip.py \
+ edge_event.py \
+ exception.py \
+ info_event.py \
+ __init__.py \
+ internal.py \
+ line_info.py \
+ line.py \
+ line_request.py \
+ line_settings.py \
+ version.py
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+"""
+Python bindings for libgpiod.
+
+This module wraps the native C API of libgpiod in a set of python classes.
+"""
+
+from . import _ext
+from . import line
+from .chip import Chip
+from .chip_info import ChipInfo
+from .edge_event import EdgeEvent
+from .exception import ChipClosedError, RequestReleasedError
+from .info_event import InfoEvent
+from .line_request import LineRequest
+from .line_settings import LineSettings
+from .version import __version__
+
+api_version = _ext.api_version
+
+
+def is_gpiochip_device(path: str) -> bool:
+ """
+ Check if the file pointed to by path is a GPIO chip character device.
+
+ Args:
+ path
+ Path to the file that should be checked.
+
+ Returns:
+ Returns true if so, False otherwise.
+ """
+ return _ext.is_gpiochip_device(path)
+
+
+def request_lines(path: str, *args, **kwargs) -> LineRequest:
+ """
+ Open a GPIO chip pointed to by 'path', request lines according to the
+ configuration arguments, close the chip and return the request object.
+
+ Args:
+ path
+ Path to the GPIO character device file.
+ *args
+ **kwargs
+ See Chip.request_lines() for configuration arguments.
+
+ Returns:
+ Returns a new LineRequest object.
+ """
+ with Chip(path) as chip:
+ return chip.request_lines(*args, **kwargs)
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+from . import _ext
+from .chip_info import ChipInfo
+from .exception import ChipClosedError
+from .info_event import InfoEvent
+from .internal import poll_fd
+from .line import Value
+from .line_info import LineInfo
+from .line_settings import LineSettings, _line_settings_to_ext
+from .line_request import LineRequest
+from collections import Counter
+from collections.abc import Iterable
+from datetime import timedelta
+from errno import ENOENT
+from select import select
+from typing import Union, Optional
+
+__all__ = "Chip"
+
+
+class Chip:
+ """
+ Represents a GPIO chip.
+
+ Chip object manages all resources associated with the GPIO chip it represents.
+
+ The gpiochip device file is opened during the object's construction. The Chip
+ object's constructor takes the path to the GPIO chip device file
+ as the only argument.
+
+ Callers must close the chip by calling the close() method when it's no longer
+ used.
+
+ Example:
+
+ chip = gpiod.Chip(\"/dev/gpiochip0\")
+ do_something(chip)
+ chip.close()
+
+ The gpiod.Chip class also supports controlled execution ('with' statement).
+
+ Example:
+
+ with gpiod.Chip(path="/dev/gpiochip0") as chip:
+ do_something(chip)
+ """
+
+ def __init__(self, path: str):
+ """
+ Open a GPIO device.
+
+ Args:
+ path:
+ Path to the GPIO character device file.
+ """
+ self._chip = _ext.Chip(path)
+ self._info = None
+
+ def __bool__(self) -> bool:
+ """
+ Boolean conversion for GPIO chips.
+
+ Returns:
+ True if the chip is open and False if it's closed.
+ """
+ return True if self._chip else False
+
+ def __enter__(self):
+ """
+ Controlled execution enter callback.
+ """
+ self._check_closed()
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback) -> None:
+ """
+ Controlled execution exit callback.
+ """
+ self.close()
+
+ def _check_closed(self) -> None:
+ if not self._chip:
+ raise ChipClosedError()
+
+ def close(self) -> None:
+ """
+ Close the associated GPIO chip descriptor. The chip object must no
+ longer be used after this method is called.
+ """
+ self._check_closed()
+ self._chip.close()
+ self._chip = None
+
+ def get_info(self) -> ChipInfo:
+ """
+ Get the information about the chip.
+
+ Returns:
+ New gpiod.ChipInfo object.
+ """
+ self._check_closed()
+
+ if not self._info:
+ self._info = self._chip.get_info()
+
+ return self._info
+
+ def line_offset_from_id(self, id: Union[str, int]) -> int:
+ """
+ Map a line's identifier to its offset within the chip.
+
+ Args:
+ id:
+ Name of the GPIO line, its offset as a string or its offset as an
+ integer.
+
+ Returns:
+ If id is an integer - it's returned as is (unless it's out of range
+ for this chip). If it's a string, the method tries to interpret it as
+ the name of the line first and tries too perform a name lookup within
+ the chip. If it fails, it tries to convert the string to an integer
+ and check if it represents a valid offset within the chip and if
+ so - returns it.
+ """
+ self._check_closed()
+
+ if not isinstance(id, int):
+ try:
+ return self._chip.line_offset_from_id(id)
+ except OSError as ex:
+ if ex.errno == ENOENT:
+ try:
+ offset = int(id)
+ except ValueError:
+ raise ex
+ else:
+ raise ex
+ else:
+ offset = id
+
+ if offset >= self.get_info().num_lines:
+ raise ValueError("line offset of out range")
+
+ return offset
+
+ def _get_line_info(self, line: Union[int, str], watch: bool) -> LineInfo:
+ self._check_closed()
+ return self._chip.get_line_info(self.line_offset_from_id(line), watch)
+
+ def get_line_info(self, line: Union[int, str]) -> LineInfo:
+ """
+ Get the snapshot of information about the line at given offset.
+
+ Args:
+ line:
+ Offset or name of the GPIO line to get information for.
+
+ Returns:
+ New LineInfo object.
+ """
+ return self._get_line_info(line, watch=False)
+
+ def watch_line_info(self, line: Union[int, str]) -> LineInfo:
+ """
+ Get the snapshot of information about the line at given offset and
+ start watching it for future changes.
+
+ Args:
+ line:
+ Offset or name of the GPIO line to get information for.
+
+ Returns:
+ New gpiod.LineInfo object.
+ """
+ return self._get_line_info(line, watch=True)
+
+ def unwatch_line_info(self, line: Union[int, str]) -> None:
+ """
+ Stop watching a line for status changes.
+
+ Args:
+ line:
+ Offset or name of the line to stop watching.
+ """
+ self._check_closed()
+ return self._chip.unwatch_line_info(self.line_offset_from_id(line))
+
+ def wait_info_event(
+ self, timeout: Optional[Union[timedelta, float]] = None
+ ) -> bool:
+ """
+ Wait for line status change events on any of the watched lines on the
+ chip.
+
+ Args:
+ timeout:
+ Wait time limit represented as either a datetime.timedelta object
+ or the number of seconds stored in a float. If set to 0, the
+ method returns immediately, if set to None it blocks indefinitely.
+
+ Returns:
+ True if an info event is ready to be read from the chip, False if the
+ wait timed out without any events.
+ """
+ self._check_closed()
+
+ return poll_fd(self.fd, timeout)
+
+ def read_info_event(self) -> InfoEvent:
+ """
+ Read a single line status change event from the chip.
+
+ Returns:
+ New gpiod.InfoEvent object.
+
+ Note:
+ This function may block if there are no available events in the queue.
+ """
+ self._check_closed()
+ return self._chip.read_info_event()
+
+ def request_lines(
+ self,
+ config: dict[tuple[Union[int, str]], Optional[LineSettings]],
+ consumer: Optional[str] = None,
+ event_buffer_size: Optional[int] = None,
+ output_values: Optional[dict[tuple[Union[int, str]], Value]] = None,
+ ) -> LineRequest:
+ """
+ Request a set of lines for exclusive usage.
+
+ Args:
+ config:
+ Dictionary mapping offsets or names (or tuples thereof) to
+ LineSettings. If None is passed as the value of the mapping,
+ default settings are used.
+ consumer:
+ Consumer string to use for this request.
+ event_buffer_size:
+ Size of the kernel edge event buffer to configure for this request.
+ output_values:
+ Dictionary mapping offsets or names to line.Value. This can be used
+ to set the desired output values globally while reusing LineSettings
+ for more lines.
+
+ Returns:
+ New LineRequest object.
+ """
+ self._check_closed()
+
+ line_cfg = _ext.LineConfig()
+
+ # Sanitize lines - don't allow offset repeatitions or offset-name conflicts.
+ for offset, count in Counter(
+ [
+ self.line_offset_from_id(line)
+ for line in (
+ lambda t: [
+ j for i in (t) for j in (i if isinstance(i, tuple) else (i,))
+ ]
+ )(tuple(config.keys()))
+ ]
+ ).items():
+ if count != 1:
+ raise ValueError(
+ "line must be configured exactly once - offset {} repeats".format(
+ offset
+ )
+ )
+
+ # If we have global output values - map line names to offsets
+ if output_values:
+ mapped_output_values = {
+ self.line_offset_from_id(line): value
+ for line, value in output_values.items()
+ }
+ else:
+ mapped_output_values = None
+
+ for lines, settings in config.items():
+ offsets = list()
+ name_map = dict()
+ offset_map = dict()
+ global_output_values = list()
+
+ if isinstance(lines, int) or isinstance(lines, str):
+ lines = (lines,)
+
+ for line in lines:
+ offset = self.line_offset_from_id(line)
+ offsets.append(offset)
+
+ # If there's a global output value for this offset, store it in the
+ # list for later.
+ if mapped_output_values:
+ global_output_values.append(
+ mapped_output_values[offset]
+ if offset in mapped_output_values
+ else Value.INACTIVE
+ )
+
+ if isinstance(line, str):
+ name_map[line] = offset
+ offset_map[offset] = line
+
+ line_cfg.add_line_settings(
+ offsets, _line_settings_to_ext(settings or LineSettings())
+ )
+
+ if len(global_output_values):
+ line_cfg.set_output_values(global_output_values)
+
+ req_internal = self._chip.request_lines(line_cfg, consumer, event_buffer_size)
+ request = LineRequest(req_internal)
+
+ request._chip_name = req_internal.chip_name
+ request._offsets = req_internal.offsets
+ request._name_map = name_map
+ request._offset_map = offset_map
+
+ request._lines = [
+ offset_map[off] if off in offset_map else off for off in request.offsets
+ ]
+
+ return request
+
+ def __repr__(self) -> str:
+ """
+ Return a string that can be used to re-create this chip object.
+ """
+ if not self._chip:
+ return "<Chip CLOSED>"
+
+ return 'Chip("{}")'.format(self.path)
+
+ def __str__(self) -> str:
+ """
+ Return a user-friendly, human-readable description of this chip.
+ """
+ if not self._chip:
+ return "<Chip CLOSED>"
+
+ return '<Chip path="{}" fd={} info={}>'.format(
+ self.path, self.fd, self.get_info()
+ )
+
+ @property
+ def path(self) -> str:
+ """
+ Filesystem path used to open this chip.
+ """
+ self._check_closed()
+ return self._chip.path
+
+ @property
+ def fd(self) -> int:
+ """
+ File descriptor associated with this chip.
+ """
+ self._check_closed()
+ return self._chip.fd
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+
+from dataclasses import dataclass
+
+__all__ = "ChipInfo"
+
+
+@dataclass(frozen=True, repr=False)
+class ChipInfo:
+ """
+ Snapshot of a chip's status.
+ """
+
+ name: str
+ label: str
+ num_lines: int
+
+ def __str__(self):
+ return '<ChipInfo name="{}" label="{}" num_lines={}>'.format(
+ self.name, self.label, self.num_lines
+ )
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+from . import _ext
+from dataclasses import dataclass
+from enum import Enum
+
+__all__ = "EdgeEvent"
+
+
+@dataclass(frozen=True, init=False, repr=False)
+class EdgeEvent:
+ """
+ Immutable object containing data about a single edge event.
+ """
+
+ class Type(Enum):
+ RISING_EDGE = _ext.EDGE_EVENT_TYPE_RISING
+ FALLING_EDGE = _ext.EDGE_EVENT_TYPE_FALLING
+
+ event_type: Type
+ timestamp_ns: int
+ line_offset: int
+ global_seqno: int
+ line_seqno: int
+
+ def __init__(
+ self,
+ event_type: int,
+ timestamp_ns: int,
+ line_offset: int,
+ global_seqno: int,
+ line_seqno: int,
+ ):
+ object.__setattr__(self, "event_type", EdgeEvent.Type(event_type))
+ object.__setattr__(self, "timestamp_ns", timestamp_ns)
+ object.__setattr__(self, "line_offset", line_offset)
+ object.__setattr__(self, "global_seqno", global_seqno)
+ object.__setattr__(self, "line_seqno", line_seqno)
+
+ def __str__(self):
+ return "<EdgeEvent type={} timestamp_ns={} line_offset={} global_seqno={} line_seqno={}>".format(
+ self.event_type,
+ self.timestamp_ns,
+ self.line_offset,
+ self.global_seqno,
+ self.line_seqno,
+ )
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+__all__ = ["ChipClosedError", "RequestReleasedError"]
+
+
+class ChipClosedError(Exception):
+ """
+ Error raised when an already closed chip is used.
+ """
+
+ def __init__(self):
+ super().__init__("I/O operation on closed chip")
+
+
+class RequestReleasedError(Exception):
+ """
+ Error raised when a released request is used.
+ """
+
+ def __init__(self):
+ super().__init__("GPIO lines have been released")
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+EXTRA_DIST = \
+ chip.c \
+ common.c \
+ internal.h \
+ line-config.c \
+ line-settings.c \
+ module.c \
+ request.c
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include "internal.h"
+
+typedef struct {
+ PyObject_HEAD;
+ struct gpiod_chip *chip;
+} chip_object;
+
+static int
+chip_init(chip_object *self, PyObject *args, PyObject *Py_UNUSED(ignored))
+{
+ struct gpiod_chip *chip;
+ char *path;
+ int ret;
+
+ ret = PyArg_ParseTuple(args, "s", &path);
+ if (!ret)
+ return -1;
+
+ Py_BEGIN_ALLOW_THREADS;
+ chip = gpiod_chip_open(path);
+ Py_END_ALLOW_THREADS;
+ if (!chip) {
+ Py_gpiod_SetErrFromErrno();
+ return -1;
+ }
+
+ self->chip = chip;
+
+ return 0;
+}
+
+static void chip_finalize(chip_object *self)
+{
+ if (self->chip)
+ PyObject_CallMethod((PyObject *)self, "close", "");
+}
+
+static PyObject *chip_path(chip_object *self, void *Py_UNUSED(ignored))
+{
+ return PyUnicode_FromString(gpiod_chip_get_path(self->chip));
+}
+
+static PyObject *chip_fd(chip_object *self, void *Py_UNUSED(ignored))
+{
+ return PyLong_FromLong(gpiod_chip_get_fd(self->chip));
+}
+
+static PyGetSetDef chip_getset[] = {
+ {
+ .name = "path",
+ .get = (getter)chip_path,
+ },
+ {
+ .name = "fd",
+ .get = (getter)chip_fd,
+ },
+ { }
+};
+
+static PyObject *chip_close(chip_object *self, PyObject *Py_UNUSED(ignored))
+{
+ Py_BEGIN_ALLOW_THREADS;
+ gpiod_chip_close(self->chip);
+ Py_END_ALLOW_THREADS;
+ self->chip = NULL;
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *chip_get_info(chip_object *self, PyObject *Py_UNUSED(ignored))
+{
+ struct gpiod_chip_info *info;
+ PyObject *type, *ret;
+
+ type = Py_gpiod_GetGlobalType("ChipInfo");
+ if (!type)
+ return NULL;
+
+ info = gpiod_chip_get_info(self->chip);
+ if (!info)
+ return PyErr_SetFromErrno(PyExc_OSError);
+
+ ret = PyObject_CallFunction(type, "ssI",
+ gpiod_chip_info_get_name(info),
+ gpiod_chip_info_get_label(info),
+ gpiod_chip_info_get_num_lines(info));
+ gpiod_chip_info_free(info);
+ return ret;
+}
+
+static PyObject *make_line_info(struct gpiod_line_info *info)
+{
+ PyObject *type;
+
+ type = Py_gpiod_GetGlobalType("LineInfo");
+ if (!type)
+ return NULL;
+
+ return PyObject_CallFunction(type, "IsOsiOiiiiOk",
+ gpiod_line_info_get_offset(info),
+ gpiod_line_info_get_name(info),
+ gpiod_line_info_is_used(info) ?
+ Py_True : Py_False,
+ gpiod_line_info_get_consumer(info),
+ gpiod_line_info_get_direction(info),
+ gpiod_line_info_is_active_low(info) ?
+ Py_True : Py_False,
+ gpiod_line_info_get_bias(info),
+ gpiod_line_info_get_drive(info),
+ gpiod_line_info_get_edge_detection(info),
+ gpiod_line_info_get_event_clock(info),
+ gpiod_line_info_is_debounced(info) ?
+ Py_True : Py_False,
+ gpiod_line_info_get_debounce_period_us(info));
+}
+
+static PyObject *chip_get_line_info(chip_object *self, PyObject *args)
+{
+ struct gpiod_line_info *info;
+ unsigned int offset;
+ PyObject *info_obj;
+ int ret, watch;
+
+ ret = PyArg_ParseTuple(args, "Ip", &offset, &watch);
+ if (!ret)
+ return NULL;
+
+ Py_BEGIN_ALLOW_THREADS;
+ if (watch)
+ info = gpiod_chip_watch_line_info(self->chip, offset);
+ else
+ info = gpiod_chip_get_line_info(self->chip, offset);
+ Py_END_ALLOW_THREADS;
+ if (!info)
+ return Py_gpiod_SetErrFromErrno();
+
+ info_obj = make_line_info(info);
+ gpiod_line_info_free(info);
+ return info_obj;
+}
+
+static PyObject *
+chip_unwatch_line_info(chip_object *self, PyObject *args)
+{
+ unsigned int offset;
+ int ret;
+
+ ret = PyArg_ParseTuple(args, "I", &offset);
+ if (!ret)
+ return NULL;
+
+ Py_BEGIN_ALLOW_THREADS;
+ ret = gpiod_chip_unwatch_line_info(self->chip, offset);
+ Py_END_ALLOW_THREADS;
+ if (ret)
+ return Py_gpiod_SetErrFromErrno();
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *
+chip_read_info_event(chip_object *self, PyObject *Py_UNUSED(ignored))
+{
+ PyObject *type, *info_obj, *event_obj;
+ struct gpiod_info_event *event;
+ struct gpiod_line_info *info;
+
+ type = Py_gpiod_GetGlobalType("InfoEvent");
+ if (!type)
+ return NULL;
+
+ Py_BEGIN_ALLOW_THREADS;
+ event = gpiod_chip_read_info_event(self->chip);
+ Py_END_ALLOW_THREADS;
+ if (!event)
+ return Py_gpiod_SetErrFromErrno();
+
+ info = gpiod_info_event_get_line_info(event);
+
+ info_obj = make_line_info(info);
+ if (!info_obj) {
+ gpiod_info_event_free(event);
+ return NULL;
+ }
+
+ event_obj = PyObject_CallFunction(type, "iKO",
+ gpiod_info_event_get_event_type(event),
+ gpiod_info_event_get_timestamp_ns(event),
+ info_obj);
+ Py_DECREF(info_obj);
+ gpiod_info_event_free(event);
+ return event_obj;
+}
+
+static PyObject *chip_line_offset_from_id(chip_object *self, PyObject *args)
+{
+ int ret, offset;
+ char *name;
+
+ ret = PyArg_ParseTuple(args, "s", &name);
+ if (!ret)
+ return NULL;
+
+ Py_BEGIN_ALLOW_THREADS;
+ offset = gpiod_chip_get_line_offset_from_name(self->chip, name);
+ Py_END_ALLOW_THREADS;
+ if (offset < 0)
+ return Py_gpiod_SetErrFromErrno();
+
+ return PyLong_FromLong(offset);
+}
+
+static struct gpiod_request_config *
+make_request_config(PyObject *consumer_obj, PyObject *event_buffer_size_obj)
+{
+ struct gpiod_request_config *req_cfg;
+ size_t event_buffer_size;
+ const char *consumer;
+
+ req_cfg = gpiod_request_config_new();
+ if (!req_cfg) {
+ Py_gpiod_SetErrFromErrno();
+ return NULL;
+ }
+
+ if (consumer_obj != Py_None) {
+ consumer = PyUnicode_AsUTF8(consumer_obj);
+ if (!consumer) {
+ gpiod_request_config_free(req_cfg);
+ return NULL;
+ }
+
+ gpiod_request_config_set_consumer(req_cfg, consumer);
+ }
+
+ if (event_buffer_size_obj != Py_None) {
+ event_buffer_size = PyLong_AsSize_t(event_buffer_size_obj);
+ if (PyErr_Occurred()) {
+ gpiod_request_config_free(req_cfg);
+ return NULL;
+ }
+
+ gpiod_request_config_set_event_buffer_size(req_cfg,
+ event_buffer_size);
+ }
+
+ return req_cfg;
+}
+
+static PyObject *chip_request_lines(chip_object *self, PyObject *args)
+{
+ PyObject *line_config, *consumer, *event_buffer_size, *req_obj;
+ struct gpiod_request_config *req_cfg;
+ struct gpiod_line_config *line_cfg;
+ struct gpiod_line_request *request;
+ int ret;
+
+ ret = PyArg_ParseTuple(args, "OOO",
+ &line_config, &consumer, &event_buffer_size);
+ if (!ret)
+ return NULL;
+
+ line_cfg = Py_gpiod_LineConfigGetData(line_config);
+ if (!line_cfg)
+ return NULL;
+
+ req_cfg = make_request_config(consumer, event_buffer_size);
+ if (!req_cfg)
+ return NULL;
+
+ Py_BEGIN_ALLOW_THREADS;
+ request = gpiod_chip_request_lines(self->chip, req_cfg, line_cfg);
+ Py_END_ALLOW_THREADS;
+ if (!request) {
+ gpiod_request_config_free(req_cfg);
+ return Py_gpiod_SetErrFromErrno();
+ }
+
+ req_obj = Py_gpiod_MakeRequestObject(request,
+ gpiod_request_config_get_event_buffer_size(req_cfg));
+ if (!req_obj)
+ gpiod_line_request_release(request);
+ gpiod_request_config_free(req_cfg);
+
+ return req_obj;
+}
+
+static PyMethodDef chip_methods[] = {
+ {
+ .ml_name = "close",
+ .ml_meth = (PyCFunction)chip_close,
+ .ml_flags = METH_NOARGS,
+ },
+ {
+ .ml_name = "get_info",
+ .ml_meth = (PyCFunction)chip_get_info,
+ .ml_flags = METH_NOARGS,
+ },
+ {
+ .ml_name = "get_line_info",
+ .ml_meth = (PyCFunction)chip_get_line_info,
+ .ml_flags = METH_VARARGS,
+ },
+ {
+ .ml_name = "unwatch_line_info",
+ .ml_meth = (PyCFunction)chip_unwatch_line_info,
+ .ml_flags = METH_VARARGS,
+ },
+ {
+ .ml_name = "read_info_event",
+ .ml_meth = (PyCFunction)chip_read_info_event,
+ .ml_flags = METH_NOARGS,
+ },
+ {
+ .ml_name = "line_offset_from_id",
+ .ml_meth = (PyCFunction)chip_line_offset_from_id,
+ .ml_flags = METH_VARARGS,
+ },
+ {
+ .ml_name = "request_lines",
+ .ml_meth = (PyCFunction)chip_request_lines,
+ .ml_flags = METH_VARARGS,
+ },
+ { }
+};
+
+PyTypeObject chip_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "gpiod._ext.Chip",
+ .tp_basicsize = sizeof(chip_object),
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_new = PyType_GenericNew,
+ .tp_init = (initproc)chip_init,
+ .tp_finalize = (destructor)chip_finalize,
+ .tp_dealloc = (destructor)Py_gpiod_dealloc,
+ .tp_getset = chip_getset,
+ .tp_methods = chip_methods,
+};
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include "internal.h"
+
+/* Generic dealloc callback for all gpiod objects. */
+void Py_gpiod_dealloc(PyObject *self)
+{
+ int ret;
+
+ ret = PyObject_CallFinalizerFromDealloc(self);
+ if (ret < 0)
+ return;
+
+ PyObject_Del(self);
+}
+
+PyObject *Py_gpiod_SetErrFromErrno(void)
+{
+ PyObject *exc;
+
+ if (errno == ENOMEM)
+ return PyErr_NoMemory();
+
+ switch (errno) {
+ case EINVAL:
+ exc = PyExc_ValueError;
+ break;
+ case EOPNOTSUPP:
+ exc = PyExc_NotImplementedError;
+ break;
+ case EPIPE:
+ exc = PyExc_BrokenPipeError;
+ break;
+ case ECHILD:
+ exc = PyExc_ChildProcessError;
+ break;
+ case EINTR:
+ exc = PyExc_InterruptedError;
+ break;
+ case EEXIST:
+ exc = PyExc_FileExistsError;
+ break;
+ case ENOENT:
+ exc = PyExc_FileNotFoundError;
+ break;
+ case EISDIR:
+ exc = PyExc_IsADirectoryError;
+ break;
+ case ENOTDIR:
+ exc = PyExc_NotADirectoryError;
+ break;
+ case EPERM:
+ exc = PyExc_PermissionError;
+ break;
+ case ETIMEDOUT:
+ exc = PyExc_TimeoutError;
+ break;
+ default:
+ exc = PyExc_OSError;
+ break;
+ }
+
+ return PyErr_SetFromErrno(exc);
+}
+
+PyObject *Py_gpiod_GetGlobalType(const char *type_name)
+{
+ PyObject *globals;
+
+ globals = PyEval_GetGlobals();
+ if (!globals)
+ return NULL;
+
+ return PyDict_GetItemString(globals, type_name);
+}
+
+unsigned int Py_gpiod_PyLongAsUnsignedInt(PyObject *pylong)
+{
+ unsigned long tmp;
+
+ tmp = PyLong_AsUnsignedLong(pylong);
+ if (PyErr_Occurred())
+ return 0;
+
+ if (tmp > UINT_MAX) {
+ PyErr_SetString(PyExc_ValueError, "value exceeding UINT_MAX");
+ return 0;
+ }
+
+ return tmp;
+}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/* SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+#ifndef __LIBGPIOD_PYTHON_MODULE_H__
+#define __LIBGPIOD_PYTHON_MODULE_H__
+
+#include <gpiod.h>
+#include <Python.h>
+
+PyObject *Py_gpiod_SetErrFromErrno(void);
+PyObject *Py_gpiod_GetGlobalType(const char *type_name);
+unsigned int Py_gpiod_PyLongAsUnsignedInt(PyObject *pylong);
+void Py_gpiod_dealloc(PyObject *self);
+PyObject *Py_gpiod_MakeRequestObject(struct gpiod_line_request *request,
+ size_t event_buffer_size);
+struct gpiod_line_config *Py_gpiod_LineConfigGetData(PyObject *obj);
+struct gpiod_line_settings *Py_gpiod_LineSettingsGetData(PyObject *obj);
+
+#endif /* __LIBGPIOD_PYTHON_MODULE_H__ */
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include "internal.h"
+
+typedef struct {
+ PyObject_HEAD;
+ struct gpiod_line_config *cfg;
+} line_config_object;
+
+static int line_config_init(line_config_object *self,
+ PyObject *Py_UNUSED(args), PyObject *Py_UNUSED(ignored))
+{
+ self->cfg = gpiod_line_config_new();
+ if (!self->cfg) {
+ Py_gpiod_SetErrFromErrno();
+ return -1;
+ }
+
+ return 0;
+}
+
+static void line_config_finalize(line_config_object *self)
+{
+ if (self->cfg)
+ gpiod_line_config_free(self->cfg);
+}
+
+static unsigned int *make_offsets(PyObject *obj, Py_ssize_t len)
+{
+ unsigned int *offsets;
+ PyObject *offset;
+ Py_ssize_t i;
+
+ offsets = PyMem_Calloc(len, sizeof(unsigned int));
+ if (!offsets)
+ return (unsigned int *)PyErr_NoMemory();
+
+ for (i = 0; i < len; i++) {
+ offset = PyList_GetItem(obj, i);
+ if (!offset) {
+ PyMem_Free(offsets);
+ return NULL;
+ }
+
+ offsets[i] = Py_gpiod_PyLongAsUnsignedInt(offset);
+ if (PyErr_Occurred()) {
+ PyMem_Free(offsets);
+ return NULL;
+ }
+ }
+
+ return offsets;
+}
+
+static PyObject *
+line_config_add_line_settings(line_config_object *self, PyObject *args)
+{
+ PyObject *offsets_obj, *settings_obj;
+ struct gpiod_line_settings *settings;
+ unsigned int *offsets;
+ Py_ssize_t num_offsets;
+ int ret;
+
+ ret = PyArg_ParseTuple(args, "OO", &offsets_obj, &settings_obj);
+ if (!ret)
+ return NULL;
+
+ num_offsets = PyObject_Size(offsets_obj);
+ if (num_offsets < 0)
+ return NULL;
+
+ offsets = make_offsets(offsets_obj, num_offsets);
+ if (!offsets)
+ return NULL;
+
+ settings = Py_gpiod_LineSettingsGetData(settings_obj);
+ if (!settings) {
+ PyMem_Free(offsets);
+ return NULL;
+ }
+
+ ret = gpiod_line_config_add_line_settings(self->cfg, offsets,
+ num_offsets, settings);
+ PyMem_Free(offsets);
+ if (ret)
+ return Py_gpiod_SetErrFromErrno();
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *
+line_config_set_output_values(line_config_object *self, PyObject *args)
+{
+ PyObject *values, *iter, *next, *val_stripped;
+ enum gpiod_line_value *valbuf;
+ Py_ssize_t num_values, pos;
+ int ret;
+
+ values = PyTuple_GetItem(args, 0);
+ if (!values)
+ return NULL;
+
+ num_values = PyObject_Size(values);
+ if (num_values < 0)
+ return NULL;
+
+ valbuf = PyMem_Calloc(num_values, sizeof(*valbuf));
+ if (!valbuf)
+ return PyErr_NoMemory();
+
+ iter = PyObject_GetIter(values);
+ if (!iter) {
+ PyMem_Free(valbuf);
+ return NULL;
+ }
+
+ for (pos = 0;; pos++) {
+ next = PyIter_Next(iter);
+ if (!next) {
+ Py_DECREF(iter);
+ break;
+ }
+
+ val_stripped = PyObject_GetAttrString(next, "value");
+ Py_DECREF(next);
+ if (!val_stripped) {
+ PyMem_Free(valbuf);
+ Py_DECREF(iter);
+ return NULL;
+ }
+
+ valbuf[pos] = PyLong_AsLong(val_stripped);
+ Py_DECREF(val_stripped);
+ if (PyErr_Occurred()) {
+ PyMem_Free(valbuf);
+ Py_DECREF(iter);
+ return NULL;
+ }
+ }
+
+ ret = gpiod_line_config_set_output_values(self->cfg,
+ valbuf, num_values);
+ PyMem_Free(valbuf);
+ if (ret)
+ return Py_gpiod_SetErrFromErrno();
+
+ Py_RETURN_NONE;
+}
+
+static PyMethodDef line_config_methods[] = {
+ {
+ .ml_name = "add_line_settings",
+ .ml_meth = (PyCFunction)line_config_add_line_settings,
+ .ml_flags = METH_VARARGS,
+ },
+ {
+ .ml_name = "set_output_values",
+ .ml_meth = (PyCFunction)line_config_set_output_values,
+ .ml_flags = METH_VARARGS,
+ },
+ { }
+};
+
+PyTypeObject line_config_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "gpiod._ext.LineConfig",
+ .tp_basicsize = sizeof(line_config_object),
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_new = PyType_GenericNew,
+ .tp_init = (initproc)line_config_init,
+ .tp_finalize = (destructor)line_config_finalize,
+ .tp_dealloc = (destructor)Py_gpiod_dealloc,
+ .tp_methods = line_config_methods,
+};
+
+struct gpiod_line_config *Py_gpiod_LineConfigGetData(PyObject *obj)
+{
+ line_config_object *line_cfg;
+ PyObject *type;
+
+ type = PyObject_Type(obj);
+ if (!type)
+ return NULL;
+
+ if ((PyTypeObject *)type != &line_config_type) {
+ PyErr_SetString(PyExc_TypeError,
+ "not a gpiod._ext.LineConfig object");
+ Py_DECREF(type);
+ return NULL;
+ }
+ Py_DECREF(type);
+
+ line_cfg = (line_config_object *)obj;
+
+ return line_cfg->cfg;
+}
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include "internal.h"
+
+typedef struct {
+ PyObject_HEAD;
+ struct gpiod_line_settings *settings;
+} line_settings_object;
+
+static int set_error(void)
+{
+ Py_gpiod_SetErrFromErrno();
+ return -1;
+}
+
+static int
+line_settings_init(line_settings_object *self, PyObject *args, PyObject *kwargs)
+{
+ static char *kwlist[] = {
+ "direction",
+ "edge_detection",
+ "bias",
+ "drive",
+ "active_low",
+ "debounce_period",
+ "event_clock",
+ "output_value",
+ NULL
+ };
+
+ enum gpiod_line_clock event_clock;
+ enum gpiod_line_direction direction;
+ enum gpiod_line_value output_value;
+ unsigned long debounce_period;
+ enum gpiod_line_drive drive;
+ enum gpiod_line_edge edge;
+ enum gpiod_line_bias bias;
+ int ret, active_low;
+
+ ret = PyArg_ParseTupleAndKeywords(args, kwargs, "IIIIpkII", kwlist,
+ &direction, &edge, &bias, &drive, &active_low,
+ &debounce_period, &event_clock, &output_value);
+ if (!ret)
+ return -1;
+
+ self->settings = gpiod_line_settings_new();
+ if (!self->settings) {
+ Py_gpiod_SetErrFromErrno();
+ return -1;
+ }
+
+ ret = gpiod_line_settings_set_direction(self->settings, direction);
+ if (ret)
+ return set_error();
+
+ ret = gpiod_line_settings_set_edge_detection(self->settings, edge);
+ if (ret)
+ return set_error();
+
+ ret = gpiod_line_settings_set_bias(self->settings, bias);
+ if (ret)
+ return set_error();
+
+ ret = gpiod_line_settings_set_drive(self->settings, drive);
+ if (ret)
+ return set_error();
+
+ gpiod_line_settings_set_active_low(self->settings, active_low);
+ gpiod_line_settings_set_debounce_period_us(self->settings,
+ debounce_period);
+
+ ret = gpiod_line_settings_set_edge_detection(self->settings, edge);
+ if (ret)
+ return set_error();
+
+ ret = gpiod_line_settings_set_output_value(self->settings,
+ output_value);
+ if (ret)
+ return set_error();
+
+ ret = gpiod_line_settings_set_event_clock(self->settings, event_clock);
+ if (ret)
+ return set_error();
+
+ return 0;
+}
+
+static void line_settings_finalize(line_settings_object *self)
+{
+ if (self->settings)
+ gpiod_line_settings_free(self->settings);
+}
+
+PyTypeObject line_settings_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "gpiod._ext.LineSettings",
+ .tp_basicsize = sizeof(line_settings_object),
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_new = PyType_GenericNew,
+ .tp_init = (initproc)line_settings_init,
+ .tp_finalize = (destructor)line_settings_finalize,
+ .tp_dealloc = (destructor)Py_gpiod_dealloc,
+};
+
+struct gpiod_line_settings *Py_gpiod_LineSettingsGetData(PyObject *obj)
+{
+ line_settings_object *settings;
+ PyObject *type;
+
+ type = PyObject_Type(obj);
+ if (!type)
+ return NULL;
+
+ if ((PyTypeObject *)type != &line_settings_type) {
+ PyErr_SetString(PyExc_TypeError,
+ "not a gpiod._ext.LineSettings object");
+ Py_DECREF(type);
+ return NULL;
+ }
+ Py_DECREF(type);
+
+ settings = (line_settings_object *)obj;
+
+ return settings->settings;
+}
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <gpiod.h>
+#include <Python.h>
+
+struct module_const {
+ const char *name;
+ long val;
+};
+
+static const struct module_const module_constants[] = {
+ {
+ .name = "VALUE_INACTIVE",
+ .val = GPIOD_LINE_VALUE_INACTIVE,
+ },
+ {
+ .name = "VALUE_ACTIVE",
+ .val = GPIOD_LINE_VALUE_ACTIVE,
+ },
+ {
+ .name = "DIRECTION_AS_IS",
+ .val = GPIOD_LINE_DIRECTION_AS_IS,
+ },
+ {
+ .name = "DIRECTION_INPUT",
+ .val = GPIOD_LINE_DIRECTION_INPUT,
+ },
+ {
+ .name = "DIRECTION_OUTPUT",
+ .val = GPIOD_LINE_DIRECTION_OUTPUT,
+ },
+ {
+ .name = "BIAS_AS_IS",
+ .val = GPIOD_LINE_BIAS_AS_IS,
+ },
+ {
+ .name = "BIAS_UNKNOWN",
+ .val = GPIOD_LINE_BIAS_UNKNOWN,
+ },
+ {
+ .name = "BIAS_DISABLED",
+ .val = GPIOD_LINE_BIAS_DISABLED,
+ },
+ {
+ .name = "BIAS_PULL_UP",
+ .val = GPIOD_LINE_BIAS_PULL_UP,
+ },
+ {
+ .name = "BIAS_PULL_DOWN",
+ .val = GPIOD_LINE_BIAS_PULL_DOWN,
+ },
+ {
+ .name = "DRIVE_PUSH_PULL",
+ .val = GPIOD_LINE_DRIVE_PUSH_PULL,
+ },
+ {
+ .name = "DRIVE_OPEN_DRAIN",
+ .val = GPIOD_LINE_DRIVE_OPEN_DRAIN,
+ },
+ {
+ .name = "DRIVE_OPEN_SOURCE",
+ .val = GPIOD_LINE_DRIVE_OPEN_SOURCE,
+ },
+ {
+ .name = "EDGE_NONE",
+ .val = GPIOD_LINE_EDGE_NONE,
+ },
+ {
+ .name = "EDGE_FALLING",
+ .val = GPIOD_LINE_EDGE_FALLING,
+ },
+ {
+ .name = "EDGE_RISING",
+ .val = GPIOD_LINE_EDGE_RISING,
+ },
+ {
+ .name = "EDGE_BOTH",
+ .val = GPIOD_LINE_EDGE_BOTH,
+ },
+ {
+ .name = "CLOCK_MONOTONIC",
+ .val = GPIOD_LINE_CLOCK_MONOTONIC,
+ },
+ {
+ .name = "CLOCK_REALTIME",
+ .val = GPIOD_LINE_CLOCK_REALTIME,
+ },
+ {
+ .name = "CLOCK_HTE",
+ .val = GPIOD_LINE_CLOCK_HTE,
+ },
+ {
+ .name = "EDGE_EVENT_TYPE_RISING",
+ .val = GPIOD_EDGE_EVENT_RISING_EDGE,
+ },
+ {
+ .name = "EDGE_EVENT_TYPE_FALLING",
+ .val = GPIOD_EDGE_EVENT_FALLING_EDGE,
+ },
+ {
+ .name = "INFO_EVENT_TYPE_LINE_REQUESTED",
+ .val = GPIOD_INFO_EVENT_LINE_REQUESTED,
+ },
+ {
+ .name = "INFO_EVENT_TYPE_LINE_RELEASED",
+ .val = GPIOD_INFO_EVENT_LINE_RELEASED,
+ },
+ {
+ .name = "INFO_EVENT_TYPE_LINE_CONFIG_CHANGED",
+ .val = GPIOD_INFO_EVENT_LINE_CONFIG_CHANGED,
+ },
+ { }
+};
+
+static PyObject *
+module_is_gpiochip_device(PyObject *Py_UNUSED(self), PyObject *args)
+{
+ const char *path;
+ int ret;
+
+ ret = PyArg_ParseTuple(args, "s", &path);
+ if (!ret)
+ return NULL;
+
+ return PyBool_FromLong(gpiod_is_gpiochip_device(path));
+}
+
+static PyMethodDef module_methods[] = {
+ {
+ .ml_name = "is_gpiochip_device",
+ .ml_meth = (PyCFunction)module_is_gpiochip_device,
+ .ml_flags = METH_VARARGS,
+ },
+ { }
+};
+
+static PyModuleDef module_def = {
+ PyModuleDef_HEAD_INIT,
+ .m_name = "gpiod._ext",
+ .m_methods = module_methods,
+};
+
+extern PyTypeObject chip_type;
+extern PyTypeObject line_config_type;
+extern PyTypeObject line_settings_type;
+extern PyTypeObject request_type;
+
+static PyTypeObject *types[] = {
+ &chip_type,
+ &line_config_type,
+ &line_settings_type,
+ &request_type,
+ NULL,
+};
+
+PyMODINIT_FUNC PyInit__ext(void)
+{
+ const struct module_const *modconst;
+ PyObject *module, *all;
+ PyTypeObject **type;
+ int ret;
+
+ module = PyModule_Create(&module_def);
+ if (!module)
+ return NULL;
+
+ ret = PyModule_AddStringConstant(module, "api_version",
+ gpiod_api_version());
+ if (ret) {
+ Py_DECREF(module);
+ return NULL;
+ }
+
+ all = PyList_New(0);
+ if (!all) {
+ Py_DECREF(module);
+ return NULL;
+ }
+
+ ret = PyModule_AddObject(module, "__all__", all);
+ if (ret) {
+ Py_DECREF(all);
+ Py_DECREF(module);
+ return NULL;
+ }
+
+ for (type = types; *type; type++) {
+ ret = PyModule_AddType(module, *type);
+ if (ret) {
+ Py_DECREF(module);
+ return NULL;
+ }
+ }
+
+ for (modconst = module_constants; modconst->name; modconst++) {
+ ret = PyModule_AddIntConstant(module,
+ modconst->name, modconst->val);
+ if (ret) {
+ Py_DECREF(module);
+ return NULL;
+ }
+ }
+
+ return module;
+}
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include "internal.h"
+
+typedef struct {
+ PyObject_HEAD;
+ struct gpiod_line_request *request;
+ unsigned int *offsets;
+ enum gpiod_line_value *values;
+ size_t num_lines;
+ struct gpiod_edge_event_buffer *buffer;
+} request_object;
+
+static int request_init(PyObject *Py_UNUSED(ignored0),
+ PyObject *Py_UNUSED(ignored1),
+ PyObject *Py_UNUSED(ignored2))
+{
+ PyErr_SetString(PyExc_NotImplementedError,
+ "_ext.LineRequest cannot be instantiated");
+
+ return -1;
+}
+
+static void request_finalize(request_object *self)
+{
+ if (self->request)
+ PyObject_CallMethod((PyObject *)self, "release", "");
+
+ if (self->offsets)
+ PyMem_Free(self->offsets);
+
+ if (self->values)
+ PyMem_Free(self->values);
+
+ if (self->buffer)
+ gpiod_edge_event_buffer_free(self->buffer);
+}
+
+static PyObject *
+request_chip_name(request_object *self, void *Py_UNUSED(ignored))
+{
+ return PyUnicode_FromString(
+ gpiod_line_request_get_chip_name(self->request));
+}
+
+static PyObject *
+request_num_lines(request_object *self, void *Py_UNUSED(ignored))
+{
+ return PyLong_FromUnsignedLong(
+ gpiod_line_request_get_num_requested_lines(self->request));
+}
+
+static PyObject *request_offsets(request_object *self, void *Py_UNUSED(ignored))
+{
+ PyObject *lines, *line;
+ unsigned int *offsets;
+ size_t num_lines, i;
+ int ret;
+
+ num_lines = gpiod_line_request_get_num_requested_lines(self->request);
+
+ offsets = PyMem_Calloc(num_lines, sizeof(unsigned int));
+ if (!offsets)
+ return PyErr_NoMemory();
+
+ gpiod_line_request_get_requested_offsets(self->request, offsets, num_lines);
+
+ lines = PyList_New(num_lines);
+ if (!lines) {
+ PyMem_Free(offsets);
+ return NULL;
+ }
+
+ for (i = 0; i < num_lines; i++) {
+ line = PyLong_FromUnsignedLong(offsets[i]);
+ if (!line) {
+ Py_DECREF(lines);
+ PyMem_Free(offsets);
+ return NULL;
+ }
+
+ ret = PyList_SetItem(lines, i, line);
+ if (ret) {
+ Py_DECREF(line);
+ Py_DECREF(lines);
+ PyMem_Free(offsets);
+ return NULL;
+ }
+ }
+
+ PyMem_Free(offsets);
+ return lines;
+}
+
+static PyObject *request_fd(request_object *self, void *Py_UNUSED(ignored))
+{
+ return PyLong_FromLong(gpiod_line_request_get_fd(self->request));
+}
+
+static PyGetSetDef request_getset[] = {
+ {
+ .name = "chip_name",
+ .get = (getter)request_chip_name,
+ },
+ {
+ .name = "num_lines",
+ .get = (getter)request_num_lines,
+ },
+ {
+ .name = "offsets",
+ .get = (getter)request_offsets,
+ },
+ {
+ .name = "fd",
+ .get = (getter)request_fd,
+ },
+ { }
+};
+
+static PyObject *
+request_release(request_object *self, PyObject *Py_UNUSED(ignored))
+{
+ Py_BEGIN_ALLOW_THREADS;
+ gpiod_line_request_release(self->request);
+ Py_END_ALLOW_THREADS;
+ self->request = NULL;
+
+ Py_RETURN_NONE;
+}
+
+static void clear_buffers(request_object *self)
+{
+ memset(self->offsets, 0, self->num_lines * sizeof(unsigned int));
+ memset(self->values, 0, self->num_lines * sizeof(int));
+}
+
+static PyObject *request_get_values(request_object *self, PyObject *args)
+{
+ PyObject *offsets, *values, *val, *type, *iter, *next;
+ Py_ssize_t num_offsets, pos;
+ int ret;
+
+ ret = PyArg_ParseTuple(args, "OO", &offsets, &values);
+ if (!ret)
+ return NULL;
+
+ num_offsets = PyObject_Size(offsets);
+ if (num_offsets < 0)
+ return NULL;
+
+ type = Py_gpiod_GetGlobalType("Value");
+ if (!type)
+ return NULL;
+
+ iter = PyObject_GetIter(offsets);
+ if (!iter)
+ return NULL;
+
+ clear_buffers(self);
+
+ for (pos = 0;; pos++) {
+ next = PyIter_Next(iter);
+ if (!next) {
+ Py_DECREF(iter);
+ break;
+ }
+
+ self->offsets[pos] = Py_gpiod_PyLongAsUnsignedInt(next);
+ Py_DECREF(next);
+ if (PyErr_Occurred()) {
+ Py_DECREF(iter);
+ return NULL;
+ }
+ }
+
+ Py_BEGIN_ALLOW_THREADS;
+ ret = gpiod_line_request_get_values_subset(self->request,
+ num_offsets,
+ self->offsets,
+ self->values);
+ Py_END_ALLOW_THREADS;
+ if (ret)
+ return Py_gpiod_SetErrFromErrno();
+
+ for (pos = 0; pos < num_offsets; pos++) {
+ val = PyObject_CallFunction(type, "i", self->values[pos]);
+ if (!val)
+ return NULL;
+
+ ret = PyList_SetItem(values, pos, val);
+ if (ret) {
+ Py_DECREF(val);
+ return NULL;
+ }
+ }
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *request_set_values(request_object *self, PyObject *args)
+{
+ PyObject *values, *key, *val, *val_stripped;
+ Py_ssize_t pos = 0;
+ int ret;
+
+ ret = PyArg_ParseTuple(args, "O", &values);
+ if (!ret)
+ return NULL;
+
+ clear_buffers(self);
+
+ while (PyDict_Next(values, &pos, &key, &val)) {
+ self->offsets[pos - 1] = Py_gpiod_PyLongAsUnsignedInt(key);
+ if (PyErr_Occurred())
+ return NULL;
+
+ val_stripped = PyObject_GetAttrString(val, "value");
+ if (!val_stripped)
+ return NULL;
+
+ self->values[pos - 1] = PyLong_AsLong(val_stripped);
+ Py_DECREF(val_stripped);
+ if (PyErr_Occurred())
+ return NULL;
+ }
+
+ Py_BEGIN_ALLOW_THREADS;
+ ret = gpiod_line_request_set_values_subset(self->request,
+ pos,
+ self->offsets,
+ self->values);
+ Py_END_ALLOW_THREADS;
+ if (ret)
+ return Py_gpiod_SetErrFromErrno();
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *request_reconfigure_lines(request_object *self, PyObject *args)
+{
+ struct gpiod_line_config *line_cfg;
+ PyObject *line_cfg_obj;
+ int ret;
+
+ ret = PyArg_ParseTuple(args, "O", &line_cfg_obj);
+ if (!ret)
+ return NULL;
+
+ line_cfg = Py_gpiod_LineConfigGetData(line_cfg_obj);
+ if (!line_cfg)
+ return NULL;
+
+ Py_BEGIN_ALLOW_THREADS;
+ ret = gpiod_line_request_reconfigure_lines(self->request, line_cfg);
+ Py_END_ALLOW_THREADS;
+ if (ret)
+ return Py_gpiod_SetErrFromErrno();
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *request_read_edge_events(request_object *self, PyObject *args)
+{
+ PyObject *max_events_obj, *event_obj, *events, *type;
+ size_t max_events, num_events, i;
+ struct gpiod_edge_event *event;
+ int ret;
+
+ ret = PyArg_ParseTuple(args, "O", &max_events_obj);
+ if (!ret)
+ return NULL;
+
+ if (max_events_obj != Py_None) {
+ max_events = PyLong_AsSize_t(max_events_obj);
+ if (PyErr_Occurred())
+ return NULL;
+ } else {
+ max_events = 64;
+ }
+
+ type = Py_gpiod_GetGlobalType("EdgeEvent");
+ if (!type)
+ return NULL;
+
+ Py_BEGIN_ALLOW_THREADS;
+ ret = gpiod_line_request_read_edge_events(self->request,
+ self->buffer, max_events);
+ Py_END_ALLOW_THREADS;
+ if (ret < 0)
+ return Py_gpiod_SetErrFromErrno();
+
+ num_events = ret;
+
+ events = PyList_New(num_events);
+ if (!events)
+ return NULL;
+
+ for (i = 0; i < num_events; i++) {
+ event = gpiod_edge_event_buffer_get_event(self->buffer, i);
+ if (!event) {
+ Py_DECREF(events);
+ return NULL;
+ }
+
+ event_obj = PyObject_CallFunction(type, "iKiii",
+ gpiod_edge_event_get_event_type(event),
+ gpiod_edge_event_get_timestamp_ns(event),
+ gpiod_edge_event_get_line_offset(event),
+ gpiod_edge_event_get_global_seqno(event),
+ gpiod_edge_event_get_line_seqno(event));
+ if (!event_obj) {
+ Py_DECREF(events);
+ return NULL;
+ }
+
+ ret = PyList_SetItem(events, i, event_obj);
+ if (ret) {
+ Py_DECREF(event_obj);
+ Py_DECREF(events);
+ return NULL;
+ }
+ }
+
+ return events;
+}
+
+static PyMethodDef request_methods[] = {
+ {
+ .ml_name = "release",
+ .ml_meth = (PyCFunction)request_release,
+ .ml_flags = METH_NOARGS,
+ },
+ {
+ .ml_name = "get_values",
+ .ml_meth = (PyCFunction)request_get_values,
+ .ml_flags = METH_VARARGS,
+ },
+ {
+ .ml_name = "set_values",
+ .ml_meth = (PyCFunction)request_set_values,
+ .ml_flags = METH_VARARGS,
+ },
+ {
+ .ml_name = "reconfigure_lines",
+ .ml_meth = (PyCFunction)request_reconfigure_lines,
+ .ml_flags = METH_VARARGS,
+ },
+ {
+ .ml_name = "read_edge_events",
+ .ml_meth = (PyCFunction)request_read_edge_events,
+ .ml_flags = METH_VARARGS,
+ },
+ { }
+};
+
+PyTypeObject request_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "gpiod._ext.Request",
+ .tp_basicsize = sizeof(request_object),
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_new = PyType_GenericNew,
+ .tp_init = (initproc)request_init,
+ .tp_finalize = (destructor)request_finalize,
+ .tp_dealloc = (destructor)Py_gpiod_dealloc,
+ .tp_getset = request_getset,
+ .tp_methods = request_methods,
+};
+
+PyObject *Py_gpiod_MakeRequestObject(struct gpiod_line_request *request,
+ size_t event_buffer_size)
+{
+ struct gpiod_edge_event_buffer *buffer;
+ enum gpiod_line_value *values;
+ request_object *req_obj;
+ unsigned int *offsets;
+ size_t num_lines;
+
+ num_lines = gpiod_line_request_get_num_requested_lines(request);
+
+ req_obj = PyObject_New(request_object, &request_type);
+ if (!req_obj)
+ return NULL;
+
+ offsets = PyMem_Calloc(num_lines, sizeof(unsigned int));
+ if (!offsets) {
+ Py_DECREF(req_obj);
+ return NULL;
+ }
+
+ values = PyMem_Calloc(num_lines, sizeof(int));
+ if (!values) {
+ PyMem_Free(offsets);
+ Py_DECREF(req_obj);
+ return NULL;
+ }
+
+ buffer = gpiod_edge_event_buffer_new(event_buffer_size);
+ if (!buffer) {
+ PyMem_Free(values);
+ PyMem_Free(offsets);
+ Py_DECREF(req_obj);
+ return Py_gpiod_SetErrFromErrno();
+ }
+
+ req_obj->request = request;
+ req_obj->offsets = offsets;
+ req_obj->values = values;
+ req_obj->num_lines = num_lines;
+ req_obj->buffer = buffer;
+
+ return (PyObject *)req_obj;
+}
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+from . import _ext
+from .line_info import LineInfo
+from dataclasses import dataclass
+from enum import Enum
+
+__all__ = "InfoEvent"
+
+
+@dataclass(frozen=True, init=False, repr=False)
+class InfoEvent:
+ """
+ Immutable object containing data about a single line info event.
+ """
+
+ class Type(Enum):
+ LINE_REQUESTED = _ext.INFO_EVENT_TYPE_LINE_REQUESTED
+ LINE_RELEASED = _ext.INFO_EVENT_TYPE_LINE_RELEASED
+ LINE_CONFIG_CHANGED = _ext.INFO_EVENT_TYPE_LINE_CONFIG_CHANGED
+
+ event_type: Type
+ timestamp_ns: int
+ line_info: LineInfo
+
+ def __init__(self, event_type: int, timestamp_ns: int, line_info: LineInfo):
+ object.__setattr__(self, "event_type", InfoEvent.Type(event_type))
+ object.__setattr__(self, "timestamp_ns", timestamp_ns)
+ object.__setattr__(self, "line_info", line_info)
+
+ def __str__(self):
+ return "<InfoEvent type={} timestamp_ns={} line_info={}>".format(
+ self.event_type, self.timestamp_ns, self.line_info
+ )
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+from datetime import timedelta
+from select import select
+from typing import Optional, Union
+
+__all__ = []
+
+
+def poll_fd(fd: int, timeout: Optional[Union[timedelta, float]] = None) -> bool:
+ if isinstance(timeout, timedelta):
+ sec = timeout.total_seconds()
+ else:
+ sec = timeout
+
+ readable, _, _ = select([fd], [], [], sec)
+ return True if fd in readable else False
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+
+from . import _ext
+from enum import Enum
+
+__all__ = ["Value", "Direction", "Bias", "Drive", "Edge", "Clock"]
+
+
+class Value(Enum):
+ """Logical line states."""
+
+ INACTIVE = _ext.VALUE_INACTIVE
+ ACTIVE = _ext.VALUE_ACTIVE
+
+
+class Direction(Enum):
+ """Direction settings."""
+
+ AS_IS = _ext.DIRECTION_AS_IS
+ INPUT = _ext.DIRECTION_INPUT
+ OUTPUT = _ext.DIRECTION_OUTPUT
+
+
+class Bias(Enum):
+ """Internal bias settings."""
+
+ AS_IS = _ext.BIAS_AS_IS
+ UNKNOWN = _ext.BIAS_UNKNOWN
+ DISABLED = _ext.BIAS_DISABLED
+ PULL_UP = _ext.BIAS_PULL_UP
+ PULL_DOWN = _ext.BIAS_PULL_DOWN
+
+
+class Drive(Enum):
+ """Drive settings."""
+
+ PUSH_PULL = _ext.DRIVE_PUSH_PULL
+ OPEN_DRAIN = _ext.DRIVE_OPEN_DRAIN
+ OPEN_SOURCE = _ext.DRIVE_OPEN_SOURCE
+
+
+class Edge(Enum):
+ """Edge detection settings."""
+
+ NONE = _ext.EDGE_NONE
+ RISING = _ext.EDGE_RISING
+ FALLING = _ext.EDGE_FALLING
+ BOTH = _ext.EDGE_BOTH
+
+
+class Clock(Enum):
+ """Event clock settings."""
+
+ MONOTONIC = _ext.CLOCK_MONOTONIC
+ REALTIME = _ext.CLOCK_REALTIME
+ HTE = _ext.CLOCK_HTE
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+from . import _ext
+from dataclasses import dataclass
+from datetime import timedelta
+from gpiod.line import Direction, Bias, Drive, Edge, Clock
+
+__all__ = "LineInfo"
+
+
+@dataclass(frozen=True, init=False, repr=False)
+class LineInfo:
+ """
+ Snapshot of a line's status.
+ """
+
+ offset: int
+ name: str
+ used: bool
+ consumer: str
+ direction: Direction
+ active_low: bool
+ bias: Bias
+ drive: Drive
+ edge_detection: Edge
+ event_clock: Clock
+ debounced: bool
+ debounce_period: timedelta
+
+ def __init__(
+ self,
+ offset: int,
+ name: str,
+ used: bool,
+ consumer: str,
+ direction: int,
+ active_low: bool,
+ bias: int,
+ drive: int,
+ edge_detection: int,
+ event_clock: int,
+ debounced: bool,
+ debounce_period_us: int,
+ ):
+ object.__setattr__(self, "offset", offset)
+ object.__setattr__(self, "name", name)
+ object.__setattr__(self, "used", used)
+ object.__setattr__(self, "consumer", consumer)
+ object.__setattr__(self, "direction", Direction(direction))
+ object.__setattr__(self, "active_low", active_low)
+ object.__setattr__(self, "bias", Bias(bias))
+ object.__setattr__(self, "drive", Drive(drive))
+ object.__setattr__(self, "edge_detection", Edge(edge_detection))
+ object.__setattr__(self, "event_clock", Clock(event_clock))
+ object.__setattr__(self, "debounced", debounced)
+ object.__setattr__(
+ self, "debounce_period", timedelta(microseconds=debounce_period_us)
+ )
+
+ def __str__(self):
+ return '<LineInfo offset={} name="{}" used={} consumer="{}" direction={} active_low={} bias={} drive={} edge_detection={} event_clock={} debounced={} debounce_period={}>'.format(
+ self.offset,
+ self.name,
+ self.used,
+ self.consumer,
+ self.direction,
+ self.active_low,
+ self.bias,
+ self.drive,
+ self.edge_detection,
+ self.event_clock,
+ self.debounced,
+ self.debounce_period,
+ )
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+from . import _ext
+from .edge_event import EdgeEvent
+from .exception import RequestReleasedError
+from .internal import poll_fd
+from .line import Value
+from .line_settings import LineSettings, _line_settings_to_ext
+from collections.abc import Iterable
+from datetime import timedelta
+from typing import Optional, Union
+
+__all__ = "LineRequest"
+
+
+class LineRequest:
+ """
+ Stores the context of a set of requested GPIO lines.
+ """
+
+ def __init__(self, req: _ext.Request):
+ """
+ DON'T USE
+
+ LineRequest objects can only be instantiated by a Chip parent. This is
+ not part of stable API.
+ """
+ self._req = req
+
+ def __bool__(self) -> bool:
+ """
+ Boolean conversion for GPIO line requests.
+
+ Returns:
+ True if the request is live and False if it's been released.
+ """
+ return True if self._req else False
+
+ def __enter__(self):
+ """
+ Controlled execution enter callback.
+ """
+ self._check_released()
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ """
+ Controlled execution exit callback.
+ """
+ self.release()
+
+ def _check_released(self) -> None:
+ if not self._req:
+ raise RequestReleasedError()
+
+ def release(self) -> None:
+ """
+ Release this request and free all associated resources. The object must
+ not be used after a call to this method.
+ """
+ self._check_released()
+ self._req.release()
+ self._req = None
+
+ def get_value(self, line: Union[int, str]) -> Value:
+ """
+ Get a single GPIO line value.
+
+ Args:
+ line:
+ Offset or name of the line to get value for.
+
+ Returns:
+ Logical value of the line.
+ """
+ return self.get_values([line])[0]
+
+ def _check_line_name(self, line):
+ if isinstance(line, str):
+ if line not in self._name_map:
+ raise ValueError("unknown line name: {}".format(line))
+
+ return True
+
+ return False
+
+ def get_values(
+ self, lines: Optional[Iterable[Union[int, str]]] = None
+ ) -> list[Value]:
+ """
+ Get values of a set of GPIO lines.
+
+ Args:
+ lines:
+ List of names or offsets of GPIO lines to get values for. Can be
+ None in which case all requested lines will be read.
+
+ Returns:
+ List of logical line values.
+ """
+ self._check_released()
+
+ lines = lines or self._lines
+
+ offsets = [
+ self._name_map[line] if self._check_line_name(line) else line
+ for line in lines
+ ]
+
+ buf = [None] * len(lines)
+
+ self._req.get_values(offsets, buf)
+ return buf
+
+ def set_value(self, line: Union[int, str], value: Value) -> None:
+ """
+ Set the value of a single GPIO line.
+
+ Args:
+ line:
+ Offset or name of the line to set.
+ value:
+ New value.
+ """
+ self.set_values({line: value})
+
+ def set_values(self, values: dict[Union[int, str], Value]) -> None:
+ """
+ Set the values of a subset of GPIO lines.
+
+ Args:
+ values:
+ Dictionary mapping line offsets or names to desired values.
+ """
+ self._check_released()
+
+ mapped = {
+ self._name_map[line] if self._check_line_name(line) else line: values[line]
+ for line in values
+ }
+
+ self._req.set_values(mapped)
+
+ def reconfigure_lines(
+ self, config: dict[tuple[Union[int, str]], LineSettings]
+ ) -> None:
+ """
+ Reconfigure requested lines.
+
+ Args:
+ config
+ Dictionary mapping offsets or names (or tuples thereof) to
+ LineSettings. If None is passed as the value of the mapping,
+ default settings are used.
+ """
+ self._check_released()
+
+ line_cfg = _ext.LineConfig()
+
+ for lines, settings in config.items():
+ if isinstance(lines, int) or isinstance(lines, str):
+ lines = [lines]
+
+ offsets = [
+ self._name_map[line] if self._check_line_name(line) else line
+ for line in lines
+ ]
+
+ line_cfg.add_line_settings(offsets, _line_settings_to_ext(settings))
+
+ self._req.reconfigure_lines(line_cfg)
+
+ def wait_edge_events(
+ self, timeout: Optional[Union[timedelta, float]] = None
+ ) -> bool:
+ """
+ Wait for edge events on any of the requested lines.
+
+ Args:
+ timeout:
+ Wait time limit expressed as either a datetime.timedelta object
+ or the number of seconds stored in a float. If set to 0, the
+ method returns immediately, if set to None it blocks indefinitely.
+
+ Returns:
+ True if events are ready to be read. False on timeout.
+ """
+ self._check_released()
+
+ return poll_fd(self.fd, timeout)
+
+ def read_edge_events(self, max_events: Optional[int] = None) -> list[EdgeEvent]:
+ """
+ Read a number of edge events from a line request.
+
+ Args:
+ max_events:
+ Maximum number of events to read.
+
+ Returns:
+ List of read EdgeEvent objects.
+ """
+ self._check_released()
+
+ return self._req.read_edge_events(max_events)
+
+ def __str__(self):
+ """
+ Return a user-friendly, human-readable description of this request.
+ """
+ if not self._req:
+ return "<LineRequest RELEASED>"
+
+ return '<LineRequest chip="{}" num_lines={} offsets={} fd={}>'.format(
+ self.chip_name, self.num_lines, self.offsets, self.fd
+ )
+
+ @property
+ def chip_name(self) -> str:
+ """
+ Name of the chip this request was made on.
+ """
+ self._check_released()
+ return self._chip_name
+
+ @property
+ def num_lines(self) -> int:
+ """
+ Number of requested lines.
+ """
+ self._check_released()
+ return len(self._offsets)
+
+ @property
+ def offsets(self) -> list[int]:
+ """
+ List of requested offsets. Lines requested by name are mapped to their
+ offsets.
+ """
+ self._check_released()
+ return self._offsets
+
+ @property
+ def lines(self) -> list[Union[int, str]]:
+ """
+ List of requested lines. Lines requested by name are listed as such.
+ """
+ self._check_released()
+ return self._lines
+
+ @property
+ def fd(self) -> int:
+ """
+ File descriptor associated with this request.
+ """
+ self._check_released()
+ return self._req.fd
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+from . import _ext
+from dataclasses import dataclass
+from datetime import timedelta
+from gpiod.line import Direction, Bias, Drive, Edge, Clock, Value
+
+__all__ = "LineSettings"
+
+
+@dataclass(repr=False)
+class LineSettings:
+ """
+ Stores a set of line properties.
+ """
+
+ direction: Direction = Direction.AS_IS
+ edge_detection: Edge = Edge.NONE
+ bias: Bias = Bias.AS_IS
+ drive: Drive = Drive.PUSH_PULL
+ active_low: bool = False
+ debounce_period: timedelta = timedelta()
+ event_clock: Clock = Clock.MONOTONIC
+ output_value: Value = Value.INACTIVE
+
+ # __repr__ generated by @dataclass uses repr for enum members resulting in
+ # an unusable representation as those are of the form: <NAME: $value>
+ def __repr__(self):
+ return "LineSettings(direction={}, edge_detection={} bias={} drive={} active_low={} debounce_period={} event_clock={} output_value={})".format(
+ str(self.direction),
+ str(self.edge_detection),
+ str(self.bias),
+ str(self.drive),
+ self.active_low,
+ repr(self.debounce_period),
+ str(self.event_clock),
+ str(self.output_value),
+ )
+
+ def __str__(self):
+ return "<LineSettings direction={} edge_detection={} bias={} drive={} active_low={} debounce_period={} event_clock={} output_value={}>".format(
+ self.direction,
+ self.edge_detection,
+ self.bias,
+ self.drive,
+ self.active_low,
+ self.debounce_period,
+ self.event_clock,
+ self.output_value,
+ )
+
+
+def _line_settings_to_ext(settings: LineSettings) -> _ext.LineSettings:
+ return _ext.LineSettings(
+ direction=settings.direction.value,
+ edge_detection=settings.edge_detection.value,
+ bias=settings.bias.value,
+ drive=settings.drive.value,
+ active_low=settings.active_low,
+ debounce_period=int(settings.debounce_period.total_seconds() * 1000000),
+ event_clock=settings.event_clock.value,
+ output_value=settings.output_value.value,
+ )
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Linaro Ltd.
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
+
+__version__ = "2.0.1"
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2023 Phil Howard <phil@gadgetoid.com>
+
+[build-system]
+requires = ["setuptools", "wheel", "packaging"]
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+from os import getenv, path, unlink
+from shutil import copytree, rmtree
+
+from setuptools import Extension, find_packages, setup
+from setuptools.command.build_ext import build_ext as orig_build_ext
+from setuptools.command.sdist import log
+from setuptools.command.sdist import sdist as orig_sdist
+from setuptools.errors import BaseError
+
+LINK_SYSTEM_LIBGPIOD = getenv("LINK_SYSTEM_LIBGPIOD") == "1"
+LIBGPIOD_MINIMUM_VERSION = "2.1.0"
+LIBGPIOD_VERSION = getenv("LIBGPIOD_VERSION")
+GPIOD_WITH_TESTS = getenv("GPIOD_WITH_TESTS") == "1"
+SRC_BASE_URL = "https://mirrors.edge.kernel.org/pub/software/libs/libgpiod/"
+TAR_FILENAME = "libgpiod-{version}.tar.gz"
+ASC_FILENAME = "sha256sums.asc"
+SHA256_CHUNK_SIZE = 2048
+
+# __version__
+with open("gpiod/version.py", "r") as fd:
+ exec(fd.read())
+
+
+def sha256(filename):
+ """
+ Return a sha256sum for a specific filename, loading the file in chunks
+ to avoid potentially excessive memory use.
+ """
+ from hashlib import sha256
+
+ sha256sum = sha256()
+ with open(filename, "rb") as f:
+ for chunk in iter(lambda: f.read(SHA256_CHUNK_SIZE), b""):
+ sha256sum.update(chunk)
+
+ return sha256sum.hexdigest()
+
+
+def find_sha256sum(asc_file, tar_filename):
+ """
+ Search through a local copy of sha256sums.asc for a specific filename
+ and return the associated sha256 sum.
+ """
+ with open(asc_file, "r") as f:
+ for line in f:
+ line = line.strip().split(" ")
+ if len(line) == 2 and line[1] == tar_filename:
+ return line[0]
+
+ raise BaseError(f"no signature found for {tar_filename}")
+
+
+def fetch_tarball(command):
+ """
+ Verify the requested LIBGPIOD_VERSION tarball exists in sha256sums.asc,
+ fetch it from https://mirrors.edge.kernel.org/pub/software/libs/libgpiod/
+ and verify its sha256sum.
+
+ If the check passes, extract the tarball and copy the lib and include
+ dirs into our source tree.
+ """
+
+ # If no LIBGPIOD_VERSION is specified in env, just run the command
+ if LIBGPIOD_VERSION is None:
+ return command
+
+ # If LIBGPIOD_VERSION is specified, apply the tarball wrapper
+ def wrapper(self):
+ # Just-in-time import of tarfile and urllib.request so these are
+ # not required for Yocto to build a vendored or linked package
+ import tarfile
+ from tempfile import TemporaryDirectory
+ from urllib.request import urlretrieve
+
+ from packaging.version import Version
+
+ # The "build" frontend will run setup.py twice within the same
+ # temporary output directory. First for "sdist" and then for "wheel"
+ # This would cause the build to fail with dirty "lib" and "include"
+ # directories.
+ # If the version in "libgpiod-version.txt" already matches our
+ # requested tarball, then skip the fetch altogether.
+ try:
+ if open("libgpiod-version.txt", "r").read() == LIBGPIOD_VERSION:
+ log.info(f"skipping tarball fetch")
+ command(self)
+ return
+ except OSError:
+ pass
+
+ # Early exit for build tree with dirty lib/include dirs
+ for check_dir in "lib", "include":
+ if path.isdir(f"./{check_dir}"):
+ raise BaseError(f"refusing to overwrite ./{check_dir}")
+
+ with TemporaryDirectory(prefix="libgpiod-") as temp_dir:
+ tarball_filename = TAR_FILENAME.format(version=LIBGPIOD_VERSION)
+ tarball_url = f"{SRC_BASE_URL}{tarball_filename}"
+ asc_url = f"{SRC_BASE_URL}{ASC_FILENAME}"
+
+ log.info(f"fetching: {asc_url}")
+
+ asc_filename, _ = urlretrieve(asc_url, path.join(temp_dir, ASC_FILENAME))
+
+ tarball_sha256 = find_sha256sum(asc_filename, tarball_filename)
+
+ if Version(LIBGPIOD_VERSION) < Version(LIBGPIOD_MINIMUM_VERSION):
+ raise BaseError(f"requires libgpiod>={LIBGPIOD_MINIMUM_VERSION}")
+
+ log.info(f"fetching: {tarball_url}")
+
+ downloaded_tarball, _ = urlretrieve(
+ tarball_url, path.join(temp_dir, tarball_filename)
+ )
+
+ log.info(f"verifying: {tarball_filename}")
+ if sha256(downloaded_tarball) != tarball_sha256:
+ raise BaseError(f"signature mismatch for {tarball_filename}")
+
+ # Unpack the downloaded tarball
+ log.info(f"unpacking: {tarball_filename}")
+ with tarfile.open(downloaded_tarball) as f:
+ f.extractall(temp_dir)
+
+ # Copy the include and lib directories we need to build libgpiod
+ base_dir = path.join(temp_dir, f"libgpiod-{LIBGPIOD_VERSION}")
+ copytree(path.join(base_dir, "include"), "./include")
+ copytree(path.join(base_dir, "lib"), "./lib")
+
+ # Save the libgpiod version for sdist
+ open("libgpiod-version.txt", "w").write(LIBGPIOD_VERSION)
+
+ # Run the command
+ command(self)
+
+ # Clean up the build directory
+ rmtree("./lib", ignore_errors=True)
+ rmtree("./include", ignore_errors=True)
+ unlink("libgpiod-version.txt")
+
+ return wrapper
+
+
+class build_ext(orig_build_ext):
+ """
+ Wrap build_ext to amend the module sources and settings to build
+ the bindings and gpiod into a combined module when a version is
+ specified and LINK_SYSTEM_LIBGPIOD=1 is not present in env.
+
+ run is wrapped with @fetch_tarball in order to fetch the sources
+ needed to build binary wheels when LIBGPIOD_VERSION is specified, eg:
+
+ LIBGPIOD_VERSION="2.0.2" python3 -m build .
+ """
+
+ @fetch_tarball
+ def run(self):
+ # Try to get the gpiod version from the .txt file included in sdist
+ try:
+ libgpiod_version = open("libgpiod-version.txt", "r").read()
+ except OSError:
+ libgpiod_version = LIBGPIOD_VERSION
+
+ if libgpiod_version and not LINK_SYSTEM_LIBGPIOD:
+ # When building the extension from an sdist with a vendored
+ # amend gpiod._ext sources and settings accordingly.
+ gpiod_ext = self.ext_map["gpiod._ext"]
+ gpiod_ext.sources += [
+ "lib/chip.c",
+ "lib/chip-info.c",
+ "lib/edge-event.c",
+ "lib/info-event.c",
+ "lib/internal.c",
+ "lib/line-config.c",
+ "lib/line-info.c",
+ "lib/line-request.c",
+ "lib/line-settings.c",
+ "lib/misc.c",
+ "lib/request-config.c",
+ ]
+ gpiod_ext.libraries = []
+ gpiod_ext.include_dirs = ["include", "lib", "gpiod/ext"]
+ gpiod_ext.extra_compile_args.append(
+ f'-DGPIOD_VERSION_STR="{libgpiod_version}"',
+ )
+
+ super().run()
+
+ # We don't ever want the module tests directory in our package
+ # since this might include gpiosim._ext or procname._ext from a
+ # previous dirty build tree.
+ rmtree(path.join(self.build_lib, "tests"), ignore_errors=True)
+
+
+class sdist(orig_sdist):
+ """
+ Wrap sdist in order to fetch the libgpiod source files for vendoring
+ into a source distribution.
+
+ run is wrapped with @fetch_tarball in order to fetch the sources
+ needed to build binary wheels when LIBGPIOD_VERSION is specified, eg:
+
+ LIBGPIOD_VERSION="2.0.2" python3 -m build . --sdist
+ """
+
+ @fetch_tarball
+ def run(self):
+ super().run()
+
+
+gpiod_ext = Extension(
+ "gpiod._ext",
+ sources=[
+ "gpiod/ext/chip.c",
+ "gpiod/ext/common.c",
+ "gpiod/ext/line-config.c",
+ "gpiod/ext/line-settings.c",
+ "gpiod/ext/module.c",
+ "gpiod/ext/request.c",
+ ],
+ define_macros=[("_GNU_SOURCE", "1")],
+ libraries=["gpiod"],
+ extra_compile_args=["-Wall", "-Wextra"],
+)
+
+gpiosim_ext = Extension(
+ "tests.gpiosim._ext",
+ sources=["tests/gpiosim/ext.c"],
+ define_macros=[("_GNU_SOURCE", "1")],
+ libraries=["gpiosim"],
+ extra_compile_args=["-Wall", "-Wextra"],
+)
+
+procname_ext = Extension(
+ "tests.procname._ext",
+ sources=["tests/procname/ext.c"],
+ define_macros=[("_GNU_SOURCE", "1")],
+ extra_compile_args=["-Wall", "-Wextra"],
+)
+
+extensions = [gpiod_ext]
+if GPIOD_WITH_TESTS:
+ extensions.append(gpiosim_ext)
+ extensions.append(procname_ext)
+
+setup(
+ name="gpiod",
+ url="https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git",
+ packages=find_packages(exclude=["tests", "tests.*"]),
+ python_requires=">=3.9.0",
+ ext_modules=extensions,
+ cmdclass={"build_ext": build_ext, "sdist": sdist},
+ version=__version__,
+ author="Bartosz Golaszewski",
+ author_email="brgl@bgdev.pl",
+ description="Python bindings for libgpiod",
+ long_description=open("README.md", "r").read(),
+ long_description_content_type="text/markdown",
+ platforms=["linux"],
+ license="LGPLv2.1",
+)
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+SUBDIRS = gpiosim procname
+
+EXTRA_DIST = \
+ helpers.py \
+ __init__.py \
+ __main__.py \
+ tests_chip_info.py \
+ tests_chip.py \
+ tests_edge_event.py \
+ tests_info_event.py \
+ tests_line_info.py \
+ tests_line_request.py \
+ tests_line_settings.py \
+ tests_module.py
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+import os
+import unittest
+
+from distutils.version import LooseVersion
+
+required_kernel_version = LooseVersion("5.19.0")
+current_version = LooseVersion(os.uname().release.split("-")[0])
+
+if current_version < required_kernel_version:
+ raise NotImplementedError(
+ "linux kernel version must be at least {} - got {}".format(
+ required_kernel_version, current_version
+ )
+ )
--- /dev/null
+#!/usr/bin/python3
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+import unittest
+
+from .tests_chip import *
+from .tests_chip_info import *
+from .tests_edge_event import *
+from .tests_info_event import *
+from .tests_line_info import *
+from .tests_line_settings import *
+from .tests_module import *
+from .tests_line_request import *
+
+from . import procname
+
+procname.set_process_name("python-gpiod")
+
+unittest.main()
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+EXTRA_DIST = \
+ chip.py \
+ ext.c \
+ __init__.py
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+from .chip import Chip
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+from . import _ext
+from enum import Enum
+from typing import Optional
+
+
+class Chip:
+ """
+ Represents a simulated GPIO chip.
+ """
+
+ class Pull(Enum):
+ DOWN = _ext.PULL_DOWN
+ UP = _ext.PULL_UP
+
+ class Value(Enum):
+ INACTIVE = _ext.VALUE_INACTIVE
+ ACTIVE = _ext.VALUE_ACTIVE
+
+ class Direction(Enum):
+ INPUT = _ext.DIRECTION_INPUT
+ OUTPUT_HIGH = _ext.DIRECTION_OUTPUT_HIGH
+ OUTPUT_LOW = _ext.DIRECTION_OUTPUT_LOW
+
+ def __init__(
+ self,
+ label: Optional[str] = None,
+ num_lines: Optional[int] = None,
+ line_names: Optional[dict[int, str]] = None,
+ hogs: Optional[dict[int, tuple[str, Direction]]] = None,
+ ):
+ self._chip = _ext.Chip()
+
+ if label:
+ self._chip.set_label(label)
+
+ if num_lines:
+ self._chip.set_num_lines(num_lines)
+
+ if line_names:
+ for off, name in line_names.items():
+ self._chip.set_line_name(off, name)
+
+ if hogs:
+ for off, (name, direction) in hogs.items():
+ self._chip.set_hog(off, name, direction.value)
+
+ self._chip.enable()
+
+ def get_value(self, offset: int) -> Value:
+ val = self._chip.get_value(offset)
+ return Chip.Value(val)
+
+ def set_pull(self, offset: int, pull: Pull) -> None:
+ self._chip.set_pull(offset, pull.value)
+
+ @property
+ def dev_path(self) -> str:
+ return self._chip.dev_path
+
+ @property
+ def name(self) -> str:
+ return self._chip.name
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <gpiosim.h>
+#include <Python.h>
+
+struct module_const {
+ const char *name;
+ long val;
+};
+
+static const struct module_const module_constants[] = {
+ {
+ .name = "PULL_DOWN",
+ .val = GPIOSIM_PULL_DOWN,
+ },
+ {
+ .name = "PULL_UP",
+ .val = GPIOSIM_PULL_UP,
+ },
+ {
+ .name = "VALUE_INACTIVE",
+ .val = GPIOSIM_VALUE_INACTIVE,
+ },
+ {
+ .name = "VALUE_ACTIVE",
+ .val = GPIOSIM_VALUE_ACTIVE,
+ },
+ {
+ .name = "DIRECTION_INPUT",
+ .val = GPIOSIM_DIRECTION_INPUT,
+ },
+ {
+ .name = "DIRECTION_OUTPUT_HIGH",
+ .val = GPIOSIM_DIRECTION_OUTPUT_HIGH,
+ },
+ {
+ .name = "DIRECTION_OUTPUT_LOW",
+ .val = GPIOSIM_DIRECTION_OUTPUT_LOW,
+ },
+ { }
+};
+
+struct module_state {
+ struct gpiosim_ctx *sim_ctx;
+};
+
+static void free_module_state(void *mod)
+{
+ struct module_state *state = PyModule_GetState((PyObject *)mod);
+
+ if (state->sim_ctx)
+ gpiosim_ctx_unref(state->sim_ctx);
+}
+
+static PyModuleDef module_def = {
+ PyModuleDef_HEAD_INIT,
+ .m_name = "gpiosim._ext",
+ .m_size = sizeof(struct module_state),
+ .m_free = free_module_state,
+};
+
+typedef struct {
+ PyObject_HEAD
+ struct gpiosim_dev *dev;
+ struct gpiosim_bank *bank;
+} chip_object;
+
+static int chip_init(chip_object *self,
+ PyObject *Py_UNUSED(ignored0),
+ PyObject *Py_UNUSED(ignored1))
+{
+ struct module_state *state;
+ PyObject *mod;
+
+ mod = PyState_FindModule(&module_def);
+ if (!mod)
+ return -1;
+
+ state = PyModule_GetState(mod);
+
+ self->dev = gpiosim_dev_new(state->sim_ctx);
+ if (!self->dev) {
+ PyErr_SetFromErrno(PyExc_OSError);
+ return -1;
+ }
+
+ self->bank = gpiosim_bank_new(self->dev);
+ if (!self->bank) {
+ PyErr_SetFromErrno(PyExc_OSError);
+ return -1;
+ }
+
+ return 0;
+}
+
+static void chip_finalize(chip_object *self)
+{
+ if (self->bank)
+ gpiosim_bank_unref(self->bank);
+
+ if (self->dev) {
+ if (gpiosim_dev_is_live(self->dev))
+ gpiosim_dev_disable(self->dev);
+
+ gpiosim_dev_unref(self->dev);
+ }
+}
+
+static void chip_dealloc(PyObject *self)
+{
+ int ret;
+
+ ret = PyObject_CallFinalizerFromDealloc(self);
+ if (ret < 0)
+ return;
+
+ PyObject_Del(self);
+}
+
+static PyObject *chip_dev_path(chip_object *self, void *Py_UNUSED(ignored))
+{
+ return PyUnicode_FromString(gpiosim_bank_get_dev_path(self->bank));
+}
+
+static PyObject *chip_name(chip_object *self, void *Py_UNUSED(ignored))
+{
+ return PyUnicode_FromString(gpiosim_bank_get_chip_name(self->bank));
+}
+
+static PyGetSetDef chip_getset[] = {
+ {
+ .name = "dev_path",
+ .get = (getter)chip_dev_path,
+ },
+ {
+ .name = "name",
+ .get = (getter)chip_name,
+ },
+ { }
+};
+
+static PyObject *chip_set_label(chip_object *self, PyObject *args)
+{
+ const char *label;
+ int ret;
+
+ ret = PyArg_ParseTuple(args, "s", &label);
+ if (!ret)
+ return NULL;
+
+ ret = gpiosim_bank_set_label(self->bank, label);
+ if (ret)
+ return PyErr_SetFromErrno(PyExc_OSError);
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *chip_set_num_lines(chip_object *self, PyObject *args)
+{
+ unsigned int num_lines;
+ int ret;
+
+ ret = PyArg_ParseTuple(args, "I", &num_lines);
+ if (!ret)
+ return NULL;
+
+ ret = gpiosim_bank_set_num_lines(self->bank, num_lines);
+ if (ret)
+ return PyErr_SetFromErrno(PyExc_OSError);
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *chip_set_line_name(chip_object *self, PyObject *args)
+{
+ unsigned int offset;
+ const char *name;
+ int ret;
+
+ ret = PyArg_ParseTuple(args, "Is", &offset, &name);
+ if (!ret)
+ return NULL;
+
+ ret = gpiosim_bank_set_line_name(self->bank, offset, name);
+ if (ret)
+ return PyErr_SetFromErrno(PyExc_OSError);
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *chip_set_hog(chip_object *self, PyObject *args)
+{
+ unsigned int offset;
+ const char *name;
+ int ret, dir;
+
+ ret = PyArg_ParseTuple(args, "Isi", &offset, &name, &dir);
+ if (!ret)
+ return NULL;
+
+ ret = gpiosim_bank_hog_line(self->bank, offset, name, dir);
+ if (ret)
+ return PyErr_SetFromErrno(PyExc_OSError);
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *chip_enable(chip_object *self, PyObject *Py_UNUSED(args))
+{
+ int ret;
+
+ ret = gpiosim_dev_enable(self->dev);
+ if (ret)
+ return PyErr_SetFromErrno(PyExc_OSError);
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *chip_get_value(chip_object *self, PyObject *args)
+{
+ unsigned int offset;
+ int ret, val;
+
+ ret = PyArg_ParseTuple(args, "I", &offset);
+ if (!ret)
+ return NULL;
+
+ val = gpiosim_bank_get_value(self->bank, offset);
+ if (val < 0)
+ return PyErr_SetFromErrno(PyExc_OSError);
+
+ return PyLong_FromLong(val);
+}
+
+static PyObject *chip_set_pull(chip_object *self, PyObject *args)
+{
+ unsigned int offset;
+ int ret, pull;
+
+ ret = PyArg_ParseTuple(args, "II", &offset, &pull);
+ if (!ret)
+ return NULL;
+
+ ret = gpiosim_bank_set_pull(self->bank, offset, pull);
+ if (ret)
+ return PyErr_SetFromErrno(PyExc_OSError);
+
+ Py_RETURN_NONE;
+}
+
+static PyMethodDef chip_methods[] = {
+ {
+ .ml_name = "set_label",
+ .ml_meth = (PyCFunction)chip_set_label,
+ .ml_flags = METH_VARARGS,
+ },
+ {
+ .ml_name = "set_num_lines",
+ .ml_meth = (PyCFunction)chip_set_num_lines,
+ .ml_flags = METH_VARARGS,
+ },
+ {
+ .ml_name = "set_line_name",
+ .ml_meth = (PyCFunction)chip_set_line_name,
+ .ml_flags = METH_VARARGS,
+ },
+ {
+ .ml_name = "set_hog",
+ .ml_meth = (PyCFunction)chip_set_hog,
+ .ml_flags = METH_VARARGS,
+ },
+ {
+ .ml_name = "enable",
+ .ml_meth = (PyCFunction)chip_enable,
+ .ml_flags = METH_NOARGS,
+ },
+ {
+ .ml_name = "get_value",
+ .ml_meth = (PyCFunction)chip_get_value,
+ .ml_flags = METH_VARARGS,
+ },
+ {
+ .ml_name = "set_pull",
+ .ml_meth = (PyCFunction)chip_set_pull,
+ .ml_flags = METH_VARARGS,
+ },
+ { }
+};
+
+static PyTypeObject chip_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "gpiosim.Chip",
+ .tp_basicsize = sizeof(chip_object),
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_new = PyType_GenericNew,
+ .tp_init = (initproc)chip_init,
+ .tp_finalize = (destructor)chip_finalize,
+ .tp_dealloc = (destructor)chip_dealloc,
+ .tp_methods = chip_methods,
+ .tp_getset = chip_getset,
+};
+
+PyMODINIT_FUNC PyInit__ext(void)
+{
+ const struct module_const *modconst;
+ struct module_state *state;
+ PyObject *module;
+ int ret;
+
+ module = PyModule_Create(&module_def);
+ if (!module)
+ return NULL;
+
+ ret = PyState_AddModule(module, &module_def);
+ if (ret) {
+ Py_DECREF(module);
+ return NULL;
+ }
+
+ state = PyModule_GetState(module);
+
+ state->sim_ctx = gpiosim_ctx_new();
+ if (!state->sim_ctx) {
+ Py_DECREF(module);
+ return PyErr_SetFromErrno(PyExc_OSError);
+ }
+
+ ret = PyModule_AddType(module, &chip_type);
+ if (ret) {
+ Py_DECREF(module);
+ return NULL;
+ }
+
+ for (modconst = module_constants; modconst->name; modconst++) {
+ ret = PyModule_AddIntConstant(module,
+ modconst->name, modconst->val);
+ if (ret) {
+ Py_DECREF(module);
+ return NULL;
+ }
+ }
+
+ return module;
+}
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+import os
+
+
+class LinkGuard:
+ def __init__(self, src, dst):
+ self.src = src
+ self.dst = dst
+
+ def __enter__(self):
+ os.symlink(self.src, self.dst)
+
+ def __exit__(self, type, val, tb):
+ os.unlink(self.dst)
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+EXTRA_DIST = \
+ ext.c \
+ __init__.py
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+from ._ext import set_process_name
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <Python.h>
+#include <sys/prctl.h>
+
+static PyObject *
+module_set_process_name(PyObject *Py_UNUSED(self), PyObject *args)
+{
+ const char *name;
+ int ret;
+
+ ret = PyArg_ParseTuple(args, "s", &name);
+ if (!ret)
+ return NULL;
+
+ ret = prctl(PR_SET_NAME, name);
+ if (ret)
+ return PyErr_SetFromErrno(PyExc_OSError);
+
+ Py_RETURN_NONE;
+}
+
+static PyMethodDef module_methods[] = {
+ {
+ .ml_name = "set_process_name",
+ .ml_meth = (PyCFunction)module_set_process_name,
+ .ml_flags = METH_VARARGS,
+ },
+ { }
+};
+
+static PyModuleDef module_def = {
+ PyModuleDef_HEAD_INIT,
+ .m_name = "procname._ext",
+ .m_methods = module_methods,
+};
+
+PyMODINIT_FUNC PyInit__ext(void)
+{
+ return PyModule_Create(&module_def);
+}
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+import errno
+import gpiod
+import os
+
+from . import gpiosim
+from .helpers import LinkGuard
+from unittest import TestCase
+
+
+class ChipConstructor(TestCase):
+ def test_open_existing_chip(self):
+ sim = gpiosim.Chip()
+
+ with gpiod.Chip(sim.dev_path):
+ pass
+
+ def test_open_existing_chip_with_keyword(self):
+ sim = gpiosim.Chip()
+
+ with gpiod.Chip(path=sim.dev_path):
+ pass
+
+ def test_open_chip_by_link(self):
+ link = "/tmp/gpiod-py-test-link.{}".format(os.getpid())
+ sim = gpiosim.Chip()
+
+ with LinkGuard(sim.dev_path, link):
+ with gpiod.Chip(link):
+ pass
+
+ def test_open_nonexistent_chip(self):
+ with self.assertRaises(OSError) as ex:
+ gpiod.Chip("/dev/nonexistent")
+
+ self.assertEqual(ex.exception.errno, errno.ENOENT)
+
+ def test_open_not_a_character_device(self):
+ with self.assertRaises(OSError) as ex:
+ gpiod.Chip("/tmp")
+
+ self.assertEqual(ex.exception.errno, errno.ENOTTY)
+
+ def test_open_not_a_gpio_device(self):
+ with self.assertRaises(OSError) as ex:
+ gpiod.Chip("/dev/null")
+
+ self.assertEqual(ex.exception.errno, errno.ENODEV)
+
+ def test_missing_path(self):
+ with self.assertRaises(TypeError):
+ gpiod.Chip()
+
+ def test_invalid_type_for_path(self):
+ with self.assertRaises(TypeError):
+ gpiod.Chip(4)
+
+
+class ChipBooleanConversion(TestCase):
+ def test_chip_bool(self):
+ sim = gpiosim.Chip()
+ chip = gpiod.Chip(sim.dev_path)
+ self.assertTrue(chip)
+ chip.close()
+ self.assertFalse(chip)
+
+
+class ChipProperties(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip()
+ self.chip = gpiod.Chip(self.sim.dev_path)
+
+ def tearDown(self):
+ self.chip.close()
+ self.sim = None
+
+ def test_get_chip_path(self):
+ self.assertEqual(self.sim.dev_path, self.chip.path)
+
+ def test_get_fd(self):
+ self.assertGreaterEqual(self.chip.fd, 0)
+
+ def test_properties_are_immutable(self):
+ with self.assertRaises(AttributeError):
+ self.chip.path = "foobar"
+
+ with self.assertRaises(AttributeError):
+ self.chip.fd = 4
+
+
+class ChipDevPathFromLink(TestCase):
+ def test_dev_path_open_by_link(self):
+ sim = gpiosim.Chip()
+ link = "/tmp/gpiod-py-test-link.{}".format(os.getpid())
+
+ with LinkGuard(sim.dev_path, link):
+ with gpiod.Chip(link) as chip:
+ self.assertEqual(chip.path, link)
+
+
+class ChipMapLine(TestCase):
+ def test_lookup_by_name_good(self):
+ sim = gpiosim.Chip(
+ num_lines=8, line_names={1: "foo", 2: "bar", 4: "baz", 5: "xyz"}
+ )
+
+ with gpiod.Chip(sim.dev_path) as chip:
+ self.assertEqual(chip.line_offset_from_id("baz"), 4)
+
+ def test_lookup_by_name_good_keyword_argument(self):
+ sim = gpiosim.Chip(
+ num_lines=8, line_names={1: "foo", 2: "bar", 4: "baz", 5: "xyz"}
+ )
+
+ with gpiod.Chip(sim.dev_path) as chip:
+ self.assertEqual(chip.line_offset_from_id(id="baz"), 4)
+
+ def test_lookup_bad_name(self):
+ sim = gpiosim.Chip(
+ num_lines=8, line_names={1: "foo", 2: "bar", 4: "baz", 5: "xyz"}
+ )
+
+ with gpiod.Chip(sim.dev_path) as chip:
+ with self.assertRaises(FileNotFoundError):
+ chip.line_offset_from_id("nonexistent")
+
+ def test_lookup_bad_offset(self):
+ sim = gpiosim.Chip()
+
+ with gpiod.Chip(sim.dev_path) as chip:
+ with self.assertRaises(ValueError):
+ chip.line_offset_from_id(4)
+
+ def test_lookup_bad_offset_as_string(self):
+ sim = gpiosim.Chip()
+
+ with gpiod.Chip(sim.dev_path) as chip:
+ with self.assertRaises(ValueError):
+ chip.line_offset_from_id("4")
+
+ def test_duplicate_names(self):
+ sim = gpiosim.Chip(
+ num_lines=8, line_names={1: "foo", 2: "bar", 4: "baz", 5: "bar"}
+ )
+
+ with gpiod.Chip(sim.dev_path) as chip:
+ self.assertEqual(chip.line_offset_from_id("bar"), 2)
+
+ def test_integer_offsets(self):
+ sim = gpiosim.Chip(num_lines=8, line_names={1: "foo", 2: "bar", 6: "baz"})
+
+ with gpiod.Chip(sim.dev_path) as chip:
+ self.assertEqual(chip.line_offset_from_id(4), 4)
+ self.assertEqual(chip.line_offset_from_id(1), 1)
+
+ def test_offsets_as_string(self):
+ sim = gpiosim.Chip(num_lines=8, line_names={1: "foo", 2: "bar", 7: "6"})
+
+ with gpiod.Chip(sim.dev_path) as chip:
+ self.assertEqual(chip.line_offset_from_id("2"), 2)
+ self.assertEqual(chip.line_offset_from_id("6"), 7)
+
+
+class ClosedChipCannotBeUsed(TestCase):
+ def test_close_chip_and_try_to_use_it(self):
+ sim = gpiosim.Chip(label="foobar")
+
+ chip = gpiod.Chip(sim.dev_path)
+ chip.close()
+
+ with self.assertRaises(gpiod.ChipClosedError):
+ chip.path
+
+ def test_close_chip_and_try_controlled_execution(self):
+ sim = gpiosim.Chip()
+
+ chip = gpiod.Chip(sim.dev_path)
+ chip.close()
+
+ with self.assertRaises(gpiod.ChipClosedError):
+ with chip:
+ chip.fd
+
+ def test_close_chip_twice(self):
+ sim = gpiosim.Chip(label="foobar")
+ chip = gpiod.Chip(sim.dev_path)
+ chip.close()
+
+ with self.assertRaises(gpiod.ChipClosedError):
+ chip.close()
+
+
+class StringRepresentation(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(num_lines=4, label="foobar")
+ self.chip = gpiod.Chip(self.sim.dev_path)
+
+ def tearDown(self):
+ self.chip.close()
+ self.sim = None
+
+ def test_repr(self):
+ self.assertEqual(repr(self.chip), 'Chip("{}")'.format(self.sim.dev_path))
+
+ def test_str(self):
+ info = self.chip.get_info()
+ self.assertEqual(
+ str(self.chip),
+ '<Chip path="{}" fd={} info=<ChipInfo name="{}" label="foobar" num_lines=4>>'.format(
+ self.sim.dev_path, self.chip.fd, info.name
+ ),
+ )
+
+
+class StringRepresentationClosed(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(num_lines=4, label="foobar")
+ self.chip = gpiod.Chip(self.sim.dev_path)
+
+ def tearDown(self):
+ self.sim = None
+
+ def test_repr_closed(self):
+ self.chip.close()
+ self.assertEqual(repr(self.chip), "<Chip CLOSED>")
+
+ def test_str_closed(self):
+ self.chip.close()
+ self.assertEqual(str(self.chip), "<Chip CLOSED>")
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+import gpiod
+
+from . import gpiosim
+from unittest import TestCase
+
+
+class ChipInfoProperties(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(label="foobar", num_lines=16)
+ self.chip = gpiod.Chip(self.sim.dev_path)
+ self.info = self.chip.get_info()
+
+ def tearDown(self):
+ self.info = None
+ self.chip.close()
+ self.chip = None
+ self.sim = None
+
+ def test_chip_info_name(self):
+ self.assertEqual(self.info.name, self.sim.name)
+
+ def test_chip_info_label(self):
+ self.assertEqual(self.info.label, "foobar")
+
+ def test_chip_info_num_lines(self):
+ self.assertEqual(self.info.num_lines, 16)
+
+ def test_chip_info_properties_are_immutable(self):
+ with self.assertRaises(AttributeError):
+ self.info.name = "foobar"
+
+ with self.assertRaises(AttributeError):
+ self.info.num_lines = 4
+
+ with self.assertRaises(AttributeError):
+ self.info.label = "foobar"
+
+
+class ChipInfoStringRepresentation(TestCase):
+ def test_chip_info_str(self):
+ sim = gpiosim.Chip(label="foobar", num_lines=16)
+
+ with gpiod.Chip(sim.dev_path) as chip:
+ info = chip.get_info()
+
+ self.assertEqual(
+ str(info),
+ '<ChipInfo name="{}" label="foobar" num_lines=16>'.format(sim.name),
+ )
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+import gpiod
+import time
+
+from . import gpiosim
+from datetime import timedelta
+from functools import partial
+from gpiod.line import Direction, Edge
+from threading import Thread
+from unittest import TestCase
+
+EventType = gpiod.EdgeEvent.Type
+Pull = gpiosim.Chip.Pull
+
+
+class EdgeEventWaitTimeout(TestCase):
+ def test_event_wait_timeout(self):
+ sim = gpiosim.Chip()
+
+ with gpiod.request_lines(
+ sim.dev_path,
+ {0: gpiod.LineSettings(edge_detection=Edge.BOTH)},
+ ) as req:
+ self.assertEqual(req.wait_edge_events(timedelta(microseconds=10000)), False)
+
+ def test_event_wait_timeout_float(self):
+ sim = gpiosim.Chip()
+
+ with gpiod.request_lines(
+ sim.dev_path,
+ {0: gpiod.LineSettings(edge_detection=Edge.BOTH)},
+ ) as req:
+ self.assertEqual(req.wait_edge_events(0.01), False)
+
+
+class EdgeEventInvalidConfig(TestCase):
+ def test_output_mode_and_edge_detection(self):
+ sim = gpiosim.Chip()
+
+ with self.assertRaises(ValueError):
+ gpiod.request_lines(
+ sim.dev_path,
+ {
+ 0: gpiod.LineSettings(
+ direction=Direction.OUTPUT, edge_detection=Edge.BOTH
+ )
+ },
+ )
+
+
+class WaitingForEdgeEvents(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(num_lines=8)
+ self.thread = None
+
+ def tearDown(self):
+ if self.thread:
+ self.thread.join()
+ del self.thread
+ self.sim = None
+
+ def trigger_falling_and_rising_edge(self, offset):
+ time.sleep(0.05)
+ self.sim.set_pull(offset, Pull.UP)
+ time.sleep(0.05)
+ self.sim.set_pull(offset, Pull.DOWN)
+
+ def trigger_rising_edge_events_on_two_offsets(self, offset0, offset1):
+ time.sleep(0.05)
+ self.sim.set_pull(offset0, Pull.UP)
+ time.sleep(0.05)
+ self.sim.set_pull(offset1, Pull.UP)
+
+ def test_both_edge_events(self):
+ with gpiod.request_lines(
+ self.sim.dev_path, {2: gpiod.LineSettings(edge_detection=Edge.BOTH)}
+ ) as req:
+ self.thread = Thread(
+ target=partial(self.trigger_falling_and_rising_edge, 2)
+ )
+ self.thread.start()
+
+ self.assertTrue(req.wait_edge_events(timedelta(seconds=1)))
+ events = req.read_edge_events()
+ self.assertEqual(len(events), 1)
+ event = events[0]
+ self.assertEqual(event.event_type, EventType.RISING_EDGE)
+ self.assertEqual(event.line_offset, 2)
+ ts_rising = event.timestamp_ns
+
+ self.assertTrue(req.wait_edge_events(timedelta(seconds=1)))
+ events = req.read_edge_events()
+ self.assertEqual(len(events), 1)
+ event = events[0]
+ self.assertEqual(event.event_type, EventType.FALLING_EDGE)
+ self.assertEqual(event.line_offset, 2)
+ ts_falling = event.timestamp_ns
+
+ self.assertGreater(ts_falling, ts_rising)
+
+ def test_rising_edge_event(self):
+ with gpiod.request_lines(
+ self.sim.dev_path, {6: gpiod.LineSettings(edge_detection=Edge.RISING)}
+ ) as req:
+ self.thread = Thread(
+ target=partial(self.trigger_falling_and_rising_edge, 6)
+ )
+ self.thread.start()
+
+ self.assertTrue(req.wait_edge_events(timedelta(seconds=1)))
+ events = req.read_edge_events()
+ self.assertEqual(len(events), 1)
+ event = events[0]
+ self.assertEqual(event.event_type, EventType.RISING_EDGE)
+ self.assertEqual(event.line_offset, 6)
+
+ self.assertFalse(req.wait_edge_events(timedelta(microseconds=10000)))
+
+ def test_rising_edge_event(self):
+ with gpiod.request_lines(
+ self.sim.dev_path, {6: gpiod.LineSettings(edge_detection=Edge.FALLING)}
+ ) as req:
+ self.thread = Thread(
+ target=partial(self.trigger_falling_and_rising_edge, 6)
+ )
+ self.thread.start()
+
+ self.assertTrue(req.wait_edge_events(timedelta(seconds=1)))
+ events = req.read_edge_events()
+ self.assertEqual(len(events), 1)
+ event = events[0]
+ self.assertEqual(event.event_type, EventType.FALLING_EDGE)
+ self.assertEqual(event.line_offset, 6)
+
+ self.assertFalse(req.wait_edge_events(timedelta(microseconds=10000)))
+
+ def test_sequence_numbers(self):
+ with gpiod.request_lines(
+ self.sim.dev_path, {(2, 4): gpiod.LineSettings(edge_detection=Edge.BOTH)}
+ ) as req:
+ self.thread = Thread(
+ target=partial(self.trigger_rising_edge_events_on_two_offsets, 2, 4)
+ )
+ self.thread.start()
+
+ self.assertTrue(req.wait_edge_events(timedelta(seconds=1)))
+ events = req.read_edge_events()
+ self.assertEqual(len(events), 1)
+ event = events[0]
+ self.assertEqual(event.event_type, EventType.RISING_EDGE)
+ self.assertEqual(event.line_offset, 2)
+ self.assertEqual(event.global_seqno, 1)
+ self.assertEqual(event.line_seqno, 1)
+
+ self.assertTrue(req.wait_edge_events(timedelta(seconds=1)))
+ events = req.read_edge_events()
+ self.assertEqual(len(events), 1)
+ event = events[0]
+ self.assertEqual(event.event_type, EventType.RISING_EDGE)
+ self.assertEqual(event.line_offset, 4)
+ self.assertEqual(event.global_seqno, 2)
+ self.assertEqual(event.line_seqno, 1)
+
+
+class ReadingMultipleEdgeEvents(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(num_lines=8)
+ self.request = gpiod.request_lines(
+ self.sim.dev_path, {1: gpiod.LineSettings(edge_detection=Edge.BOTH)}
+ )
+ self.line_seqno = 1
+ self.global_seqno = 1
+ self.sim.set_pull(1, Pull.UP)
+ time.sleep(0.05)
+ self.sim.set_pull(1, Pull.DOWN)
+ time.sleep(0.05)
+ self.sim.set_pull(1, Pull.UP)
+ time.sleep(0.05)
+
+ def tearDown(self):
+ self.request.release()
+ del self.request
+ del self.sim
+
+ def test_read_multiple_events(self):
+ self.assertTrue(self.request.wait_edge_events(timedelta(seconds=1)))
+ events = self.request.read_edge_events()
+ self.assertEqual(len(events), 3)
+
+ for event in events:
+ self.assertEqual(event.line_offset, 1)
+ self.assertEqual(event.line_seqno, self.line_seqno)
+ self.assertEqual(event.global_seqno, self.global_seqno)
+ self.line_seqno += 1
+ self.global_seqno += 1
+
+
+class EdgeEventStringRepresentation(TestCase):
+ def test_edge_event_str(self):
+ sim = gpiosim.Chip()
+
+ with gpiod.request_lines(
+ path=sim.dev_path, config={0: gpiod.LineSettings(edge_detection=Edge.BOTH)}
+ ) as req:
+ sim.set_pull(0, Pull.UP)
+ event = req.read_edge_events()[0]
+ self.assertRegex(
+ str(event),
+ "<EdgeEvent type=Type\.RISING_EDGE timestamp_ns=[0-9]+ line_offset=0 global_seqno=1 line_seqno=1>",
+ )
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+import datetime
+import errno
+import gpiod
+import threading
+import time
+import unittest
+
+from . import gpiosim
+from dataclasses import FrozenInstanceError
+from functools import partial
+from gpiod.line import Direction
+from unittest import TestCase
+
+EventType = gpiod.InfoEvent.Type
+
+
+class InfoEventDataclassBehavior(TestCase):
+ def test_info_event_props_are_frozen(self):
+ sim = gpiosim.Chip()
+
+ with gpiod.Chip(sim.dev_path) as chip:
+ chip.watch_line_info(0)
+ with chip.request_lines(config={0: None}) as request:
+ self.assertTrue(chip.wait_info_event(datetime.timedelta(seconds=1)))
+ event = chip.read_info_event()
+
+ with self.assertRaises(FrozenInstanceError):
+ event.event_type = 4
+
+ with self.assertRaises(FrozenInstanceError):
+ event.timestamp_ns = 4
+
+ with self.assertRaises(FrozenInstanceError):
+ event.line_info = 4
+
+
+def request_reconfigure_release_line(chip_path, offset):
+ time.sleep(0.1)
+ with gpiod.request_lines(chip_path, config={offset: None}) as request:
+ time.sleep(0.1)
+ request.reconfigure_lines(
+ config={offset: gpiod.LineSettings(direction=Direction.OUTPUT)}
+ )
+ time.sleep(0.1)
+
+
+class WatchingInfoEventWorks(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(num_lines=8, line_names={4: "foobar"})
+ self.chip = gpiod.Chip(self.sim.dev_path)
+ self.thread = None
+
+ def tearDown(self):
+ if self.thread:
+ self.thread.join()
+ self.thread = None
+
+ self.chip.close()
+ self.chip = None
+ self.sim = None
+
+ def test_watch_line_info_returns_line_info(self):
+ info = self.chip.watch_line_info(7)
+ self.assertEqual(info.offset, 7)
+
+ def test_watch_line_info_keyword_argument(self):
+ info = self.chip.watch_line_info(line=7)
+
+ def test_watch_line_info_offset_out_of_range(self):
+ with self.assertRaises(ValueError):
+ self.chip.watch_line_info(8)
+
+ def test_watch_line_info_no_arguments(self):
+ with self.assertRaises(TypeError):
+ self.chip.watch_line_info()
+
+ def test_watch_line_info_by_line_name(self):
+ self.chip.watch_line_info("foobar")
+
+ def test_watch_line_info_invalid_argument_type(self):
+ with self.assertRaises(TypeError):
+ self.chip.watch_line_info(None)
+
+ def test_wait_for_event_timeout(self):
+ info = self.chip.watch_line_info(7)
+ self.assertFalse(
+ self.chip.wait_info_event(datetime.timedelta(microseconds=10000))
+ )
+
+ def test_request_reconfigure_release_events(self):
+ info = self.chip.watch_line_info(7)
+ self.assertEqual(info.direction, Direction.INPUT)
+
+ self.thread = threading.Thread(
+ target=partial(request_reconfigure_release_line, self.sim.dev_path, 7)
+ )
+ self.thread.start()
+
+ self.assertTrue(self.chip.wait_info_event(datetime.timedelta(seconds=1)))
+ event = self.chip.read_info_event()
+ self.assertEqual(event.event_type, EventType.LINE_REQUESTED)
+ self.assertEqual(event.line_info.offset, 7)
+ self.assertEqual(event.line_info.direction, Direction.INPUT)
+ ts_req = event.timestamp_ns
+
+ # Check that we can use a float directly instead of datetime.timedelta.
+ self.assertTrue(self.chip.wait_info_event(1.0))
+ event = self.chip.read_info_event()
+ self.assertEqual(event.event_type, EventType.LINE_CONFIG_CHANGED)
+ self.assertEqual(event.line_info.offset, 7)
+ self.assertEqual(event.line_info.direction, Direction.OUTPUT)
+ ts_rec = event.timestamp_ns
+
+ self.assertTrue(self.chip.wait_info_event(datetime.timedelta(seconds=1)))
+ event = self.chip.read_info_event()
+ self.assertEqual(event.event_type, EventType.LINE_RELEASED)
+ self.assertEqual(event.line_info.offset, 7)
+ self.assertEqual(event.line_info.direction, Direction.OUTPUT)
+ ts_rel = event.timestamp_ns
+
+ # No more events.
+ self.assertFalse(
+ self.chip.wait_info_event(datetime.timedelta(microseconds=10000))
+ )
+
+ # Check timestamps are really monotonic.
+ self.assertGreater(ts_rel, ts_rec)
+ self.assertGreater(ts_rec, ts_req)
+
+
+class UnwatchingLineInfo(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(num_lines=8, line_names={4: "foobar"})
+ self.chip = gpiod.Chip(self.sim.dev_path)
+
+ def tearDown(self):
+ self.chip.close()
+ self.chip = None
+ self.sim = None
+
+ def test_unwatch_line_info(self):
+ self.chip.watch_line_info(0)
+ with self.chip.request_lines(config={0: None}) as request:
+ self.assertTrue(self.chip.wait_info_event(datetime.timedelta(seconds=1)))
+ event = self.chip.read_info_event()
+ self.assertEqual(event.event_type, EventType.LINE_REQUESTED)
+ self.chip.unwatch_line_info(0)
+
+ self.assertFalse(
+ self.chip.wait_info_event(datetime.timedelta(microseconds=10000))
+ )
+
+ def test_unwatch_not_watched_line(self):
+ with self.assertRaises(OSError) as ex:
+ self.chip.unwatch_line_info(2)
+
+ self.assertEqual(ex.exception.errno, errno.EBUSY)
+
+ def test_unwatch_line_info_no_argument(self):
+ with self.assertRaises(TypeError):
+ self.chip.unwatch_line_info()
+
+ def test_unwatch_line_info_by_line_name(self):
+ self.chip.watch_line_info(4)
+ with self.chip.request_lines(config={4: None}) as request:
+ self.assertIsNotNone(self.chip.read_info_event())
+ self.chip.unwatch_line_info("foobar")
+
+ self.assertFalse(
+ self.chip.wait_info_event(datetime.timedelta(microseconds=10000))
+ )
+
+
+class InfoEventStringRepresentation(TestCase):
+ def test_info_event_str(self):
+ sim = gpiosim.Chip()
+
+ with gpiod.Chip(sim.dev_path) as chip:
+ chip.watch_line_info(0)
+ with chip.request_lines(config={0: None}) as request:
+ self.assertTrue(chip.wait_info_event(datetime.timedelta(seconds=1)))
+ event = chip.read_info_event()
+ self.assertRegex(
+ str(event),
+ '<InfoEvent type=Type\.LINE_REQUESTED timestamp_ns=[0-9]+ line_info=<LineInfo offset=0 name="None" used=True consumer="\?" direction=Direction\.INPUT active_low=False bias=Bias\.UNKNOWN drive=Drive\.PUSH_PULL edge_detection=Edge\.NONE event_clock=Clock\.MONOTONIC debounced=False debounce_period=0:00:00>>',
+ )
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+import errno
+import gpiod
+import unittest
+
+from . import gpiosim
+from gpiod.line import Direction, Bias, Drive, Clock
+
+HogDir = gpiosim.Chip.Direction
+
+
+class GetLineInfo(unittest.TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(
+ num_lines=4,
+ line_names={0: "foobar"},
+ )
+
+ self.chip = gpiod.Chip(self.sim.dev_path)
+
+ def tearDown(self):
+ self.chip.close()
+ self.chip = None
+ self.sim = None
+
+ def test_get_line_info_by_offset(self):
+ self.chip.get_line_info(0)
+
+ def test_get_line_info_by_offset_keyword(self):
+ self.chip.get_line_info(line=0)
+
+ def test_get_line_info_by_name(self):
+ self.chip.get_line_info("foobar")
+
+ def test_get_line_info_by_name_keyword(self):
+ self.chip.get_line_info(line="foobar")
+
+ def test_get_line_info_by_offset_string(self):
+ self.chip.get_line_info("2")
+
+ def test_offset_out_of_range(self):
+ with self.assertRaises(ValueError) as ex:
+ self.chip.get_line_info(4)
+
+ def test_no_offset(self):
+ with self.assertRaises(TypeError):
+ self.chip.get_line_info()
+
+
+class LinePropertiesCanBeRead(unittest.TestCase):
+ def test_basic_properties(self):
+ sim = gpiosim.Chip(
+ num_lines=8,
+ line_names={1: "foo", 2: "bar", 4: "baz", 5: "xyz"},
+ hogs={3: ("hog3", HogDir.OUTPUT_HIGH), 4: ("hog4", HogDir.OUTPUT_LOW)},
+ )
+
+ with gpiod.Chip(sim.dev_path) as chip:
+ info4 = chip.get_line_info(4)
+ info6 = chip.get_line_info(6)
+
+ self.assertEqual(info4.offset, 4)
+ self.assertEqual(info4.name, "baz")
+ self.assertTrue(info4.used)
+ self.assertEqual(info4.consumer, "hog4")
+ self.assertEqual(info4.direction, Direction.OUTPUT)
+ self.assertFalse(info4.active_low)
+ self.assertEqual(info4.bias, Bias.UNKNOWN)
+ self.assertEqual(info4.drive, Drive.PUSH_PULL)
+ self.assertEqual(info4.event_clock, Clock.MONOTONIC)
+ self.assertFalse(info4.debounced)
+ self.assertEqual(info4.debounce_period.total_seconds(), 0.0)
+
+ self.assertEqual(info6.offset, 6)
+ self.assertEqual(info6.name, None)
+ self.assertFalse(info6.used)
+ self.assertEqual(info6.consumer, None)
+ self.assertEqual(info6.direction, Direction.INPUT)
+ self.assertFalse(info6.active_low)
+ self.assertEqual(info6.bias, Bias.UNKNOWN)
+ self.assertEqual(info6.drive, Drive.PUSH_PULL)
+ self.assertEqual(info6.event_clock, Clock.MONOTONIC)
+ self.assertFalse(info6.debounced)
+ self.assertEqual(info6.debounce_period.total_seconds(), 0.0)
+
+
+class LineInfoStringRepresentation(unittest.TestCase):
+ def test_line_info_str(self):
+ sim = gpiosim.Chip(
+ line_names={0: "foo"}, hogs={0: ("hogger", HogDir.OUTPUT_HIGH)}
+ )
+
+ with gpiod.Chip(sim.dev_path) as chip:
+ info = chip.get_line_info(0)
+
+ self.assertEqual(
+ str(info),
+ '<LineInfo offset=0 name="foo" used=True consumer="hogger" direction=Direction.OUTPUT active_low=False bias=Bias.UNKNOWN drive=Drive.PUSH_PULL edge_detection=Edge.NONE event_clock=Clock.MONOTONIC debounced=False debounce_period=0:00:00>',
+ )
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+import errno
+import gpiod
+
+from . import gpiosim
+from gpiod.line import Direction, Edge, Value
+from unittest import TestCase
+
+Pull = gpiosim.Chip.Pull
+SimVal = gpiosim.Chip.Value
+
+
+class ChipLineRequestsBehaveCorrectlyWithInvalidArguments(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(num_lines=8)
+ self.chip = gpiod.Chip(self.sim.dev_path)
+
+ def tearDown(self):
+ self.chip.close()
+ del self.chip
+ del self.sim
+
+ def test_passing_invalid_types_as_configs(self):
+ with self.assertRaises(AttributeError):
+ self.chip.request_lines("foobar")
+
+ with self.assertRaises(AttributeError):
+ self.chip.request_lines(None, "foobar")
+
+ def test_offset_out_of_range(self):
+ with self.assertRaises(ValueError):
+ self.chip.request_lines(config={(1, 0, 4, 8): None})
+
+ def test_line_name_not_found(self):
+ with self.assertRaises(FileNotFoundError):
+ self.chip.request_lines(config={"foo": None})
+
+ def test_request_no_arguments(self):
+ with self.assertRaises(TypeError):
+ self.chip.request_lines()
+
+
+class ModuleLineRequestsBehaveCorrectlyWithInvalidArguments(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(num_lines=8)
+
+ def tearDown(self):
+ del self.sim
+
+ def test_passing_invalid_types_as_configs(self):
+ with self.assertRaises(AttributeError):
+ gpiod.request_lines(self.sim.dev_path, "foobar")
+
+ with self.assertRaises(AttributeError):
+ gpiod.request_lines(self.sim.dev_path, None, "foobar")
+
+ def test_offset_out_of_range(self):
+ with self.assertRaises(ValueError):
+ gpiod.request_lines(self.sim.dev_path, config={(1, 0, 4, 8): None})
+
+ def test_line_name_not_found(self):
+ with self.assertRaises(FileNotFoundError):
+ gpiod.request_lines(self.sim.dev_path, config={"foo": None})
+
+ def test_request_no_arguments(self):
+ with self.assertRaises(TypeError):
+ gpiod.request_lines()
+
+
+class ChipLineRequestWorks(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(num_lines=8, line_names={5: "foo", 7: "bar"})
+ self.chip = gpiod.Chip(self.sim.dev_path)
+
+ def tearDown(self):
+ self.chip.close()
+ del self.chip
+ del self.sim
+
+ def test_request_with_positional_arguments(self):
+ with self.chip.request_lines({(0, 5, 3, 1): None}, "foobar", 32) as req:
+ self.assertEqual(req.offsets, [0, 5, 3, 1])
+ self.assertEqual(self.chip.get_line_info(0).consumer, "foobar")
+
+ def test_request_with_keyword_arguments(self):
+ with self.chip.request_lines(
+ config={(0, 5, 6): None},
+ consumer="foobar",
+ event_buffer_size=16,
+ ) as req:
+ self.assertEqual(req.offsets, [0, 5, 6])
+ self.assertEqual(self.chip.get_line_info(0).consumer, "foobar")
+
+ def test_request_single_offset_as_int(self):
+ with self.chip.request_lines(config={4: None}) as req:
+ self.assertEqual(req.offsets, [4])
+
+ def test_request_single_offset_as_tuple(self):
+ with self.chip.request_lines(config={(4): None}) as req:
+ self.assertEqual(req.offsets, [4])
+
+ def test_request_by_name(self):
+ with self.chip.request_lines(config={(1, 2, "foo", "bar"): None}) as req:
+ self.assertEqual(req.offsets, [1, 2, 5, 7])
+
+ def test_request_single_line_by_name(self):
+ with self.chip.request_lines(config={"foo": None}) as req:
+ self.assertEqual(req.offsets, [5])
+
+
+class ModuleLineRequestWorks(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(num_lines=8, line_names={5: "foo", 7: "bar"})
+
+ def tearDown(self):
+ del self.sim
+
+ def test_request_with_positional_arguments(self):
+ with gpiod.request_lines(
+ self.sim.dev_path, {(0, 5, 3, 1): None}, "foobar", 32
+ ) as req:
+ self.assertEqual(req.offsets, [0, 5, 3, 1])
+ with gpiod.Chip(self.sim.dev_path) as chip:
+ self.assertEqual(chip.get_line_info(5).consumer, "foobar")
+
+ def test_request_with_keyword_arguments(self):
+ with gpiod.request_lines(
+ path=self.sim.dev_path,
+ config={(0, 5, 6): None},
+ consumer="foobar",
+ event_buffer_size=16,
+ ) as req:
+ self.assertEqual(req.offsets, [0, 5, 6])
+ with gpiod.Chip(self.sim.dev_path) as chip:
+ self.assertEqual(chip.get_line_info(5).consumer, "foobar")
+
+ def test_request_single_offset_as_int(self):
+ with gpiod.request_lines(path=self.sim.dev_path, config={4: None}) as req:
+ self.assertEqual(req.offsets, [4])
+
+ def test_request_single_offset_as_tuple(self):
+ with gpiod.request_lines(path=self.sim.dev_path, config={(4): None}) as req:
+ self.assertEqual(req.offsets, [4])
+
+ def test_request_by_name(self):
+ with gpiod.request_lines(
+ self.sim.dev_path, {(1, 2, "foo", "bar"): None}
+ ) as req:
+ self.assertEqual(req.offsets, [1, 2, 5, 7])
+
+
+class LineRequestGettingValues(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(num_lines=8)
+ self.req = gpiod.request_lines(
+ self.sim.dev_path,
+ {(0, 1, 2, 3): gpiod.LineSettings(direction=Direction.INPUT)},
+ )
+
+ def tearDown(self):
+ self.req.release()
+ del self.req
+ del self.sim
+
+ def test_get_single_value(self):
+ self.sim.set_pull(1, Pull.UP)
+
+ self.assertEqual(self.req.get_values([1]), [Value.ACTIVE])
+
+ def test_get_single_value_helper(self):
+ self.sim.set_pull(1, Pull.UP)
+
+ self.assertEqual(self.req.get_value(1), Value.ACTIVE)
+
+ def test_get_values_for_subset_of_lines(self):
+ self.sim.set_pull(0, Pull.UP)
+ self.sim.set_pull(1, Pull.DOWN)
+ self.sim.set_pull(3, Pull.UP)
+
+ self.assertEqual(
+ self.req.get_values([0, 1, 3]), [Value.ACTIVE, Value.INACTIVE, Value.ACTIVE]
+ )
+
+ def test_get_all_values(self):
+ self.sim.set_pull(0, Pull.DOWN)
+ self.sim.set_pull(1, Pull.UP)
+ self.sim.set_pull(2, Pull.UP)
+ self.sim.set_pull(3, Pull.UP)
+
+ self.assertEqual(
+ self.req.get_values(),
+ [Value.INACTIVE, Value.ACTIVE, Value.ACTIVE, Value.ACTIVE],
+ )
+
+ def test_get_values_invalid_offset(self):
+ with self.assertRaises(ValueError):
+ self.req.get_values([9])
+
+ def test_get_values_invalid_argument_type(self):
+ with self.assertRaises(TypeError):
+ self.req.get_values(True)
+
+
+class LineRequestGettingValuesByName(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(num_lines=4, line_names={2: "foo", 3: "bar", 1: "baz"})
+ self.req = gpiod.request_lines(
+ self.sim.dev_path,
+ {(0, "baz", "bar", "foo"): gpiod.LineSettings(direction=Direction.INPUT)},
+ )
+
+ def tearDown(self):
+ self.req.release()
+ del self.req
+ del self.sim
+
+ def test_get_values_by_name(self):
+ self.sim.set_pull(1, Pull.UP)
+ self.sim.set_pull(2, Pull.DOWN)
+ self.sim.set_pull(3, Pull.UP)
+
+ self.assertEqual(
+ self.req.get_values(["foo", "bar", 1]),
+ [Value.INACTIVE, Value.ACTIVE, Value.ACTIVE],
+ )
+
+ def test_get_values_by_bad_name(self):
+ with self.assertRaises(ValueError):
+ self.req.get_values(["xyz"])
+
+
+class LineRequestSettingValues(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(num_lines=8)
+ self.req = gpiod.request_lines(
+ self.sim.dev_path,
+ {(0, 1, 2, 3): gpiod.LineSettings(direction=Direction.OUTPUT)},
+ )
+
+ def tearDown(self):
+ self.req.release()
+ del self.req
+ del self.sim
+
+ def test_set_single_value(self):
+ self.req.set_values({1: Value.ACTIVE})
+ self.assertEqual(self.sim.get_value(1), SimVal.ACTIVE)
+
+ def test_set_single_value_helper(self):
+ self.req.set_value(1, Value.ACTIVE)
+ self.assertEqual(self.sim.get_value(1), SimVal.ACTIVE)
+
+ def test_set_values_for_subset_of_lines(self):
+ self.req.set_values({0: Value.ACTIVE, 1: Value.INACTIVE, 3: Value.ACTIVE})
+
+ self.assertEqual(self.sim.get_value(0), SimVal.ACTIVE)
+ self.assertEqual(self.sim.get_value(1), SimVal.INACTIVE)
+ self.assertEqual(self.sim.get_value(3), SimVal.ACTIVE)
+
+ def test_set_values_invalid_offset(self):
+ with self.assertRaises(ValueError):
+ self.req.set_values({9: Value.ACTIVE})
+
+
+class LineRequestSettingValuesByName(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(num_lines=4, line_names={2: "foo", 3: "bar", 1: "baz"})
+ self.req = gpiod.request_lines(
+ self.sim.dev_path,
+ {(0, "baz", "bar", "foo"): gpiod.LineSettings(direction=Direction.OUTPUT)},
+ )
+
+ def tearDown(self):
+ self.req.release()
+ del self.req
+ del self.sim
+
+ def test_set_values_by_name(self):
+ self.req.set_values(
+ {"foo": Value.INACTIVE, "bar": Value.ACTIVE, 1: Value.ACTIVE}
+ )
+
+ self.assertEqual(self.sim.get_value(2), SimVal.INACTIVE)
+ self.assertEqual(self.sim.get_value(1), SimVal.ACTIVE)
+ self.assertEqual(self.sim.get_value(3), SimVal.ACTIVE)
+
+ def test_set_values_by_bad_name(self):
+ with self.assertRaises(ValueError):
+ self.req.set_values({"xyz": Value.ACTIVE})
+
+
+class LineRequestComplexConfig(TestCase):
+ def test_complex_config(self):
+ sim = gpiosim.Chip(num_lines=8)
+
+ with gpiod.Chip(sim.dev_path) as chip:
+ with chip.request_lines(
+ config={
+ (0, 2, 4): gpiod.LineSettings(
+ direction=Direction.OUTPUT, output_value=Value.ACTIVE
+ ),
+ (1, 3, 5): gpiod.LineSettings(
+ direction=Direction.INPUT, edge_detection=Edge.BOTH
+ ),
+ },
+ ) as req:
+ self.assertEqual(chip.get_line_info(2).direction, Direction.OUTPUT)
+ self.assertEqual(chip.get_line_info(3).edge_detection, Edge.BOTH)
+
+
+class RepeatingLinesInRequestConfig(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(num_lines=4, line_names={0: "foo", 2: "bar"})
+ self.chip = gpiod.Chip(self.sim.dev_path)
+
+ def tearDown(self):
+ self.chip.close()
+ del self.chip
+ del self.sim
+
+ def test_offsets_repeating_within_the_same_tuple(self):
+ with self.assertRaises(ValueError):
+ self.chip.request_lines({(0, 1, 2, 1): None})
+
+ def test_offsets_repeating_in_different_tuples(self):
+ with self.assertRaises(ValueError):
+ self.chip.request_lines({(0, 1, 2): None, (3, 4, 0): None})
+
+ def test_offset_and_name_conflict_in_the_same_tuple(self):
+ with self.assertRaises(ValueError):
+ self.chip.request_lines({(2, "bar"): None})
+
+ def test_offset_and_name_conflict_in_different_tuples(self):
+ with self.assertRaises(ValueError):
+ self.chip.request_lines({(0, 1, 2): None, (4, 5, "bar"): None})
+
+
+class LineRequestPropertiesWork(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(num_lines=16, line_names={0: "foo", 2: "bar", 5: "baz"})
+
+ def tearDown(self):
+ del self.sim
+
+ def test_property_fd(self):
+ with gpiod.request_lines(
+ self.sim.dev_path,
+ config={
+ 0: gpiod.LineSettings(
+ direction=Direction.INPUT, edge_detection=Edge.BOTH
+ )
+ },
+ ) as req:
+ self.assertGreaterEqual(req.fd, 0)
+
+ def test_property_num_lines(self):
+ with gpiod.request_lines(
+ self.sim.dev_path, config={(0, 2, 3, 5, 6, 8, 12): None}
+ ) as req:
+ self.assertEqual(req.num_lines, 7)
+
+ def test_property_offsets(self):
+ with gpiod.request_lines(
+ self.sim.dev_path, config={(1, 6, 12, 4): None}
+ ) as req:
+ self.assertEqual(req.offsets, [1, 6, 12, 4])
+
+ def test_property_lines(self):
+ with gpiod.request_lines(
+ self.sim.dev_path, config={("foo", 1, "bar", 4, "baz"): None}
+ ) as req:
+ self.assertEqual(req.lines, ["foo", 1, "bar", 4, "baz"])
+
+
+class LineRequestConsumerString(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(num_lines=4)
+ self.chip = gpiod.Chip(self.sim.dev_path)
+
+ def tearDown(self):
+ self.chip.close()
+ del self.chip
+ del self.sim
+
+ def test_custom_consumer(self):
+ with self.chip.request_lines(
+ consumer="foobar", config={(2, 3): None}
+ ) as request:
+ info = self.chip.get_line_info(2)
+ self.assertEqual(info.consumer, "foobar")
+
+ def test_empty_consumer(self):
+ with self.chip.request_lines(consumer="", config={(2, 3): None}) as request:
+ info = self.chip.get_line_info(2)
+ self.assertEqual(info.consumer, "?")
+
+ def test_default_consumer(self):
+ with self.chip.request_lines(config={(2, 3): None}) as request:
+ info = self.chip.get_line_info(2)
+ self.assertEqual(info.consumer, "?")
+
+
+class LineRequestSetOutputValues(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(
+ num_lines=4, line_names={0: "foo", 1: "bar", 2: "baz", 3: "xyz"}
+ )
+
+ def tearDown(self):
+ del self.sim
+
+ def test_request_with_globally_set_output_values(self):
+ with gpiod.request_lines(
+ self.sim.dev_path,
+ config={(0, 1, 2, 3): gpiod.LineSettings(direction=Direction.OUTPUT)},
+ output_values={
+ 0: Value.ACTIVE,
+ 1: Value.INACTIVE,
+ 2: Value.ACTIVE,
+ 3: Value.INACTIVE,
+ },
+ ) as request:
+ self.assertEqual(self.sim.get_value(0), SimVal.ACTIVE)
+ self.assertEqual(self.sim.get_value(1), SimVal.INACTIVE)
+ self.assertEqual(self.sim.get_value(2), SimVal.ACTIVE)
+ self.assertEqual(self.sim.get_value(3), SimVal.INACTIVE)
+
+ def test_request_with_globally_set_output_values_with_mapping(self):
+ with gpiod.request_lines(
+ self.sim.dev_path,
+ config={(0, 1, 2, 3): gpiod.LineSettings(direction=Direction.OUTPUT)},
+ output_values={"baz": Value.ACTIVE, "foo": Value.ACTIVE},
+ ) as request:
+ self.assertEqual(self.sim.get_value(0), SimVal.ACTIVE)
+ self.assertEqual(self.sim.get_value(1), SimVal.INACTIVE)
+ self.assertEqual(self.sim.get_value(2), SimVal.ACTIVE)
+ self.assertEqual(self.sim.get_value(3), SimVal.INACTIVE)
+
+ def test_request_with_globally_set_output_values_bad_mapping(self):
+ with self.assertRaises(FileNotFoundError):
+ with gpiod.request_lines(
+ self.sim.dev_path,
+ config={(0, 1, 2, 3): gpiod.LineSettings(direction=Direction.OUTPUT)},
+ output_values={"foobar": Value.ACTIVE},
+ ) as request:
+ pass
+
+ def test_request_with_globally_set_output_values_bad_offset(self):
+ with self.assertRaises(ValueError):
+ with gpiod.request_lines(
+ self.sim.dev_path,
+ config={(0, 1, 2, 3): gpiod.LineSettings(direction=Direction.OUTPUT)},
+ output_values={5: Value.ACTIVE},
+ ) as request:
+ pass
+
+
+class ReconfigureRequestedLines(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(num_lines=8, line_names={3: "foo", 4: "bar", 6: "baz"})
+ self.chip = gpiod.Chip(self.sim.dev_path)
+ self.req = self.chip.request_lines(
+ {(0, 2, "foo", "baz"): gpiod.LineSettings(direction=Direction.OUTPUT)}
+ )
+
+ def tearDown(self):
+ self.chip.close()
+ del self.chip
+ self.req.release()
+ del self.req
+ del self.sim
+
+ def test_reconfigure_by_offsets(self):
+ info = self.chip.get_line_info(2)
+ self.assertEqual(info.direction, Direction.OUTPUT)
+ self.req.reconfigure_lines(
+ {(0, 2, 3, 6): gpiod.LineSettings(direction=Direction.INPUT)}
+ )
+ info = self.chip.get_line_info(2)
+ self.assertEqual(info.direction, Direction.INPUT)
+
+ def test_reconfigure_by_names(self):
+ info = self.chip.get_line_info(2)
+ self.assertEqual(info.direction, Direction.OUTPUT)
+ self.req.reconfigure_lines(
+ {(0, 2, "foo", "baz"): gpiod.LineSettings(direction=Direction.INPUT)}
+ )
+ info = self.chip.get_line_info(2)
+ self.assertEqual(info.direction, Direction.INPUT)
+
+
+class ReleasedLineRequestCannotBeUsed(TestCase):
+ def test_using_released_line_request(self):
+ sim = gpiosim.Chip()
+
+ with gpiod.Chip(sim.dev_path) as chip:
+ req = chip.request_lines(config={0: None})
+ req.release()
+
+ with self.assertRaises(gpiod.RequestReleasedError):
+ req.fd
+
+
+class LineRequestSurvivesParentChip(TestCase):
+ def test_line_request_survives_parent_chip(self):
+ sim = gpiosim.Chip()
+
+ chip = gpiod.Chip(sim.dev_path)
+ try:
+ req = chip.request_lines(
+ config={0: gpiod.LineSettings(direction=Direction.INPUT)}
+ )
+ except:
+ chip.close()
+ raise
+
+ chip.close()
+ self.assertEqual(req.get_values([0]), [Value.INACTIVE])
+ req.release()
+
+
+class LineRequestStringRepresentation(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(num_lines=8)
+
+ def tearDown(self):
+ del self.sim
+
+ def test_str(self):
+ with gpiod.Chip(self.sim.dev_path) as chip:
+ with chip.request_lines(config={(2, 6, 4, 1): None}) as req:
+ self.assertEqual(
+ str(req),
+ '<LineRequest chip="{}" num_lines=4 offsets=[2, 6, 4, 1] fd={}>'.format(
+ self.sim.name, req.fd
+ ),
+ )
+
+ def test_str_released(self):
+ req = gpiod.request_lines(self.sim.dev_path, config={(2, 6, 4, 1): None})
+ req.release()
+ self.assertEqual(str(req), "<LineRequest RELEASED>")
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+import gpiod
+
+from . import gpiosim
+from datetime import timedelta
+from gpiod.line import Direction, Edge, Bias, Drive, Value, Clock
+from unittest import TestCase
+
+
+class LineSettingsConstructor(TestCase):
+ def test_default_values(self):
+ settings = gpiod.LineSettings()
+
+ self.assertEqual(settings.direction, Direction.AS_IS)
+ self.assertEqual(settings.edge_detection, Edge.NONE)
+ self.assertEqual(settings.bias, Bias.AS_IS)
+ self.assertEqual(settings.drive, Drive.PUSH_PULL)
+ self.assertFalse(settings.active_low)
+ self.assertEqual(settings.debounce_period.total_seconds(), 0.0)
+ self.assertEqual(settings.event_clock, Clock.MONOTONIC)
+ self.assertEqual(settings.output_value, Value.INACTIVE)
+
+ def test_keyword_arguments(self):
+ settings = gpiod.LineSettings(
+ direction=Direction.INPUT,
+ edge_detection=Edge.BOTH,
+ bias=Bias.PULL_UP,
+ event_clock=Clock.REALTIME,
+ )
+
+ self.assertEqual(settings.direction, Direction.INPUT)
+ self.assertEqual(settings.edge_detection, Edge.BOTH)
+ self.assertEqual(settings.bias, Bias.PULL_UP)
+ self.assertEqual(settings.drive, Drive.PUSH_PULL)
+ self.assertFalse(settings.active_low)
+ self.assertEqual(settings.debounce_period.total_seconds(), 0.0)
+ self.assertEqual(settings.event_clock, Clock.REALTIME)
+ self.assertEqual(settings.output_value, Value.INACTIVE)
+
+
+class LineSettingsAttributes(TestCase):
+ def test_line_settings_attributes_are_mutable(self):
+ settings = gpiod.LineSettings()
+
+ settings.direction = Direction.INPUT
+ settings.edge_detection = Edge.BOTH
+ settings.bias = Bias.DISABLED
+ settings.debounce_period = timedelta(microseconds=3000)
+ settings.event_clock = Clock.HTE
+
+ self.assertEqual(settings.direction, Direction.INPUT)
+ self.assertEqual(settings.edge_detection, Edge.BOTH)
+ self.assertEqual(settings.bias, Bias.DISABLED)
+ self.assertEqual(settings.drive, Drive.PUSH_PULL)
+ self.assertFalse(settings.active_low)
+ self.assertEqual(settings.debounce_period.total_seconds(), 0.003)
+ self.assertEqual(settings.event_clock, Clock.HTE)
+ self.assertEqual(settings.output_value, Value.INACTIVE)
+
+
+class LineSettingsStringRepresentation(TestCase):
+ def setUp(self):
+ self.settings = gpiod.LineSettings(
+ direction=Direction.OUTPUT, drive=Drive.OPEN_SOURCE, active_low=True
+ )
+
+ def test_repr(self):
+ self.assertEqual(
+ repr(self.settings),
+ "LineSettings(direction=Direction.OUTPUT, edge_detection=Edge.NONE bias=Bias.AS_IS drive=Drive.OPEN_SOURCE active_low=True debounce_period=datetime.timedelta(0) event_clock=Clock.MONOTONIC output_value=Value.INACTIVE)",
+ )
+
+ def test_str(self):
+ self.assertEqual(
+ str(self.settings),
+ "<LineSettings direction=Direction.OUTPUT edge_detection=Edge.NONE bias=Bias.AS_IS drive=Drive.OPEN_SOURCE active_low=True debounce_period=0:00:00 event_clock=Clock.MONOTONIC output_value=Value.INACTIVE>",
+ )
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+import gpiod
+import os
+import unittest
+
+from . import gpiosim
+from .helpers import LinkGuard
+from unittest import TestCase
+
+
+class IsGPIOChip(TestCase):
+ def test_is_gpiochip_bad(self):
+ self.assertFalse(gpiod.is_gpiochip_device("/dev/null"))
+ self.assertFalse(gpiod.is_gpiochip_device("/dev/nonexistent"))
+
+ def test_is_gpiochip_invalid_argument(self):
+ with self.assertRaises(TypeError):
+ gpiod.is_gpiochip_device(4)
+
+ def test_is_gpiochip_superfluous_argument(self):
+ with self.assertRaises(TypeError):
+ gpiod.is_gpiochip_device("/dev/null", 4)
+
+ def test_is_gpiochip_missing_argument(self):
+ with self.assertRaises(TypeError):
+ gpiod.is_gpiochip_device()
+
+ def test_is_gpiochip_good(self):
+ sim = gpiosim.Chip()
+ self.assertTrue(gpiod.is_gpiochip_device(sim.dev_path))
+
+ def test_is_gpiochip_good_keyword_argument(self):
+ sim = gpiosim.Chip()
+ self.assertTrue(gpiod.is_gpiochip_device(path=sim.dev_path))
+
+ def test_is_gpiochip_link_good(self):
+ link = "/tmp/gpiod-py-test-link.{}".format(os.getpid())
+ sim = gpiosim.Chip()
+
+ with LinkGuard(sim.dev_path, link):
+ self.assertTrue(gpiod.is_gpiochip_device(link))
+
+ def test_is_gpiochip_link_bad(self):
+ link = "/tmp/gpiod-py-test-link.{}".format(os.getpid())
+
+ with LinkGuard("/dev/null", link):
+ self.assertFalse(gpiod.is_gpiochip_device(link))
+
+
+class VersionString(TestCase):
+
+ VERSION_PATTERN = "^\\d+\\.\\d+(\\.\\d+|\\-devel|\\-rc\\d+)?$"
+
+ def test_api_version_string(self):
+ self.assertRegex(gpiod.api_version, VersionString.VERSION_PATTERN)
+
+ def test_module_version(self):
+ self.assertRegex(gpiod.__version__, VersionString.VERSION_PATTERN)
--- /dev/null
+# SPDX-License-Identifier: CC0-1.0
+# SPDX-FileCopyrightText: 2022 Linaro Ltd.
+# SPDX-FileCopyrightText: 2022 Viresh Kumar <viresh.kumar@linaro.org>
+
+target
+Cargo.lock
--- /dev/null
+# SPDX-License-Identifier: CC0-1.0
+# SPDX-FileCopyrightText: 2022 Linaro Ltd.
+# SPDX-FileCopyrightText: 2022 Viresh Kumar <viresh.kumar@linaro.org>
+
+[workspace]
+
+members = [
+ "gpiosim-sys",
+ "libgpiod",
+ "libgpiod-sys"
+]
+
+resolver = "2"
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Linaro Ltd.
+# SPDX-FileCopyrightText: 2022 Viresh Kumar <viresh.kumar@linaro.org>
+
+EXTRA_DIST = Cargo.toml
+SUBDIRS = gpiosim-sys libgpiod libgpiod-sys
--- /dev/null
+# SPDX-License-Identifier: CC0-1.0
+# SPDX-FileCopyrightText: 2022 Linaro Ltd.
+# SPDX-FileCopyrightText: 2022 Viresh Kumar <viresh.kumar@linaro.org>
+
+[package]
+name = "gpiosim-sys"
+version = "0.1.0"
+authors = ["Viresh Kumar <viresh.kumar@linaro.org>"]
+description = "gpiosim header bindings"
+repository = "https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git"
+categories = ["external-ffi-bindings", "os::linux-apis"]
+rust-version = "1.60"
+keywords = ["libgpiod", "gpio", "gpiosim"]
+license = "Apache-2.0 OR BSD-3-Clause"
+edition = "2021"
+
+[dependencies]
+errno = "0.2.8"
+libgpiod = { path = "../libgpiod" }
+
+[build-dependencies]
+bindgen = "0.63"
+cc = "1.0.46"
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Linaro Ltd.
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
+
+EXTRA_DIST = build.rs Cargo.toml README.md
+SUBDIRS = src
--- /dev/null
+<!--
+SPDX-License-Identifier: CC0-1.0
+SPDX-FileCopyrightText: 2022 Linaro Ltd.
+SPDX-FileCopyrightText: 2022 Viresh Kumar <viresh.kumar@linaro.org>
+-->
+
+# Generated gpiosim Rust FFI bindings
+Automatically generated Rust FFI bindings via
+ [bindgen](https://github.com/rust-lang/rust-bindgen).
+
+## License
+
+This project is licensed under either of
+
+- [Apache License](http://www.apache.org/licenses/LICENSE-2.0), Version 2.0
+- [BSD-3-Clause License](https://opensource.org/licenses/BSD-3-Clause)
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
+// SPDX-FileCopyrightText: 2022 Linaro Ltd.
+// SPDX-FileCopyrightText: 2022 Viresh Kumar <viresh.kumar@linaro.org>
+
+extern crate bindgen;
+
+use std::env;
+use std::path::PathBuf;
+
+fn generate_bindings() {
+ // Tell cargo to invalidate the built crate whenever following files change
+ println!("cargo:rerun-if-changed=../../../tests/gpiosim/gpiosim.h");
+
+ // The bindgen::Builder is the main entry point
+ // to bindgen, and lets you build up options for
+ // the resulting bindings.
+ let bindings = bindgen::Builder::default()
+ // The input header we would like to generate
+ // bindings for.
+ .header("../../../tests/gpiosim/gpiosim.h")
+ // Tell cargo to invalidate the built crate whenever any of the
+ // included header files changed.
+ .parse_callbacks(Box::new(bindgen::CargoCallbacks))
+ // Finish the builder and generate the bindings.
+ .generate()
+ // Unwrap the Result and panic on failure.
+ .expect("Unable to generate bindings");
+
+ // Write the bindings to the $OUT_DIR/bindings.rs file.
+ let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
+ bindings
+ .write_to_file(out_path.join("bindings.rs"))
+ .expect("Couldn't write bindings!");
+}
+
+fn main() {
+ generate_bindings();
+
+ println!("cargo:rustc-link-lib=kmod");
+ println!("cargo:rustc-link-lib=mount");
+ println!("cargo:rustc-link-search=./../../tests/gpiosim/.libs/");
+ println!("cargo:rustc-link-lib=static=gpiosim");
+}
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Linaro Ltd.
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
+
+EXTRA_DIST = lib.rs sim.rs
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
+// SPDX-FileCopyrightText: 2022 Linaro Ltd.
+// SPDX-FileCopyrightText: 2022 Viresh Kumar <viresh.kumar@linaro.org>
+
+use libgpiod::{Error, OperationType, Result};
+
+#[allow(non_camel_case_types, non_upper_case_globals)]
+#[cfg_attr(test, allow(deref_nullptr, non_snake_case))]
+#[allow(dead_code)]
+mod bindings_raw {
+ include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
+}
+use bindings_raw::*;
+
+mod sim;
+pub use sim::*;
+
+use crate::{
+ gpiosim_direction_GPIOSIM_DIRECTION_INPUT as GPIOSIM_DIRECTION_INPUT,
+ gpiosim_direction_GPIOSIM_DIRECTION_OUTPUT_HIGH as GPIOSIM_DIRECTION_OUTPUT_HIGH,
+ gpiosim_direction_GPIOSIM_DIRECTION_OUTPUT_LOW as GPIOSIM_DIRECTION_OUTPUT_LOW,
+ gpiosim_pull_GPIOSIM_PULL_DOWN as GPIOSIM_PULL_DOWN,
+ gpiosim_pull_GPIOSIM_PULL_UP as GPIOSIM_PULL_UP,
+ gpiosim_value_GPIOSIM_VALUE_ACTIVE as GPIOSIM_VALUE_ACTIVE,
+ gpiosim_value_GPIOSIM_VALUE_ERROR as GPIOSIM_VALUE_ERROR,
+ gpiosim_value_GPIOSIM_VALUE_INACTIVE as GPIOSIM_VALUE_INACTIVE,
+};
+
+/// Value settings.
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub enum Value {
+ /// Active
+ Active,
+ /// Inactive
+ InActive,
+}
+
+impl Value {
+ pub(crate) fn new(val: gpiosim_value) -> Result<Self> {
+ Ok(match val {
+ GPIOSIM_VALUE_INACTIVE => Value::InActive,
+ GPIOSIM_VALUE_ACTIVE => Value::Active,
+ GPIOSIM_VALUE_ERROR => {
+ return Err(Error::OperationFailed(
+ OperationType::SimBankGetVal,
+ errno::errno(),
+ ))
+ }
+ _ => return Err(Error::InvalidEnumValue("Value", val)),
+ })
+ }
+}
+
+/// Direction settings.
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub enum Direction {
+ /// Direction is input - for reading the value of an externally driven GPIO line.
+ Input,
+ /// Direction is output - for driving the GPIO line, value is high.
+ OutputHigh,
+ /// Direction is output - for driving the GPIO line, value is low.
+ OutputLow,
+}
+
+impl Direction {
+ fn val(self) -> gpiosim_direction {
+ match self {
+ Direction::Input => GPIOSIM_DIRECTION_INPUT,
+ Direction::OutputHigh => GPIOSIM_DIRECTION_OUTPUT_HIGH,
+ Direction::OutputLow => GPIOSIM_DIRECTION_OUTPUT_LOW,
+ }
+ }
+}
+
+/// Internal pull settings.
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub enum Pull {
+ /// The internal pull-up is enabled.
+ Up,
+ /// The internal pull-down is enabled.
+ Down,
+}
+
+impl Pull {
+ fn val(self) -> gpiosim_pull {
+ match self {
+ Pull::Up => GPIOSIM_PULL_UP,
+ Pull::Down => GPIOSIM_PULL_DOWN,
+ }
+ }
+}
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
+// SPDX-FileCopyrightText: 2022 Linaro Ltd.
+// SPDX-FileCopyrightText: 2022 Viresh Kumar <viresh.kumar@linaro.org>
+
+use std::ffi::{CStr, CString};
+use std::os::raw::c_char;
+use std::path::PathBuf;
+use std::str;
+
+use libgpiod::{line::Offset, Error, OperationType, Result};
+
+use crate::*;
+
+/// Sim Ctx
+#[derive(Debug)]
+struct SimCtx {
+ ctx: *mut gpiosim_ctx,
+}
+
+// Safe as the pointer is guaranteed to be valid and the associated resource
+// won't be freed until the object is dropped.
+unsafe impl Send for SimCtx {}
+
+impl SimCtx {
+ fn new() -> Result<Self> {
+ // SAFETY: `gpiosim_ctx` returned by gpiosim is guaranteed to live
+ // as long as the `struct SimCtx`.
+ let ctx = unsafe { gpiosim_ctx_new() };
+ if ctx.is_null() {
+ return Err(Error::OperationFailed(
+ OperationType::SimCtxNew,
+ errno::errno(),
+ ));
+ }
+
+ Ok(Self { ctx })
+ }
+}
+
+impl Drop for SimCtx {
+ fn drop(&mut self) {
+ // SAFETY: `gpiosim_ctx` is guaranteed to be valid here.
+ unsafe { gpiosim_ctx_unref(self.ctx) }
+ }
+}
+
+/// Sim Dev
+#[derive(Debug)]
+struct SimDev {
+ dev: *mut gpiosim_dev,
+}
+
+// Safe as the pointer is guaranteed to be valid and the associated resource
+// won't be freed until the object is dropped.
+unsafe impl Send for SimDev {}
+
+impl SimDev {
+ fn new(ctx: &SimCtx) -> Result<Self> {
+ // SAFETY: `gpiosim_dev` returned by gpiosim is guaranteed to live
+ // as long as the `struct SimDev`.
+ let dev = unsafe { gpiosim_dev_new(ctx.ctx) };
+ if dev.is_null() {
+ return Err(Error::OperationFailed(
+ OperationType::SimDevNew,
+ errno::errno(),
+ ));
+ }
+
+ Ok(Self { dev })
+ }
+
+ fn enable(&self) -> Result<()> {
+ // SAFETY: `gpiosim_dev` is guaranteed to be valid here.
+ let ret = unsafe { gpiosim_dev_enable(self.dev) };
+
+ if ret == -1 {
+ Err(Error::OperationFailed(
+ OperationType::SimDevEnable,
+ errno::errno(),
+ ))
+ } else {
+ Ok(())
+ }
+ }
+
+ fn disable(&self) -> Result<()> {
+ // SAFETY: `gpiosim_dev` is guaranteed to be valid here.
+ let ret = unsafe { gpiosim_dev_disable(self.dev) };
+
+ if ret == -1 {
+ Err(Error::OperationFailed(
+ OperationType::SimDevDisable,
+ errno::errno(),
+ ))
+ } else {
+ Ok(())
+ }
+ }
+}
+
+impl Drop for SimDev {
+ fn drop(&mut self) {
+ // SAFETY: `gpiosim_dev` is guaranteed to be valid here.
+ unsafe { gpiosim_dev_unref(self.dev) }
+ }
+}
+
+/// Sim Bank
+#[derive(Debug)]
+struct SimBank {
+ bank: *mut gpiosim_bank,
+}
+
+// Safe as the pointer is guaranteed to be valid and the associated resource
+// won't be freed until the object is dropped.
+unsafe impl Send for SimBank {}
+
+impl SimBank {
+ fn new(dev: &SimDev) -> Result<Self> {
+ // SAFETY: `gpiosim_bank` returned by gpiosim is guaranteed to live
+ // as long as the `struct SimBank`.
+ let bank = unsafe { gpiosim_bank_new(dev.dev) };
+ if bank.is_null() {
+ return Err(Error::OperationFailed(
+ OperationType::SimBankNew,
+ errno::errno(),
+ ));
+ }
+
+ Ok(Self { bank })
+ }
+
+ fn chip_name(&self) -> Result<&str> {
+ // SAFETY: The string returned by gpiosim is guaranteed to live as long
+ // as the `struct SimBank`.
+ let name = unsafe { gpiosim_bank_get_chip_name(self.bank) };
+
+ // SAFETY: The string is guaranteed to be valid here.
+ unsafe { CStr::from_ptr(name) }
+ .to_str()
+ .map_err(Error::StringNotUtf8)
+ }
+
+ fn dev_path(&self) -> Result<PathBuf> {
+ // SAFETY: The string returned by gpiosim is guaranteed to live as long
+ // as the `struct SimBank`.
+ let path = unsafe { gpiosim_bank_get_dev_path(self.bank) };
+
+ // SAFETY: The string is guaranteed to be valid here.
+ let path = unsafe { CStr::from_ptr(path) }
+ .to_str()
+ .map_err(Error::StringNotUtf8)?;
+
+ Ok(PathBuf::from(path))
+ }
+
+ fn val(&self, offset: Offset) -> Result<Value> {
+ // SAFETY: `gpiosim_bank` is guaranteed to be valid here.
+ let ret = unsafe { gpiosim_bank_get_value(self.bank, offset) };
+
+ if ret == -1 {
+ Err(Error::OperationFailed(
+ OperationType::SimBankGetVal,
+ errno::errno(),
+ ))
+ } else {
+ Value::new(ret)
+ }
+ }
+
+ fn set_label(&self, label: &str) -> Result<()> {
+ let label = CString::new(label).map_err(|_| Error::InvalidString)?;
+
+ // SAFETY: `gpiosim_bank` is guaranteed to be valid here.
+ let ret = unsafe { gpiosim_bank_set_label(self.bank, label.as_ptr() as *const c_char) };
+
+ if ret == -1 {
+ Err(Error::OperationFailed(
+ OperationType::SimBankSetLabel,
+ errno::errno(),
+ ))
+ } else {
+ Ok(())
+ }
+ }
+
+ fn set_num_lines(&self, num: usize) -> Result<()> {
+ // SAFETY: `gpiosim_bank` is guaranteed to be valid here.
+ let ret = unsafe { gpiosim_bank_set_num_lines(self.bank, num) };
+ if ret == -1 {
+ Err(Error::OperationFailed(
+ OperationType::SimBankSetNumLines,
+ errno::errno(),
+ ))
+ } else {
+ Ok(())
+ }
+ }
+
+ fn set_line_name(&self, offset: Offset, name: &str) -> Result<()> {
+ let name = CString::new(name).map_err(|_| Error::InvalidString)?;
+
+ // SAFETY: `gpiosim_bank` is guaranteed to be valid here.
+ let ret = unsafe {
+ gpiosim_bank_set_line_name(self.bank, offset, name.as_ptr() as *const c_char)
+ };
+
+ if ret == -1 {
+ Err(Error::OperationFailed(
+ OperationType::SimBankSetLineName,
+ errno::errno(),
+ ))
+ } else {
+ Ok(())
+ }
+ }
+
+ fn set_pull(&self, offset: Offset, pull: Pull) -> Result<()> {
+ // SAFETY: `gpiosim_bank` is guaranteed to be valid here.
+ let ret = unsafe { gpiosim_bank_set_pull(self.bank, offset, pull.val()) };
+
+ if ret == -1 {
+ Err(Error::OperationFailed(
+ OperationType::SimBankSetPull,
+ errno::errno(),
+ ))
+ } else {
+ Ok(())
+ }
+ }
+
+ fn hog_line(&self, offset: Offset, name: &str, dir: Direction) -> Result<()> {
+ let name = CString::new(name).map_err(|_| Error::InvalidString)?;
+
+ // SAFETY: `gpiosim_bank` is guaranteed to be valid here.
+ let ret = unsafe {
+ gpiosim_bank_hog_line(self.bank, offset, name.as_ptr() as *const c_char, dir.val())
+ };
+
+ if ret == -1 {
+ Err(Error::OperationFailed(
+ OperationType::SimBankHogLine,
+ errno::errno(),
+ ))
+ } else {
+ Ok(())
+ }
+ }
+}
+
+impl Drop for SimBank {
+ fn drop(&mut self) {
+ // SAFETY: `gpiosim_bank` is guaranteed to be valid here.
+ unsafe { gpiosim_bank_unref(self.bank) }
+ }
+}
+
+/// GPIO SIM
+#[derive(Debug)]
+pub struct Sim {
+ _ctx: SimCtx,
+ dev: SimDev,
+ bank: SimBank,
+}
+
+impl Sim {
+ pub fn new(ngpio: Option<usize>, label: Option<&str>, enable: bool) -> Result<Self> {
+ let ctx = SimCtx::new()?;
+ let dev = SimDev::new(&ctx)?;
+ let bank = SimBank::new(&dev)?;
+
+ if let Some(ngpio) = ngpio {
+ bank.set_num_lines(ngpio)?;
+ }
+
+ if let Some(label) = label {
+ bank.set_label(label)?;
+ }
+
+ if enable {
+ dev.enable()?;
+ }
+
+ Ok(Self {
+ _ctx: ctx,
+ dev,
+ bank,
+ })
+ }
+
+ pub fn chip_name(&self) -> &str {
+ self.bank.chip_name().unwrap()
+ }
+
+ pub fn dev_path(&self) -> PathBuf {
+ self.bank.dev_path().unwrap()
+ }
+
+ pub fn val(&self, offset: Offset) -> Result<Value> {
+ self.bank.val(offset)
+ }
+
+ pub fn set_label(&self, label: &str) -> Result<()> {
+ self.bank.set_label(label)
+ }
+
+ pub fn set_num_lines(&self, num: usize) -> Result<()> {
+ self.bank.set_num_lines(num)
+ }
+
+ pub fn set_line_name(&self, offset: Offset, name: &str) -> Result<()> {
+ self.bank.set_line_name(offset, name)
+ }
+
+ pub fn set_pull(&self, offset: Offset, pull: Pull) -> Result<()> {
+ self.bank.set_pull(offset, pull)
+ }
+
+ pub fn hog_line(&self, offset: Offset, name: &str, dir: Direction) -> Result<()> {
+ self.bank.hog_line(offset, name, dir)
+ }
+
+ pub fn enable(&self) -> Result<()> {
+ self.dev.enable()
+ }
+
+ pub fn disable(&self) -> Result<()> {
+ self.dev.disable()
+ }
+}
--- /dev/null
+# SPDX-License-Identifier: CC0-1.0
+# SPDX-FileCopyrightText: 2022 Linaro Ltd.
+# SPDX-FileCopyrightText: 2022 Viresh Kumar <viresh.kumar@linaro.org>
+
+[package]
+name = "libgpiod-sys"
+version = "0.1.0"
+authors = ["Viresh Kumar <viresh.kumar@linaro.org>"]
+description = "libgpiod public header bindings"
+repository = "https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git"
+categories = ["external-ffi-bindings", "os::linux-apis"]
+rust-version = "1.60"
+keywords = ["libgpiod", "gpio"]
+license = "Apache-2.0 OR BSD-3-Clause"
+edition = "2021"
+
+exclude = [
+ "Makefile.am",
+]
+
+[dependencies]
+
+[build-dependencies]
+bindgen = "0.63"
+system-deps = "2.0"
+
+[package.metadata.system-deps]
+libgpiod = "2"
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Linaro Ltd.
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
+
+EXTRA_DIST = build.rs Cargo.toml README.md wrapper.h
+SUBDIRS = src
--- /dev/null
+<!--
+SPDX-License-Identifier: CC0-1.0
+SPDX-FileCopyrightText: 2022 Linaro Ltd.
+SPDX-FileCopyrightText: 2022 Viresh Kumar <viresh.kumar@linaro.org>
+-->
+
+# Generated libgpiod-sys Rust FFI bindings
+Automatically generated Rust FFI bindings via
+ [bindgen](https://github.com/rust-lang/rust-bindgen).
+
+Typically, you will want to use the safe `libgpiod` wrapper crate instead of
+these unsafe wrappers around the C lib.
+
+## Build requirements
+
+A compatible variant of the C library needs to detectable using pkg-config.
+Alternatively, one can inform the build system about the location of the
+libs and headers by setting environment variables. The mechanism for that is
+documented in the
+[system_deps crate documentation](https://docs.rs/system-deps/6.1.0/system_deps/#overriding-build-flags).
+
+If installing libgpiod is undesired, one can set the following environent
+variables in order to build against the intermediate build results of a `make`
+build of the C lib (paths are relative to the Cargo.toml):
+
+ export SYSTEM_DEPS_LIBGPIOD_NO_PKG_CONFIG=1
+ export SYSTEM_DEPS_LIBGPIOD_SEARCH_NATIVE="<PATH-TO-LIBGPIOD>/lib/.libs/"
+ export SYSTEM_DEPS_LIBGPIOD_LIB=gpiod
+ export SYSTEM_DEPS_LIBGPIOD_INCLUDE="<PATH-TO-LIBGPIOD>/include/"
+
+## License
+
+This project is licensed under either of
+
+- [Apache License](http://www.apache.org/licenses/LICENSE-2.0), Version 2.0
+- [BSD-3-Clause License](https://opensource.org/licenses/BSD-3-Clause)
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
+// SPDX-FileCopyrightText: 2022-2023 Linaro Ltd.
+// SPDX-FileCopyrightText: 2022 Viresh Kumar <viresh.kumar@linaro.org>
+// SPDX-FileCopyrightText: 2023 Erik Schilling <erik.schilling@linaro.org>
+
+use std::env;
+use std::path::PathBuf;
+
+fn main() {
+ // Probe dependency info based on the metadata from Cargo.toml
+ // (and potentially other sources like environment, pkg-config, ...)
+ // https://docs.rs/system-deps/latest/system_deps/#overriding-build-flags
+ let libs = system_deps::Config::new().probe().unwrap();
+
+ // Tell cargo to invalidate the built crate whenever following files change
+ println!("cargo:rerun-if-changed=wrapper.h");
+
+ // The bindgen::Builder is the main entry point
+ // to bindgen, and lets you build up options for
+ // the resulting bindings.
+ let mut builder = bindgen::Builder::default()
+ // The input header we would like to generate
+ // bindings for.
+ .header("wrapper.h")
+ // Tell cargo to invalidate the built crate whenever any of the
+ // included header files changed.
+ .parse_callbacks(Box::new(bindgen::CargoCallbacks));
+
+ // Inform bindgen about the include paths identified by system_deps.
+ for (_name, lib) in libs {
+ for include_path in lib.include_paths {
+ builder = builder.clang_arg("-I").clang_arg(
+ include_path
+ .to_str()
+ .expect("Failed to convert include_path to &str!"),
+ );
+ }
+ }
+
+ // Finish the builder and generate the bindings.
+ let bindings = builder
+ .generate()
+ // Unwrap the Result and panic on failure.
+ .expect("Unable to generate bindings");
+
+ // Write the bindings to the $OUT_DIR/bindings.rs file.
+ let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
+ bindings
+ .write_to_file(out_path.join("bindings.rs"))
+ .expect("Couldn't write bindings!");
+}
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Linaro Ltd.
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
+
+EXTRA_DIST = lib.rs
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
+// SPDX-FileCopyrightText: 2022 Linaro Ltd.
+// SPDX-FileCopyrightText: 2022 Viresh Kumar <viresh.kumar@linaro.org>
+
+#[allow(non_camel_case_types, non_upper_case_globals)]
+#[cfg_attr(test, allow(deref_nullptr, non_snake_case))]
+
+mod bindings_raw {
+ include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
+}
+pub use bindings_raw::*;
--- /dev/null
+/* SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause */
+/* SPDX-FileCopyrightText: 2023 Linaro Ltd. */
+/* SPDX-FileCopyrightText: 2023 Erik Schilling <erik.schilling@linaro.org> */
+
+#include <gpiod.h>
--- /dev/null
+# SPDX-License-Identifier: CC0-1.0
+# SPDX-FileCopyrightText: 2022 Linaro Ltd.
+# SPDX-FileCopyrightText: 2022 Viresh Kumar <viresh.kumar@linaro.org>
+
+[package]
+name = "libgpiod"
+version = "0.2.1"
+authors = ["Viresh Kumar <viresh.kumar@linaro.org>"]
+description = "libgpiod wrappers"
+repository = "https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git"
+categories = ["api-bindings", "hardware-support", "embedded", "os::linux-apis"]
+rust-version = "1.60"
+keywords = ["libgpiod", "gpio"]
+license = "Apache-2.0 OR BSD-3-Clause"
+edition = "2021"
+
+exclude = [
+ "Makefile.am",
+]
+
+[features]
+vnext = []
+
+[dependencies]
+errno = "0.2.8"
+intmap = "2.0.0"
+libc = "0.2.39"
+libgpiod-sys = { version = "0.1", path = "../libgpiod-sys" }
+thiserror = "1.0"
+
+[dev-dependencies]
+gpiosim-sys = { path = "../gpiosim-sys" }
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Linaro Ltd.
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
+
+# We do not want to build against the system libs when building with make. So we
+# specify the paths to the build directory of the C lib.
+command = SYSTEM_DEPS_LIBGPIOD_NO_PKG_CONFIG=1 \
+ SYSTEM_DEPS_LIBGPIOD_SEARCH_NATIVE="${PWD}/../../../lib/.libs/" \
+ SYSTEM_DEPS_LIBGPIOD_LIB=gpiod \
+ SYSTEM_DEPS_LIBGPIOD_INCLUDE="${PWD}/../../../include/" \
+ cargo build --features=vnext --release --lib
+
+if WITH_TESTS
+command += --tests
+endif
+
+if WITH_EXAMPLES
+command += --examples
+endif
+
+all:
+ $(command)
+
+clean:
+ cargo clean
+
+EXTRA_DIST = Cargo.toml
+SUBDIRS = examples src tests
--- /dev/null
+<!--
+SPDX-License-Identifier: CC0-1.0
+SPDX-FileCopyrightText: 2023 Linaro Ltd.
+SPDX-FileCopyrightText: 2023 Erik Schilling <erik.schilling@linaro.org>
+-->
+
+# Safe wrapper around Rust FFI bindings for libgpiod
+
+[libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/tree/README)
+is a C library that provides an easy to use abstraction over the Linux GPIO
+character driver. This crate builds on top of `libgpiod-sys` and exports a safe
+interface to the C library.
+
+## Build requirements
+
+By default, `libgpiod-sys` builds against the libgpiod version identified via
+`pkg-config`. See the `README.md` of `libgpiod-sys` for options to override
+that.
+
+Currently at least libgpiod 2.0 is required with the default feature set.
+
+## Features
+
+The Rust bindings will usually be built against whatever libgpiod version a
+system provides. Hence, only the functionality of the oldest supported libgpiod
+C library will be exposed by default.
+
+Setting flags allows to increase the base version and export features of newer
+versions:
+
+- `vnext`: The upcoming, still unreleased version of the C lib
+
+## License
+
+This project is licensed under either of
+
+- [Apache License](http://www.apache.org/licenses/LICENSE-2.0), Version 2.0
+- [BSD-3-Clause License](https://opensource.org/licenses/BSD-3-Clause)
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Linaro Ltd.
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
+
+EXTRA_DIST = \
+ buffered_event_lifetimes.rs \
+ find_line_by_name.rs \
+ get_chip_info.rs \
+ get_line_info.rs \
+ get_line_value.rs \
+ get_multiple_line_values.rs \
+ reconfigure_input_to_output.rs \
+ toggle_line_value.rs \
+ toggle_multiple_line_values.rs \
+ watch_line_info.rs \
+ watch_line_rising.rs \
+ watch_line_value.rs \
+ watch_multiple_line_values.rs
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
+// SPDX-FileCopyrightText: 2022 Linaro Ltd.
+// SPDX-FileCopyrightText: 2022 Viresh Kumar <viresh.kumar@linaro.org>
+//
+// An example demonstrating that an edge event must be cloned to outlive
+// subsequent writes to the containing event buffer.
+
+use libgpiod::line;
+
+fn main() -> libgpiod::Result<()> {
+ // Example configuration - customize to suit your situation
+ let chip_path = "/dev/gpiochip0";
+ let line_offset = 5;
+
+ let mut lsettings = line::Settings::new()?;
+ lsettings.set_edge_detection(Some(line::Edge::Both))?;
+
+ let mut lconfig = line::Config::new()?;
+ lconfig.add_line_settings(&[line_offset], lsettings)?;
+
+ let mut rconfig = libgpiod::request::Config::new()?;
+ rconfig.set_consumer("buffered-event-lifetimes")?;
+
+ let chip = libgpiod::chip::Chip::open(&chip_path)?;
+ let request = chip.request_lines(Some(&rconfig), &lconfig)?;
+
+ let mut buffer = libgpiod::request::Buffer::new(4)?;
+
+ loop {
+ // Blocks until at least one event is available
+ let mut events = request.read_edge_events(&mut buffer)?;
+
+ // This can't be used across the next read_edge_events().
+ let event = events.next().unwrap()?;
+
+ // This will out live `event` and the next read_edge_events().
+ let cloned_event = libgpiod::request::Event::try_clone(event)?;
+
+ let events = request.read_edge_events(&mut buffer)?;
+ for event in events {
+ let event = event?;
+ println!(
+ "line: {} type: {:?} event #{}",
+ event.line_offset(),
+ event.event_type(),
+ event.line_seqno(),
+ );
+ }
+
+ // `cloned_event` is still available to be used.
+ println!(
+ "line: {} type: {:?} event #{}",
+ cloned_event.line_offset(),
+ cloned_event.event_type(),
+ cloned_event.line_seqno(),
+ );
+ }
+}
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
+// SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+//
+// Minimal example of finding a line with the given name.
+
+fn main() -> libgpiod::Result<()> {
+ // Example configuration - customize to suit your situation
+ let line_name = "GPIO19";
+
+ // Names are not guaranteed unique, so this finds the first line with
+ // the given name.
+ for chip in libgpiod::gpiochip_devices(&"/dev")? {
+ let offset = chip.line_offset_from_name(line_name);
+
+ if offset.is_ok() {
+ let info = chip.info()?;
+ println!(
+ "{}: {} {}",
+ line_name,
+ info.name()?,
+ offset?
+ );
+ return Ok(());
+ }
+ }
+
+ println!("line '{}' not found", line_name);
+ Ok(())
+}
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
+// SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+//
+// Minimal example of reading the info for a chip.
+
+use libgpiod::{self, Result};
+
+fn main() -> Result<()> {
+ // Example configuration - customize to suit your situation
+ let chip_path = "/dev/gpiochip0";
+
+ let chip = libgpiod::chip::Chip::open(&chip_path)?;
+ let info = chip.info()?;
+ println!(
+ "{} [{}] ({} lines)",
+ info.name()?,
+ info.label()?,
+ info.num_lines(),
+ );
+
+ Ok(())
+}
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
+// SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+//
+// Minimal example of reading the info for a line.
+
+use libgpiod::line::Direction;
+
+fn main() -> libgpiod::Result<()> {
+ // Example configuration - customize to suit your situation
+ let chip_path = "/dev/gpiochip0";
+ let line_offset = 3;
+
+ let chip = libgpiod::chip::Chip::open(&chip_path)?;
+ let info = chip.line_info(line_offset)?;
+
+ let name = info.name().unwrap_or("unnamed");
+ let consumer = info.consumer().unwrap_or("unused");
+
+ let dir = match info.direction()? {
+ Direction::AsIs => "none",
+ Direction::Input => "input",
+ Direction::Output => "output",
+ };
+
+ let low = if info.is_active_low() {
+ "active-low"
+ } else {
+ "active-high"
+ };
+
+ println!(
+ "line {:>3}: {:>12} {:>12} {:>8} {:>10}",
+ line_offset, name, consumer, dir, low
+ );
+
+ Ok(())
+}
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
+// SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+//
+// Minimal example of reading a single line.
+
+use libgpiod::line;
+
+fn main() -> libgpiod::Result<()> {
+ // Example configuration - customize to suit your situation
+ let chip_path = "/dev/gpiochip0";
+ let line_offset = 5;
+
+ let mut lsettings = line::Settings::new()?;
+ lsettings.set_direction(line::Direction::Input)?;
+
+ let mut lconfig = line::Config::new()?;
+ lconfig.add_line_settings(&[line_offset], lsettings)?;
+
+ let mut rconfig = libgpiod::request::Config::new()?;
+ rconfig.set_consumer("get-line-value")?;
+
+ let chip = libgpiod::chip::Chip::open(&chip_path)?;
+ let request = chip.request_lines(Some(&rconfig), &lconfig)?;
+
+ let value = request.value(line_offset)?;
+ println!("{}={:?}", line_offset, value);
+ Ok(())
+}
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
+// SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+//
+// Minimal example of reading multiple lines.
+
+use libgpiod::line::{self, Direction};
+
+fn main() -> libgpiod::Result<()> {
+ // Example configuration - customize to suit your situation
+ let chip_path = "/dev/gpiochip0";
+ let line_offsets = [5, 3, 7];
+
+ let mut lsettings = line::Settings::new()?;
+ let mut lconfig = line::Config::new()?;
+
+ lsettings.set_direction(Direction::Input)?;
+ lconfig.add_line_settings(&line_offsets, lsettings)?;
+
+ let chip = libgpiod::chip::Chip::open(&chip_path)?;
+
+ let mut rconfig = libgpiod::request::Config::new()?;
+ rconfig.set_consumer("get-multiple-line-values")?;
+
+ let request = chip.request_lines(Some(&rconfig), &lconfig)?;
+ let values = request.values()?;
+
+ println!("{:?}", values);
+ Ok(())
+}
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
+// SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+//
+// Example of a bi-directional line requested as input and then switched to output.
+
+use libgpiod::line;
+
+fn main() -> libgpiod::Result<()> {
+ // Example configuration - customize to suit your situation
+ let chip_path = "/dev/gpiochip0";
+ let line_offset = 5;
+
+ let mut lsettings = line::Settings::new()?;
+ lsettings.set_direction(line::Direction::Input)?;
+
+ let mut lconfig = line::Config::new()?;
+ lconfig.add_line_settings(&[line_offset], lsettings)?;
+
+ let mut rconfig = libgpiod::request::Config::new()?;
+ rconfig.set_consumer("reconfigure-input-to-output")?;
+
+ let chip = libgpiod::chip::Chip::open(&chip_path)?;
+ // request the line initially as an input
+ let mut request = chip.request_lines(Some(&rconfig), &lconfig)?;
+
+ // read the current line value
+ let value = request.value(line_offset)?;
+ println!("{}={:?} (input)", line_offset, value);
+
+ // switch the line to an output and drive it low
+ let mut lsettings = line::Settings::new()?;
+ lsettings.set_direction(line::Direction::Output)?;
+ lsettings.set_output_value(line::Value::InActive)?;
+ lconfig.add_line_settings(&[line_offset], lsettings)?;
+ request.reconfigure_lines(&lconfig)?;
+
+ // report the current driven value
+ let value = request.value(line_offset)?;
+ println!("{}={:?} (output)", line_offset, value);
+
+ Ok(())
+}
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
+// SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+//
+// Minimal example of toggling a single line.
+
+use core::time::Duration;
+use libgpiod::line::{self, Value};
+
+fn toggle_value(value: Value) -> Value {
+ match value {
+ Value::Active => Value::InActive,
+ Value::InActive => Value::Active,
+ }
+}
+
+fn main() -> libgpiod::Result<()> {
+ // Example configuration - customize to suit your situation
+ let chip_path = "/dev/gpiochip0";
+ let line_offset = 5;
+ let mut value = Value::Active;
+
+ let mut settings = line::Settings::new()?;
+ settings
+ .set_direction(line::Direction::Output)?
+ .set_output_value(value)?;
+
+ let mut lconfig = line::Config::new()?;
+ lconfig.add_line_settings(&[line_offset], settings)?;
+
+ let mut rconfig = libgpiod::request::Config::new()?;
+ rconfig.set_consumer("toggle-line-value")?;
+
+ let chip = libgpiod::chip::Chip::open(&chip_path)?;
+ let mut req = chip.request_lines(Some(&rconfig), &lconfig)?;
+
+ loop {
+ println!("{}={:?}", line_offset, value);
+ std::thread::sleep(Duration::from_secs(1));
+ value = toggle_value(value);
+ req.set_value(line_offset, value)?;
+ }
+}
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
+// SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+//
+// Minimal example of toggling multiple lines.
+
+use core::time::Duration;
+use libgpiod::line::{self, Offset, Value};
+
+fn toggle_value(value: Value) -> Value {
+ match value {
+ Value::Active => Value::InActive,
+ Value::InActive => Value::Active,
+ }
+}
+
+fn toggle_values(values: &mut [Value]) {
+ for i in values.iter_mut() {
+ *i = toggle_value(*i);
+ }
+}
+
+fn print_values(offsets: &[Offset], values: &[Value]) {
+ for i in 0..offsets.len() {
+ print!("{}={:?} ", offsets[i], values[i]);
+ }
+ println!();
+}
+
+fn main() -> libgpiod::Result<()> {
+ // Example configuration - customize to suit your situation
+ let chip_path = "/dev/gpiochip0";
+ let line_offsets = [5, 3, 7];
+ let mut values = vec![Value::Active, Value::Active, Value::InActive];
+
+ let mut lsettings = line::Settings::new()?;
+ lsettings.set_direction(line::Direction::Output)?;
+
+ let mut lconfig = line::Config::new()?;
+ lconfig
+ .add_line_settings(&line_offsets, lsettings)?
+ .set_output_values(&values)?;
+
+ let mut rconfig = libgpiod::request::Config::new()?;
+ rconfig.set_consumer("toggle-multiple-line-values")?;
+
+ let chip = libgpiod::chip::Chip::open(&chip_path)?;
+ let mut req = chip.request_lines(Some(&rconfig), &lconfig)?;
+
+ loop {
+ print_values(&line_offsets, &values);
+ std::thread::sleep(Duration::from_secs(1));
+ toggle_values(&mut values);
+ req.set_values(&values)?;
+ }
+}
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
+// SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+//
+// Minimal example of watching for info changes on particular lines.
+
+use libgpiod::line::InfoChangeKind;
+
+fn main() -> libgpiod::Result<()> {
+ // Example configuration - customize to suit your situation
+ let chip_path = "/dev/gpiochip0";
+ let line_offsets = [5, 3, 7];
+
+ let chip = libgpiod::chip::Chip::open(&chip_path)?;
+ for offset in line_offsets {
+ let _info = chip.watch_line_info(offset)?;
+ }
+
+ loop {
+ // Blocks until at least one event is available.
+ let event = chip.read_info_event()?;
+ println!(
+ "line: {} {:<9} {:?}",
+ event.line_info()?.offset(),
+ match event.event_type()? {
+ InfoChangeKind::LineRequested => "Requested",
+ InfoChangeKind::LineReleased => "Released",
+ InfoChangeKind::LineConfigChanged => "Reconfig",
+ },
+ event.timestamp()
+ );
+ }
+}
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
+// SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+//
+// Minimal example of watching for edges on a single line.
+
+use libgpiod::line;
+
+fn main() -> libgpiod::Result<()> {
+ // Example configuration - customize to suit your situation
+ let chip_path = "/dev/gpiochip0";
+ let line_offset = 5;
+
+ let mut lsettings = line::Settings::new()?;
+ lsettings.set_edge_detection(Some(line::Edge::Rising))?;
+
+ let mut lconfig = line::Config::new()?;
+ lconfig.add_line_settings(&[line_offset], lsettings)?;
+
+ let mut rconfig = libgpiod::request::Config::new()?;
+ rconfig.set_consumer("watch-line-value")?;
+
+ let chip = libgpiod::chip::Chip::open(&chip_path)?;
+ let request = chip.request_lines(Some(&rconfig), &lconfig)?;
+
+ // A larger buffer is an optimisation for reading bursts of events from the
+ // kernel, but that is not necessary in this case, so 1 is fine.
+ let mut buffer = libgpiod::request::Buffer::new(1)?;
+ loop {
+ // blocks until at least one event is available
+ let events = request.read_edge_events(&mut buffer)?;
+ for event in events {
+ let event = event?;
+ println!(
+ "line: {} type: {:<7} event #{}",
+ event.line_offset(),
+ match event.event_type()? {
+ line::EdgeKind::Rising => "Rising",
+ line::EdgeKind::Falling => "Falling",
+ },
+ event.line_seqno()
+ );
+ }
+ }
+}
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
+// SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+//
+// Minimal example of watching for edges on a single line.
+
+use libgpiod::line;
+use std::time::Duration;
+
+fn main() -> libgpiod::Result<()> {
+ // Example configuration - customize to suit your situation
+ let chip_path = "/dev/gpiochip0";
+ let line_offset = 5;
+
+ let mut lsettings = line::Settings::new()?;
+ // Assume a button connecting the pin to ground,
+ // so pull it up and provide some debounce.
+ lsettings
+ .set_edge_detection(Some(line::Edge::Both))?
+ .set_bias(Some(line::Bias::PullUp))?
+ .set_debounce_period(Duration::from_millis(10));
+
+ let mut lconfig = line::Config::new()?;
+ lconfig.add_line_settings(&[line_offset], lsettings)?;
+
+ let mut rconfig = libgpiod::request::Config::new()?;
+ rconfig.set_consumer("watch-line-value")?;
+
+ let chip = libgpiod::chip::Chip::open(&chip_path)?;
+ let request = chip.request_lines(Some(&rconfig), &lconfig)?;
+
+ // A larger buffer is an optimisation for reading bursts of events from the
+ // kernel, but that is not necessary in this case, so 1 is fine.
+ let mut buffer = libgpiod::request::Buffer::new(1)?;
+ loop {
+ // blocks until at least one event is available
+ let events = request.read_edge_events(&mut buffer)?;
+ for event in events {
+ let event = event?;
+ println!(
+ "line: {} type: {:<7} event #{}",
+ event.line_offset(),
+ match event.event_type()? {
+ line::EdgeKind::Rising => "Rising",
+ line::EdgeKind::Falling => "Falling",
+ },
+ event.line_seqno()
+ );
+ }
+ }
+}
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
+// SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+//
+// Minimal example of watching for edges on multiple lines.
+
+use libgpiod::{
+ line::{self, EdgeKind},
+ request,
+};
+
+fn main() -> libgpiod::Result<()> {
+ // Example configuration - customize to suit your situation
+ let chip_path = "/dev/gpiochip0";
+ let line_offsets = [5, 3, 7];
+
+ let mut lsettings = line::Settings::new()?;
+ lsettings.set_edge_detection(Some(line::Edge::Both))?;
+
+ let mut lconfig = line::Config::new()?;
+ lconfig.add_line_settings(&line_offsets, lsettings)?;
+
+ let mut rconfig = request::Config::new()?;
+ rconfig.set_consumer("watch-multiple-line-values")?;
+
+ let chip = libgpiod::chip::Chip::open(&chip_path)?;
+ let request = chip.request_lines(Some(&rconfig), &lconfig)?;
+
+ let mut buffer = request::Buffer::new(4)?;
+ loop {
+ // Blocks until at least one event is available.
+ let events = request.read_edge_events(&mut buffer)?;
+ for event in events {
+ let event = event?;
+ println!(
+ "offset: {} type: {:<7} event #{} line event #{}",
+ event.line_offset(),
+ match event.event_type()? {
+ EdgeKind::Rising => "Rising",
+ EdgeKind::Falling => "Falling",
+ },
+ event.global_seqno(),
+ event.line_seqno(),
+ );
+ }
+ }
+}
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Linaro Ltd.
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
+
+EXTRA_DIST = \
+ chip.rs \
+ edge_event.rs \
+ event_buffer.rs \
+ info_event.rs \
+ lib.rs \
+ line_config.rs \
+ line_info.rs \
+ line_request.rs \
+ line_settings.rs \
+ request_config.rs
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
+// SPDX-FileCopyrightText: 2022 Linaro Ltd.
+// SPDX-FileCopyrightText: 2022 Viresh Kumar <viresh.kumar@linaro.org>
+
+pub mod info {
+ /// GPIO chip info event related definitions.
+ pub use crate::info_event::*;
+}
+
+use std::cmp::Ordering;
+use std::ffi::{CStr, CString};
+use std::os::{raw::c_char, unix::prelude::AsRawFd};
+use std::path::Path;
+use std::ptr;
+use std::str;
+use std::time::Duration;
+
+use super::{
+ gpiod,
+ line::{self, Offset},
+ request, Error, OperationType, Result,
+};
+
+/// GPIO chip
+///
+/// A GPIO chip object is associated with an open file descriptor to the GPIO
+/// character device. It exposes basic information about the chip and allows
+/// callers to retrieve information about each line, watch lines for state
+/// changes and make line requests.
+#[derive(Debug, Eq, PartialEq)]
+pub struct Chip {
+ chip: *mut gpiod::gpiod_chip,
+}
+
+// SAFETY: Safe as chip is modeling a owned chip instance that may be freely
+// moved to other threads
+unsafe impl Send for Chip {}
+
+impl Chip {
+ /// Find a chip by path.
+ pub fn open<P: AsRef<Path>>(path: &P) -> Result<Self> {
+ // Null-terminate the string
+ let path = path.as_ref().to_string_lossy() + "\0";
+
+ // SAFETY: The `gpiod_chip` returned by libgpiod is guaranteed to live as long
+ // as the `struct Internal`.
+ let chip = unsafe { gpiod::gpiod_chip_open(path.as_ptr() as *const c_char) };
+ if chip.is_null() {
+ return Err(Error::OperationFailed(
+ OperationType::ChipOpen,
+ errno::errno(),
+ ));
+ }
+
+ Ok(Self { chip })
+ }
+
+ /// Get the chip name as represented in the kernel.
+ pub fn info(&self) -> Result<Info> {
+ Info::new(self)
+ }
+
+ /// Get the path used to find the chip.
+ pub fn path(&self) -> Result<&str> {
+ // SAFETY: The string returned by libgpiod is guaranteed to live as long
+ // as the `struct Chip`.
+ let path = unsafe { gpiod::gpiod_chip_get_path(self.chip) };
+
+ // SAFETY: The string is guaranteed to be valid here by the C API.
+ unsafe { CStr::from_ptr(path) }
+ .to_str()
+ .map_err(Error::StringNotUtf8)
+ }
+
+ /// Get a snapshot of information about the line.
+ pub fn line_info(&self, offset: Offset) -> Result<line::Info> {
+ // SAFETY: The `gpiod_line_info` returned by libgpiod is guaranteed to live as long
+ // as the `struct Info`.
+ let info = unsafe { gpiod::gpiod_chip_get_line_info(self.chip, offset) };
+
+ if info.is_null() {
+ return Err(Error::OperationFailed(
+ OperationType::ChipGetLineInfo,
+ errno::errno(),
+ ));
+ }
+
+ // SAFETY: We verified that the pointer is valid. We own the pointer and
+ // no longer use it after converting it into a Info instance.
+ Ok(unsafe { line::Info::from_raw(info) })
+ }
+
+ /// Get the current snapshot of information about the line at given offset and start watching
+ /// it for future changes.
+ pub fn watch_line_info(&self, offset: Offset) -> Result<line::Info> {
+ // SAFETY: `gpiod_line_info` is guaranteed to be valid here.
+ let info = unsafe { gpiod::gpiod_chip_watch_line_info(self.chip, offset) };
+
+ if info.is_null() {
+ return Err(Error::OperationFailed(
+ OperationType::ChipWatchLineInfo,
+ errno::errno(),
+ ));
+ }
+
+ // SAFETY: We verified that the pointer is valid. We own the instance and
+ // no longer use it after converting it into a Info instance.
+ Ok(unsafe { line::Info::from_raw(info) })
+ }
+
+ /// Stop watching a line
+ pub fn unwatch(&self, offset: Offset) {
+ // SAFETY: `gpiod_chip` is guaranteed to be valid here.
+ unsafe {
+ gpiod::gpiod_chip_unwatch_line_info(self.chip, offset);
+ }
+ }
+
+ /// Wait for line status events on any of the watched lines on the chip.
+ pub fn wait_info_event(&self, timeout: Option<Duration>) -> Result<bool> {
+ let timeout = match timeout {
+ Some(x) => x.as_nanos() as i64,
+ // Block indefinitely
+ None => -1,
+ };
+
+ // SAFETY: `gpiod_chip` is guaranteed to be valid here.
+ let ret = unsafe { gpiod::gpiod_chip_wait_info_event(self.chip, timeout) };
+
+ match ret {
+ -1 => Err(Error::OperationFailed(
+ OperationType::ChipWaitInfoEvent,
+ errno::errno(),
+ )),
+ 0 => Ok(false),
+ _ => Ok(true),
+ }
+ }
+
+ /// Read a single line status change event from the chip. If no events are
+ /// pending, this function will block.
+ pub fn read_info_event(&self) -> Result<info::Event> {
+ // SAFETY: The `gpiod_info_event` returned by libgpiod is guaranteed to live as long
+ // as the `struct Event`.
+ let event = unsafe { gpiod::gpiod_chip_read_info_event(self.chip) };
+ if event.is_null() {
+ return Err(Error::OperationFailed(
+ OperationType::ChipReadInfoEvent,
+ errno::errno(),
+ ));
+ }
+
+ Ok(info::Event::new(event))
+ }
+
+ /// Map a GPIO line's name to its offset within the chip.
+ pub fn line_offset_from_name(&self, name: &str) -> Result<Offset> {
+ let name = CString::new(name).map_err(|_| Error::InvalidString)?;
+
+ // SAFETY: `gpiod_chip` is guaranteed to be valid here.
+ let ret = unsafe {
+ gpiod::gpiod_chip_get_line_offset_from_name(self.chip, name.as_ptr() as *const c_char)
+ };
+
+ if ret == -1 {
+ Err(Error::OperationFailed(
+ OperationType::ChipGetLineOffsetFromName,
+ errno::errno(),
+ ))
+ } else {
+ Ok(ret as u32)
+ }
+ }
+
+ /// Request a set of lines for exclusive usage.
+ pub fn request_lines(
+ &self,
+ rconfig: Option<&request::Config>,
+ lconfig: &line::Config,
+ ) -> Result<request::Request> {
+ let req_cfg = match rconfig {
+ Some(cfg) => cfg.config,
+ _ => ptr::null(),
+ } as *mut gpiod::gpiod_request_config;
+
+ // SAFETY: The `gpiod_line_request` returned by libgpiod is guaranteed to live as long
+ // as the `struct Request`.
+ let request =
+ unsafe { gpiod::gpiod_chip_request_lines(self.chip, req_cfg, lconfig.config) };
+
+ if request.is_null() {
+ return Err(Error::OperationFailed(
+ OperationType::ChipRequestLines,
+ errno::errno(),
+ ));
+ }
+
+ request::Request::new(request)
+ }
+}
+
+impl Drop for Chip {
+ /// Close the chip and release all associated resources.
+ fn drop(&mut self) {
+ // SAFETY: `gpiod_chip` is guaranteed to be valid here.
+ unsafe { gpiod::gpiod_chip_close(self.chip) }
+ }
+}
+
+impl AsRawFd for Chip {
+ /// Get the file descriptor associated with the chip.
+ ///
+ /// The returned file descriptor must not be closed by the caller, else other methods for the
+ /// `struct Chip` may fail.
+ fn as_raw_fd(&self) -> i32 {
+ // SAFETY: `gpiod_chip` is guaranteed to be valid here.
+ unsafe { gpiod::gpiod_chip_get_fd(self.chip) }
+ }
+}
+
+/// GPIO chip Information
+#[derive(Debug, Eq)]
+pub struct Info {
+ info: *mut gpiod::gpiod_chip_info,
+}
+
+impl Info {
+ /// Find a GPIO chip by path.
+ fn new(chip: &Chip) -> Result<Self> {
+ // SAFETY: `chip.chip` is guaranteed to be valid here.
+ let info = unsafe { gpiod::gpiod_chip_get_info(chip.chip) };
+ if info.is_null() {
+ return Err(Error::OperationFailed(
+ OperationType::ChipGetInfo,
+ errno::errno(),
+ ));
+ }
+
+ Ok(Self { info })
+ }
+
+ /// Get the GPIO chip name as represented in the kernel.
+ pub fn name(&self) -> Result<&str> {
+ // SAFETY: The string returned by libgpiod is guaranteed to live as long
+ // as the `struct Chip`.
+ let name = unsafe { gpiod::gpiod_chip_info_get_name(self.info) };
+
+ // SAFETY: The string is guaranteed to be valid here by the C API.
+ unsafe { CStr::from_ptr(name) }
+ .to_str()
+ .map_err(Error::StringNotUtf8)
+ }
+
+ /// Get the GPIO chip label as represented in the kernel.
+ pub fn label(&self) -> Result<&str> {
+ // SAFETY: The string returned by libgpiod is guaranteed to live as long
+ // as the `struct Chip`.
+ let label = unsafe { gpiod::gpiod_chip_info_get_label(self.info) };
+
+ // SAFETY: The string is guaranteed to be valid here by the C API.
+ unsafe { CStr::from_ptr(label) }
+ .to_str()
+ .map_err(Error::StringNotUtf8)
+ }
+
+ /// Get the number of GPIO lines exposed by the chip.
+ pub fn num_lines(&self) -> usize {
+ // SAFETY: `gpiod_chip` is guaranteed to be valid here.
+ unsafe { gpiod::gpiod_chip_info_get_num_lines(self.info) }
+ }
+}
+
+impl PartialEq for Info {
+ fn eq(&self, other: &Self) -> bool {
+ self.name().unwrap().eq(other.name().unwrap())
+ }
+}
+
+impl PartialOrd for Info {
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+ let name = match self.name() {
+ Ok(name) => name,
+ _ => return None,
+ };
+
+ let other_name = match other.name() {
+ Ok(name) => name,
+ _ => return None,
+ };
+
+ name.partial_cmp(other_name)
+ }
+}
+
+impl Drop for Info {
+ /// Close the GPIO chip info and release all associated resources.
+ fn drop(&mut self) {
+ // SAFETY: `gpiod_chip` is guaranteed to be valid here.
+ unsafe { gpiod::gpiod_chip_info_free(self.info) }
+ }
+}
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
+// SPDX-FileCopyrightText: 2022 Linaro Ltd.
+// SPDX-FileCopyrightText: 2022 Viresh Kumar <viresh.kumar@linaro.org>
+
+use std::time::Duration;
+
+use super::{
+ gpiod,
+ line::{EdgeKind, Offset},
+ Error, OperationType, Result,
+};
+
+/// Line edge events handling
+///
+/// An edge event object contains information about a single line edge event.
+/// It contains the event type, timestamp and the offset of the line on which
+/// the event occurred as well as two sequence numbers (global for all lines
+/// in the associated request and local for this line only).
+///
+/// Edge events are stored into an edge-event buffer object to improve
+/// performance and to limit the number of memory allocations when a large
+/// number of events are being read.
+
+#[derive(Debug, Eq, PartialEq)]
+pub struct Event(*mut gpiod::gpiod_edge_event);
+
+// SAFETY: Event models a wrapper around an owned gpiod_edge_event and may
+// be safely sent to other threads.
+unsafe impl Send for Event {}
+
+impl Event {
+ /// Makes a copy of the event object.
+ pub fn try_clone(event: &Event) -> Result<Event> {
+ // SAFETY: `gpiod_edge_event` is guaranteed to be valid here.
+ let event = unsafe { gpiod::gpiod_edge_event_copy(event.0) };
+ if event.is_null() {
+ return Err(Error::OperationFailed(
+ OperationType::EdgeEventCopy,
+ errno::errno(),
+ ));
+ }
+
+ Ok(Self(event))
+ }
+
+ /// Get the event type.
+ pub fn event_type(&self) -> Result<EdgeKind> {
+ // SAFETY: `gpiod_edge_event` is guaranteed to be valid here.
+ EdgeKind::new(unsafe { gpiod::gpiod_edge_event_get_event_type(self.0) })
+ }
+
+ /// Get the timestamp of the event.
+ pub fn timestamp(&self) -> Duration {
+ // SAFETY: `gpiod_edge_event` is guaranteed to be valid here.
+ Duration::from_nanos(unsafe { gpiod::gpiod_edge_event_get_timestamp_ns(self.0) })
+ }
+
+ /// Get the offset of the line on which the event was triggered.
+ pub fn line_offset(&self) -> Offset {
+ // SAFETY: `gpiod_edge_event` is guaranteed to be valid here.
+ unsafe { gpiod::gpiod_edge_event_get_line_offset(self.0) }
+ }
+
+ /// Get the global sequence number of the event.
+ ///
+ /// Returns sequence number of the event relative to all lines in the
+ /// associated line request.
+ pub fn global_seqno(&self) -> usize {
+ // SAFETY: `gpiod_edge_event` is guaranteed to be valid here.
+ unsafe {
+ gpiod::gpiod_edge_event_get_global_seqno(self.0)
+ .try_into()
+ .unwrap()
+ }
+ }
+
+ /// Get the event sequence number specific to concerned line.
+ ///
+ /// Returns sequence number of the event relative to the line within the
+ /// lifetime of the associated line request.
+ pub fn line_seqno(&self) -> usize {
+ // SAFETY: `gpiod_edge_event` is guaranteed to be valid here.
+ unsafe {
+ gpiod::gpiod_edge_event_get_line_seqno(self.0)
+ .try_into()
+ .unwrap()
+ }
+ }
+}
+
+impl Drop for Event {
+ /// Free the edge event.
+ fn drop(&mut self) {
+ // SAFETY: `gpiod_edge_event` is guaranteed to be valid here.
+ unsafe { gpiod::gpiod_edge_event_free(self.0) };
+ }
+}
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
+// SPDX-FileCopyrightText: 2022 Linaro Ltd.
+// SPDX-FileCopyrightText: 2022 Viresh Kumar <viresh.kumar@linaro.org>
+
+use std::ptr;
+
+use super::{
+ gpiod,
+ request::{Event, Request},
+ Error, OperationType, Result,
+};
+
+/// Line edge events
+///
+/// An iterator over the elements of type `Event`.
+
+pub struct Events<'a> {
+ buffer: &'a mut Buffer,
+ read_index: usize,
+ len: usize,
+}
+
+impl<'a> Events<'a> {
+ pub fn new(buffer: &'a mut Buffer, len: usize) -> Self {
+ Self {
+ buffer,
+ read_index: 0,
+ len,
+ }
+ }
+
+ /// Get the number of contained events in the snapshot, this doesn't change
+ /// on reading events from the iterator.
+ pub fn len(&self) -> usize {
+ self.len
+ }
+
+ /// Check if buffer is empty.
+ pub fn is_empty(&self) -> bool {
+ self.len == 0
+ }
+}
+
+impl<'a> Iterator for Events<'a> {
+ type Item = Result<&'a Event>;
+
+ fn nth(&mut self, n: usize) -> Option<Self::Item> {
+ if self.read_index + n >= self.len {
+ return None;
+ }
+
+ self.read_index += n + 1;
+ Some(self.buffer.event(self.read_index - 1))
+ }
+
+ fn next(&mut self) -> Option<Self::Item> {
+ // clippy false-positive, fixed in next clippy release:
+ // https://github.com/rust-lang/rust-clippy/issues/9820
+ #[allow(clippy::iter_nth_zero)]
+ self.nth(0)
+ }
+}
+
+/// Line edge events buffer
+#[derive(Debug, Eq, PartialEq)]
+pub struct Buffer {
+ pub(crate) buffer: *mut gpiod::gpiod_edge_event_buffer,
+ events: Vec<*mut gpiod::gpiod_edge_event>,
+}
+
+// SAFETY: Buffer models an owned gpiod_edge_event_buffer. However, there may
+// be events tied to it. Concurrent access from multiple threads to a buffer
+// and its associated events is not allowed by the C lib.
+// In Rust, those events will always be borrowed from a buffer instance. Thus,
+// either Rust prevents the user to move the Buffer while there are still
+// borrowed events, or we can safely send the the Buffer.
+unsafe impl Send for Buffer {}
+
+impl Buffer {
+ /// Create a new edge event buffer.
+ ///
+ /// If capacity equals 0, it will be set to a default value of 64. If
+ /// capacity is larger than 1024, it will be limited to 1024.
+ pub fn new(capacity: usize) -> Result<Self> {
+ // SAFETY: The `gpiod_edge_event_buffer` returned by libgpiod is guaranteed to live as long
+ // as the `struct Buffer`.
+ let buffer = unsafe { gpiod::gpiod_edge_event_buffer_new(capacity) };
+ if buffer.is_null() {
+ return Err(Error::OperationFailed(
+ OperationType::EdgeEventBufferNew,
+ errno::errno(),
+ ));
+ }
+
+ // SAFETY: `gpiod_edge_event_buffer` is guaranteed to be valid here.
+ let capacity = unsafe { gpiod::gpiod_edge_event_buffer_get_capacity(buffer) };
+
+ Ok(Self {
+ buffer,
+ events: vec![ptr::null_mut(); capacity],
+ })
+ }
+
+ /// Get the capacity of the event buffer.
+ pub fn capacity(&self) -> usize {
+ self.events.len()
+ }
+
+ /// Get edge events from a line request.
+ ///
+ /// This function will block if no event was queued for the line.
+ pub fn read_edge_events(&mut self, request: &Request) -> Result<Events> {
+ for i in 0..self.events.len() {
+ self.events[i] = ptr::null_mut();
+ }
+
+ // SAFETY: `gpiod_line_request` is guaranteed to be valid here.
+ let ret = unsafe {
+ gpiod::gpiod_line_request_read_edge_events(
+ request.request,
+ self.buffer,
+ self.events.len(),
+ )
+ };
+
+ if ret == -1 {
+ Err(Error::OperationFailed(
+ OperationType::LineRequestReadEdgeEvent,
+ errno::errno(),
+ ))
+ } else {
+ let ret = ret as usize;
+
+ if ret > self.events.len() {
+ Err(Error::TooManyEvents(ret, self.events.len()))
+ } else {
+ Ok(Events::new(self, ret))
+ }
+ }
+ }
+
+ /// Read an event stored in the buffer.
+ fn event<'a>(&mut self, index: usize) -> Result<&'a Event> {
+ if self.events[index].is_null() {
+ // SAFETY: The `gpiod_edge_event` returned by libgpiod is guaranteed to live as long
+ // as the `struct Event`.
+ let event = unsafe {
+ gpiod::gpiod_edge_event_buffer_get_event(self.buffer, index.try_into().unwrap())
+ };
+
+ if event.is_null() {
+ return Err(Error::OperationFailed(
+ OperationType::EdgeEventBufferGetEvent,
+ errno::errno(),
+ ));
+ }
+
+ self.events[index] = event;
+ }
+
+ // SAFETY: Safe as the underlying events object won't get freed until the time the returned
+ // reference is still used.
+ Ok(unsafe {
+ // This will not lead to `drop(event)`.
+ (self.events.as_ptr().add(index) as *const Event)
+ .as_ref()
+ .unwrap()
+ })
+ }
+}
+
+impl Drop for Buffer {
+ /// Free the edge event buffer and release all associated resources.
+ fn drop(&mut self) {
+ // SAFETY: `gpiod_edge_event_buffer` is guaranteed to be valid here.
+ unsafe { gpiod::gpiod_edge_event_buffer_free(self.buffer) };
+ }
+}
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
+// SPDX-FileCopyrightText: 2022 Linaro Ltd.
+// SPDX-FileCopyrightText: 2022 Viresh Kumar <viresh.kumar@linaro.org>
+
+use std::time::Duration;
+
+use super::{
+ gpiod,
+ line::{self, InfoChangeKind},
+ Error, OperationType, Result,
+};
+
+/// Line status watch events
+///
+/// Accessors for the info event objects allowing to monitor changes in GPIO
+/// line state.
+///
+/// Callers can be notified about changes in line's state using the interfaces
+/// exposed by GPIO chips. Each info event contains information about the event
+/// itself (timestamp, type) as well as a snapshot of line's state in the form
+/// of a line-info object.
+
+#[derive(Debug, Eq, PartialEq)]
+pub struct Event {
+ pub(crate) event: *mut gpiod::gpiod_info_event,
+}
+
+// SAFETY: Event models a wrapper around an owned gpiod_info_event and may be
+// safely sent to other threads.
+unsafe impl Send for Event {}
+
+impl Event {
+ /// Get a single chip's line's status change event.
+ pub(crate) fn new(event: *mut gpiod::gpiod_info_event) -> Self {
+ Self { event }
+ }
+
+ /// Get the event type of the status change event.
+ pub fn event_type(&self) -> Result<InfoChangeKind> {
+ // SAFETY: `gpiod_info_event` is guaranteed to be valid here.
+ InfoChangeKind::new(unsafe { gpiod::gpiod_info_event_get_event_type(self.event) })
+ }
+
+ /// Get the timestamp of the event, read from the monotonic clock.
+ pub fn timestamp(&self) -> Duration {
+ // SAFETY: `gpiod_info_event` is guaranteed to be valid here.
+ Duration::from_nanos(unsafe { gpiod::gpiod_info_event_get_timestamp_ns(self.event) })
+ }
+
+ /// Get the line-info object associated with the event.
+ pub fn line_info(&self) -> Result<&line::InfoRef> {
+ // SAFETY: `gpiod_line_info` is guaranteed to be valid here.
+ let info = unsafe { gpiod::gpiod_info_event_get_line_info(self.event) };
+
+ if info.is_null() {
+ return Err(Error::OperationFailed(
+ OperationType::InfoEventGetLineInfo,
+ errno::errno(),
+ ));
+ }
+
+ // SAFETY: The pointer is valid. The returned reference receives the
+ // lifetime '0 - the same as &self. &self also controls lifetime and
+ // ownership of the owning object. Therefore, the borrow prevents moving
+ // of the owning object to another thread.
+ Ok(unsafe { line::InfoRef::from_raw(info) })
+ }
+}
+
+impl Drop for Event {
+ /// Free the info event object and release all associated resources.
+ fn drop(&mut self) {
+ // SAFETY: `gpiod_info_event` is guaranteed to be valid here.
+ unsafe { gpiod::gpiod_info_event_free(self.event) }
+ }
+}
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
+// SPDX-FileCopyrightText: 2022 Linaro Ltd.
+// SPDX-FileCopyrightText: 2022 Viresh Kumar <viresh.kumar@linaro.org>
+//
+// Rust wrappers for GPIOD APIs
+
+//! libgpiod public API
+//!
+//! This is the complete documentation of the public Rust API made available to
+//! users of libgpiod.
+//!
+//! The API is logically split into several parts such as: GPIO chip & line
+//! operators, GPIO events handling etc.
+
+use std::ffi::CStr;
+use std::fs;
+use std::os::raw::c_char;
+use std::path::Path;
+use std::time::Duration;
+use std::{fmt, str};
+
+use intmap::IntMap;
+use thiserror::Error as ThisError;
+
+use libgpiod_sys as gpiod;
+
+use gpiod::{
+ gpiod_edge_event_type_GPIOD_EDGE_EVENT_FALLING_EDGE as GPIOD_EDGE_EVENT_FALLING_EDGE,
+ gpiod_edge_event_type_GPIOD_EDGE_EVENT_RISING_EDGE as GPIOD_EDGE_EVENT_RISING_EDGE,
+ gpiod_info_event_type_GPIOD_INFO_EVENT_LINE_CONFIG_CHANGED as GPIOD_INFO_EVENT_LINE_CONFIG_CHANGED,
+ gpiod_info_event_type_GPIOD_INFO_EVENT_LINE_RELEASED as GPIOD_INFO_EVENT_LINE_RELEASED,
+ gpiod_info_event_type_GPIOD_INFO_EVENT_LINE_REQUESTED as GPIOD_INFO_EVENT_LINE_REQUESTED,
+ gpiod_line_bias_GPIOD_LINE_BIAS_AS_IS as GPIOD_LINE_BIAS_AS_IS,
+ gpiod_line_bias_GPIOD_LINE_BIAS_DISABLED as GPIOD_LINE_BIAS_DISABLED,
+ gpiod_line_bias_GPIOD_LINE_BIAS_PULL_DOWN as GPIOD_LINE_BIAS_PULL_DOWN,
+ gpiod_line_bias_GPIOD_LINE_BIAS_PULL_UP as GPIOD_LINE_BIAS_PULL_UP,
+ gpiod_line_bias_GPIOD_LINE_BIAS_UNKNOWN as GPIOD_LINE_BIAS_UNKNOWN,
+ gpiod_line_clock_GPIOD_LINE_CLOCK_HTE as GPIOD_LINE_CLOCK_HTE,
+ gpiod_line_clock_GPIOD_LINE_CLOCK_MONOTONIC as GPIOD_LINE_CLOCK_MONOTONIC,
+ gpiod_line_clock_GPIOD_LINE_CLOCK_REALTIME as GPIOD_LINE_CLOCK_REALTIME,
+ gpiod_line_direction_GPIOD_LINE_DIRECTION_AS_IS as GPIOD_LINE_DIRECTION_AS_IS,
+ gpiod_line_direction_GPIOD_LINE_DIRECTION_INPUT as GPIOD_LINE_DIRECTION_INPUT,
+ gpiod_line_direction_GPIOD_LINE_DIRECTION_OUTPUT as GPIOD_LINE_DIRECTION_OUTPUT,
+ gpiod_line_drive_GPIOD_LINE_DRIVE_OPEN_DRAIN as GPIOD_LINE_DRIVE_OPEN_DRAIN,
+ gpiod_line_drive_GPIOD_LINE_DRIVE_OPEN_SOURCE as GPIOD_LINE_DRIVE_OPEN_SOURCE,
+ gpiod_line_drive_GPIOD_LINE_DRIVE_PUSH_PULL as GPIOD_LINE_DRIVE_PUSH_PULL,
+ gpiod_line_edge_GPIOD_LINE_EDGE_BOTH as GPIOD_LINE_EDGE_BOTH,
+ gpiod_line_edge_GPIOD_LINE_EDGE_FALLING as GPIOD_LINE_EDGE_FALLING,
+ gpiod_line_edge_GPIOD_LINE_EDGE_NONE as GPIOD_LINE_EDGE_NONE,
+ gpiod_line_edge_GPIOD_LINE_EDGE_RISING as GPIOD_LINE_EDGE_RISING,
+ gpiod_line_value_GPIOD_LINE_VALUE_ACTIVE as GPIOD_LINE_VALUE_ACTIVE,
+ gpiod_line_value_GPIOD_LINE_VALUE_ERROR as GPIOD_LINE_VALUE_ERROR,
+ gpiod_line_value_GPIOD_LINE_VALUE_INACTIVE as GPIOD_LINE_VALUE_INACTIVE,
+};
+
+/// Operation types, used with OperationFailed() Error.
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub enum OperationType {
+ ChipOpen,
+ ChipWaitInfoEvent,
+ ChipGetLine,
+ ChipGetLineInfo,
+ ChipGetLineOffsetFromName,
+ ChipGetInfo,
+ ChipReadInfoEvent,
+ ChipRequestLines,
+ ChipWatchLineInfo,
+ EdgeEventBufferGetEvent,
+ EdgeEventCopy,
+ EdgeEventBufferNew,
+ InfoEventGetLineInfo,
+ LineConfigNew,
+ LineConfigAddSettings,
+ LineConfigSetOutputValues,
+ LineConfigGetOffsets,
+ LineConfigGetSettings,
+ LineInfoCopy,
+ LineRequestReconfigLines,
+ LineRequestGetVal,
+ LineRequestGetValSubset,
+ LineRequestSetVal,
+ LineRequestSetValSubset,
+ LineRequestReadEdgeEvent,
+ LineRequestWaitEdgeEvent,
+ LineSettingsNew,
+ LineSettingsCopy,
+ LineSettingsGetOutVal,
+ LineSettingsSetDirection,
+ LineSettingsSetEdgeDetection,
+ LineSettingsSetBias,
+ LineSettingsSetDrive,
+ LineSettingsSetActiveLow,
+ LineSettingsSetDebouncePeriod,
+ LineSettingsSetEventClock,
+ LineSettingsSetOutputValue,
+ RequestConfigNew,
+ RequestConfigGetConsumer,
+ SimBankGetVal,
+ SimBankNew,
+ SimBankSetLabel,
+ SimBankSetNumLines,
+ SimBankSetLineName,
+ SimBankSetPull,
+ SimBankHogLine,
+ SimCtxNew,
+ SimDevNew,
+ SimDevEnable,
+ SimDevDisable,
+}
+
+impl fmt::Display for OperationType {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{:?}", self)
+ }
+}
+
+/// Result of libgpiod operations.
+pub type Result<T> = std::result::Result<T, Error>;
+
+/// Error codes for libgpiod operations.
+#[derive(Copy, Clone, Debug, Eq, PartialEq, ThisError)]
+pub enum Error {
+ #[error("Failed to get {0}")]
+ NullString(&'static str),
+ #[error("String not utf8: {0:?}")]
+ StringNotUtf8(str::Utf8Error),
+ #[error("Invalid String")]
+ InvalidString,
+ #[error("Invalid enum {0} value: {1}")]
+ InvalidEnumValue(&'static str, i32),
+ #[error("Operation {0} Failed: {1}")]
+ OperationFailed(OperationType, errno::Errno),
+ #[error("Invalid Arguments")]
+ InvalidArguments,
+ #[error("Event count more than buffer capacity: {0} > {1}")]
+ TooManyEvents(usize, usize),
+ #[error("Std Io Error")]
+ IoError,
+}
+
+mod info_event;
+
+/// GPIO chip related definitions.
+pub mod chip;
+
+mod edge_event;
+mod event_buffer;
+mod line_request;
+mod request_config;
+
+/// GPIO chip request related definitions.
+pub mod request {
+ pub use crate::edge_event::*;
+ pub use crate::event_buffer::*;
+ pub use crate::line_request::*;
+ pub use crate::request_config::*;
+}
+
+mod line_config;
+mod line_info;
+mod line_settings;
+
+/// GPIO chip line related definitions.
+pub mod line {
+ pub use crate::line_config::*;
+ pub use crate::line_info::*;
+ pub use crate::line_settings::*;
+
+ use super::*;
+
+ /// Value settings.
+ #[derive(Copy, Clone, Debug, Eq, PartialEq)]
+ pub enum Value {
+ /// Active
+ Active,
+ /// Inactive
+ InActive,
+ }
+
+ /// Maps offset to Value.
+ pub type ValueMap = IntMap<Value>;
+
+ /// Maps offsets to Settings
+ pub type SettingsMap = IntMap<Settings>;
+
+ impl Value {
+ pub fn new(val: gpiod::gpiod_line_value) -> Result<Self> {
+ Ok(match val {
+ GPIOD_LINE_VALUE_INACTIVE => Value::InActive,
+ GPIOD_LINE_VALUE_ACTIVE => Value::Active,
+ GPIOD_LINE_VALUE_ERROR => {
+ return Err(Error::OperationFailed(
+ OperationType::LineRequestGetVal,
+ errno::errno(),
+ ))
+ }
+ _ => return Err(Error::InvalidEnumValue("Value", val)),
+ })
+ }
+
+ pub(crate) fn value(&self) -> gpiod::gpiod_line_value {
+ match self {
+ Value::Active => GPIOD_LINE_VALUE_ACTIVE,
+ Value::InActive => GPIOD_LINE_VALUE_INACTIVE,
+ }
+ }
+ }
+
+ /// Offset type.
+ pub type Offset = u32;
+
+ /// Direction settings.
+ #[derive(Copy, Clone, Debug, Eq, PartialEq)]
+ pub enum Direction {
+ /// Request the line(s), but don't change direction.
+ AsIs,
+ /// Direction is input - for reading the value of an externally driven GPIO line.
+ Input,
+ /// Direction is output - for driving the GPIO line.
+ Output,
+ }
+
+ impl Direction {
+ pub(crate) fn new(dir: gpiod::gpiod_line_direction) -> Result<Self> {
+ Ok(match dir {
+ GPIOD_LINE_DIRECTION_AS_IS => Direction::AsIs,
+ GPIOD_LINE_DIRECTION_INPUT => Direction::Input,
+ GPIOD_LINE_DIRECTION_OUTPUT => Direction::Output,
+ _ => return Err(Error::InvalidEnumValue("Direction", dir as i32)),
+ })
+ }
+
+ pub(crate) fn gpiod_direction(&self) -> gpiod::gpiod_line_direction {
+ match self {
+ Direction::AsIs => GPIOD_LINE_DIRECTION_AS_IS,
+ Direction::Input => GPIOD_LINE_DIRECTION_INPUT,
+ Direction::Output => GPIOD_LINE_DIRECTION_OUTPUT,
+ }
+ }
+ }
+
+ /// Internal bias settings.
+ #[derive(Copy, Clone, Debug, Eq, PartialEq)]
+ pub enum Bias {
+ /// The internal bias is disabled.
+ Disabled,
+ /// The internal pull-up bias is enabled.
+ PullUp,
+ /// The internal pull-down bias is enabled.
+ PullDown,
+ }
+
+ impl Bias {
+ pub(crate) fn new(bias: gpiod::gpiod_line_bias) -> Result<Option<Self>> {
+ Ok(match bias {
+ GPIOD_LINE_BIAS_UNKNOWN => None,
+ GPIOD_LINE_BIAS_AS_IS => None,
+ GPIOD_LINE_BIAS_DISABLED => Some(Bias::Disabled),
+ GPIOD_LINE_BIAS_PULL_UP => Some(Bias::PullUp),
+ GPIOD_LINE_BIAS_PULL_DOWN => Some(Bias::PullDown),
+ _ => return Err(Error::InvalidEnumValue("Bias", bias as i32)),
+ })
+ }
+
+ pub(crate) fn gpiod_bias(bias: Option<Bias>) -> gpiod::gpiod_line_bias {
+ match bias {
+ None => GPIOD_LINE_BIAS_AS_IS,
+ Some(bias) => match bias {
+ Bias::Disabled => GPIOD_LINE_BIAS_DISABLED,
+ Bias::PullUp => GPIOD_LINE_BIAS_PULL_UP,
+ Bias::PullDown => GPIOD_LINE_BIAS_PULL_DOWN,
+ },
+ }
+ }
+ }
+
+ /// Drive settings.
+ #[derive(Copy, Clone, Debug, Eq, PartialEq)]
+ pub enum Drive {
+ /// Drive setting is push-pull.
+ PushPull,
+ /// Line output is open-drain.
+ OpenDrain,
+ /// Line output is open-source.
+ OpenSource,
+ }
+
+ impl Drive {
+ pub(crate) fn new(drive: gpiod::gpiod_line_drive) -> Result<Self> {
+ Ok(match drive {
+ GPIOD_LINE_DRIVE_PUSH_PULL => Drive::PushPull,
+ GPIOD_LINE_DRIVE_OPEN_DRAIN => Drive::OpenDrain,
+ GPIOD_LINE_DRIVE_OPEN_SOURCE => Drive::OpenSource,
+ _ => return Err(Error::InvalidEnumValue("Drive", drive as i32)),
+ })
+ }
+
+ pub(crate) fn gpiod_drive(&self) -> gpiod::gpiod_line_drive {
+ match self {
+ Drive::PushPull => GPIOD_LINE_DRIVE_PUSH_PULL,
+ Drive::OpenDrain => GPIOD_LINE_DRIVE_OPEN_DRAIN,
+ Drive::OpenSource => GPIOD_LINE_DRIVE_OPEN_SOURCE,
+ }
+ }
+ }
+
+ /// Edge detection settings.
+ #[derive(Copy, Clone, Debug, Eq, PartialEq)]
+ pub enum Edge {
+ /// Line detects rising edge events.
+ Rising,
+ /// Line detects falling edge events.
+ Falling,
+ /// Line detects both rising and falling edge events.
+ Both,
+ }
+
+ impl Edge {
+ pub(crate) fn new(edge: gpiod::gpiod_line_edge) -> Result<Option<Self>> {
+ Ok(match edge {
+ GPIOD_LINE_EDGE_NONE => None,
+ GPIOD_LINE_EDGE_RISING => Some(Edge::Rising),
+ GPIOD_LINE_EDGE_FALLING => Some(Edge::Falling),
+ GPIOD_LINE_EDGE_BOTH => Some(Edge::Both),
+ _ => return Err(Error::InvalidEnumValue("Edge", edge as i32)),
+ })
+ }
+
+ pub(crate) fn gpiod_edge(edge: Option<Edge>) -> gpiod::gpiod_line_edge {
+ match edge {
+ None => GPIOD_LINE_EDGE_NONE,
+ Some(edge) => match edge {
+ Edge::Rising => GPIOD_LINE_EDGE_RISING,
+ Edge::Falling => GPIOD_LINE_EDGE_FALLING,
+ Edge::Both => GPIOD_LINE_EDGE_BOTH,
+ },
+ }
+ }
+ }
+
+ /// Line setting kind.
+ #[derive(Copy, Clone, Debug, Eq, PartialEq)]
+ pub enum SettingKind {
+ /// Line direction.
+ Direction,
+ /// Bias.
+ Bias,
+ /// Drive.
+ Drive,
+ /// Edge detection.
+ EdgeDetection,
+ /// Active-low setting.
+ ActiveLow,
+ /// Debounce period.
+ DebouncePeriod,
+ /// Event clock type.
+ EventClock,
+ /// Output value.
+ OutputValue,
+ }
+
+ /// Line settings.
+ #[derive(Copy, Clone, Debug, Eq, PartialEq)]
+ pub enum SettingVal {
+ /// Line direction.
+ Direction(Direction),
+ /// Bias.
+ Bias(Option<Bias>),
+ /// Drive.
+ Drive(Drive),
+ /// Edge detection.
+ EdgeDetection(Option<Edge>),
+ /// Active-low setting.
+ ActiveLow(bool),
+ /// Debounce period.
+ DebouncePeriod(Duration),
+ /// Event clock type.
+ EventClock(EventClock),
+ /// Output value.
+ OutputValue(Value),
+ }
+
+ impl fmt::Display for SettingVal {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{:?}", self)
+ }
+ }
+
+ /// Event clock settings.
+ #[derive(Copy, Clone, Debug, Eq, PartialEq)]
+ pub enum EventClock {
+ /// Line uses the monotonic clock for edge event timestamps.
+ Monotonic,
+ /// Line uses the realtime clock for edge event timestamps.
+ Realtime,
+ /// Line uses the hardware timestamp engine clock for edge event timestamps.
+ HTE,
+ }
+
+ impl EventClock {
+ pub(crate) fn new(clock: gpiod::gpiod_line_clock) -> Result<Self> {
+ Ok(match clock {
+ GPIOD_LINE_CLOCK_MONOTONIC => EventClock::Monotonic,
+ GPIOD_LINE_CLOCK_REALTIME => EventClock::Realtime,
+ GPIOD_LINE_CLOCK_HTE => EventClock::HTE,
+ _ => return Err(Error::InvalidEnumValue("Eventclock", clock as i32)),
+ })
+ }
+
+ pub(crate) fn gpiod_clock(&self) -> gpiod::gpiod_line_clock {
+ match self {
+ EventClock::Monotonic => GPIOD_LINE_CLOCK_MONOTONIC,
+ EventClock::Realtime => GPIOD_LINE_CLOCK_REALTIME,
+ EventClock::HTE => GPIOD_LINE_CLOCK_HTE,
+ }
+ }
+ }
+
+ /// Line status change event types.
+ #[derive(Copy, Clone, Debug, Eq, PartialEq)]
+ pub enum InfoChangeKind {
+ /// Line has been requested.
+ LineRequested,
+ /// Previously requested line has been released.
+ LineReleased,
+ /// Line configuration has changed.
+ LineConfigChanged,
+ }
+
+ impl InfoChangeKind {
+ pub(crate) fn new(kind: gpiod::gpiod_info_event_type) -> Result<Self> {
+ Ok(match kind {
+ GPIOD_INFO_EVENT_LINE_REQUESTED => InfoChangeKind::LineRequested,
+ GPIOD_INFO_EVENT_LINE_RELEASED => InfoChangeKind::LineReleased,
+ GPIOD_INFO_EVENT_LINE_CONFIG_CHANGED => InfoChangeKind::LineConfigChanged,
+ _ => return Err(Error::InvalidEnumValue("InfoChangeKind", kind as i32)),
+ })
+ }
+ }
+
+ /// Edge event types.
+ #[derive(Copy, Clone, Debug, Eq, PartialEq)]
+ pub enum EdgeKind {
+ /// Rising edge event.
+ Rising,
+ /// Falling edge event.
+ Falling,
+ }
+
+ impl EdgeKind {
+ pub(crate) fn new(kind: gpiod::gpiod_edge_event_type) -> Result<Self> {
+ Ok(match kind {
+ GPIOD_EDGE_EVENT_RISING_EDGE => EdgeKind::Rising,
+ GPIOD_EDGE_EVENT_FALLING_EDGE => EdgeKind::Falling,
+ _ => return Err(Error::InvalidEnumValue("EdgeEvent", kind as i32)),
+ })
+ }
+ }
+}
+
+/// Various libgpiod-related functions.
+
+/// Check if the file pointed to by path is a GPIO chip character device.
+///
+/// Returns true if the file exists and is a GPIO chip character device or a
+/// symbolic link to it.
+pub fn is_gpiochip_device<P: AsRef<Path>>(path: &P) -> bool {
+ // Null-terminate the string
+ let path = path.as_ref().to_string_lossy() + "\0";
+
+ // SAFETY: libgpiod won't access the path reference once the call returns.
+ unsafe { gpiod::gpiod_is_gpiochip_device(path.as_ptr() as *const c_char) }
+}
+
+/// GPIO devices.
+///
+/// Returns a vector of unique available GPIO Chips.
+///
+/// The chips are sorted in ascending order of the chip names.
+pub fn gpiochip_devices<P: AsRef<Path>>(path: &P) -> Result<Vec<chip::Chip>> {
+ let mut devices = Vec::new();
+
+ for entry in fs::read_dir(path).map_err(|_| Error::IoError)?.flatten() {
+ let path = entry.path();
+
+ if is_gpiochip_device(&path) {
+ let chip = chip::Chip::open(&path)?;
+ let info = chip.info()?;
+
+ devices.push((chip, info));
+ }
+ }
+
+ devices.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
+ devices.dedup_by(|a, b| a.1.eq(&b.1));
+
+ Ok(devices.into_iter().map(|a| a.0).collect())
+}
+
+/// Get the API version of the libgpiod library as a human-readable string.
+pub fn libgpiod_version() -> Result<&'static str> {
+ // SAFETY: The string returned by libgpiod is guaranteed to live forever.
+ let version = unsafe { gpiod::gpiod_api_version() };
+
+ if version.is_null() {
+ return Err(Error::NullString("GPIO library version"));
+ }
+
+ // SAFETY: The string is guaranteed to be valid here by the C API.
+ unsafe { CStr::from_ptr(version) }
+ .to_str()
+ .map_err(Error::StringNotUtf8)
+}
+
+/// Get the API version of the libgpiod crate as a human-readable string.
+pub fn crate_version() -> &'static str {
+ env!("CARGO_PKG_VERSION")
+}
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
+// SPDX-FileCopyrightText: 2022 Linaro Ltd.
+// SPDX-FileCopyrightText: 2022 Viresh Kumar <viresh.kumar@linaro.org>
+
+use super::{
+ gpiod,
+ line::{Offset, Settings, SettingsMap, Value},
+ Error, OperationType, Result,
+};
+
+/// Line configuration objects.
+///
+/// The line-config object contains the configuration for lines that can be
+/// used in two cases:
+/// - when making a line request
+/// - when reconfiguring a set of already requested lines.
+///
+/// A new line-config object is empty. Using it in a request will lead to an
+/// error. In order for a line-config to become useful, it needs to be assigned
+/// at least one offset-to-settings mapping by calling
+/// ::gpiod_line_config_add_line_settings.
+///
+/// When calling ::gpiod_chip_request_lines, the library will request all
+/// offsets that were assigned settings in the order that they were assigned.
+
+#[derive(Debug, Eq, PartialEq)]
+pub struct Config {
+ pub(crate) config: *mut gpiod::gpiod_line_config,
+}
+
+// SAFETY: Config models a wrapper around an owned gpiod_line_config and may be
+// safely sent to other threads.
+unsafe impl Send for Config {}
+
+impl Config {
+ /// Create a new line config object.
+ pub fn new() -> Result<Self> {
+ // SAFETY: The `gpiod_line_config` returned by libgpiod is guaranteed to live as long
+ // as the `struct Config`.
+ let config = unsafe { gpiod::gpiod_line_config_new() };
+
+ if config.is_null() {
+ return Err(Error::OperationFailed(
+ OperationType::LineConfigNew,
+ errno::errno(),
+ ));
+ }
+
+ Ok(Self { config })
+ }
+
+ /// Resets the entire configuration stored in the object. This is useful if
+ /// the user wants to reuse the object without reallocating it.
+ pub fn reset(&mut self) {
+ // SAFETY: `gpiod_line_config` is guaranteed to be valid here.
+ unsafe { gpiod::gpiod_line_config_reset(self.config) }
+ }
+
+ /// Add line settings for a set of offsets.
+ pub fn add_line_settings(
+ &mut self,
+ offsets: &[Offset],
+ settings: Settings,
+ ) -> Result<&mut Self> {
+ // SAFETY: `gpiod_line_config` is guaranteed to be valid here.
+ let ret = unsafe {
+ gpiod::gpiod_line_config_add_line_settings(
+ self.config,
+ offsets.as_ptr(),
+ offsets.len(),
+ settings.settings,
+ )
+ };
+
+ if ret == -1 {
+ Err(Error::OperationFailed(
+ OperationType::LineConfigAddSettings,
+ errno::errno(),
+ ))
+ } else {
+ Ok(self)
+ }
+ }
+
+ /// Set output values for a number of lines.
+ pub fn set_output_values(&mut self, values: &[Value]) -> Result<&mut Self> {
+ let mut mapped_values = Vec::new();
+ for value in values {
+ mapped_values.push(value.value());
+ }
+
+ let ret = unsafe {
+ gpiod::gpiod_line_config_set_output_values(
+ self.config,
+ mapped_values.as_ptr(),
+ values.len(),
+ )
+ };
+
+ if ret == -1 {
+ Err(Error::OperationFailed(
+ OperationType::LineConfigSetOutputValues,
+ errno::errno(),
+ ))
+ } else {
+ Ok(self)
+ }
+ }
+
+ /// Get a mapping of offsets to line settings stored by this object.
+ pub fn line_settings(&self) -> Result<SettingsMap> {
+ let mut map = SettingsMap::new();
+ // SAFETY: gpiod_line_config is guaranteed to be valid here
+ let num_lines = unsafe { gpiod::gpiod_line_config_get_num_configured_offsets(self.config) };
+ let mut offsets = vec![0; num_lines];
+
+ // SAFETY: gpiod_line_config is guaranteed to be valid here.
+ let num_stored = unsafe {
+ gpiod::gpiod_line_config_get_configured_offsets(
+ self.config,
+ offsets.as_mut_ptr(),
+ num_lines,
+ )
+ };
+
+ for offset in &offsets[0..num_stored] {
+ // SAFETY: `gpiod_line_config` is guaranteed to be valid here.
+ let settings =
+ unsafe { gpiod::gpiod_line_config_get_line_settings(self.config, *offset) };
+ if settings.is_null() {
+ return Err(Error::OperationFailed(
+ OperationType::LineConfigGetSettings,
+ errno::errno(),
+ ));
+ }
+
+ map.insert(*offset as u64, Settings::new_with_settings(settings));
+ }
+
+ Ok(map)
+ }
+}
+
+impl Drop for Config {
+ /// Free the line config object and release all associated resources.
+ fn drop(&mut self) {
+ // SAFETY: `gpiod_line_config` is guaranteed to be valid here.
+ unsafe { gpiod::gpiod_line_config_free(self.config) }
+ }
+}
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
+// SPDX-FileCopyrightText: 2022 Linaro Ltd.
+// SPDX-FileCopyrightText: 2022 Viresh Kumar <viresh.kumar@linaro.org>
+
+use std::ops::Deref;
+use std::str;
+use std::time::Duration;
+use std::{ffi::CStr, marker::PhantomData};
+
+use super::{
+ gpiod,
+ line::{Bias, Direction, Drive, Edge, EventClock, Offset},
+ Error, Result,
+};
+
+/// Line info reference
+///
+/// Exposes functions for retrieving kernel information about both requested and
+/// free lines. Line info object contains an immutable snapshot of a line's status.
+///
+/// The line info contains all the publicly available information about a
+/// line, which does not include the line value. The line must be requested
+/// to access the line value.
+///
+/// [InfoRef] only abstracts a reference to a [gpiod::gpiod_line_info] instance whose lifetime is managed
+/// by a different object instance. The owned counter-part of this type is [Info].
+#[derive(Debug)]
+#[repr(transparent)]
+pub struct InfoRef {
+ _info: gpiod::gpiod_line_info,
+ // Avoid the automatic `Sync` implementation.
+ //
+ // The C lib does not allow parallel invocations of the API. We could model
+ // that by restricting all wrapper functions to `&mut Info` - which would
+ // ensure exclusive access. But that would make the API a bit weird...
+ // So instead we just suppress the `Sync` implementation, which suppresses
+ // the `Send` implementation on `&Info` - disallowing to send it to other
+ // threads, making concurrent use impossible.
+ _not_sync: PhantomData<*mut gpiod::gpiod_line_info>,
+}
+
+impl InfoRef {
+ /// Converts a non-owning pointer to a wrapper reference of a specific
+ /// lifetime
+ ///
+ /// No ownership will be assumed, the pointer must be free'd by the original
+ /// owner.
+ ///
+ /// SAFETY: The pointer must point to an instance that is valid for the
+ /// entire lifetime 'a. The instance must be owned by an object that is
+ /// owned by the thread invoking this method. The owning object may not be
+ /// moved to another thread for the entire lifetime 'a.
+ pub(crate) unsafe fn from_raw<'a>(info: *mut gpiod::gpiod_line_info) -> &'a InfoRef {
+ &*(info as *mut _)
+ }
+
+ fn as_raw_ptr(&self) -> *mut gpiod::gpiod_line_info {
+ self as *const _ as *mut _
+ }
+
+ /// Clones the line info object.
+ pub fn try_clone(&self) -> Result<Info> {
+ // SAFETY: `gpiod_line_info` is guaranteed to be valid here.
+ let copy = unsafe { gpiod::gpiod_line_info_copy(self.as_raw_ptr()) };
+ if copy.is_null() {
+ return Err(Error::OperationFailed(
+ crate::OperationType::LineInfoCopy,
+ errno::errno(),
+ ));
+ }
+
+ // SAFETY: The copy succeeded, we are the owner and stop using the
+ // pointer after this.
+ Ok(unsafe { Info::from_raw(copy) })
+ }
+
+ /// Get the offset of the line within the GPIO chip.
+ ///
+ /// The offset uniquely identifies the line on the chip. The combination of the chip and offset
+ /// uniquely identifies the line within the system.
+ pub fn offset(&self) -> Offset {
+ // SAFETY: `gpiod_line_info` is guaranteed to be valid here.
+ unsafe { gpiod::gpiod_line_info_get_offset(self.as_raw_ptr()) }
+ }
+
+ /// Get GPIO line's name.
+ pub fn name(&self) -> Result<&str> {
+ // SAFETY: The string returned by libgpiod is guaranteed to live as long
+ // as the `struct Info`.
+ let name = unsafe { gpiod::gpiod_line_info_get_name(self.as_raw_ptr()) };
+ if name.is_null() {
+ return Err(Error::NullString("GPIO line's name"));
+ }
+
+ // SAFETY: The string is guaranteed to be valid here by the C API.
+ unsafe { CStr::from_ptr(name) }
+ .to_str()
+ .map_err(Error::StringNotUtf8)
+ }
+
+ /// Returns True if the line is in use, false otherwise.
+ ///
+ /// The user space can't know exactly why a line is busy. It may have been
+ /// requested by another process or hogged by the kernel. It only matters that
+ /// the line is used and we can't request it.
+ pub fn is_used(&self) -> bool {
+ // SAFETY: `gpiod_line_info` is guaranteed to be valid here.
+ unsafe { gpiod::gpiod_line_info_is_used(self.as_raw_ptr()) }
+ }
+
+ /// Get the GPIO line's consumer name.
+ pub fn consumer(&self) -> Result<&str> {
+ // SAFETY: The string returned by libgpiod is guaranteed to live as long
+ // as the `struct Info`.
+ let name = unsafe { gpiod::gpiod_line_info_get_consumer(self.as_raw_ptr()) };
+ if name.is_null() {
+ return Err(Error::NullString("GPIO line's consumer name"));
+ }
+
+ // SAFETY: The string is guaranteed to be valid here by the C API.
+ unsafe { CStr::from_ptr(name) }
+ .to_str()
+ .map_err(Error::StringNotUtf8)
+ }
+
+ /// Get the GPIO line's direction.
+ pub fn direction(&self) -> Result<Direction> {
+ // SAFETY: `gpiod_line_info` is guaranteed to be valid here.
+ Direction::new(unsafe { gpiod::gpiod_line_info_get_direction(self.as_raw_ptr()) })
+ }
+
+ /// Returns true if the line is "active-low", false otherwise.
+ pub fn is_active_low(&self) -> bool {
+ // SAFETY: `gpiod_line_info` is guaranteed to be valid here.
+ unsafe { gpiod::gpiod_line_info_is_active_low(self.as_raw_ptr()) }
+ }
+
+ /// Get the GPIO line's bias setting.
+ pub fn bias(&self) -> Result<Option<Bias>> {
+ // SAFETY: `gpiod_line_info` is guaranteed to be valid here.
+ Bias::new(unsafe { gpiod::gpiod_line_info_get_bias(self.as_raw_ptr()) })
+ }
+
+ /// Get the GPIO line's drive setting.
+ pub fn drive(&self) -> Result<Drive> {
+ // SAFETY: `gpiod_line_info` is guaranteed to be valid here.
+ Drive::new(unsafe { gpiod::gpiod_line_info_get_drive(self.as_raw_ptr()) })
+ }
+
+ /// Get the current edge detection setting of the line.
+ pub fn edge_detection(&self) -> Result<Option<Edge>> {
+ // SAFETY: `gpiod_line_info` is guaranteed to be valid here.
+ Edge::new(unsafe { gpiod::gpiod_line_info_get_edge_detection(self.as_raw_ptr()) })
+ }
+
+ /// Get the current event clock setting used for edge event timestamps.
+ pub fn event_clock(&self) -> Result<EventClock> {
+ // SAFETY: `gpiod_line_info` is guaranteed to be valid here.
+ EventClock::new(unsafe { gpiod::gpiod_line_info_get_event_clock(self.as_raw_ptr()) })
+ }
+
+ /// Returns true if the line is debounced (either by hardware or by the
+ /// kernel software debouncer), false otherwise.
+ pub fn is_debounced(&self) -> bool {
+ // SAFETY: `gpiod_line_info` is guaranteed to be valid here.
+ unsafe { gpiod::gpiod_line_info_is_debounced(self.as_raw_ptr()) }
+ }
+
+ /// Get the debounce period of the line.
+ pub fn debounce_period(&self) -> Duration {
+ // c_ulong may be 32bit OR 64bit, clippy reports a false-positive here:
+ // https://github.com/rust-lang/rust-clippy/issues/10555
+ #[allow(clippy::unnecessary_cast)]
+ // SAFETY: `gpiod_line_info` is guaranteed to be valid here.
+ Duration::from_micros(unsafe {
+ gpiod::gpiod_line_info_get_debounce_period_us(self.as_raw_ptr()) as u64
+ })
+ }
+}
+
+/// Line info
+///
+/// This is the owned counterpart to [InfoRef]. Due to a [Deref] implementation,
+/// all functions of [InfoRef] can also be called on this type.
+#[derive(Debug)]
+pub struct Info {
+ info: *mut gpiod::gpiod_line_info,
+}
+
+// SAFETY: Info models a owned instance whose ownership may be safely
+// transferred to other threads.
+unsafe impl Send for Info {}
+
+impl Info {
+ /// Converts a owned pointer into an owned instance
+ ///
+ /// Assumes sole ownership over a [gpiod::gpiod_line_info] instance.
+ ///
+ /// SAFETY: The pointer must point to an instance that is valid. After
+ /// constructing an [Info] the pointer MUST NOT be used for any other
+ /// purpose anymore. All interactions with the libgpiod API have to happen
+ /// through this object.
+ pub(crate) unsafe fn from_raw(info: *mut gpiod::gpiod_line_info) -> Info {
+ Info { info }
+ }
+}
+
+impl Deref for Info {
+ type Target = InfoRef;
+
+ fn deref(&self) -> &Self::Target {
+ // SAFETY: The pointer is valid for the entire lifetime '0. Info is not
+ // Sync. Therefore, no &Info may be held by a different thread. Hence,
+ // the current thread owns it. Since we borrow with the lifetime of '0,
+ // no move to a different thread can occur while a reference remains
+ // being hold.
+ unsafe { InfoRef::from_raw(self.info) }
+ }
+}
+
+impl Drop for Info {
+ fn drop(&mut self) {
+ // SAFETY: `gpiod_line_info` is guaranteed to be valid here.
+ unsafe { gpiod::gpiod_line_info_free(self.info) }
+ }
+}
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
+// SPDX-FileCopyrightText: 2022 Linaro Ltd.
+// SPDX-FileCopyrightText: 2022 Viresh Kumar <viresh.kumar@linaro.org>
+
+#[cfg(feature = "vnext")]
+use std::ffi::CStr;
+use std::os::unix::prelude::AsRawFd;
+use std::time::Duration;
+
+use super::{
+ gpiod,
+ line::{self, Offset, Value, ValueMap},
+ request, Error, OperationType, Result,
+};
+
+/// Line request operations
+///
+/// Allows interaction with a set of requested lines.
+#[derive(Debug, Eq, PartialEq)]
+pub struct Request {
+ pub(crate) request: *mut gpiod::gpiod_line_request,
+}
+
+// SAFETY: Request models a wrapper around an owned gpiod_line_request and may
+// be safely sent to other threads.
+unsafe impl Send for Request {}
+
+impl Request {
+ /// Request a set of lines for exclusive usage.
+ pub(crate) fn new(request: *mut gpiod::gpiod_line_request) -> Result<Self> {
+ Ok(Self { request })
+ }
+
+ /// Get the name of the chip this request was made on.
+ #[cfg(feature = "vnext")]
+ pub fn chip_name(&self) -> Result<&str> {
+ // SAFETY: The `gpiod_line_request` is guaranteed to be live as long
+ // as `&self`
+ let name = unsafe { gpiod::gpiod_line_request_get_chip_name(self.request) };
+
+ // SAFETY: The string is guaranteed to be valid, non-null and immutable
+ // by the C API for the lifetime of the `gpiod_line_request`.
+ unsafe { CStr::from_ptr(name) }
+ .to_str()
+ .map_err(Error::StringNotUtf8)
+ }
+
+ /// Get the number of lines in the request.
+ pub fn num_lines(&self) -> usize {
+ // SAFETY: `gpiod_line_request` is guaranteed to be valid here.
+ unsafe { gpiod::gpiod_line_request_get_num_requested_lines(self.request) }
+ }
+
+ /// Get the offsets of lines in the request.
+ pub fn offsets(&self) -> Vec<Offset> {
+ let mut offsets = vec![0; self.num_lines()];
+
+ // SAFETY: `gpiod_line_request` is guaranteed to be valid here.
+ let num_offsets = unsafe {
+ gpiod::gpiod_line_request_get_requested_offsets(
+ self.request,
+ offsets.as_mut_ptr(),
+ self.num_lines(),
+ )
+ };
+ offsets.shrink_to(num_offsets);
+ offsets
+ }
+
+ /// Get the value (0 or 1) of a single line associated with the request.
+ pub fn value(&self, offset: Offset) -> Result<Value> {
+ // SAFETY: `gpiod_line_request` is guaranteed to be valid here.
+ let value = unsafe { gpiod::gpiod_line_request_get_value(self.request, offset) };
+
+ if value != 0 && value != 1 {
+ Err(Error::OperationFailed(
+ OperationType::LineRequestGetVal,
+ errno::errno(),
+ ))
+ } else {
+ Value::new(value)
+ }
+ }
+
+ /// Get values of a subset of lines associated with the request.
+ pub fn values_subset(&self, offsets: &[Offset]) -> Result<ValueMap> {
+ let mut values = vec![0; offsets.len()];
+
+ // SAFETY: `gpiod_line_request` is guaranteed to be valid here.
+ let ret = unsafe {
+ gpiod::gpiod_line_request_get_values_subset(
+ self.request,
+ offsets.len(),
+ offsets.as_ptr(),
+ values.as_mut_ptr(),
+ )
+ };
+
+ if ret == -1 {
+ Err(Error::OperationFailed(
+ OperationType::LineRequestGetValSubset,
+ errno::errno(),
+ ))
+ } else {
+ let mut map = ValueMap::new();
+
+ for (i, val) in values.iter().enumerate() {
+ map.insert(offsets[i].into(), Value::new(*val)?);
+ }
+
+ Ok(map)
+ }
+ }
+
+ /// Get values of all lines associated with the request.
+ pub fn values(&self) -> Result<ValueMap> {
+ self.values_subset(&self.offsets())
+ }
+
+ /// Set the value of a single line associated with the request.
+ pub fn set_value(&mut self, offset: Offset, value: Value) -> Result<&mut Self> {
+ // SAFETY: `gpiod_line_request` is guaranteed to be valid here.
+ let ret =
+ unsafe { gpiod::gpiod_line_request_set_value(self.request, offset, value.value()) };
+
+ if ret == -1 {
+ Err(Error::OperationFailed(
+ OperationType::LineRequestSetVal,
+ errno::errno(),
+ ))
+ } else {
+ Ok(self)
+ }
+ }
+
+ /// Set values of a subset of lines associated with the request.
+ pub fn set_values_subset(&mut self, map: ValueMap) -> Result<&mut Self> {
+ let mut offsets = Vec::new();
+ let mut values = Vec::new();
+
+ for (offset, value) in map {
+ offsets.push(offset as u32);
+ values.push(value.value());
+ }
+
+ // SAFETY: `gpiod_line_request` is guaranteed to be valid here.
+ let ret = unsafe {
+ gpiod::gpiod_line_request_set_values_subset(
+ self.request,
+ offsets.len(),
+ offsets.as_ptr(),
+ values.as_ptr(),
+ )
+ };
+
+ if ret == -1 {
+ Err(Error::OperationFailed(
+ OperationType::LineRequestSetValSubset,
+ errno::errno(),
+ ))
+ } else {
+ Ok(self)
+ }
+ }
+
+ /// Set values of all lines associated with the request.
+ pub fn set_values(&mut self, values: &[Value]) -> Result<&mut Self> {
+ if values.len() != self.num_lines() {
+ return Err(Error::InvalidArguments);
+ }
+
+ let mut new_values = Vec::new();
+ for value in values {
+ new_values.push(value.value());
+ }
+
+ // SAFETY: `gpiod_line_request` is guaranteed to be valid here.
+ let ret =
+ unsafe { gpiod::gpiod_line_request_set_values(self.request, new_values.as_ptr()) };
+
+ if ret == -1 {
+ Err(Error::OperationFailed(
+ OperationType::LineRequestSetVal,
+ errno::errno(),
+ ))
+ } else {
+ Ok(self)
+ }
+ }
+
+ /// Update the configuration of lines associated with the line request.
+ pub fn reconfigure_lines(&mut self, lconfig: &line::Config) -> Result<&mut Self> {
+ // SAFETY: `gpiod_line_request` is guaranteed to be valid here.
+ let ret =
+ unsafe { gpiod::gpiod_line_request_reconfigure_lines(self.request, lconfig.config) };
+
+ if ret == -1 {
+ Err(Error::OperationFailed(
+ OperationType::LineRequestReconfigLines,
+ errno::errno(),
+ ))
+ } else {
+ Ok(self)
+ }
+ }
+
+ /// Wait for edge events on any of the lines associated with the request.
+ pub fn wait_edge_events(&self, timeout: Option<Duration>) -> Result<bool> {
+ let timeout = match timeout {
+ Some(x) => x.as_nanos() as i64,
+ // Block indefinitely
+ None => -1,
+ };
+
+ // SAFETY: `gpiod_line_request` is guaranteed to be valid here.
+ let ret = unsafe { gpiod::gpiod_line_request_wait_edge_events(self.request, timeout) };
+
+ match ret {
+ -1 => Err(Error::OperationFailed(
+ OperationType::LineRequestWaitEdgeEvent,
+ errno::errno(),
+ )),
+ 0 => Ok(false),
+ _ => Ok(true),
+ }
+ }
+
+ /// Get a number of edge events from a line request.
+ ///
+ /// This function will block if no event was queued for the line.
+ pub fn read_edge_events<'a>(
+ &'a self,
+ buffer: &'a mut request::Buffer,
+ ) -> Result<request::Events> {
+ buffer.read_edge_events(self)
+ }
+}
+
+impl AsRawFd for Request {
+ /// Get the file descriptor associated with the line request.
+ fn as_raw_fd(&self) -> i32 {
+ // SAFETY: `gpiod_line_request` is guaranteed to be valid here.
+ unsafe { gpiod::gpiod_line_request_get_fd(self.request) }
+ }
+}
+
+impl Drop for Request {
+ /// Release the requested lines and free all associated resources.
+ fn drop(&mut self) {
+ // SAFETY: `gpiod_line_request` is guaranteed to be valid here.
+ unsafe { gpiod::gpiod_line_request_release(self.request) }
+ }
+}
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
+// SPDX-FileCopyrightText: 2022 Linaro Ltd.
+// SPDX-FileCopyrightText: 2022 Viresh Kumar <viresh.kumar@linaro.org>
+
+use std::time::Duration;
+
+use super::{
+ gpiod,
+ line::{Bias, Direction, Drive, Edge, EventClock, SettingKind, SettingVal, Value},
+ Error, OperationType, Result,
+};
+
+/// Line settings objects.
+///
+/// Line settings object contains a set of line properties that can be used
+/// when requesting lines or reconfiguring an existing request.
+///
+/// Mutators in general can only fail if the new property value is invalid. The
+/// return values can be safely ignored - the object remains valid even after
+/// a mutator fails and simply uses the sane default appropriate for given
+/// property.
+
+#[derive(Debug, Eq, PartialEq)]
+pub struct Settings {
+ pub(crate) settings: *mut gpiod::gpiod_line_settings,
+}
+
+// SAFETY: Settings models a wrapper around an owned gpiod_line_settings and may
+// be safely sent to other threads.
+unsafe impl Send for Settings {}
+
+impl Settings {
+ /// Create a new line settings object.
+ pub fn new() -> Result<Self> {
+ // SAFETY: The `gpiod_line_settings` returned by libgpiod is guaranteed to live as long
+ // as the `struct Settings`.
+ let settings = unsafe { gpiod::gpiod_line_settings_new() };
+
+ if settings.is_null() {
+ return Err(Error::OperationFailed(
+ OperationType::LineSettingsNew,
+ errno::errno(),
+ ));
+ }
+
+ Ok(Self { settings })
+ }
+
+ pub fn new_with_settings(settings: *mut gpiod::gpiod_line_settings) -> Self {
+ Self { settings }
+ }
+
+ /// Resets the line settings object to its default values.
+ pub fn reset(&mut self) {
+ // SAFETY: `gpiod_line_settings` is guaranteed to be valid here.
+ unsafe { gpiod::gpiod_line_settings_reset(self.settings) }
+ }
+
+ /// Makes a copy of the settings object.
+ pub fn try_clone(&self) -> Result<Self> {
+ // SAFETY: `gpiod_line_settings` is guaranteed to be valid here.
+ let settings = unsafe { gpiod::gpiod_line_settings_copy(self.settings) };
+ if settings.is_null() {
+ return Err(Error::OperationFailed(
+ OperationType::LineSettingsCopy,
+ errno::errno(),
+ ));
+ }
+
+ Ok(Self { settings })
+ }
+
+ /// Set line prop setting.
+ pub fn set_prop(&mut self, props: &[SettingVal]) -> Result<&mut Self> {
+ for property in props {
+ match property {
+ SettingVal::Direction(prop) => self.set_direction(*prop)?,
+ SettingVal::EdgeDetection(prop) => self.set_edge_detection(*prop)?,
+ SettingVal::Bias(prop) => self.set_bias(*prop)?,
+ SettingVal::Drive(prop) => self.set_drive(*prop)?,
+ SettingVal::ActiveLow(prop) => self.set_active_low(*prop),
+ SettingVal::DebouncePeriod(prop) => self.set_debounce_period(*prop),
+ SettingVal::EventClock(prop) => self.set_event_clock(*prop)?,
+ SettingVal::OutputValue(prop) => self.set_output_value(*prop)?,
+ };
+ }
+
+ Ok(self)
+ }
+
+ /// Get the line prop setting.
+ pub fn prop(&self, property: SettingKind) -> Result<SettingVal> {
+ Ok(match property {
+ SettingKind::Direction => SettingVal::Direction(self.direction()?),
+ SettingKind::EdgeDetection => SettingVal::EdgeDetection(self.edge_detection()?),
+ SettingKind::Bias => SettingVal::Bias(self.bias()?),
+ SettingKind::Drive => SettingVal::Drive(self.drive()?),
+ SettingKind::ActiveLow => SettingVal::ActiveLow(self.active_low()),
+ SettingKind::DebouncePeriod => SettingVal::DebouncePeriod(self.debounce_period()?),
+ SettingKind::EventClock => SettingVal::EventClock(self.event_clock()?),
+ SettingKind::OutputValue => SettingVal::OutputValue(self.output_value()?),
+ })
+ }
+
+ /// Set the line direction.
+ pub fn set_direction(&mut self, direction: Direction) -> Result<&mut Self> {
+ // SAFETY: `gpiod_line_settings` is guaranteed to be valid here.
+ let ret = unsafe {
+ gpiod::gpiod_line_settings_set_direction(self.settings, direction.gpiod_direction())
+ };
+
+ if ret == -1 {
+ Err(Error::OperationFailed(
+ OperationType::LineSettingsSetDirection,
+ errno::errno(),
+ ))
+ } else {
+ Ok(self)
+ }
+ }
+
+ /// Get the direction setting.
+ pub fn direction(&self) -> Result<Direction> {
+ // SAFETY: `gpiod_line_settings` is guaranteed to be valid here.
+ Direction::new(unsafe { gpiod::gpiod_line_settings_get_direction(self.settings) })
+ }
+
+ /// Set the edge event detection setting.
+ pub fn set_edge_detection(&mut self, edge: Option<Edge>) -> Result<&mut Self> {
+ // SAFETY: `gpiod_line_settings` is guaranteed to be valid here.
+ let ret = unsafe {
+ gpiod::gpiod_line_settings_set_edge_detection(self.settings, Edge::gpiod_edge(edge))
+ };
+
+ if ret == -1 {
+ Err(Error::OperationFailed(
+ OperationType::LineSettingsSetEdgeDetection,
+ errno::errno(),
+ ))
+ } else {
+ Ok(self)
+ }
+ }
+
+ /// Get the edge event detection setting.
+ pub fn edge_detection(&self) -> Result<Option<Edge>> {
+ // SAFETY: `gpiod_line_settings` is guaranteed to be valid here.
+ Edge::new(unsafe { gpiod::gpiod_line_settings_get_edge_detection(self.settings) })
+ }
+
+ /// Set the bias setting.
+ pub fn set_bias(&mut self, bias: Option<Bias>) -> Result<&mut Self> {
+ // SAFETY: `gpiod_line_settings` is guaranteed to be valid here.
+ let ret =
+ unsafe { gpiod::gpiod_line_settings_set_bias(self.settings, Bias::gpiod_bias(bias)) };
+
+ if ret == -1 {
+ Err(Error::OperationFailed(
+ OperationType::LineSettingsSetBias,
+ errno::errno(),
+ ))
+ } else {
+ Ok(self)
+ }
+ }
+
+ /// Get the bias setting.
+ pub fn bias(&self) -> Result<Option<Bias>> {
+ // SAFETY: `gpiod_line_settings` is guaranteed to be valid here.
+ Bias::new(unsafe { gpiod::gpiod_line_settings_get_bias(self.settings) })
+ }
+
+ /// Set the drive setting.
+ pub fn set_drive(&mut self, drive: Drive) -> Result<&mut Self> {
+ // SAFETY: `gpiod_line_settings` is guaranteed to be valid here.
+ let ret =
+ unsafe { gpiod::gpiod_line_settings_set_drive(self.settings, drive.gpiod_drive()) };
+
+ if ret == -1 {
+ Err(Error::OperationFailed(
+ OperationType::LineSettingsSetDrive,
+ errno::errno(),
+ ))
+ } else {
+ Ok(self)
+ }
+ }
+
+ /// Get the drive setting.
+ pub fn drive(&self) -> Result<Drive> {
+ // SAFETY: `gpiod_line_settings` is guaranteed to be valid here.
+ Drive::new(unsafe { gpiod::gpiod_line_settings_get_drive(self.settings) })
+ }
+
+ /// Set active-low setting.
+ pub fn set_active_low(&mut self, active_low: bool) -> &mut Self {
+ // SAFETY: `gpiod_line_settings` is guaranteed to be valid here.
+ unsafe {
+ gpiod::gpiod_line_settings_set_active_low(self.settings, active_low);
+ }
+ self
+ }
+
+ /// Check the active-low setting.
+ pub fn active_low(&self) -> bool {
+ // SAFETY: `gpiod_line_settings` is guaranteed to be valid here.
+ unsafe { gpiod::gpiod_line_settings_get_active_low(self.settings) }
+ }
+
+ /// Set the debounce period setting.
+ pub fn set_debounce_period(&mut self, period: Duration) -> &mut Self {
+ // SAFETY: `gpiod_line_settings` is guaranteed to be valid here.
+ unsafe {
+ gpiod::gpiod_line_settings_set_debounce_period_us(
+ self.settings,
+ period.as_micros().try_into().unwrap(),
+ );
+ }
+
+ self
+ }
+
+ /// Get the debounce period.
+ pub fn debounce_period(&self) -> Result<Duration> {
+ // c_ulong may be 32bit OR 64bit, clippy reports a false-positive here:
+ // https://github.com/rust-lang/rust-clippy/issues/10555
+ #[allow(clippy::unnecessary_cast)]
+ // SAFETY: `gpiod_line_settings` is guaranteed to be valid here.
+ Ok(Duration::from_micros(unsafe {
+ gpiod::gpiod_line_settings_get_debounce_period_us(self.settings) as u64
+ }))
+ }
+
+ /// Set the event clock setting.
+ pub fn set_event_clock(&mut self, clock: EventClock) -> Result<&mut Self> {
+ // SAFETY: `gpiod_line_settings` is guaranteed to be valid here.
+ let ret = unsafe {
+ gpiod::gpiod_line_settings_set_event_clock(self.settings, clock.gpiod_clock())
+ };
+
+ if ret == -1 {
+ Err(Error::OperationFailed(
+ OperationType::LineSettingsSetEventClock,
+ errno::errno(),
+ ))
+ } else {
+ Ok(self)
+ }
+ }
+
+ /// Get the event clock setting.
+ pub fn event_clock(&self) -> Result<EventClock> {
+ // SAFETY: `gpiod_line_settings` is guaranteed to be valid here.
+ EventClock::new(unsafe { gpiod::gpiod_line_settings_get_event_clock(self.settings) })
+ }
+
+ /// Set the output value setting.
+ pub fn set_output_value(&mut self, value: Value) -> Result<&mut Self> {
+ // SAFETY: `gpiod_line_settings` is guaranteed to be valid here.
+ let ret =
+ unsafe { gpiod::gpiod_line_settings_set_output_value(self.settings, value.value()) };
+
+ if ret == -1 {
+ Err(Error::OperationFailed(
+ OperationType::LineSettingsSetOutputValue,
+ errno::errno(),
+ ))
+ } else {
+ Ok(self)
+ }
+ }
+
+ /// Get the output value, 0 or 1.
+ pub fn output_value(&self) -> Result<Value> {
+ // SAFETY: `gpiod_line_settings` is guaranteed to be valid here.
+ let value = unsafe { gpiod::gpiod_line_settings_get_output_value(self.settings) };
+
+ if value != 0 && value != 1 {
+ Err(Error::OperationFailed(
+ OperationType::LineSettingsGetOutVal,
+ errno::errno(),
+ ))
+ } else {
+ Value::new(value)
+ }
+ }
+}
+
+impl Drop for Settings {
+ /// Free the line settings object and release all associated resources.
+ fn drop(&mut self) {
+ // SAFETY: `gpiod_line_settings` is guaranteed to be valid here.
+ unsafe { gpiod::gpiod_line_settings_free(self.settings) }
+ }
+}
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
+// SPDX-FileCopyrightText: 2022 Linaro Ltd.
+// SPDX-FileCopyrightText: 2022 Viresh Kumar <viresh.kumar@linaro.org>
+
+use std::ffi::{CStr, CString};
+use std::os::raw::c_char;
+use std::str;
+
+use super::{gpiod, Error, OperationType, Result};
+
+/// Request configuration objects
+///
+/// Request config objects are used to pass a set of options to the kernel at
+/// the time of the line request. The mutators don't return error values. If the
+/// values are invalid, in general they are silently adjusted to acceptable
+/// ranges.
+
+#[derive(Debug, Eq, PartialEq)]
+pub struct Config {
+ pub(crate) config: *mut gpiod::gpiod_request_config,
+}
+
+// SAFETY: Config models a wrapper around an owned gpiod_request_config and may
+// be safely sent to other threads.
+unsafe impl Send for Config {}
+
+impl Config {
+ /// Create a new request config object.
+ pub fn new() -> Result<Self> {
+ // SAFETY: The `gpiod_request_config` returned by libgpiod is guaranteed to live as long
+ // as the `struct Config`.
+ let config = unsafe { gpiod::gpiod_request_config_new() };
+ if config.is_null() {
+ return Err(Error::OperationFailed(
+ OperationType::RequestConfigNew,
+ errno::errno(),
+ ));
+ }
+
+ Ok(Self { config })
+ }
+
+ /// Set the consumer name for the request.
+ ///
+ /// If the consumer string is too long, it will be truncated to the max
+ /// accepted length.
+ pub fn set_consumer(&mut self, consumer: &str) -> Result<&mut Self> {
+ let consumer = CString::new(consumer).map_err(|_| Error::InvalidString)?;
+
+ // SAFETY: `gpiod_request_config` is guaranteed to be valid here.
+ unsafe {
+ gpiod::gpiod_request_config_set_consumer(
+ self.config,
+ consumer.as_ptr() as *const c_char,
+ )
+ }
+
+ Ok(self)
+ }
+
+ /// Get the consumer name configured in the request config.
+ pub fn consumer(&self) -> Result<&str> {
+ // SAFETY: The string returned by libgpiod is guaranteed to live as long
+ // as the `struct Config`.
+ let consumer = unsafe { gpiod::gpiod_request_config_get_consumer(self.config) };
+ if consumer.is_null() {
+ return Err(Error::OperationFailed(
+ OperationType::RequestConfigGetConsumer,
+ errno::errno(),
+ ));
+ }
+
+ // SAFETY: The string is guaranteed to be valid here by the C API.
+ unsafe { CStr::from_ptr(consumer) }
+ .to_str()
+ .map_err(Error::StringNotUtf8)
+ }
+
+ /// Set the size of the kernel event buffer for the request.
+ pub fn set_event_buffer_size(&mut self, size: usize) -> &mut Self {
+ // SAFETY: `gpiod_request_config` is guaranteed to be valid here.
+ unsafe { gpiod::gpiod_request_config_set_event_buffer_size(self.config, size) }
+
+ self
+ }
+
+ /// Get the edge event buffer size setting for the request config.
+ pub fn event_buffer_size(&self) -> usize {
+ // SAFETY: `gpiod_request_config` is guaranteed to be valid here.
+ unsafe { gpiod::gpiod_request_config_get_event_buffer_size(self.config) }
+ }
+}
+
+impl Drop for Config {
+ /// Free the request config object and release all associated resources.
+ fn drop(&mut self) {
+ // SAFETY: `gpiod_request_config` is guaranteed to be valid here.
+ unsafe { gpiod::gpiod_request_config_free(self.config) }
+ }
+}
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Linaro Ltd.
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
+
+EXTRA_DIST = \
+ chip.rs \
+ edge_event.rs \
+ info_event.rs \
+ line_config.rs \
+ line_info.rs \
+ line_request.rs \
+ line_settings.rs \
+ request_config.rs
+
+SUBDIRS = common
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
+// SPDX-FileCopyrightText: 2022 Linaro Ltd.
+// SPDX-FileCopyrightText: 2022 Viresh Kumar <viresh.kumar@linaro.org>
+
+mod common;
+
+mod chip {
+ use libc::{ENODEV, ENOENT, ENOTTY};
+ use std::path::PathBuf;
+
+ use gpiosim_sys::Sim;
+ use libgpiod::{chip::Chip, Error as ChipError, OperationType};
+
+ mod open {
+ use super::*;
+
+ #[test]
+ fn nonexistent_file() {
+ assert_eq!(
+ Chip::open(&PathBuf::from("/dev/nonexistent")).unwrap_err(),
+ ChipError::OperationFailed(OperationType::ChipOpen, errno::Errno(ENOENT))
+ );
+ }
+
+ #[test]
+ fn no_dev_file() {
+ assert_eq!(
+ Chip::open(&PathBuf::from("/tmp")).unwrap_err(),
+ ChipError::OperationFailed(OperationType::ChipOpen, errno::Errno(ENOTTY))
+ );
+ }
+
+ #[test]
+ fn non_gpio_char_dev_file() {
+ assert_eq!(
+ Chip::open(&PathBuf::from("/dev/null")).unwrap_err(),
+ ChipError::OperationFailed(OperationType::ChipOpen, errno::Errno(ENODEV))
+ );
+ }
+
+ #[test]
+ fn gpiosim_file() {
+ let sim = Sim::new(None, None, true).unwrap();
+ assert!(Chip::open(&sim.dev_path()).is_ok());
+ }
+ }
+
+ mod verify {
+ use super::*;
+ const NGPIO: usize = 16;
+ const LABEL: &str = "foobar";
+
+ #[test]
+ fn basic_helpers() {
+ let sim = Sim::new(Some(NGPIO), Some(LABEL), true).unwrap();
+ let chip = Chip::open(&sim.dev_path()).unwrap();
+ let info = chip.info().unwrap();
+
+ assert_eq!(info.label().unwrap(), LABEL);
+ assert_eq!(info.name().unwrap(), sim.chip_name());
+ assert_eq!(chip.path().unwrap(), sim.dev_path().to_str().unwrap());
+ assert_eq!(info.num_lines(), NGPIO);
+ }
+
+ #[test]
+ fn find_line() {
+ let sim = Sim::new(Some(NGPIO), None, false).unwrap();
+ sim.set_line_name(0, "zero").unwrap();
+ sim.set_line_name(2, "two").unwrap();
+ sim.set_line_name(3, "three").unwrap();
+ sim.set_line_name(5, "five").unwrap();
+ sim.set_line_name(10, "ten").unwrap();
+ sim.set_line_name(11, "ten").unwrap();
+ sim.enable().unwrap();
+
+ let chip = Chip::open(&sim.dev_path()).unwrap();
+
+ // Success case
+ assert_eq!(chip.line_offset_from_name("zero").unwrap(), 0);
+ assert_eq!(chip.line_offset_from_name("two").unwrap(), 2);
+ assert_eq!(chip.line_offset_from_name("three").unwrap(), 3);
+ assert_eq!(chip.line_offset_from_name("five").unwrap(), 5);
+
+ // Success with duplicate names, should return first entry
+ assert_eq!(chip.line_offset_from_name("ten").unwrap(), 10);
+
+ // Failure
+ assert_eq!(
+ chip.line_offset_from_name("nonexistent").unwrap_err(),
+ ChipError::OperationFailed(
+ OperationType::ChipGetLineOffsetFromName,
+ errno::Errno(ENOENT),
+ )
+ );
+ }
+ }
+}
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Linaro Ltd.
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
+
+EXTRA_DIST = config.rs mod.rs
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
+// SPDX-FileCopyrightText: 2022 Linaro Ltd.
+// SPDX-FileCopyrightText: 2022 Viresh Kumar <viresh.kumar@linaro.org>
+
+use std::sync::{Arc, Mutex};
+use std::time::Duration;
+
+use gpiosim_sys::{Pull, Sim, Value as SimValue};
+use libgpiod::{
+ chip::Chip,
+ line::{self, Bias, Direction, Drive, Edge, EventClock, Offset, SettingVal, Value},
+ request, Result,
+};
+
+pub(crate) struct TestConfig {
+ sim: Arc<Mutex<Sim>>,
+ chip: Option<Chip>,
+ request: Option<request::Request>,
+ rconfig: request::Config,
+ lconfig: line::Config,
+ lsettings: Option<line::Settings>,
+}
+
+impl TestConfig {
+ pub(crate) fn new(ngpio: usize) -> Result<Self> {
+ Ok(Self {
+ sim: Arc::new(Mutex::new(Sim::new(Some(ngpio), None, true)?)),
+ chip: None,
+ request: None,
+ rconfig: request::Config::new().unwrap(),
+ lconfig: line::Config::new().unwrap(),
+ lsettings: Some(line::Settings::new().unwrap()),
+ })
+ }
+
+ pub(crate) fn set_pull(&self, offsets: &[Offset], pulls: &[Pull]) {
+ for i in 0..pulls.len() {
+ self.sim
+ .lock()
+ .unwrap()
+ .set_pull(offsets[i], pulls[i])
+ .unwrap();
+ }
+ }
+
+ pub(crate) fn rconfig_set_consumer(&mut self, consumer: &str) {
+ self.rconfig.set_consumer(consumer).unwrap();
+ }
+
+ pub(crate) fn lconfig_val(&mut self, dir: Option<Direction>, val: Option<Value>) {
+ let mut settings = Vec::new();
+
+ if let Some(dir) = dir {
+ settings.push(SettingVal::Direction(dir));
+ }
+
+ if let Some(val) = val {
+ settings.push(SettingVal::OutputValue(val));
+ }
+
+ if !settings.is_empty() {
+ self.lsettings().set_prop(&settings).unwrap();
+ }
+ }
+
+ pub(crate) fn lconfig_bias(&mut self, dir: Direction, bias: Option<Bias>) {
+ let settings = vec![SettingVal::Direction(dir), SettingVal::Bias(bias)];
+ self.lsettings().set_prop(&settings).unwrap();
+ }
+
+ pub(crate) fn lconfig_clock(&mut self, clock: EventClock) {
+ let settings = vec![SettingVal::EventClock(clock)];
+ self.lsettings().set_prop(&settings).unwrap();
+ }
+
+ pub(crate) fn lconfig_debounce(&mut self, duration: Duration) {
+ let settings = vec![
+ SettingVal::Direction(Direction::Input),
+ SettingVal::DebouncePeriod(duration),
+ ];
+ self.lsettings().set_prop(&settings).unwrap();
+ }
+
+ pub(crate) fn lconfig_drive(&mut self, dir: Direction, drive: Drive) {
+ let settings = vec![SettingVal::Direction(dir), SettingVal::Drive(drive)];
+ self.lsettings().set_prop(&settings).unwrap();
+ }
+
+ pub(crate) fn lconfig_edge(&mut self, dir: Option<Direction>, edge: Option<Edge>) {
+ let mut settings = Vec::new();
+
+ if let Some(dir) = dir {
+ settings.push(SettingVal::Direction(dir));
+ }
+
+ settings.push(SettingVal::EdgeDetection(edge));
+ self.lsettings().set_prop(&settings).unwrap();
+ }
+
+ pub(crate) fn lconfig_add_settings(&mut self, offsets: &[Offset]) {
+ self.lconfig
+ .add_line_settings(offsets, self.lsettings.take().unwrap())
+ .unwrap();
+ }
+
+ pub(crate) fn request_lines(&mut self) -> Result<()> {
+ let chip = Chip::open(&self.sim.lock().unwrap().dev_path())?;
+
+ self.request = Some(chip.request_lines(Some(&self.rconfig), &self.lconfig)?);
+ self.chip = Some(chip);
+
+ Ok(())
+ }
+
+ pub(crate) fn sim(&self) -> Arc<Mutex<Sim>> {
+ self.sim.clone()
+ }
+
+ pub(crate) fn sim_val(&self, offset: Offset) -> Result<SimValue> {
+ self.sim.lock().unwrap().val(offset)
+ }
+
+ pub(crate) fn chip(&self) -> &Chip {
+ self.chip.as_ref().unwrap()
+ }
+
+ pub(crate) fn lsettings(&mut self) -> &mut line::Settings {
+ self.lsettings.as_mut().unwrap()
+ }
+
+ pub(crate) fn request(&mut self) -> &mut request::Request {
+ self.request.as_mut().unwrap()
+ }
+}
+
+impl Drop for TestConfig {
+ fn drop(&mut self) {
+ // Explicit freeing is important to make sure "request" get freed
+ // before "sim" and "chip".
+ self.request = None;
+ }
+}
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
+// SPDX-FileCopyrightText: 2022 Linaro Ltd.
+// SPDX-FileCopyrightText: 2022 Viresh Kumar <viresh.kumar@linaro.org>
+
+#[allow(dead_code)]
+mod config;
+
+#[allow(unused_imports)]
+pub(crate) use config::*;
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
+// SPDX-FileCopyrightText: 2022 Linaro Ltd.
+// SPDX-FileCopyrightText: 2022 Viresh Kumar <viresh.kumar@linaro.org>
+
+mod common;
+
+mod edge_event {
+ use std::time::Duration;
+
+ use crate::common::*;
+ use gpiosim_sys::{Pull, Sim};
+ use libgpiod::{
+ line::{Edge, EdgeKind, Offset},
+ request,
+ };
+
+ const NGPIO: usize = 8;
+
+ mod buffer_capacity {
+ use super::*;
+
+ #[test]
+ fn default_capacity() {
+ assert_eq!(request::Buffer::new(0).unwrap().capacity(), 64);
+ }
+
+ #[test]
+ fn user_defined_capacity() {
+ assert_eq!(request::Buffer::new(123).unwrap().capacity(), 123);
+ }
+
+ #[test]
+ fn max_capacity() {
+ assert_eq!(request::Buffer::new(1024 * 2).unwrap().capacity(), 1024);
+ }
+ }
+
+ mod trigger {
+ use super::*;
+ use std::{
+ sync::{Arc, Mutex},
+ thread,
+ };
+
+ // Helpers to generate events
+ fn trigger_falling_and_rising_edge(sim: Arc<Mutex<Sim>>, offset: Offset) {
+ thread::spawn(move || {
+ thread::sleep(Duration::from_millis(30));
+ sim.lock().unwrap().set_pull(offset, Pull::Up).unwrap();
+
+ thread::sleep(Duration::from_millis(30));
+ sim.lock().unwrap().set_pull(offset, Pull::Down).unwrap();
+ });
+ }
+
+ fn trigger_rising_edge_events_on_two_offsets(sim: Arc<Mutex<Sim>>, offset: [Offset; 2]) {
+ thread::spawn(move || {
+ thread::sleep(Duration::from_millis(30));
+ sim.lock().unwrap().set_pull(offset[0], Pull::Up).unwrap();
+
+ thread::sleep(Duration::from_millis(30));
+ sim.lock().unwrap().set_pull(offset[1], Pull::Up).unwrap();
+ });
+ }
+
+ fn trigger_multiple_events(sim: Arc<Mutex<Sim>>, offset: Offset) {
+ sim.lock().unwrap().set_pull(offset, Pull::Up).unwrap();
+ thread::sleep(Duration::from_millis(10));
+
+ sim.lock().unwrap().set_pull(offset, Pull::Down).unwrap();
+ thread::sleep(Duration::from_millis(10));
+
+ sim.lock().unwrap().set_pull(offset, Pull::Up).unwrap();
+ thread::sleep(Duration::from_millis(10));
+ }
+
+ #[test]
+ fn both_edges() {
+ const GPIO: Offset = 2;
+ let mut buf = request::Buffer::new(0).unwrap();
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_edge(None, Some(Edge::Both));
+ config.lconfig_add_settings(&[GPIO]);
+ config.request_lines().unwrap();
+
+ // Generate events
+ trigger_falling_and_rising_edge(config.sim(), GPIO);
+
+ // Rising event
+ assert!(config
+ .request()
+ .wait_edge_events(Some(Duration::from_secs(1)))
+ .unwrap());
+
+ let mut events = config.request().read_edge_events(&mut buf).unwrap();
+ assert_eq!(events.len(), 1);
+
+ let event = events.next().unwrap().unwrap();
+ let ts_rising = event.timestamp();
+ assert_eq!(event.event_type().unwrap(), EdgeKind::Rising);
+ assert_eq!(event.line_offset(), GPIO);
+
+ // Falling event
+ assert!(config
+ .request()
+ .wait_edge_events(Some(Duration::from_secs(1)))
+ .unwrap());
+
+ let mut events = config.request().read_edge_events(&mut buf).unwrap();
+ assert_eq!(events.len(), 1);
+
+ let event = events.next().unwrap().unwrap();
+ let ts_falling = event.timestamp();
+ assert_eq!(event.event_type().unwrap(), EdgeKind::Falling);
+ assert_eq!(event.line_offset(), GPIO);
+
+ // No events available
+ assert!(!config
+ .request()
+ .wait_edge_events(Some(Duration::from_millis(100)))
+ .unwrap());
+
+ assert!(ts_falling > ts_rising);
+ }
+
+ #[test]
+ fn rising_edge() {
+ const GPIO: Offset = 6;
+ let mut buf = request::Buffer::new(0).unwrap();
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_edge(None, Some(Edge::Rising));
+ config.lconfig_add_settings(&[GPIO]);
+ config.request_lines().unwrap();
+
+ // Generate events
+ trigger_falling_and_rising_edge(config.sim(), GPIO);
+
+ // Rising event
+ assert!(config
+ .request()
+ .wait_edge_events(Some(Duration::from_secs(1)))
+ .unwrap());
+
+ let mut events = config.request().read_edge_events(&mut buf).unwrap();
+ assert_eq!(events.len(), 1);
+
+ let event = events.next().unwrap().unwrap();
+ assert_eq!(event.event_type().unwrap(), EdgeKind::Rising);
+ assert_eq!(event.line_offset(), GPIO);
+
+ // No events available
+ assert!(!config
+ .request()
+ .wait_edge_events(Some(Duration::from_millis(100)))
+ .unwrap());
+ }
+
+ #[test]
+ fn falling_edge() {
+ const GPIO: Offset = 7;
+ let mut buf = request::Buffer::new(0).unwrap();
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_edge(None, Some(Edge::Falling));
+ config.lconfig_add_settings(&[GPIO]);
+ config.request_lines().unwrap();
+
+ // Generate events
+ trigger_falling_and_rising_edge(config.sim(), GPIO);
+
+ // Falling event
+ assert!(config
+ .request()
+ .wait_edge_events(Some(Duration::from_secs(1)))
+ .unwrap());
+
+ let mut events = config.request().read_edge_events(&mut buf).unwrap();
+ assert_eq!(events.len(), 1);
+
+ let event = events.next().unwrap().unwrap();
+ assert_eq!(event.event_type().unwrap(), EdgeKind::Falling);
+ assert_eq!(event.line_offset(), GPIO);
+
+ // No events available
+ assert!(!config
+ .request()
+ .wait_edge_events(Some(Duration::from_millis(100)))
+ .unwrap());
+ }
+
+ #[test]
+ fn edge_sequence() {
+ const GPIO: [u32; 2] = [0, 1];
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_edge(None, Some(Edge::Both));
+ config.lconfig_add_settings(&GPIO);
+ config.request_lines().unwrap();
+
+ // Generate events
+ trigger_rising_edge_events_on_two_offsets(config.sim(), GPIO);
+
+ // Rising event GPIO 0
+ let mut buf = request::Buffer::new(0).unwrap();
+ assert!(config
+ .request()
+ .wait_edge_events(Some(Duration::from_secs(1)))
+ .unwrap());
+
+ let mut events = config.request().read_edge_events(&mut buf).unwrap();
+ assert_eq!(events.len(), 1);
+
+ let event = events.next().unwrap().unwrap();
+ assert_eq!(event.event_type().unwrap(), EdgeKind::Rising);
+ assert_eq!(event.line_offset(), GPIO[0]);
+ assert_eq!(event.global_seqno(), 1);
+ assert_eq!(event.line_seqno(), 1);
+
+ // Rising event GPIO 1
+ assert!(config
+ .request()
+ .wait_edge_events(Some(Duration::from_secs(1)))
+ .unwrap());
+
+ let mut events = config.request().read_edge_events(&mut buf).unwrap();
+ assert_eq!(events.len(), 1);
+
+ let event = events.next().unwrap().unwrap();
+ assert_eq!(event.event_type().unwrap(), EdgeKind::Rising);
+ assert_eq!(event.line_offset(), GPIO[1]);
+ assert_eq!(event.global_seqno(), 2);
+ assert_eq!(event.line_seqno(), 1);
+
+ // No events available
+ assert!(!config
+ .request()
+ .wait_edge_events(Some(Duration::from_millis(100)))
+ .unwrap());
+ }
+
+ #[test]
+ fn multiple_events() {
+ const GPIO: Offset = 1;
+ let mut buf = request::Buffer::new(0).unwrap();
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_edge(None, Some(Edge::Both));
+ config.lconfig_add_settings(&[GPIO]);
+ config.request_lines().unwrap();
+
+ // Generate events
+ trigger_multiple_events(config.sim(), GPIO);
+
+ // Read multiple events
+ assert!(config
+ .request()
+ .wait_edge_events(Some(Duration::from_secs(1)))
+ .unwrap());
+
+ let events = config.request().read_edge_events(&mut buf).unwrap();
+ assert_eq!(events.len(), 3);
+
+ let mut global_seqno = 1;
+ let mut line_seqno = 1;
+
+ // Verify sequence number of events
+ for event in events {
+ let event = event.unwrap();
+ assert_eq!(event.line_offset(), GPIO);
+ assert_eq!(event.global_seqno(), global_seqno);
+ assert_eq!(event.line_seqno(), line_seqno);
+
+ global_seqno += 1;
+ line_seqno += 1;
+ }
+ }
+
+ #[test]
+ fn over_capacity() {
+ const GPIO: Offset = 2;
+ let mut buf = request::Buffer::new(2).unwrap();
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_edge(None, Some(Edge::Both));
+ config.lconfig_add_settings(&[GPIO]);
+ config.request_lines().unwrap();
+
+ // Generate events
+ trigger_multiple_events(config.sim(), GPIO);
+
+ // Read multiple events
+ assert!(config
+ .request()
+ .wait_edge_events(Some(Duration::from_secs(1)))
+ .unwrap());
+
+ let events = config.request().read_edge_events(&mut buf).unwrap();
+ assert_eq!(events.len(), 2);
+ }
+ }
+}
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
+// SPDX-FileCopyrightText: 2022 Linaro Ltd.
+// SPDX-FileCopyrightText: 2022 Viresh Kumar <viresh.kumar@linaro.org>
+
+mod common;
+
+mod info_event {
+ use libc::EINVAL;
+ use std::{
+ sync::{
+ mpsc::{self, Receiver, Sender},
+ Arc, Mutex,
+ },
+ thread,
+ time::Duration,
+ };
+
+ use gpiosim_sys::Sim;
+ use libgpiod::{
+ chip::Chip,
+ line::{self, Direction, InfoChangeKind, Offset},
+ request, Error as ChipError, OperationType,
+ };
+
+ fn request_reconfigure_line(chip: Arc<Mutex<Chip>>, tx: Sender<()>, rx: Receiver<()>) {
+ thread::spawn(move || {
+ let mut lconfig1 = line::Config::new().unwrap();
+ let lsettings = line::Settings::new().unwrap();
+ lconfig1.add_line_settings(&[7], lsettings).unwrap();
+ let rconfig = request::Config::new().unwrap();
+
+ let mut request = chip
+ .lock()
+ .unwrap()
+ .request_lines(Some(&rconfig), &lconfig1)
+ .unwrap();
+
+ // Signal the parent to continue
+ tx.send(()).expect("Could not send signal on channel");
+
+ // Wait for parent to signal
+ rx.recv().expect("Could not receive from channel");
+
+ let mut lconfig2 = line::Config::new().unwrap();
+ let mut lsettings = line::Settings::new().unwrap();
+ lsettings.set_direction(Direction::Output).unwrap();
+ lconfig2.add_line_settings(&[7], lsettings).unwrap();
+
+ request.reconfigure_lines(&lconfig2).unwrap();
+
+ // Signal the parent to continue
+ tx.send(()).expect("Could not send signal on channel");
+
+ // Wait for parent to signal
+ rx.recv().expect("Could not receive from channel");
+ });
+ }
+
+ mod watch {
+ use super::*;
+ const NGPIO: usize = 8;
+ const GPIO: Offset = 7;
+
+ #[test]
+ fn line_info() {
+ let sim = Sim::new(Some(NGPIO), None, true).unwrap();
+ let chip = Chip::open(&sim.dev_path()).unwrap();
+
+ assert_eq!(
+ chip.watch_line_info(NGPIO as u32).unwrap_err(),
+ ChipError::OperationFailed(OperationType::ChipWatchLineInfo, errno::Errno(EINVAL))
+ );
+
+ let info = chip.watch_line_info(GPIO).unwrap();
+ assert_eq!(info.offset(), GPIO);
+
+ // No events available
+ assert!(!chip
+ .wait_info_event(Some(Duration::from_millis(100)))
+ .unwrap());
+ }
+
+ #[test]
+ fn reconfigure() {
+ let sim = Sim::new(Some(NGPIO), None, true).unwrap();
+ let chip = Arc::new(Mutex::new(Chip::open(&sim.dev_path()).unwrap()));
+ let info = chip.lock().unwrap().watch_line_info(GPIO).unwrap();
+
+ assert_eq!(info.direction().unwrap(), Direction::Input);
+
+ // Thread synchronizing mechanism
+ let (tx_main, rx_thread) = mpsc::channel();
+ let (tx_thread, rx_main) = mpsc::channel();
+
+ // Generate events
+ request_reconfigure_line(chip.clone(), tx_thread, rx_thread);
+
+ // Wait for thread to signal
+ rx_main.recv().expect("Could not receive from channel");
+
+ // Line requested event
+ assert!(chip
+ .lock()
+ .unwrap()
+ .wait_info_event(Some(Duration::from_secs(1)))
+ .unwrap());
+ let event = chip.lock().unwrap().read_info_event().unwrap();
+ let ts_req = event.timestamp();
+
+ assert_eq!(event.event_type().unwrap(), InfoChangeKind::LineRequested);
+ assert_eq!(
+ event.line_info().unwrap().direction().unwrap(),
+ Direction::Input
+ );
+
+ // Signal the thread to continue
+ tx_main.send(()).expect("Could not send signal on channel");
+
+ // Wait for thread to signal
+ rx_main.recv().expect("Could not receive from channel");
+
+ // Line changed event
+ assert!(chip
+ .lock()
+ .unwrap()
+ .wait_info_event(Some(Duration::from_secs(1)))
+ .unwrap());
+ let event = chip.lock().unwrap().read_info_event().unwrap();
+ let ts_rec = event.timestamp();
+
+ assert_eq!(
+ event.event_type().unwrap(),
+ InfoChangeKind::LineConfigChanged
+ );
+ assert_eq!(
+ event.line_info().unwrap().direction().unwrap(),
+ Direction::Output
+ );
+
+ // Signal the thread to continue
+ tx_main.send(()).expect("Could not send signal on channel");
+
+ // Line released event
+ assert!(chip
+ .lock()
+ .unwrap()
+ .wait_info_event(Some(Duration::from_secs(1)))
+ .unwrap());
+ let event = chip.lock().unwrap().read_info_event().unwrap();
+ let ts_rel = event.timestamp();
+
+ assert_eq!(event.event_type().unwrap(), InfoChangeKind::LineReleased);
+
+ // No events available
+ assert!(!chip
+ .lock()
+ .unwrap()
+ .wait_info_event(Some(Duration::from_millis(100)))
+ .unwrap());
+
+ // Check timestamps are really monotonic.
+ assert!(ts_rel > ts_rec);
+ assert!(ts_rec > ts_req);
+ }
+ }
+}
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
+// SPDX-FileCopyrightText: 2022 Linaro Ltd.
+// SPDX-FileCopyrightText: 2022 Viresh Kumar <viresh.kumar@linaro.org>
+
+mod common;
+
+mod line_config {
+ use gpiosim_sys::Sim;
+ use libgpiod::chip::Chip;
+ use libgpiod::line::{
+ self, Bias, Direction, Drive, Edge, EventClock, SettingKind, SettingVal, Value,
+ };
+
+ #[test]
+ fn settings() {
+ let mut lsettings1 = line::Settings::new().unwrap();
+ lsettings1
+ .set_direction(Direction::Input)
+ .unwrap()
+ .set_edge_detection(Some(Edge::Both))
+ .unwrap()
+ .set_bias(Some(Bias::PullDown))
+ .unwrap()
+ .set_drive(Drive::PushPull)
+ .unwrap();
+
+ let mut lsettings2 = line::Settings::new().unwrap();
+ lsettings2
+ .set_direction(Direction::Output)
+ .unwrap()
+ .set_active_low(true)
+ .set_event_clock(EventClock::Realtime)
+ .unwrap()
+ .set_output_value(Value::Active)
+ .unwrap();
+
+ // Add settings for multiple lines
+ let mut lconfig = line::Config::new().unwrap();
+ lconfig.add_line_settings(&[0, 1, 2], lsettings1).unwrap();
+ lconfig.add_line_settings(&[4, 5], lsettings2).unwrap();
+
+ let settings_map = lconfig.line_settings().unwrap();
+
+ // Retrieve settings
+ let lsettings = settings_map.get(1).unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::Direction).unwrap(),
+ SettingVal::Direction(Direction::Input)
+ );
+ assert_eq!(
+ lsettings.prop(SettingKind::EdgeDetection).unwrap(),
+ SettingVal::EdgeDetection(Some(Edge::Both))
+ );
+ assert_eq!(
+ lsettings.prop(SettingKind::Bias).unwrap(),
+ SettingVal::Bias(Some(Bias::PullDown))
+ );
+ assert_eq!(
+ lsettings.prop(SettingKind::Drive).unwrap(),
+ SettingVal::Drive(Drive::PushPull)
+ );
+
+ let lsettings = settings_map.get(5).unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::Direction).unwrap(),
+ SettingVal::Direction(Direction::Output)
+ );
+ assert_eq!(
+ lsettings.prop(SettingKind::ActiveLow).unwrap(),
+ SettingVal::ActiveLow(true)
+ );
+ assert_eq!(
+ lsettings.prop(SettingKind::EventClock).unwrap(),
+ SettingVal::EventClock(EventClock::Realtime)
+ );
+ assert_eq!(
+ lsettings.prop(SettingKind::OutputValue).unwrap(),
+ SettingVal::OutputValue(Value::Active)
+ );
+ }
+
+ #[test]
+ fn set_global_output_values() {
+ let sim = Sim::new(Some(4), None, true).unwrap();
+ let mut settings = line::Settings::new().unwrap();
+ settings.set_direction(Direction::Output).unwrap();
+
+ let mut config = line::Config::new().unwrap();
+ config
+ .add_line_settings(&[0, 1, 2, 3], settings)
+ .unwrap()
+ .set_output_values(&[
+ Value::Active,
+ Value::InActive,
+ Value::Active,
+ Value::InActive,
+ ])
+ .unwrap();
+
+ let chip = Chip::open(&sim.dev_path()).unwrap();
+ let _request = chip.request_lines(None, &config);
+
+ assert_eq!(sim.val(0).unwrap(), gpiosim_sys::Value::Active);
+ assert_eq!(sim.val(1).unwrap(), gpiosim_sys::Value::InActive);
+ assert_eq!(sim.val(2).unwrap(), gpiosim_sys::Value::Active);
+ assert_eq!(sim.val(3).unwrap(), gpiosim_sys::Value::InActive);
+ }
+
+ #[test]
+ fn read_back_global_output_values() {
+ let mut settings = line::Settings::new().unwrap();
+ settings
+ .set_direction(Direction::Output)
+ .unwrap()
+ .set_output_value(Value::Active)
+ .unwrap();
+
+ let mut config = line::Config::new().unwrap();
+ config
+ .add_line_settings(&[0, 1, 2, 3], settings)
+ .unwrap()
+ .set_output_values(&[
+ Value::Active,
+ Value::InActive,
+ Value::Active,
+ Value::InActive,
+ ])
+ .unwrap();
+
+ assert_eq!(
+ config
+ .line_settings()
+ .unwrap()
+ .get(1)
+ .unwrap()
+ .output_value()
+ .unwrap(),
+ Value::InActive
+ );
+ }
+}
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
+// SPDX-FileCopyrightText: 2022 Linaro Ltd.
+// SPDX-FileCopyrightText: 2022 Viresh Kumar <viresh.kumar@linaro.org>
+
+mod common;
+
+mod line_info {
+ use libc::EINVAL;
+ use std::time::Duration;
+
+ use crate::common::*;
+ use gpiosim_sys::{Direction as SimDirection, Sim};
+ use libgpiod::{
+ chip::Chip,
+ line::{Bias, Direction, Drive, Edge, EventClock},
+ Error as ChipError, OperationType,
+ };
+
+ const NGPIO: usize = 8;
+
+ mod properties {
+ use std::thread;
+
+ use libgpiod::{line, request};
+
+ use super::*;
+
+ #[test]
+ fn default() {
+ let sim = Sim::new(Some(NGPIO), None, false).unwrap();
+ sim.set_line_name(4, "four").unwrap();
+ sim.hog_line(4, "hog4", SimDirection::OutputLow).unwrap();
+ sim.enable().unwrap();
+
+ let chip = Chip::open(&sim.dev_path()).unwrap();
+
+ let info4 = chip.line_info(4).unwrap();
+ assert_eq!(info4.offset(), 4);
+ assert_eq!(info4.name().unwrap(), "four");
+ assert!(info4.is_used());
+ assert_eq!(info4.consumer().unwrap(), "hog4");
+ assert_eq!(info4.direction().unwrap(), Direction::Output);
+ assert!(!info4.is_active_low());
+ assert_eq!(info4.bias().unwrap(), None);
+ assert_eq!(info4.drive().unwrap(), Drive::PushPull);
+ assert_eq!(info4.edge_detection().unwrap(), None);
+ assert_eq!(info4.event_clock().unwrap(), EventClock::Monotonic);
+ assert!(!info4.is_debounced());
+ assert_eq!(info4.debounce_period(), Duration::from_millis(0));
+
+ assert_eq!(
+ chip.line_info(NGPIO as u32).unwrap_err(),
+ ChipError::OperationFailed(OperationType::ChipGetLineInfo, errno::Errno(EINVAL))
+ );
+ }
+
+ #[test]
+ fn name_and_offset() {
+ let sim = Sim::new(Some(NGPIO), None, false).unwrap();
+
+ // Line 0 has no name
+ for i in 1..NGPIO {
+ sim.set_line_name(i as u32, &i.to_string()).unwrap();
+ }
+ sim.enable().unwrap();
+
+ let chip = Chip::open(&sim.dev_path()).unwrap();
+ let info = chip.line_info(0).unwrap();
+
+ assert_eq!(info.offset(), 0);
+ assert_eq!(
+ info.name().unwrap_err(),
+ ChipError::NullString("GPIO line's name")
+ );
+
+ for i in 1..NGPIO {
+ let info = chip.line_info(i as u32).unwrap();
+
+ assert_eq!(info.offset(), i as u32);
+ assert_eq!(info.name().unwrap(), &i.to_string());
+ }
+ }
+
+ #[test]
+ fn is_used() {
+ let sim = Sim::new(Some(NGPIO), None, false).unwrap();
+ sim.hog_line(0, "hog", SimDirection::OutputHigh).unwrap();
+ sim.enable().unwrap();
+
+ let chip = Chip::open(&sim.dev_path()).unwrap();
+
+ let info = chip.line_info(0).unwrap();
+ assert!(info.is_used());
+
+ let info = chip.line_info(1).unwrap();
+ assert!(!info.is_used());
+ }
+
+ #[test]
+ fn consumer() {
+ let sim = Sim::new(Some(NGPIO), None, false).unwrap();
+ sim.hog_line(0, "hog", SimDirection::OutputHigh).unwrap();
+ sim.enable().unwrap();
+
+ let chip = Chip::open(&sim.dev_path()).unwrap();
+
+ let info = chip.line_info(0).unwrap();
+ assert_eq!(info.consumer().unwrap(), "hog");
+
+ let info = chip.line_info(1).unwrap();
+ assert_eq!(
+ info.consumer().unwrap_err(),
+ ChipError::NullString("GPIO line's consumer name")
+ );
+ }
+
+ #[test]
+ fn direction() {
+ let sim = Sim::new(Some(NGPIO), None, false).unwrap();
+ sim.hog_line(0, "hog", SimDirection::Input).unwrap();
+ sim.hog_line(1, "hog", SimDirection::OutputHigh).unwrap();
+ sim.hog_line(2, "hog", SimDirection::OutputLow).unwrap();
+ sim.enable().unwrap();
+
+ let chip = Chip::open(&sim.dev_path()).unwrap();
+
+ let info = chip.line_info(0).unwrap();
+ assert_eq!(info.direction().unwrap(), Direction::Input);
+
+ let info = chip.line_info(1).unwrap();
+ assert_eq!(info.direction().unwrap(), Direction::Output);
+
+ let info = chip.line_info(2).unwrap();
+ assert_eq!(info.direction().unwrap(), Direction::Output);
+ }
+
+ #[test]
+ fn bias() {
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.bias().unwrap(), None);
+
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_bias(Direction::Input, Some(Bias::PullUp));
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.bias().unwrap(), Some(Bias::PullUp));
+
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_bias(Direction::Input, Some(Bias::PullDown));
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.bias().unwrap(), Some(Bias::PullDown));
+
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_bias(Direction::Input, Some(Bias::Disabled));
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.bias().unwrap(), Some(Bias::Disabled));
+ }
+
+ #[test]
+ fn drive() {
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.drive().unwrap(), Drive::PushPull);
+
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_drive(Direction::Input, Drive::PushPull);
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.drive().unwrap(), Drive::PushPull);
+
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_drive(Direction::Output, Drive::OpenDrain);
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.drive().unwrap(), Drive::OpenDrain);
+
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_drive(Direction::Output, Drive::OpenSource);
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.drive().unwrap(), Drive::OpenSource);
+ }
+
+ #[test]
+ fn edge() {
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.edge_detection().unwrap(), None);
+
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_edge(Some(Direction::Input), Some(Edge::Both));
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.edge_detection().unwrap(), Some(Edge::Both));
+
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_edge(Some(Direction::Input), Some(Edge::Rising));
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.edge_detection().unwrap(), Some(Edge::Rising));
+
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_edge(Some(Direction::Input), Some(Edge::Falling));
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.edge_detection().unwrap(), Some(Edge::Falling));
+ }
+
+ #[test]
+ fn event_clock() {
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.event_clock().unwrap(), EventClock::Monotonic);
+
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_clock(EventClock::Monotonic);
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.event_clock().unwrap(), EventClock::Monotonic);
+
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_clock(EventClock::Realtime);
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.event_clock().unwrap(), EventClock::Realtime);
+ }
+
+ #[test]
+ #[ignore]
+ fn event_clock_hte() {
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_clock(EventClock::HTE);
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.event_clock().unwrap(), EventClock::HTE);
+ }
+
+ #[test]
+ fn debounce() {
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert!(!info.is_debounced());
+ assert_eq!(info.debounce_period(), Duration::from_millis(0));
+
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_debounce(Duration::from_millis(100));
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert!(info.is_debounced());
+ assert_eq!(info.debounce_period(), Duration::from_millis(100));
+ }
+
+ fn generate_line_event(chip: &Chip) {
+ let mut line_config = line::Config::new().unwrap();
+ line_config
+ .add_line_settings(&[0], line::Settings::new().unwrap())
+ .unwrap();
+
+ let mut request = chip
+ .request_lines(Some(&request::Config::new().unwrap()), &line_config)
+ .unwrap();
+
+ let mut new_line_config = line::Config::new().unwrap();
+ let mut settings = line::Settings::new().unwrap();
+ settings.set_direction(Direction::Output).unwrap();
+ new_line_config.add_line_settings(&[0], settings).unwrap();
+ request.reconfigure_lines(&new_line_config).unwrap();
+ }
+
+ #[test]
+ fn ownership() {
+ let sim = Sim::new(Some(1), None, false).unwrap();
+ sim.set_line_name(0, "Test line").unwrap();
+ sim.enable().unwrap();
+
+ let chip = Chip::open(&sim.dev_path()).unwrap();
+
+ // start watching line
+ chip.watch_line_info(0).unwrap();
+
+ generate_line_event(&chip);
+
+ // read generated event
+ let event = chip.read_info_event().unwrap();
+ let info = event.line_info().unwrap();
+ assert_eq!(info.name().unwrap(), "Test line");
+
+ // clone info and move to separate thread
+ let info = info.try_clone().unwrap();
+
+ // drop the original event with the associated line_info
+ drop(event);
+
+ // assert that we can still read the name
+ thread::scope(|s| {
+ s.spawn(move || {
+ assert_eq!(info.name().unwrap(), "Test line");
+ });
+ });
+ }
+ }
+}
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
+// SPDX-FileCopyrightText: 2022 Linaro Ltd.
+// SPDX-FileCopyrightText: 2022 Viresh Kumar <viresh.kumar@linaro.org>
+
+mod common;
+
+mod line_request {
+ use libc::{E2BIG, EINVAL};
+ use std::time::Duration;
+
+ use crate::common::*;
+ use gpiosim_sys::{Pull, Value as SimValue};
+ use libgpiod::{
+ line::{
+ self, Bias, Direction, Drive, Edge, EventClock, Offset, SettingVal, Value, ValueMap,
+ },
+ Error as ChipError, OperationType,
+ };
+
+ const NGPIO: usize = 8;
+
+ mod invalid_arguments {
+ use super::*;
+
+ #[test]
+ fn no_offsets() {
+ let mut config = TestConfig::new(NGPIO).unwrap();
+
+ assert_eq!(
+ config.request_lines().unwrap_err(),
+ ChipError::OperationFailed(OperationType::ChipRequestLines, errno::Errno(EINVAL))
+ );
+ }
+
+ #[test]
+ fn out_of_bound_offsets() {
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_add_settings(&[2, 0, 8, 4]);
+
+ assert_eq!(
+ config.request_lines().unwrap_err(),
+ ChipError::OperationFailed(OperationType::ChipRequestLines, errno::Errno(EINVAL))
+ );
+ }
+
+ #[test]
+ fn dir_out_edge_failure() {
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_edge(Some(Direction::Output), Some(Edge::Both));
+ config.lconfig_add_settings(&[0]);
+
+ assert_eq!(
+ config.request_lines().unwrap_err(),
+ ChipError::OperationFailed(OperationType::ChipRequestLines, errno::Errno(EINVAL))
+ );
+ }
+ }
+
+ mod verify {
+ use super::*;
+
+ #[test]
+ #[cfg(feature = "vnext")]
+ fn chip_name() {
+ const GPIO: Offset = 2;
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_add_settings(&[GPIO]);
+ config.request_lines().unwrap();
+
+ let arc = config.sim();
+ let sim = arc.lock().unwrap();
+ let chip_name = sim.chip_name();
+
+ assert_eq!(config.request().chip_name().unwrap(), chip_name);
+ }
+
+ #[test]
+ fn custom_consumer() {
+ const GPIO: Offset = 2;
+ const CONSUMER: &str = "foobar";
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.rconfig_set_consumer(CONSUMER);
+ config.lconfig_add_settings(&[GPIO]);
+ config.request_lines().unwrap();
+
+ let info = config.chip().line_info(GPIO).unwrap();
+
+ assert!(info.is_used());
+ assert_eq!(info.consumer().unwrap(), CONSUMER);
+ }
+
+ #[test]
+ fn empty_consumer() {
+ const GPIO: Offset = 2;
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_add_settings(&[GPIO]);
+ config.request_lines().unwrap();
+
+ let info = config.chip().line_info(GPIO).unwrap();
+
+ assert!(info.is_used());
+ assert_eq!(info.consumer().unwrap(), "?");
+ }
+
+ #[test]
+ fn read_values() {
+ let offsets = [7, 1, 0, 6, 2];
+ let pulls = [Pull::Up, Pull::Up, Pull::Down, Pull::Up, Pull::Down];
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.set_pull(&offsets, &pulls);
+ config.lconfig_val(Some(Direction::Input), None);
+ config.lconfig_add_settings(&offsets);
+ config.request_lines().unwrap();
+
+ let request = config.request();
+
+ // Single values read properly
+ assert_eq!(request.value(7).unwrap(), Value::Active);
+
+ // Values read properly
+ let map = request.values().unwrap();
+ for i in 0..offsets.len() {
+ assert_eq!(
+ *map.get(offsets[i].into()).unwrap(),
+ match pulls[i] {
+ Pull::Down => Value::InActive,
+ _ => Value::Active,
+ }
+ );
+ }
+
+ // Subset of values read properly
+ let map = request.values_subset(&[2, 0, 6]).unwrap();
+ assert_eq!(*map.get(2).unwrap(), Value::InActive);
+ assert_eq!(*map.get(0).unwrap(), Value::InActive);
+ assert_eq!(*map.get(6).unwrap(), Value::Active);
+
+ // Value read properly after reconfigure
+ let mut lsettings = line::Settings::new().unwrap();
+ lsettings.set_active_low(true);
+ let mut lconfig = line::Config::new().unwrap();
+ lconfig.add_line_settings(&offsets, lsettings).unwrap();
+ request.reconfigure_lines(&lconfig).unwrap();
+ assert_eq!(request.value(7).unwrap(), Value::InActive);
+ }
+
+ #[test]
+ fn set_output_values() {
+ let offsets = [0, 1, 3, 4];
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_val(Some(Direction::Output), Some(Value::Active));
+ config.lconfig_add_settings(&offsets);
+ config.request_lines().unwrap();
+
+ assert_eq!(config.sim_val(0).unwrap(), SimValue::Active);
+ assert_eq!(config.sim_val(1).unwrap(), SimValue::Active);
+ assert_eq!(config.sim_val(3).unwrap(), SimValue::Active);
+ assert_eq!(config.sim_val(4).unwrap(), SimValue::Active);
+
+ // Default
+ assert_eq!(config.sim_val(2).unwrap(), SimValue::InActive);
+ }
+
+ #[test]
+ fn update_output_values() {
+ let offsets = [0, 1, 3, 4];
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_val(Some(Direction::Output), Some(Value::InActive));
+ config.lconfig_add_settings(&offsets);
+ config.request_lines().unwrap();
+
+ // Set single value
+ config.request().set_value(1, Value::Active).unwrap();
+ assert_eq!(config.sim_val(0).unwrap(), SimValue::InActive);
+ assert_eq!(config.sim_val(1).unwrap(), SimValue::Active);
+ assert_eq!(config.sim_val(3).unwrap(), SimValue::InActive);
+ assert_eq!(config.sim_val(4).unwrap(), SimValue::InActive);
+ config.request().set_value(1, Value::InActive).unwrap();
+ assert_eq!(config.sim_val(1).unwrap(), SimValue::InActive);
+
+ // Set values of subset
+ let mut map = ValueMap::new();
+ map.insert(4, Value::Active);
+ map.insert(3, Value::Active);
+ config.request().set_values_subset(map).unwrap();
+ assert_eq!(config.sim_val(0).unwrap(), SimValue::InActive);
+ assert_eq!(config.sim_val(1).unwrap(), SimValue::InActive);
+ assert_eq!(config.sim_val(3).unwrap(), SimValue::Active);
+ assert_eq!(config.sim_val(4).unwrap(), SimValue::Active);
+
+ let mut map = ValueMap::new();
+ map.insert(4, Value::InActive);
+ map.insert(3, Value::InActive);
+ config.request().set_values_subset(map).unwrap();
+ assert_eq!(config.sim_val(3).unwrap(), SimValue::InActive);
+ assert_eq!(config.sim_val(4).unwrap(), SimValue::InActive);
+
+ // Set all values
+ config
+ .request()
+ .set_values(&[
+ Value::Active,
+ Value::InActive,
+ Value::Active,
+ Value::InActive,
+ ])
+ .unwrap();
+ assert_eq!(config.sim_val(0).unwrap(), SimValue::Active);
+ assert_eq!(config.sim_val(1).unwrap(), SimValue::InActive);
+ assert_eq!(config.sim_val(3).unwrap(), SimValue::Active);
+ assert_eq!(config.sim_val(4).unwrap(), SimValue::InActive);
+ config
+ .request()
+ .set_values(&[
+ Value::InActive,
+ Value::InActive,
+ Value::InActive,
+ Value::InActive,
+ ])
+ .unwrap();
+ assert_eq!(config.sim_val(0).unwrap(), SimValue::InActive);
+ assert_eq!(config.sim_val(1).unwrap(), SimValue::InActive);
+ assert_eq!(config.sim_val(3).unwrap(), SimValue::InActive);
+ assert_eq!(config.sim_val(4).unwrap(), SimValue::InActive);
+ }
+
+ #[test]
+ fn set_bias() {
+ let offsets = [3];
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_bias(Direction::Input, Some(Bias::PullUp));
+ config.lconfig_add_settings(&offsets);
+ config.request_lines().unwrap();
+ config.request();
+
+ // Set single value
+ assert_eq!(config.sim_val(3).unwrap(), SimValue::Active);
+ }
+
+ #[test]
+ fn no_events() {
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_edge(None, Some(Edge::Both));
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+
+ // No events available
+ assert!(!config
+ .request()
+ .wait_edge_events(Some(Duration::from_millis(100)))
+ .unwrap());
+ }
+ }
+
+ mod reconfigure {
+ use super::*;
+
+ #[test]
+ fn e2big() {
+ let offsets = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
+ let mut config = TestConfig::new(16).unwrap();
+ config.lconfig_add_settings(&offsets);
+ config.request_lines().unwrap();
+
+ let request = config.request();
+
+ // Reconfigure
+ let mut lsettings = line::Settings::new().unwrap();
+ lsettings.set_direction(Direction::Input).unwrap();
+ let mut lconfig = line::Config::new().unwrap();
+
+ // The uAPI config has only 10 attribute slots, this should pass.
+ for offset in offsets {
+ lsettings.set_debounce_period(Duration::from_millis((100 + offset).into()));
+ lconfig
+ .add_line_settings(&[offset as Offset], lsettings.try_clone().unwrap())
+ .unwrap();
+ }
+
+ assert!(request.reconfigure_lines(&lconfig).is_ok());
+
+ // The uAPI config has only 10 attribute slots, and this is the 11th entry.
+ // This should fail with E2BIG.
+ lsettings.set_debounce_period(Duration::from_millis(100 + 11));
+ lconfig.add_line_settings(&[11], lsettings).unwrap();
+
+ assert_eq!(
+ request.reconfigure_lines(&lconfig).unwrap_err(),
+ ChipError::OperationFailed(
+ OperationType::LineRequestReconfigLines,
+ errno::Errno(E2BIG),
+ )
+ );
+ }
+
+ #[test]
+ fn bias() {
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.bias().unwrap(), None);
+
+ // Reconfigure
+ let mut lconfig = line::Config::new().unwrap();
+ let mut lsettings = line::Settings::new().unwrap();
+ lsettings
+ .set_prop(&[
+ SettingVal::Direction(Direction::Input),
+ SettingVal::Bias(Some(Bias::PullUp)),
+ ])
+ .unwrap();
+ lconfig.add_line_settings(&[0], lsettings).unwrap();
+ config.request().reconfigure_lines(&lconfig).unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.bias().unwrap(), Some(Bias::PullUp));
+
+ let mut lconfig = line::Config::new().unwrap();
+ let mut lsettings = line::Settings::new().unwrap();
+ lsettings
+ .set_prop(&[
+ SettingVal::Direction(Direction::Input),
+ SettingVal::Bias(Some(Bias::PullDown)),
+ ])
+ .unwrap();
+ lconfig.add_line_settings(&[0], lsettings).unwrap();
+ config.request().reconfigure_lines(&lconfig).unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.bias().unwrap(), Some(Bias::PullDown));
+
+ let mut lconfig = line::Config::new().unwrap();
+ let mut lsettings = line::Settings::new().unwrap();
+ lsettings
+ .set_prop(&[
+ SettingVal::Direction(Direction::Input),
+ SettingVal::Bias(Some(Bias::Disabled)),
+ ])
+ .unwrap();
+ lconfig.add_line_settings(&[0], lsettings).unwrap();
+ config.request().reconfigure_lines(&lconfig).unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.bias().unwrap(), Some(Bias::Disabled));
+ }
+
+ #[test]
+ fn drive() {
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.drive().unwrap(), Drive::PushPull);
+
+ // Reconfigure
+ let mut lconfig = line::Config::new().unwrap();
+ let mut lsettings = line::Settings::new().unwrap();
+ lsettings
+ .set_prop(&[
+ SettingVal::Direction(Direction::Input),
+ SettingVal::Drive(Drive::PushPull),
+ ])
+ .unwrap();
+ lconfig.add_line_settings(&[0], lsettings).unwrap();
+ config.request().reconfigure_lines(&lconfig).unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.drive().unwrap(), Drive::PushPull);
+
+ let mut lconfig = line::Config::new().unwrap();
+ let mut lsettings = line::Settings::new().unwrap();
+ lsettings
+ .set_prop(&[
+ SettingVal::Direction(Direction::Output),
+ SettingVal::Drive(Drive::OpenDrain),
+ ])
+ .unwrap();
+ lconfig.add_line_settings(&[0], lsettings).unwrap();
+ config.request().reconfigure_lines(&lconfig).unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.drive().unwrap(), Drive::OpenDrain);
+
+ let mut lconfig = line::Config::new().unwrap();
+ let mut lsettings = line::Settings::new().unwrap();
+ lsettings
+ .set_prop(&[
+ SettingVal::Direction(Direction::Output),
+ SettingVal::Drive(Drive::OpenSource),
+ ])
+ .unwrap();
+ lconfig.add_line_settings(&[0], lsettings).unwrap();
+ config.request().reconfigure_lines(&lconfig).unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.drive().unwrap(), Drive::OpenSource);
+ }
+
+ #[test]
+ fn edge() {
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.edge_detection().unwrap(), None);
+
+ // Reconfigure
+ let mut lconfig = line::Config::new().unwrap();
+ let mut lsettings = line::Settings::new().unwrap();
+ lsettings
+ .set_prop(&[
+ SettingVal::Direction(Direction::Input),
+ SettingVal::EdgeDetection(Some(Edge::Both)),
+ ])
+ .unwrap();
+ lconfig.add_line_settings(&[0], lsettings).unwrap();
+ config.request().reconfigure_lines(&lconfig).unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.edge_detection().unwrap(), Some(Edge::Both));
+
+ let mut lconfig = line::Config::new().unwrap();
+ let mut lsettings = line::Settings::new().unwrap();
+ lsettings
+ .set_prop(&[
+ SettingVal::Direction(Direction::Input),
+ SettingVal::EdgeDetection(Some(Edge::Rising)),
+ ])
+ .unwrap();
+ lconfig.add_line_settings(&[0], lsettings).unwrap();
+ config.request().reconfigure_lines(&lconfig).unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.edge_detection().unwrap(), Some(Edge::Rising));
+
+ let mut lconfig = line::Config::new().unwrap();
+ let mut lsettings = line::Settings::new().unwrap();
+ lsettings
+ .set_prop(&[
+ SettingVal::Direction(Direction::Input),
+ SettingVal::EdgeDetection(Some(Edge::Falling)),
+ ])
+ .unwrap();
+ lconfig.add_line_settings(&[0], lsettings).unwrap();
+ config.request().reconfigure_lines(&lconfig).unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.edge_detection().unwrap(), Some(Edge::Falling));
+ }
+
+ #[test]
+ fn event_clock() {
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.event_clock().unwrap(), EventClock::Monotonic);
+
+ // Reconfigure
+ let mut lconfig = line::Config::new().unwrap();
+ let mut lsettings = line::Settings::new().unwrap();
+ lsettings.set_event_clock(EventClock::Monotonic).unwrap();
+ lconfig.add_line_settings(&[0], lsettings).unwrap();
+ config.request().reconfigure_lines(&lconfig).unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.event_clock().unwrap(), EventClock::Monotonic);
+
+ let mut lconfig = line::Config::new().unwrap();
+ let mut lsettings = line::Settings::new().unwrap();
+ lsettings.set_event_clock(EventClock::Realtime).unwrap();
+ lconfig.add_line_settings(&[0], lsettings).unwrap();
+ config.request().reconfigure_lines(&lconfig).unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.event_clock().unwrap(), EventClock::Realtime);
+ }
+
+ #[test]
+ #[ignore]
+ fn event_clock_hte() {
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.event_clock().unwrap(), EventClock::Monotonic);
+
+ let request = config.request();
+
+ // Reconfigure
+ let mut lconfig = line::Config::new().unwrap();
+ let mut lsettings = line::Settings::new().unwrap();
+ lsettings.set_event_clock(EventClock::HTE).unwrap();
+ lconfig.add_line_settings(&[0], lsettings).unwrap();
+ request.reconfigure_lines(&lconfig).unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.event_clock().unwrap(), EventClock::HTE);
+ }
+
+ #[test]
+ fn debounce() {
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert!(!info.is_debounced());
+ assert_eq!(info.debounce_period(), Duration::from_millis(0));
+
+ let request = config.request();
+
+ // Reconfigure
+ let mut lconfig = line::Config::new().unwrap();
+ let mut lsettings = line::Settings::new().unwrap();
+ lsettings
+ .set_prop(&[
+ SettingVal::Direction(Direction::Input),
+ SettingVal::DebouncePeriod(Duration::from_millis(100)),
+ ])
+ .unwrap();
+ lconfig.add_line_settings(&[0], lsettings).unwrap();
+ request.reconfigure_lines(&lconfig).unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert!(info.is_debounced());
+ assert_eq!(info.debounce_period(), Duration::from_millis(100));
+ }
+ }
+}
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
+// SPDX-FileCopyrightText: 2022 Linaro Ltd.
+// SPDX-FileCopyrightText: 2022 Viresh Kumar <viresh.kumar@linaro.org>
+
+mod common;
+
+mod line_settings {
+ use std::time::Duration;
+
+ use libgpiod::line::{
+ self, Bias, Direction, Drive, Edge, EventClock, SettingKind, SettingVal, Value,
+ };
+
+ #[test]
+ fn direction() {
+ let mut lsettings = line::Settings::new().unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::Direction).unwrap(),
+ SettingVal::Direction(Direction::AsIs)
+ );
+
+ lsettings.set_direction(Direction::Input).unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::Direction).unwrap(),
+ SettingVal::Direction(Direction::Input)
+ );
+
+ lsettings.set_direction(Direction::Output).unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::Direction).unwrap(),
+ SettingVal::Direction(Direction::Output)
+ );
+ }
+
+ #[test]
+ fn edge_detection() {
+ let mut lsettings = line::Settings::new().unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::EdgeDetection).unwrap(),
+ SettingVal::EdgeDetection(None)
+ );
+
+ lsettings.set_edge_detection(Some(Edge::Both)).unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::EdgeDetection).unwrap(),
+ SettingVal::EdgeDetection(Some(Edge::Both))
+ );
+
+ lsettings.set_edge_detection(Some(Edge::Rising)).unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::EdgeDetection).unwrap(),
+ SettingVal::EdgeDetection(Some(Edge::Rising))
+ );
+
+ lsettings.set_edge_detection(Some(Edge::Falling)).unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::EdgeDetection).unwrap(),
+ SettingVal::EdgeDetection(Some(Edge::Falling))
+ );
+ }
+
+ #[test]
+ fn bias() {
+ let mut lsettings = line::Settings::new().unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::Bias).unwrap(),
+ SettingVal::Bias(None)
+ );
+
+ lsettings.set_bias(Some(Bias::PullDown)).unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::Bias).unwrap(),
+ SettingVal::Bias(Some(Bias::PullDown))
+ );
+
+ lsettings.set_bias(Some(Bias::PullUp)).unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::Bias).unwrap(),
+ SettingVal::Bias(Some(Bias::PullUp))
+ );
+ }
+
+ #[test]
+ fn drive() {
+ let mut lsettings = line::Settings::new().unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::Drive).unwrap(),
+ SettingVal::Drive(Drive::PushPull)
+ );
+
+ lsettings.set_drive(Drive::PushPull).unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::Drive).unwrap(),
+ SettingVal::Drive(Drive::PushPull)
+ );
+
+ lsettings.set_drive(Drive::OpenDrain).unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::Drive).unwrap(),
+ SettingVal::Drive(Drive::OpenDrain)
+ );
+
+ lsettings.set_drive(Drive::OpenSource).unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::Drive).unwrap(),
+ SettingVal::Drive(Drive::OpenSource)
+ );
+ }
+
+ #[test]
+ fn active_low() {
+ let mut lsettings = line::Settings::new().unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::ActiveLow).unwrap(),
+ SettingVal::ActiveLow(false)
+ );
+
+ lsettings.set_active_low(true);
+ assert_eq!(
+ lsettings.prop(SettingKind::ActiveLow).unwrap(),
+ SettingVal::ActiveLow(true)
+ );
+
+ lsettings.set_active_low(false);
+ assert_eq!(
+ lsettings.prop(SettingKind::ActiveLow).unwrap(),
+ SettingVal::ActiveLow(false)
+ );
+ }
+
+ #[test]
+ fn debounce_period() {
+ let mut lsettings = line::Settings::new().unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::DebouncePeriod).unwrap(),
+ SettingVal::DebouncePeriod(Duration::from_millis(0))
+ );
+
+ lsettings.set_debounce_period(Duration::from_millis(5));
+ assert_eq!(
+ lsettings.prop(SettingKind::DebouncePeriod).unwrap(),
+ SettingVal::DebouncePeriod(Duration::from_millis(5))
+ );
+ }
+
+ #[test]
+ fn event_clock() {
+ let mut lsettings = line::Settings::new().unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::EventClock).unwrap(),
+ SettingVal::EventClock(EventClock::Monotonic)
+ );
+
+ lsettings.set_event_clock(EventClock::Realtime).unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::EventClock).unwrap(),
+ SettingVal::EventClock(EventClock::Realtime)
+ );
+
+ lsettings.set_event_clock(EventClock::Monotonic).unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::EventClock).unwrap(),
+ SettingVal::EventClock(EventClock::Monotonic)
+ );
+ }
+
+ #[test]
+ #[ignore]
+ fn event_clock_hte() {
+ let mut lsettings = line::Settings::new().unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::EventClock).unwrap(),
+ SettingVal::EventClock(EventClock::Monotonic)
+ );
+
+ lsettings.set_event_clock(EventClock::HTE).unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::EventClock).unwrap(),
+ SettingVal::EventClock(EventClock::HTE)
+ );
+ }
+
+ #[test]
+ fn output_value() {
+ let mut lsettings = line::Settings::new().unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::OutputValue).unwrap(),
+ SettingVal::OutputValue(Value::InActive)
+ );
+
+ lsettings.set_output_value(Value::Active).unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::OutputValue).unwrap(),
+ SettingVal::OutputValue(Value::Active)
+ );
+
+ lsettings.set_output_value(Value::InActive).unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::OutputValue).unwrap(),
+ SettingVal::OutputValue(Value::InActive)
+ );
+ }
+}
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
+// SPDX-FileCopyrightText: 2022 Linaro Ltd.
+// SPDX-FileCopyrightText: 2022 Viresh Kumar <viresh.kumar@linaro.org>
+
+mod common;
+
+mod request_config {
+ use libgpiod::{request, Error as ChipError, OperationType};
+
+ mod verify {
+ use super::*;
+
+ #[test]
+ fn default() {
+ let rconfig = request::Config::new().unwrap();
+
+ assert_eq!(rconfig.event_buffer_size(), 0);
+ assert_eq!(
+ rconfig.consumer().unwrap_err(),
+ ChipError::OperationFailed(
+ OperationType::RequestConfigGetConsumer,
+ errno::Errno(0),
+ )
+ );
+ }
+
+ #[test]
+ fn initialized() {
+ const CONSUMER: &str = "foobar";
+ let mut rconfig = request::Config::new().unwrap();
+ rconfig.set_consumer(CONSUMER).unwrap();
+ rconfig.set_event_buffer_size(64);
+
+ assert_eq!(rconfig.event_buffer_size(), 64);
+ assert_eq!(rconfig.consumer().unwrap(), CONSUMER);
+ }
+ }
+}
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2017-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+AC_PREREQ([2.69])
+
+AC_INIT([libgpiod], [2.1.3])
+AC_SUBST(EXTRA_VERSION, [])
+
+AC_DEFINE_UNQUOTED([GPIOD_VERSION_STR],
+ ["$PACKAGE_VERSION$EXTRA_VERSION"],
+ [Full library version string.])
+AC_SUBST(VERSION_STR, [$PACKAGE_VERSION$EXTRA_VERSION])
+
+# From the libtool manual:
+#
+# (...)
+# 3. If the library source code has changed at all since the last update, then
+# increment revision ('c:r:a' becomes 'c:r+1:a').
+# 4. If any interfaces have been added, removed, or changed since the last
+# update, increment current, and set revision to 0.
+# 5. If any interfaces have been added since the last public release, then
+# increment age.
+# 6. If any interfaces have been removed or changed since the last public
+# release, then set age to 0.
+#
+# Define the libtool version as (C.R.A):
+# NOTE: this version only applies to the core C library.
+AC_SUBST(ABI_VERSION, [4.2.1])
+# Have a separate ABI version for C++ bindings:
+AC_SUBST(ABI_CXX_VERSION, [3.0.1])
+# ABI version for libgpiosim (we need this since it can be installed if we
+# enable tests).
+AC_SUBST(ABI_GPIOSIM_VERSION, [1.1.0])
+
+AC_CONFIG_AUX_DIR([autostuff])
+AC_CONFIG_MACRO_DIRS([m4])
+AM_INIT_AUTOMAKE([foreign subdir-objects])
+
+m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
+m4_pattern_forbid([^AX_],
+ [Unexpanded AX_ macro found. Please install GNU autoconf-archive.])
+
+AC_CONFIG_SRCDIR([lib])
+AC_CONFIG_HEADERS([config.h])
+
+AC_DEFINE([_GNU_SOURCE], [], [We want GNU extensions])
+
+# Silence warning: ar: 'u' modifier ignored since 'D' is the default
+AC_SUBST(AR_FLAGS, [cr])
+
+AM_PROG_AR
+AC_PROG_CC
+AC_PROG_CXX
+AC_PROG_INSTALL
+AC_PROG_EGREP
+
+LT_INIT
+
+AC_DEFUN([ERR_NOT_FOUND],
+ [AC_MSG_ERROR([$1 not found (needed to build $2)], [1])])
+
+AC_DEFUN([FUNC_NOT_FOUND_LIB],
+ [ERR_NOT_FOUND([$1()], [the library])])
+
+AC_DEFUN([HEADER_NOT_FOUND_LIB],
+ [ERR_NOT_FOUND([$1 header], [the library])])
+
+AC_DEFUN([HEADER_NOT_FOUND_TESTS],
+ [ERR_NOT_FOUND([$1 header], [the test suite])])
+
+AC_DEFUN([HEADER_NOT_FOUND_CXX],
+ [ERR_NOT_FOUND([$1 header], [C++ bindings])])
+
+# This is always checked (library needs this)
+AC_HEADER_STDC
+AC_FUNC_MALLOC
+AC_HEADER_STDBOOL
+AC_CHECK_FUNC([ioctl], [], [FUNC_NOT_FOUND_LIB([ioctl])])
+AC_CHECK_FUNC([open], [], [FUNC_NOT_FOUND_LIB([open])])
+AC_CHECK_FUNC([close], [], [FUNC_NOT_FOUND_LIB([close])])
+AC_CHECK_FUNC([read], [], [FUNC_NOT_FOUND_LIB([read])])
+AC_CHECK_FUNC([ppoll], [], [FUNC_NOT_FOUND_LIB([ppoll])])
+AC_CHECK_FUNC([realpath], [], [FUNC_NOT_FOUND_LIB([realpath])])
+AC_CHECK_FUNC([readlink], [], [FUNC_NOT_FOUND_LIB([readlink])])
+AC_CHECK_HEADERS([fcntl.h], [], [HEADER_NOT_FOUND_LIB([fcntl.h])])
+AC_CHECK_HEADERS([getopt.h], [], [HEADER_NOT_FOUND_LIB([getopt.h])])
+AC_CHECK_HEADERS([dirent.h], [], [HEADER_NOT_FOUND_LIB([dirent.h])])
+AC_CHECK_HEADERS([poll.h], [], [HEADER_NOT_FOUND_LIB([poll.h])])
+AC_CHECK_HEADERS([sys/sysmacros.h], [], [HEADER_NOT_FOUND_LIB([sys/sysmacros.h])])
+AC_CHECK_HEADERS([sys/ioctl.h], [], [HEADER_NOT_FOUND_LIB([sys/ioctl.h])])
+AC_CHECK_HEADERS([sys/param.h], [], [HEADER_NOT_FOUND_LIB([sys/param.h])])
+AC_CHECK_HEADERS([sys/stat.h], [], [HEADER_NOT_FOUND_LIB([sys/stat.h])])
+AC_CHECK_HEADERS([sys/types.h], [], [HEADER_NOT_FOUND_LIB([sys/types.h])])
+AC_CHECK_HEADERS([linux/const.h], [], [HEADER_NOT_FOUND_LIB([linux/const.h])])
+AC_CHECK_HEADERS([linux/ioctl.h], [], [HEADER_NOT_FOUND_LIB([linux/ioctl.h])])
+AC_CHECK_HEADERS([linux/types.h], [], [HEADER_NOT_FOUND_LIB([linux/types.h])])
+
+AC_ARG_ENABLE([tools],
+ [AS_HELP_STRING([--enable-tools],[enable libgpiod command-line tools [default=no]])],
+ [if test "x$enableval" = xyes; then with_tools=true; fi],
+ [with_tools=false])
+AM_CONDITIONAL([WITH_TOOLS], [test "x$with_tools" = xtrue])
+
+AC_DEFUN([FUNC_NOT_FOUND_TOOLS],
+ [ERR_NOT_FOUND([$1()], [tools])])
+
+AC_ARG_ENABLE([gpioset-interactive],
+ [AS_HELP_STRING([--enable-gpioset-interactive],
+ [enable gpioset interactive mode [default=no]])],
+ [if test "x$enableval" = xyes; then with_gpioset_interactive=true; fi],
+ [with_gpioset_interactive=false])
+AM_CONDITIONAL([WITH_GPIOSET_INTERACTIVE],
+ [test "x$with_gpioset_interactive" = xtrue])
+
+# FIXME Figure out why this AS_IF() cannot be dropped without causing the other
+# PKG_CHECK_MODULES() expansions fail to execute pkg-config.
+AS_IF([test "x$with_tools" = xtrue],
+ [# These are only needed to build tools
+ AC_CHECK_FUNC([daemon], [], [FUNC_NOT_FOUND_TOOLS([daemon])])
+ AC_CHECK_FUNC([asprintf], [], [FUNC_NOT_FOUND_LIB([asprintf])])
+ AC_CHECK_FUNC([scandir], [], [FUNC_NOT_FOUND_LIB([scandir])])
+ AC_CHECK_FUNC([versionsort], [], [FUNC_NOT_FOUND_LIB([versionsort])])
+ AS_IF([test "x$with_gpioset_interactive" = xtrue],
+ [PKG_CHECK_MODULES([LIBEDIT], [libedit >= 3.1])])
+ ])
+
+AC_ARG_ENABLE([tests],
+ [AS_HELP_STRING([--enable-tests],[enable libgpiod tests [default=no]])],
+ [if test "x$enableval" = xyes; then with_tests=true; fi],
+ [with_tests=false])
+AM_CONDITIONAL([WITH_TESTS], [test "x$with_tests" = xtrue])
+
+AC_ARG_ENABLE([profiling],
+ [AS_HELP_STRING([--enable-profiling],
+ [enable gcov profiling on the core library and tests [default=no]])],
+ [if test "x$enableval" = xyes; then with_profiling=true; fi],
+ [with_profiling=false])
+if test "x$with_profiling" = xtrue
+then
+ AC_SUBST(PROFILING_CFLAGS, ["-fprofile-arcs -ftest-coverage"])
+ AC_SUBST(PROFILING_LDFLAGS, ["-lgcov"])
+fi
+
+AC_DEFUN([FUNC_NOT_FOUND_TESTS],
+ [ERR_NOT_FOUND([$1()], [tests])])
+
+if test "x$with_tests" = xtrue
+then
+ # For libgpiosim
+ PKG_CHECK_MODULES([KMOD], [libkmod >= 18])
+ PKG_CHECK_MODULES([MOUNT], [mount >= 2.33.1])
+
+ # For core library tests
+ PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.50])
+ PKG_CHECK_MODULES([GIO], [gio-2.0 >= 2.50])
+
+ AC_CHECK_FUNC([atexit], [], [FUNC_NOT_FOUND_LIB([atexit])])
+ AC_CHECK_FUNC([asprintf], [], [FUNC_NOT_FOUND_LIB([asprintf])])
+ AC_CHECK_FUNC([prctl], [], [FUNC_NOT_FOUND_LIB([prctl])])
+ AC_CHECK_FUNC([unlink], [], [FUNC_NOT_FOUND_LIB([unlink])])
+ AC_CHECK_FUNC([unlinkat], [], [FUNC_NOT_FOUND_LIB([unlinkat])])
+ AC_CHECK_FUNC([openat], [], [FUNC_NOT_FOUND_LIB([openat])])
+ AC_CHECK_FUNC([mkdirat], [], [FUNC_NOT_FOUND_LIB([mkdirat])])
+ AC_CHECK_FUNC([write], [], [FUNC_NOT_FOUND_LIB([write])])
+ AC_CHECK_FUNC([twalk], [], [FUNC_NOT_FOUND_LIB([twalk])])
+ AC_CHECK_FUNC([tsearch], [], [FUNC_NOT_FOUND_LIB([tsearch])])
+ AC_CHECK_FUNC([tdestroy], [], [FUNC_NOT_FOUND_LIB([tdestroy])])
+ AC_CHECK_FUNC([tdelete], [], [FUNC_NOT_FOUND_LIB([tdelete])])
+ AC_CHECK_HEADERS([sys/utsname.h], [], [HEADER_NOT_FOUND_LIB([sys/utsname.h])])
+ AC_CHECK_HEADERS([sys/mount.h], [], [HEADER_NOT_FOUND_LIB([sys/mount.h])])
+ AC_CHECK_HEADERS([sys/prctl.h], [], [HEADER_NOT_FOUND_LIB([sys/prctl.h])])
+ AC_CHECK_HEADERS([sys/random.h], [], [HEADER_NOT_FOUND_LIB([sys/random.h])])
+ AC_CHECK_HEADERS([linux/version.h], [], [HEADER_NOT_FOUND_LIB([linux/version.h])])
+ AC_CHECK_HEADERS([pthread.h], [], [HEADER_NOT_FOUND_LIB([pthread.h])])
+ AC_CHECK_LIB(pthread, pthread_mutex_lock, [], ERR_NOT_FOUND([pthread library], [tests]))
+
+ if test "x$with_tools" = xtrue
+ then
+ AC_CHECK_PROG([has_shunit2], [shunit2], [true], [false])
+ if test "x$has_shunit2" = "xfalse"
+ then
+ AC_MSG_NOTICE([shunit2 not found - gpio-tools tests cannot be run])
+ fi
+ fi
+fi
+
+AC_ARG_ENABLE([examples],
+ [AS_HELP_STRING([--enable-examples], [enable building code examples[default=no]])],
+ [if test "x$enableval" = xyes; then with_examples=true; fi],
+ [with_examples=false])
+AM_CONDITIONAL([WITH_EXAMPLES], [test "x$with_examples" = xtrue])
+
+AC_ARG_ENABLE([bindings-cxx],
+ [AS_HELP_STRING([--enable-bindings-cxx],[enable C++ bindings [default=no]])],
+ [if test "x$enableval" = xyes; then with_bindings_cxx=true; fi],
+ [with_bindings_cxx=false])
+AM_CONDITIONAL([WITH_BINDINGS_CXX], [test "x$with_bindings_cxx" = xtrue])
+
+if test "x$with_bindings_cxx" = xtrue
+then
+ LT_LANG([C++])
+ # This needs autoconf-archive
+ AX_CXX_COMPILE_STDCXX_17([ext], [mandatory])
+
+ if test "x$with_tests" = xtrue
+ then
+ PKG_CHECK_MODULES([CATCH2], [catch2],, [
+ AC_LANG_PUSH([C++])
+ AC_CHECK_HEADERS([catch2/catch.hpp], [], [HEADER_NOT_FOUND_CXX([catch2/catch.hpp])])
+ AC_LANG_POP([C++])
+ ])
+ fi
+fi
+
+AC_ARG_ENABLE([bindings-python],
+ [AS_HELP_STRING([--enable-bindings-python],[enable python3 bindings [default=no]])],
+ [if test "x$enableval" = xyes; then with_bindings_python=true; fi],
+ [with_bindings_python=false])
+AM_CONDITIONAL([WITH_BINDINGS_PYTHON], [test "x$with_bindings_python" = xtrue])
+
+if test "x$with_bindings_python" = xtrue
+then
+ AM_PATH_PYTHON([3.9], [],
+ [AC_MSG_ERROR([python3 not found - needed for python bindings])])
+ AC_CHECK_PROG([has_python_config], [python3-config], [true], [false])
+
+ if test "x$has_python_config" = xfalse
+ then
+ AC_MSG_ERROR([python3-config not found - needed for python bindings])
+ fi
+fi
+
+AC_ARG_ENABLE([bindings-rust],
+ [AS_HELP_STRING([--enable-bindings-rust],[enable rust bindings [default=no]])],
+ [if test "x$enableval" = xyes; then with_bindings_rust=true; fi],
+ [with_bindings_rust=false])
+AM_CONDITIONAL([WITH_BINDINGS_RUST], [test "x$with_bindings_rust" = xtrue])
+
+if test "x$with_bindings_rust" = xtrue
+then
+ AC_CHECK_PROG([has_cargo], [cargo], [true], [false])
+ if test "x$has_cargo" = xfalse
+ then
+ AC_MSG_ERROR([cargo not found - needed for rust bindings])
+ fi
+fi
+
+AC_CHECK_PROG([has_doxygen], [doxygen], [true], [false])
+AM_CONDITIONAL([HAS_DOXYGEN], [test "x$has_doxygen" = xtrue])
+if test "x$has_doxygen" = xfalse
+then
+ AC_MSG_NOTICE([doxygen not found - documentation cannot be generated])
+fi
+AM_COND_IF([HAS_DOXYGEN], [AC_CONFIG_FILES([Doxyfile])])
+
+if test "x$cross_compiling" = xno
+then
+ AC_CHECK_PROG([has_help2man], [help2man], [true], [false])
+fi
+AM_CONDITIONAL([WITH_MANPAGES], [test "x$has_help2man" = xtrue])
+if test "x$has_help2man" = xfalse
+then
+ AC_MSG_NOTICE([help2man not found - man pages cannot be generated automatically])
+fi
+
+AC_CONFIG_FILES([Makefile
+ include/Makefile
+ lib/Makefile
+ lib/libgpiod.pc
+ contrib/Makefile
+ examples/Makefile
+ tools/Makefile
+ tests/Makefile
+ tests/gpiosim/Makefile
+ bindings/cxx/libgpiodcxx.pc
+ bindings/Makefile
+ bindings/cxx/Makefile
+ bindings/cxx/gpiodcxx/Makefile
+ bindings/cxx/examples/Makefile
+ bindings/cxx/tests/Makefile
+ bindings/python/Makefile
+ bindings/python/gpiod/Makefile
+ bindings/python/gpiod/ext/Makefile
+ bindings/python/examples/Makefile
+ bindings/python/tests/Makefile
+ bindings/python/tests/gpiosim/Makefile
+ bindings/python/tests/procname/Makefile
+ bindings/rust/libgpiod-sys/src/Makefile
+ bindings/rust/libgpiod-sys/Makefile
+ bindings/rust/libgpiod/src/Makefile
+ bindings/rust/libgpiod/tests/common/Makefile
+ bindings/rust/libgpiod/tests/Makefile
+ bindings/rust/libgpiod/Makefile
+ bindings/rust/libgpiod/examples/Makefile
+ bindings/rust/Makefile
+ bindings/rust/gpiosim-sys/src/Makefile
+ bindings/rust/gpiosim-sys/Makefile
+ man/Makefile])
+
+AC_OUTPUT
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2023 Benjamin Li <benl@squareup.com>
+
+// Instructions:
+// - Check out this repository as external/libgpiod.
+// - Move this build file to the project's root directory.
+
+//
+// libgpiod main library
+//
+
+cc_library {
+ name: "libgpiod",
+ defaults: [
+ "libgpiod_defaults",
+ ],
+ srcs: [
+ "lib/*.c",
+ "bindings/cxx/*.cpp",
+ ],
+ export_include_dirs: [
+ "include",
+ "bindings/cxx",
+ ],
+}
+
+cc_defaults {
+ name: "libgpiod_defaults",
+ device_specific: true,
+ cpp_std: "gnu++17",
+ cflags: [
+ // You may want to edit this with the version from configure.ac of
+ // the release you are using.
+ "-DGPIOD_VERSION_STR=\"unstable\"",
+ ],
+ cppflags: [
+ // Google C++ style is to not use exceptions, but this library does
+ // use them.
+ "-fexceptions",
+ ],
+ // Google C++ style is to not use runtime type information, but this
+ // library does use it.
+ rtti: true,
+}
+
+//
+// libgpiod tools
+//
+
+phony {
+ name: "libgpiod_tools",
+ required: [
+ "gpiodetect",
+ "gpioget",
+ "gpioinfo",
+ "gpiomon",
+ "gpionotify",
+ "gpioset",
+ ],
+}
+
+cc_binary {
+ name: "gpiodetect",
+ defaults: [
+ "libgpiod_defaults",
+ "libgpiod_tools_defaults",
+ ],
+ srcs: [
+ "tools/gpiodetect.c",
+ ],
+}
+
+cc_binary {
+ name: "gpioget",
+ defaults: [
+ "libgpiod_defaults",
+ "libgpiod_tools_defaults",
+ ],
+ srcs: [
+ "tools/gpioget.c",
+ ],
+}
+
+cc_binary {
+ name: "gpioinfo",
+ defaults: [
+ "libgpiod_defaults",
+ "libgpiod_tools_defaults",
+ ],
+ srcs: [
+ "tools/gpioinfo.c",
+ ],
+}
+
+cc_binary {
+ name: "gpiomon",
+ defaults: [
+ "libgpiod_defaults",
+ "libgpiod_tools_defaults",
+ ],
+ srcs: [
+ "tools/gpiomon.c",
+ ],
+}
+
+cc_binary {
+ name: "gpionotify",
+ defaults: [
+ "libgpiod_defaults",
+ "libgpiod_tools_defaults",
+ ],
+ srcs: [
+ "tools/gpionotify.c",
+ ],
+}
+
+cc_binary {
+ name: "gpioset",
+ defaults: [
+ "libgpiod_defaults",
+ "libgpiod_tools_defaults",
+ ],
+ srcs: [
+ "tools/gpioset.c",
+ ],
+}
+
+cc_defaults {
+ name: "libgpiod_tools_defaults",
+ srcs: [
+ "tools/tools-common.c",
+ ],
+ shared_libs: [
+ "libgpiod",
+ ],
+}
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2023 Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
+
+EXTRA_DIST = Android.bp
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+async_watch_line_value
+find_line_by_name
+get_chip_info
+get_line_info
+get_line_value
+get_multiple_line_values
+reconfigure_input_to_output
+toggle_line_value
+toggle_multiple_line_values
+watch_line_info
+watch_line_rising
+watch_line_value
+watch_multiple_line_values
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+AM_CFLAGS = -I$(top_srcdir)/include/ -include $(top_builddir)/config.h
+AM_CFLAGS += -Wall -Wextra -g -std=gnu89
+
+LDADD = $(top_builddir)/lib/libgpiod.la
+
+noinst_PROGRAMS = \
+ async_watch_line_value \
+ find_line_by_name \
+ get_chip_info \
+ get_line_info \
+ get_line_value \
+ get_multiple_line_values \
+ reconfigure_input_to_output \
+ toggle_line_value \
+ toggle_multiple_line_values \
+ watch_line_info \
+ watch_line_rising \
+ watch_line_value \
+ watch_multiple_line_values
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+/* Minimal example of asynchronously watching for edges on a single line. */
+
+#include <errno.h>
+#include <gpiod.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Request a line as input with edge detection. */
+static struct gpiod_line_request *request_input_line(const char *chip_path,
+ unsigned int offset,
+ const char *consumer)
+{
+ struct gpiod_request_config *req_cfg = NULL;
+ struct gpiod_line_request *request = NULL;
+ struct gpiod_line_settings *settings;
+ struct gpiod_line_config *line_cfg;
+ struct gpiod_chip *chip;
+ int ret;
+
+ chip = gpiod_chip_open(chip_path);
+ if (!chip)
+ return NULL;
+
+ settings = gpiod_line_settings_new();
+ if (!settings)
+ goto close_chip;
+
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+ /* Assume a button connecting the pin to ground, so pull it up... */
+ gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_PULL_UP);
+ /* ... and provide some debounce. */
+ gpiod_line_settings_set_debounce_period_us(settings, 10000);
+
+ line_cfg = gpiod_line_config_new();
+ if (!line_cfg)
+ goto free_settings;
+
+ ret = gpiod_line_config_add_line_settings(line_cfg, &offset, 1,
+ settings);
+ if (ret)
+ goto free_line_config;
+
+ if (consumer) {
+ req_cfg = gpiod_request_config_new();
+ if (!req_cfg)
+ goto free_line_config;
+
+ gpiod_request_config_set_consumer(req_cfg, consumer);
+ }
+
+ request = gpiod_chip_request_lines(chip, req_cfg, line_cfg);
+
+ gpiod_request_config_free(req_cfg);
+
+free_line_config:
+ gpiod_line_config_free(line_cfg);
+
+free_settings:
+ gpiod_line_settings_free(settings);
+
+close_chip:
+ gpiod_chip_close(chip);
+
+ return request;
+}
+
+static const char *edge_event_type_str(struct gpiod_edge_event *event)
+{
+ switch (gpiod_edge_event_get_event_type(event)) {
+ case GPIOD_EDGE_EVENT_RISING_EDGE:
+ return "Rising";
+ case GPIOD_EDGE_EVENT_FALLING_EDGE:
+ return "Falling";
+ default:
+ return "Unknown";
+ }
+}
+
+int main(void)
+{
+ /* Example configuration - customize to suit your situation. */
+ static const char *const chip_path = "/dev/gpiochip0";
+ static const unsigned int line_offset = 5;
+
+ struct gpiod_edge_event_buffer *event_buffer;
+ struct gpiod_line_request *request;
+ struct gpiod_edge_event *event;
+ int i, ret, event_buf_size;
+ struct pollfd pollfd;
+
+ request = request_input_line(chip_path, line_offset,
+ "async-watch-line-value");
+ if (!request) {
+ fprintf(stderr, "failed to request line: %s\n",
+ strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ /*
+ * A larger buffer is an optimisation for reading bursts of events from
+ * the kernel, but that is not necessary in this case, so 1 is fine.
+ */
+ event_buf_size = 1;
+ event_buffer = gpiod_edge_event_buffer_new(event_buf_size);
+ if (!event_buffer) {
+ fprintf(stderr, "failed to create event buffer: %s\n",
+ strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ pollfd.fd = gpiod_line_request_get_fd(request);
+ pollfd.events = POLLIN;
+
+ for (;;) {
+ ret = poll(&pollfd, 1, -1);
+ if (ret == -1) {
+ fprintf(stderr, "error waiting for edge events: %s\n",
+ strerror(errno));
+ return EXIT_FAILURE;
+ }
+ ret = gpiod_line_request_read_edge_events(request, event_buffer,
+ event_buf_size);
+ if (ret == -1) {
+ fprintf(stderr, "error reading edge events: %s\n",
+ strerror(errno));
+ return EXIT_FAILURE;
+ }
+ for (i = 0; i < ret; i++) {
+ event = gpiod_edge_event_buffer_get_event(event_buffer,
+ i);
+ printf("offset: %d type: %-7s event #%ld\n",
+ gpiod_edge_event_get_line_offset(event),
+ edge_event_type_str(event),
+ gpiod_edge_event_get_line_seqno(event));
+ }
+ }
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+/* Minimal example of finding a line with the given name. */
+
+#include <dirent.h>
+#include <errno.h>
+#include <gpiod.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+static int chip_dir_filter(const struct dirent *entry)
+{
+ struct stat sb;
+ int ret = 0;
+ char *path;
+
+ if (asprintf(&path, "/dev/%s", entry->d_name) < 0)
+ return 0;
+
+ if ((lstat(path, &sb) == 0) && (!S_ISLNK(sb.st_mode)) &&
+ gpiod_is_gpiochip_device(path))
+ ret = 1;
+
+ free(path);
+
+ return ret;
+}
+
+static int all_chip_paths(char ***paths_ptr)
+{
+ int i, j, num_chips, ret = 0;
+ struct dirent **entries;
+ char **paths;
+
+ num_chips = scandir("/dev/", &entries, chip_dir_filter, versionsort);
+ if (num_chips < 0)
+ return 0;
+
+ paths = calloc(num_chips, sizeof(*paths));
+ if (!paths)
+ return 0;
+
+ for (i = 0; i < num_chips; i++) {
+ if (asprintf(&paths[i], "/dev/%s", entries[i]->d_name) < 0) {
+ for (j = 0; j < i; j++)
+ free(paths[j]);
+
+ free(paths);
+ return 0;
+ }
+ }
+
+ *paths_ptr = paths;
+ ret = num_chips;
+
+ for (i = 0; i < num_chips; i++)
+ free(entries[i]);
+
+ free(entries);
+ return ret;
+}
+
+int main(void)
+{
+ /* Example configuration - customize to suit your situation. */
+ static const char *const line_name = "GPIO19";
+
+ struct gpiod_chip_info *cinfo;
+ int i, num_chips, offset;
+ struct gpiod_chip *chip;
+ char **chip_paths;
+
+ /*
+ * Names are not guaranteed unique, so this finds the first line with
+ * the given name.
+ */
+ num_chips = all_chip_paths(&chip_paths);
+ for (i = 0; i < num_chips; i++) {
+ chip = gpiod_chip_open(chip_paths[i]);
+ if (!chip)
+ continue;
+
+ offset = gpiod_chip_get_line_offset_from_name(chip, line_name);
+ if (offset == -1)
+ goto close_chip;
+
+ cinfo = gpiod_chip_get_info(chip);
+ if (!cinfo)
+ goto close_chip;
+
+ printf("%s: %s %d\n", line_name,
+ gpiod_chip_info_get_name(cinfo), offset);
+
+ return EXIT_SUCCESS;
+
+close_chip:
+ gpiod_chip_close(chip);
+ }
+
+ printf("line '%s' not found\n", line_name);
+ return EXIT_FAILURE;
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+/* Minimal example of reading the info for a chip. */
+
+#include <errno.h>
+#include <gpiod.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+int main(void)
+{
+ /* Example configuration - customize to suit your situation. */
+ static const char *const chip_path = "/dev/gpiochip0";
+
+ struct gpiod_chip_info *info;
+ struct gpiod_chip *chip;
+
+ chip = gpiod_chip_open(chip_path);
+ if (!chip) {
+ fprintf(stderr, "failed to open chip: %s\n", strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ info = gpiod_chip_get_info(chip);
+ if (!info) {
+ fprintf(stderr, "failed to read info: %s\n", strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ printf("%s [%s] (%zu lines)\n", gpiod_chip_info_get_name(info),
+ gpiod_chip_info_get_label(info),
+ gpiod_chip_info_get_num_lines(info));
+
+ gpiod_chip_info_free(info);
+ gpiod_chip_close(chip);
+
+ return EXIT_SUCCESS;
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+/* Minimal example of reading the info for a line. */
+
+#include <errno.h>
+#include <gpiod.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+int main(void)
+{
+ /* Example configuration - customize to suit your situation. */
+ static const char *const chip_path = "/dev/gpiochip0";
+ static const unsigned int line_offset = 3;
+
+ const char *name, *consumer, *dir;
+ struct gpiod_line_info *info;
+ struct gpiod_chip *chip;
+
+ chip = gpiod_chip_open(chip_path);
+ if (!chip) {
+ fprintf(stderr, "failed to open chip: %s\n", strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ info = gpiod_chip_get_line_info(chip, line_offset);
+ if (!info) {
+ fprintf(stderr, "failed to read info: %s\n", strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ name = gpiod_line_info_get_name(info);
+ if (!name)
+ name = "unnamed";
+
+ consumer = gpiod_line_info_get_consumer(info);
+ if (!consumer)
+ consumer = "unused";
+
+ dir = (gpiod_line_info_get_direction(info) ==
+ GPIOD_LINE_DIRECTION_INPUT) ?
+ "input" :
+ "output";
+
+ printf("line %3d: %12s %12s %8s %10s\n",
+ gpiod_line_info_get_offset(info), name, consumer, dir,
+ gpiod_line_info_is_active_low(info) ? "active-low" :
+ "active-high");
+
+ gpiod_line_info_free(info);
+ gpiod_chip_close(chip);
+
+ return EXIT_SUCCESS;
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+/* Minimal example of reading a single line. */
+
+#include <errno.h>
+#include <gpiod.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Request a line as input. */
+static struct gpiod_line_request *request_input_line(const char *chip_path,
+ unsigned int offset,
+ const char *consumer)
+{
+ struct gpiod_request_config *req_cfg = NULL;
+ struct gpiod_line_request *request = NULL;
+ struct gpiod_line_settings *settings;
+ struct gpiod_line_config *line_cfg;
+ struct gpiod_chip *chip;
+ int ret;
+
+ chip = gpiod_chip_open(chip_path);
+ if (!chip)
+ return NULL;
+
+ settings = gpiod_line_settings_new();
+ if (!settings)
+ goto close_chip;
+
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+
+ line_cfg = gpiod_line_config_new();
+ if (!line_cfg)
+ goto free_settings;
+
+ ret = gpiod_line_config_add_line_settings(line_cfg, &offset, 1,
+ settings);
+ if (ret)
+ goto free_line_config;
+
+ if (consumer) {
+ req_cfg = gpiod_request_config_new();
+ if (!req_cfg)
+ goto free_line_config;
+
+ gpiod_request_config_set_consumer(req_cfg, consumer);
+ }
+
+ request = gpiod_chip_request_lines(chip, req_cfg, line_cfg);
+
+ gpiod_request_config_free(req_cfg);
+
+free_line_config:
+ gpiod_line_config_free(line_cfg);
+
+free_settings:
+ gpiod_line_settings_free(settings);
+
+close_chip:
+ gpiod_chip_close(chip);
+
+ return request;
+}
+
+static int print_value(unsigned int offset, enum gpiod_line_value value)
+{
+ if (value == GPIOD_LINE_VALUE_ACTIVE)
+ printf("%d=Active\n", offset);
+ else if (value == GPIOD_LINE_VALUE_INACTIVE) {
+ printf("%d=Inactive\n", offset);
+ } else {
+ fprintf(stderr, "error reading value: %s\n",
+ strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
+
+int main(void)
+{
+ /* Example configuration - customize to suit your situation. */
+ static const char *const chip_path = "/dev/gpiochip0";
+ static const unsigned int line_offset = 5;
+
+ struct gpiod_line_request *request;
+ enum gpiod_line_value value;
+ int ret;
+
+ request = request_input_line(chip_path, line_offset, "get-line-value");
+ if (!request) {
+ fprintf(stderr, "failed to request line: %s\n",
+ strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ value = gpiod_line_request_get_value(request, line_offset);
+ ret = print_value(line_offset, value);
+
+ /* not strictly required here, but if the app wasn't exiting... */
+ gpiod_line_request_release(request);
+
+ return ret;
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+/* Minimal example of reading multiple lines. */
+
+#include <errno.h>
+#include <gpiod.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define NUM_LINES 3
+
+/* Request a line as input. */
+static struct gpiod_line_request *
+request_input_lines(const char *chip_path, const unsigned int *offsets,
+ unsigned int num_lines, const char *consumer)
+{
+ struct gpiod_request_config *req_cfg = NULL;
+ struct gpiod_line_request *request = NULL;
+ struct gpiod_line_settings *settings;
+ struct gpiod_line_config *line_cfg;
+ struct gpiod_chip *chip;
+ unsigned int i;
+ int ret;
+
+ chip = gpiod_chip_open(chip_path);
+ if (!chip)
+ return NULL;
+
+ settings = gpiod_line_settings_new();
+ if (!settings)
+ goto close_chip;
+
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+
+ line_cfg = gpiod_line_config_new();
+ if (!line_cfg)
+ goto free_settings;
+
+ for (i = 0; i < num_lines; i++) {
+ ret = gpiod_line_config_add_line_settings(line_cfg, &offsets[i],
+ 1, settings);
+ if (ret)
+ goto free_line_config;
+ }
+
+ if (consumer) {
+ req_cfg = gpiod_request_config_new();
+ if (!req_cfg)
+ goto free_line_config;
+
+ gpiod_request_config_set_consumer(req_cfg, consumer);
+ }
+
+ request = gpiod_chip_request_lines(chip, req_cfg, line_cfg);
+
+ gpiod_request_config_free(req_cfg);
+
+free_line_config:
+ gpiod_line_config_free(line_cfg);
+
+free_settings:
+ gpiod_line_settings_free(settings);
+
+close_chip:
+ gpiod_chip_close(chip);
+
+ return request;
+}
+
+static int print_values(const unsigned int *offsets, unsigned int num_lines,
+ enum gpiod_line_value *values)
+{
+ unsigned int i;
+
+ for (i = 0; i < num_lines; i++) {
+ if (values[i] == GPIOD_LINE_VALUE_ACTIVE)
+ printf("%d=Active ", offsets[i]);
+ else if (values[i] == GPIOD_LINE_VALUE_INACTIVE) {
+ printf("%d=Inactive ", offsets[i]);
+ } else {
+ fprintf(stderr, "error reading value: %s\n",
+ strerror(errno));
+ return EXIT_FAILURE;
+ }
+ }
+
+ printf("\n");
+
+ return EXIT_SUCCESS;
+}
+
+int main(void)
+{
+ /* Example configuration - customize to suit your situation. */
+ static const char *const chip_path = "/dev/gpiochip0";
+ static const unsigned int line_offsets[NUM_LINES] = { 5, 3, 7 };
+
+ enum gpiod_line_value values[NUM_LINES];
+ struct gpiod_line_request *request;
+ int ret;
+
+ request = request_input_lines(chip_path, line_offsets, NUM_LINES,
+ "get-multiple-line-values");
+ if (!request) {
+ fprintf(stderr, "failed to request lines: %s\n",
+ strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ ret = gpiod_line_request_get_values(request, values);
+ if (ret == -1) {
+ fprintf(stderr, "failed to get values: %s\n", strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ return print_values(line_offsets, NUM_LINES, values);
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+/*
+ * Example of a bi-directional line requested as input and then switched
+ * to output.
+ */
+
+#include <errno.h>
+#include <gpiod.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Request a line as input. */
+static struct gpiod_line_request *request_input_line(const char *chip_path,
+ unsigned int offset,
+ const char *consumer)
+{
+ struct gpiod_request_config *req_cfg = NULL;
+ struct gpiod_line_request *request = NULL;
+ struct gpiod_line_settings *settings;
+ struct gpiod_line_config *line_cfg;
+ struct gpiod_chip *chip;
+ int ret;
+
+ chip = gpiod_chip_open(chip_path);
+ if (!chip)
+ return NULL;
+
+ settings = gpiod_line_settings_new();
+ if (!settings)
+ goto close_chip;
+
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+
+ line_cfg = gpiod_line_config_new();
+ if (!line_cfg)
+ goto free_settings;
+
+ ret = gpiod_line_config_add_line_settings(line_cfg, &offset, 1,
+ settings);
+ if (ret)
+ goto free_line_config;
+
+ if (consumer) {
+ req_cfg = gpiod_request_config_new();
+ if (!req_cfg)
+ goto free_line_config;
+
+ gpiod_request_config_set_consumer(req_cfg, consumer);
+ }
+
+ request = gpiod_chip_request_lines(chip, req_cfg, line_cfg);
+
+ gpiod_request_config_free(req_cfg);
+
+free_line_config:
+ gpiod_line_config_free(line_cfg);
+
+free_settings:
+ gpiod_line_settings_free(settings);
+
+close_chip:
+ gpiod_chip_close(chip);
+
+ return request;
+}
+
+static int reconfigure_as_output_line(struct gpiod_line_request *request,
+ unsigned int offset,
+ enum gpiod_line_value value)
+{
+ struct gpiod_request_config *req_cfg = NULL;
+ struct gpiod_line_settings *settings;
+ struct gpiod_line_config *line_cfg;
+ int ret = -1;
+
+ settings = gpiod_line_settings_new();
+ if (!settings)
+ return -1;
+
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+ gpiod_line_settings_set_output_value(settings, value);
+
+ line_cfg = gpiod_line_config_new();
+ if (!line_cfg)
+ goto free_settings;
+
+ ret = gpiod_line_config_add_line_settings(line_cfg, &offset, 1,
+ settings);
+ if (ret)
+ goto free_line_config;
+
+ ret = gpiod_line_request_reconfigure_lines(request, line_cfg);
+
+ gpiod_request_config_free(req_cfg);
+
+free_line_config:
+ gpiod_line_config_free(line_cfg);
+
+free_settings:
+ gpiod_line_settings_free(settings);
+
+ return ret;
+}
+
+static const char * value_str(enum gpiod_line_value value)
+{
+ if (value == GPIOD_LINE_VALUE_ACTIVE)
+ return "Active";
+ else if (value == GPIOD_LINE_VALUE_INACTIVE) {
+ return "Inactive";
+ } else {
+ return "Unknown";
+ }
+}
+
+int main(void)
+{
+ /* Example configuration - customize to suit your situation. */
+ static const char *const chip_path = "/dev/gpiochip0";
+ static const unsigned int line_offset = 5;
+
+ struct gpiod_line_request *request;
+ enum gpiod_line_value value;
+ int ret;
+
+ /* request the line initially as an input */
+ request = request_input_line(chip_path, line_offset,
+ "reconfigure-input-to-output");
+ if (!request) {
+ fprintf(stderr, "failed to request line: %s\n",
+ strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ /* read the current line value */
+ value = gpiod_line_request_get_value(request, line_offset);
+ printf("%d=%s (input)\n", line_offset, value_str(value));
+
+ /* switch the line to an output and drive it low */
+ ret = reconfigure_as_output_line(request, line_offset,
+ GPIOD_LINE_VALUE_INACTIVE);
+
+ /* report the current driven value */
+ value = gpiod_line_request_get_value(request, line_offset);
+ printf("%d=%s (output)\n", line_offset, value_str(value));
+
+ /* not strictly required here, but if the app wasn't exiting... */
+ gpiod_line_request_release(request);
+
+ return ret;
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+/* Minimal example of toggling a single line. */
+
+#include <errno.h>
+#include <gpiod.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static struct gpiod_line_request *
+request_output_line(const char *chip_path, unsigned int offset,
+ enum gpiod_line_value value, const char *consumer)
+{
+ struct gpiod_request_config *req_cfg = NULL;
+ struct gpiod_line_request *request = NULL;
+ struct gpiod_line_settings *settings;
+ struct gpiod_line_config *line_cfg;
+ struct gpiod_chip *chip;
+ int ret;
+
+ chip = gpiod_chip_open(chip_path);
+ if (!chip)
+ return NULL;
+
+ settings = gpiod_line_settings_new();
+ if (!settings)
+ goto close_chip;
+
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+ gpiod_line_settings_set_output_value(settings, value);
+
+ line_cfg = gpiod_line_config_new();
+ if (!line_cfg)
+ goto free_settings;
+
+ ret = gpiod_line_config_add_line_settings(line_cfg, &offset, 1,
+ settings);
+ if (ret)
+ goto free_line_config;
+
+ if (consumer) {
+ req_cfg = gpiod_request_config_new();
+ if (!req_cfg)
+ goto free_line_config;
+
+ gpiod_request_config_set_consumer(req_cfg, consumer);
+ }
+
+ request = gpiod_chip_request_lines(chip, req_cfg, line_cfg);
+
+ gpiod_request_config_free(req_cfg);
+
+free_line_config:
+ gpiod_line_config_free(line_cfg);
+
+free_settings:
+ gpiod_line_settings_free(settings);
+
+close_chip:
+ gpiod_chip_close(chip);
+
+ return request;
+}
+
+static enum gpiod_line_value toggle_line_value(enum gpiod_line_value value)
+{
+ return (value == GPIOD_LINE_VALUE_ACTIVE) ? GPIOD_LINE_VALUE_INACTIVE :
+ GPIOD_LINE_VALUE_ACTIVE;
+}
+
+static const char * value_str(enum gpiod_line_value value)
+{
+ if (value == GPIOD_LINE_VALUE_ACTIVE)
+ return "Active";
+ else if (value == GPIOD_LINE_VALUE_INACTIVE) {
+ return "Inactive";
+ } else {
+ return "Unknown";
+ }
+}
+
+int main(void)
+{
+ /* Example configuration - customize to suit your situation. */
+ static const char *const chip_path = "/dev/gpiochip0";
+ static const unsigned int line_offset = 5;
+
+ enum gpiod_line_value value = GPIOD_LINE_VALUE_ACTIVE;
+ struct gpiod_line_request *request;
+
+ request = request_output_line(chip_path, line_offset, value,
+ "toggle-line-value");
+ if (!request) {
+ fprintf(stderr, "failed to request line: %s\n",
+ strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ for (;;) {
+ printf("%d=%s\n", line_offset, value_str(value));
+ sleep(1);
+ value = toggle_line_value(value);
+ gpiod_line_request_set_value(request, line_offset, value);
+ }
+
+ gpiod_line_request_release(request);
+
+ return EXIT_SUCCESS;
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+/* Minimal example of toggling multiple lines. */
+
+#include <errno.h>
+#include <gpiod.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define NUM_LINES 3
+
+static struct gpiod_line_request *
+request_output_lines(const char *chip_path, const unsigned int *offsets,
+ enum gpiod_line_value *values, unsigned int num_lines,
+ const char *consumer)
+{
+ struct gpiod_request_config *rconfig = NULL;
+ struct gpiod_line_request *request = NULL;
+ struct gpiod_line_settings *settings;
+ struct gpiod_line_config *lconfig;
+ struct gpiod_chip *chip;
+ unsigned int i;
+ int ret;
+
+ chip = gpiod_chip_open(chip_path);
+ if (!chip)
+ return NULL;
+
+ settings = gpiod_line_settings_new();
+ if (!settings)
+ goto close_chip;
+
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+
+ lconfig = gpiod_line_config_new();
+ if (!lconfig)
+ goto free_settings;
+
+ for (i = 0; i < num_lines; i++) {
+ ret = gpiod_line_config_add_line_settings(lconfig, &offsets[i],
+ 1, settings);
+ if (ret)
+ goto free_line_config;
+ }
+ gpiod_line_config_set_output_values(lconfig, values, num_lines);
+
+ if (consumer) {
+ rconfig = gpiod_request_config_new();
+ if (!rconfig)
+ goto free_line_config;
+
+ gpiod_request_config_set_consumer(rconfig, consumer);
+ }
+
+ request = gpiod_chip_request_lines(chip, rconfig, lconfig);
+
+ gpiod_request_config_free(rconfig);
+
+free_line_config:
+ gpiod_line_config_free(lconfig);
+
+free_settings:
+ gpiod_line_settings_free(settings);
+
+close_chip:
+ gpiod_chip_close(chip);
+
+ return request;
+}
+
+static enum gpiod_line_value toggle_line_value(enum gpiod_line_value value)
+{
+ return (value == GPIOD_LINE_VALUE_ACTIVE) ? GPIOD_LINE_VALUE_INACTIVE :
+ GPIOD_LINE_VALUE_ACTIVE;
+}
+
+static void toggle_line_values(enum gpiod_line_value *values,
+ unsigned int num_lines)
+{
+ unsigned int i;
+
+ for (i = 0; i < num_lines; i++)
+ values[i] = toggle_line_value(values[i]);
+}
+
+static void print_values(const unsigned int *offsets,
+ const enum gpiod_line_value *values,
+ unsigned int num_lines)
+{
+ unsigned int i;
+
+ for (i = 0; i < num_lines; i++) {
+ if (values[i] == GPIOD_LINE_VALUE_ACTIVE)
+ printf("%d=Active ", offsets[i]);
+ else
+ printf("%d=Inactive ", offsets[i]);
+ }
+
+ printf("\n");
+}
+
+int main(void)
+{
+ /* Example configuration - customize to suit your situation. */
+ static const char *const chip_path = "/dev/gpiochip0";
+ static const unsigned int line_offsets[NUM_LINES] = { 5, 3, 7 };
+
+ enum gpiod_line_value values[NUM_LINES] = { GPIOD_LINE_VALUE_ACTIVE,
+ GPIOD_LINE_VALUE_ACTIVE,
+ GPIOD_LINE_VALUE_INACTIVE };
+ struct gpiod_line_request *request;
+
+ request = request_output_lines(chip_path, line_offsets, values,
+ NUM_LINES,
+ "toggle-multiple-line-values");
+ if (!request) {
+ fprintf(stderr, "failed to request line: %s\n",
+ strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ for (;;) {
+ print_values(line_offsets, values, NUM_LINES);
+ sleep(1);
+ toggle_line_values(values, NUM_LINES);
+ gpiod_line_request_set_values(request, values);
+ }
+
+ gpiod_line_request_release(request);
+
+ return EXIT_SUCCESS;
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+/* Minimal example of watching for info changes on particular lines. */
+
+#include <errno.h>
+#include <gpiod.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define NUM_LINES 3
+
+static const char *event_type(struct gpiod_info_event *event)
+{
+ switch (gpiod_info_event_get_event_type(event)) {
+ case GPIOD_INFO_EVENT_LINE_REQUESTED:
+ return "Requested";
+ case GPIOD_INFO_EVENT_LINE_RELEASED:
+ return "Released";
+ case GPIOD_INFO_EVENT_LINE_CONFIG_CHANGED:
+ return "Reconfig";
+ default:
+ return "Unknown";
+ }
+}
+
+int main(void)
+{
+ /* Example configuration - customize to suit your situation. */
+ static const char *const chip_path = "/dev/gpiochip0";
+ static const unsigned int line_offsets[NUM_LINES] = { 5, 3, 7 };
+
+ struct gpiod_info_event *event;
+ struct gpiod_line_info *info;
+ struct gpiod_chip *chip;
+ uint64_t timestamp_ns;
+ unsigned int i;
+
+ chip = gpiod_chip_open(chip_path);
+ if (!chip) {
+ fprintf(stderr, "failed to open chip: %s\n", strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ for (i = 0; i < NUM_LINES; i++) {
+ info = gpiod_chip_watch_line_info(chip, line_offsets[i]);
+ if (!info) {
+ fprintf(stderr, "failed to read info: %s\n",
+ strerror(errno));
+ return EXIT_FAILURE;
+ }
+ }
+
+ for (;;) {
+ /* Blocks until an event is available. */
+ event = gpiod_chip_read_info_event(chip);
+ if (!event) {
+ fprintf(stderr, "failed to read event: %s\n",
+ strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ info = gpiod_info_event_get_line_info(event);
+ timestamp_ns = gpiod_info_event_get_timestamp_ns(event);
+ printf("line %3d: %-9s %" PRIu64 ".%" PRIu64 "\n",
+ gpiod_line_info_get_offset(info), event_type(event),
+ timestamp_ns / 1000000000, timestamp_ns % 1000000000);
+
+ gpiod_info_event_free(event);
+ }
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+/* Minimal example of watching for rising edges on a single line. */
+
+#include <errno.h>
+#include <gpiod.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Request a line as input with edge detection. */
+static struct gpiod_line_request *request_input_line(const char *chip_path,
+ unsigned int offset,
+ const char *consumer)
+{
+ struct gpiod_request_config *req_cfg = NULL;
+ struct gpiod_line_request *request = NULL;
+ struct gpiod_line_settings *settings;
+ struct gpiod_line_config *line_cfg;
+ struct gpiod_chip *chip;
+ int ret;
+
+ chip = gpiod_chip_open(chip_path);
+ if (!chip)
+ return NULL;
+
+ settings = gpiod_line_settings_new();
+ if (!settings)
+ goto close_chip;
+
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_RISING);
+
+ line_cfg = gpiod_line_config_new();
+ if (!line_cfg)
+ goto free_settings;
+
+ ret = gpiod_line_config_add_line_settings(line_cfg, &offset, 1,
+ settings);
+ if (ret)
+ goto free_line_config;
+
+ if (consumer) {
+ req_cfg = gpiod_request_config_new();
+ if (!req_cfg)
+ goto free_line_config;
+
+ gpiod_request_config_set_consumer(req_cfg, consumer);
+ }
+
+ request = gpiod_chip_request_lines(chip, req_cfg, line_cfg);
+
+ gpiod_request_config_free(req_cfg);
+
+free_line_config:
+ gpiod_line_config_free(line_cfg);
+
+free_settings:
+ gpiod_line_settings_free(settings);
+
+close_chip:
+ gpiod_chip_close(chip);
+
+ return request;
+}
+
+static const char *edge_event_type_str(struct gpiod_edge_event *event)
+{
+ switch (gpiod_edge_event_get_event_type(event)) {
+ case GPIOD_EDGE_EVENT_RISING_EDGE:
+ return "Rising";
+ case GPIOD_EDGE_EVENT_FALLING_EDGE:
+ return "Falling";
+ default:
+ return "Unknown";
+ }
+}
+
+int main(void)
+{
+ /* Example configuration - customize to suit your situation. */
+ static const char *const chip_path = "/dev/gpiochip0";
+ static const unsigned int line_offset = 5;
+
+ struct gpiod_edge_event_buffer *event_buffer;
+ struct gpiod_line_request *request;
+ struct gpiod_edge_event *event;
+ int i, ret, event_buf_size;
+
+ request = request_input_line(chip_path, line_offset,
+ "watch-line-value");
+ if (!request) {
+ fprintf(stderr, "failed to request line: %s\n",
+ strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ /*
+ * A larger buffer is an optimisation for reading bursts of events from
+ * the kernel, but that is not necessary in this case, so 1 is fine.
+ */
+ event_buf_size = 1;
+ event_buffer = gpiod_edge_event_buffer_new(event_buf_size);
+ if (!event_buffer) {
+ fprintf(stderr, "failed to create event buffer: %s\n",
+ strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ for (;;) {
+ /* Blocks until at least one event is available. */
+ ret = gpiod_line_request_read_edge_events(request, event_buffer,
+ event_buf_size);
+ if (ret == -1) {
+ fprintf(stderr, "error reading edge events: %s\n",
+ strerror(errno));
+ return EXIT_FAILURE;
+ }
+ for (i = 0; i < ret; i++) {
+ event = gpiod_edge_event_buffer_get_event(event_buffer,
+ i);
+ printf("offset: %d type: %-7s event #%ld\n",
+ gpiod_edge_event_get_line_offset(event),
+ edge_event_type_str(event),
+ gpiod_edge_event_get_line_seqno(event));
+ }
+ }
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+/* Minimal example of watching for edges on a single line. */
+
+#include <errno.h>
+#include <gpiod.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Request a line as input with edge detection. */
+static struct gpiod_line_request *request_input_line(const char *chip_path,
+ unsigned int offset,
+ const char *consumer)
+{
+ struct gpiod_request_config *req_cfg = NULL;
+ struct gpiod_line_request *request = NULL;
+ struct gpiod_line_settings *settings;
+ struct gpiod_line_config *line_cfg;
+ struct gpiod_chip *chip;
+ int ret;
+
+ chip = gpiod_chip_open(chip_path);
+ if (!chip)
+ return NULL;
+
+ settings = gpiod_line_settings_new();
+ if (!settings)
+ goto close_chip;
+
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+ /* Assume a button connecting the pin to ground, so pull it up... */
+ gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_PULL_UP);
+ /* ... and provide some debounce. */
+ gpiod_line_settings_set_debounce_period_us(settings, 10000);
+
+ line_cfg = gpiod_line_config_new();
+ if (!line_cfg)
+ goto free_settings;
+
+ ret = gpiod_line_config_add_line_settings(line_cfg, &offset, 1,
+ settings);
+ if (ret)
+ goto free_line_config;
+
+ if (consumer) {
+ req_cfg = gpiod_request_config_new();
+ if (!req_cfg)
+ goto free_line_config;
+
+ gpiod_request_config_set_consumer(req_cfg, consumer);
+ }
+
+ request = gpiod_chip_request_lines(chip, req_cfg, line_cfg);
+
+ gpiod_request_config_free(req_cfg);
+
+free_line_config:
+ gpiod_line_config_free(line_cfg);
+
+free_settings:
+ gpiod_line_settings_free(settings);
+
+close_chip:
+ gpiod_chip_close(chip);
+
+ return request;
+}
+
+static const char *edge_event_type_str(struct gpiod_edge_event *event)
+{
+ switch (gpiod_edge_event_get_event_type(event)) {
+ case GPIOD_EDGE_EVENT_RISING_EDGE:
+ return "Rising";
+ case GPIOD_EDGE_EVENT_FALLING_EDGE:
+ return "Falling";
+ default:
+ return "Unknown";
+ }
+}
+
+int main(void)
+{
+ /* Example configuration - customize to suit your situation. */
+ static const char *const chip_path = "/dev/gpiochip0";
+ static const unsigned int line_offset = 5;
+
+ struct gpiod_edge_event_buffer *event_buffer;
+ struct gpiod_line_request *request;
+ struct gpiod_edge_event *event;
+ int i, ret, event_buf_size;
+
+ request = request_input_line(chip_path, line_offset,
+ "watch-line-value");
+ if (!request) {
+ fprintf(stderr, "failed to request line: %s\n",
+ strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ /*
+ * A larger buffer is an optimisation for reading bursts of events from
+ * the kernel, but that is not necessary in this case, so 1 is fine.
+ */
+ event_buf_size = 1;
+ event_buffer = gpiod_edge_event_buffer_new(event_buf_size);
+ if (!event_buffer) {
+ fprintf(stderr, "failed to create event buffer: %s\n",
+ strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ for (;;) {
+ /* Blocks until at least one event is available. */
+ ret = gpiod_line_request_read_edge_events(request, event_buffer,
+ event_buf_size);
+ if (ret == -1) {
+ fprintf(stderr, "error reading edge events: %s\n",
+ strerror(errno));
+ return EXIT_FAILURE;
+ }
+ for (i = 0; i < ret; i++) {
+ event = gpiod_edge_event_buffer_get_event(event_buffer,
+ i);
+ printf("offset: %d type: %-7s event #%ld\n",
+ gpiod_edge_event_get_line_offset(event),
+ edge_event_type_str(event),
+ gpiod_edge_event_get_line_seqno(event));
+ }
+ }
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>
+
+/* Minimal example of watching for edges on multiple lines. */
+
+#include <errno.h>
+#include <gpiod.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define NUM_LINES 3
+
+/* Request a line as input with edge detection. */
+static struct gpiod_line_request *
+request_input_lines(const char *chip_path, const unsigned int *offsets,
+ unsigned int num_lines, const char *consumer)
+{
+ struct gpiod_request_config *req_cfg = NULL;
+ struct gpiod_line_request *request = NULL;
+ struct gpiod_line_settings *settings;
+ struct gpiod_line_config *line_cfg;
+ struct gpiod_chip *chip;
+ unsigned int i;
+ int ret;
+
+ chip = gpiod_chip_open(chip_path);
+ if (!chip)
+ return NULL;
+
+ settings = gpiod_line_settings_new();
+ if (!settings)
+ goto close_chip;
+
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+ /* Assume a button connecting the pin to ground, so pull it up... */
+ gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_PULL_UP);
+ /* ... and provide some debounce. */
+ gpiod_line_settings_set_debounce_period_us(settings, 10000);
+
+ line_cfg = gpiod_line_config_new();
+ if (!line_cfg)
+ goto free_settings;
+
+ for (i = 0; i < num_lines; i++) {
+ ret = gpiod_line_config_add_line_settings(line_cfg, &offsets[i],
+ 1, settings);
+ if (ret)
+ goto free_line_config;
+ }
+
+ if (consumer) {
+ req_cfg = gpiod_request_config_new();
+ if (!req_cfg)
+ goto free_line_config;
+
+ gpiod_request_config_set_consumer(req_cfg, consumer);
+ }
+
+ request = gpiod_chip_request_lines(chip, req_cfg, line_cfg);
+
+ gpiod_request_config_free(req_cfg);
+
+free_line_config:
+ gpiod_line_config_free(line_cfg);
+
+free_settings:
+ gpiod_line_settings_free(settings);
+
+close_chip:
+ gpiod_chip_close(chip);
+
+ return request;
+}
+
+static const char *edge_event_type_str(struct gpiod_edge_event *event)
+{
+ switch (gpiod_edge_event_get_event_type(event)) {
+ case GPIOD_EDGE_EVENT_RISING_EDGE:
+ return "Rising";
+ case GPIOD_EDGE_EVENT_FALLING_EDGE:
+ return "Falling";
+ default:
+ return "Unknown";
+ }
+}
+
+int main(void)
+{
+ /* Example configuration - customize to suit your situation. */
+ static const char *const chip_path = "/dev/gpiochip0";
+ static const unsigned int line_offsets[NUM_LINES] = { 5, 3, 7 };
+
+ struct gpiod_edge_event_buffer *event_buffer;
+ struct gpiod_line_request *request;
+ struct gpiod_edge_event *event;
+ int i, ret, event_buf_size;
+
+ request = request_input_lines(chip_path, line_offsets, NUM_LINES,
+ "watch-multiple-line-values");
+ if (!request) {
+ fprintf(stderr, "failed to request line: %s\n",
+ strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ /*
+ * A larger buffer is an optimisation for reading bursts of events from
+ * the kernel, so even a value of 1 would be fine.
+ * The size here allows for a simultaneous event on each of the lines
+ * to be copied in one read.
+ */
+ event_buf_size = NUM_LINES;
+ event_buffer = gpiod_edge_event_buffer_new(event_buf_size);
+ if (!event_buffer) {
+ fprintf(stderr, "failed to create event buffer: %s\n",
+ strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ for (;;) {
+ /* Blocks until at least one event is available. */
+ ret = gpiod_line_request_read_edge_events(request, event_buffer,
+ event_buf_size);
+ if (ret == -1) {
+ fprintf(stderr, "error reading edge events: %s\n",
+ strerror(errno));
+ return EXIT_FAILURE;
+ }
+ for (i = 0; i < ret; i++) {
+ event = gpiod_edge_event_buffer_get_event(event_buffer,
+ i);
+ printf("offset: %d type: %-7s event #%ld\n",
+ gpiod_edge_event_get_line_offset(event),
+ edge_event_type_str(event),
+ gpiod_edge_event_get_line_seqno(event));
+ }
+ }
+}
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+
+include_HEADERS = gpiod.h
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/* SPDX-FileCopyrightText: 2017-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+/**
+ * @file gpiod.h
+ */
+
+#ifndef __LIBGPIOD_GPIOD_H__
+#define __LIBGPIOD_GPIOD_H__
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @mainpage libgpiod public API
+ *
+ * This is the complete documentation of the public API made available to
+ * users of libgpiod.
+ *
+ * The API is logically split into several sections. For each opaque data
+ * class, there's a set of functions for manipulating it. Together they can be
+ * thought of as objects and their methods in OOP parlance.
+ *
+ * General note on error handling: all functions exported by libgpiod that
+ * can fail, set errno to one of the error values defined in errno.h upon
+ * failure. The way of notifying the caller that an error occurred varies
+ * between functions, but in general a function that returns an int, returns -1
+ * on error, while a function returning a pointer indicates an error condition
+ * by returning a NULL pointer. It's not practical to list all possible error
+ * codes for every function as they propagate errors from the underlying libc
+ * functions.
+ *
+ * In general libgpiod functions are NULL-aware. For functions that are
+ * logically methods of data classes - ones that take a pointer to the object
+ * of that class as the first argument - passing a NULL pointer will result in
+ * the program aborting the execution. For non-methods, init functions and
+ * methods that take a pointer as any of the subsequent arguments, the handling
+ * of a NULL-pointer depends on the implementation and may range from gracefully
+ * handling it, ignoring it or returning an error.
+ *
+ * libgpiod is thread-aware but does not provide any further thread-safety
+ * guarantees. This requires the user to ensure that at most one thread may
+ * work with an object at any time. Sharing objects across threads is allowed
+ * if a suitable synchronization mechanism serializes the access. Different,
+ * standalone objects can safely be used concurrently. Most libgpiod objects
+ * are standalone. Exceptions - such as events allocated in buffers - exist and
+ * are noted in the documentation.
+ */
+
+/**
+ * @struct gpiod_chip
+ * @{
+ *
+ * Refer to @ref chips for functions that operate on gpiod_chip.
+ *
+ * @}
+*/
+struct gpiod_chip;
+
+/**
+ * @struct gpiod_chip_info
+ * @{
+ *
+ * Refer to @ref chip_info for functions that operate on gpiod_chip_info.
+ *
+ * @}
+*/
+struct gpiod_chip_info;
+
+/**
+ * @struct gpiod_line_info
+ * @{
+ *
+ * Refer to @ref line_info for functions that operate on gpiod_line_info.
+ *
+ * @}
+*/
+struct gpiod_line_info;
+
+/**
+ * @struct gpiod_line_settings
+ * @{
+ *
+ * Refer to @ref line_settings for functions that operate on
+ * gpiod_line_settings.
+ *
+ * @}
+*/
+struct gpiod_line_settings;
+
+/**
+ * @struct gpiod_line_config
+ * @{
+ *
+ * Refer to @ref line_config for functions that operate on gpiod_line_config.
+ *
+ * @}
+*/
+struct gpiod_line_config;
+
+/**
+ * @struct gpiod_request_config
+ * @{
+ *
+ * Refer to @ref request_config for functions that operate on
+ * gpiod_request_config.
+ *
+ * @}
+*/
+struct gpiod_request_config;
+
+/**
+ * @struct gpiod_line_request
+ * @{
+ *
+ * Refer to @ref line_request for functions that operate on
+ * gpiod_line_request.
+ *
+ * @}
+*/
+struct gpiod_line_request;
+
+/**
+ * @struct gpiod_info_event
+ * @{
+ *
+ * Refer to @ref line_watch for functions that operate on gpiod_info_event.
+ *
+ * @}
+*/
+struct gpiod_info_event;
+
+/**
+ * @struct gpiod_edge_event
+ * @{
+ *
+ * Refer to @ref edge_event for functions that operate on gpiod_edge_event.
+ *
+ * @}
+*/
+struct gpiod_edge_event;
+
+/**
+ * @struct gpiod_edge_event_buffer
+ * @{
+ *
+ * Refer to @ref edge_event for functions that operate on
+ * gpiod_edge_event_buffer.
+ *
+ * @}
+*/
+struct gpiod_edge_event_buffer;
+
+/**
+ * @defgroup chips GPIO chips
+ * @{
+ *
+ * Functions and data structures for GPIO chip operations.
+ *
+ * A GPIO chip object is associated with an open file descriptor to the GPIO
+ * character device. It exposes basic information about the chip and allows
+ * callers to retrieve information about each line, watch lines for state
+ * changes and make line requests.
+ */
+
+/**
+ * @brief Open a chip by path.
+ * @param path Path to the gpiochip device file.
+ * @return GPIO chip object or NULL if an error occurred. The returned object
+ * must be closed by the caller using ::gpiod_chip_close.
+ */
+struct gpiod_chip *gpiod_chip_open(const char *path);
+
+/**
+ * @brief Close the chip and release all associated resources.
+ * @param chip Chip to close.
+ */
+void gpiod_chip_close(struct gpiod_chip *chip);
+
+/**
+ * @brief Get information about the chip.
+ * @param chip GPIO chip object.
+ * @return New GPIO chip info object or NULL if an error occurred. The returned
+ * object must be freed by the caller using ::gpiod_chip_info_free.
+ */
+struct gpiod_chip_info *gpiod_chip_get_info(struct gpiod_chip *chip);
+
+/**
+ * @brief Get the path used to open the chip.
+ * @param chip GPIO chip object.
+ * @return Path to the file passed as argument to ::gpiod_chip_open. The
+ * returned pointer is valid for the lifetime of the chip object and
+ * must not be freed by the caller.
+ */
+const char *gpiod_chip_get_path(struct gpiod_chip *chip);
+
+/**
+ * @brief Get a snapshot of information about a line.
+ * @param chip GPIO chip object.
+ * @param offset The offset of the GPIO line.
+ * @return New GPIO line info object or NULL if an error occurred. The returned
+ * object must be freed by the caller using ::gpiod_line_info_free.
+ */
+struct gpiod_line_info *gpiod_chip_get_line_info(struct gpiod_chip *chip,
+ unsigned int offset);
+
+/**
+ * @brief Get a snapshot of the status of a line and start watching it for
+ * future changes.
+ * @param chip GPIO chip object.
+ * @param offset The offset of the GPIO line.
+ * @return New GPIO line info object or NULL if an error occurred. The returned
+ * object must be freed by the caller using ::gpiod_line_info_free.
+ * @note Line status does not include the line value. To monitor the line
+ * value the line must be requested as an input with edge detection set.
+ */
+struct gpiod_line_info *gpiod_chip_watch_line_info(struct gpiod_chip *chip,
+ unsigned int offset);
+
+/**
+ * @brief Stop watching a line for status changes.
+ * @param chip GPIO chip object.
+ * @param offset The offset of the line to stop watching.
+ * @return 0 on success, -1 on failure.
+ */
+int gpiod_chip_unwatch_line_info(struct gpiod_chip *chip, unsigned int offset);
+
+/**
+ * @brief Get the file descriptor associated with the chip.
+ * @param chip GPIO chip object.
+ * @return File descriptor number for the chip.
+ *
+ * This function never fails. The returned file descriptor must not be closed
+ * by the caller. Call ::gpiod_chip_close to close the file descriptor by
+ * closing the chip owning it.
+ */
+int gpiod_chip_get_fd(struct gpiod_chip *chip);
+
+/**
+ * @brief Wait for line status change events on any of the watched lines
+ * on the chip.
+ * @param chip GPIO chip object.
+ * @param timeout_ns Wait time limit in nanoseconds. If set to 0, the function
+ * returns immediatelly. If set to a negative number, the
+ * function blocks indefinitely until an event becomes
+ * available.
+ * @return 0 if wait timed out, -1 if an error occurred, 1 if an event is
+ * pending.
+ */
+int gpiod_chip_wait_info_event(struct gpiod_chip *chip, int64_t timeout_ns);
+
+/**
+ * @brief Read a single line status change event from the chip.
+ * @param chip GPIO chip object.
+ * @return Newly read watch event object or NULL on error. The event must be
+ * freed by the caller using ::gpiod_info_event_free.
+ * @note If no events are pending, this function will block.
+ */
+struct gpiod_info_event *gpiod_chip_read_info_event(struct gpiod_chip *chip);
+
+/**
+ * @brief Map a line's name to its offset within the chip.
+ * @param chip GPIO chip object.
+ * @param name Name of the GPIO line to map.
+ * @return Offset of the line within the chip or -1 on error.
+ * @note If a line with given name is not exposed by the chip, the function
+ * sets errno to ENOENT.
+ */
+int gpiod_chip_get_line_offset_from_name(struct gpiod_chip *chip,
+ const char *name);
+
+/**
+ * @brief Request a set of lines for exclusive usage.
+ * @param chip GPIO chip object.
+ * @param req_cfg Request config object. Can be NULL for default settings.
+ * @param line_cfg Line config object.
+ * @return New line request object or NULL if an error occurred. The request
+ * must be released by the caller using ::gpiod_line_request_release.
+ */
+struct gpiod_line_request *
+gpiod_chip_request_lines(struct gpiod_chip *chip,
+ struct gpiod_request_config *req_cfg,
+ struct gpiod_line_config *line_cfg);
+
+/**
+ * @}
+ *
+ * @defgroup chip_info Chip info
+ * @{
+ *
+ * Functions for retrieving kernel information about chips.
+ *
+ * Line info object contains an immutable snapshot of a chip's status.
+ *
+ * The chip info contains all the publicly available information about a
+ * chip.
+ *
+ * Some accessor methods return pointers. Those pointers refer to internal
+ * fields. The lifetimes of those fields are tied to the lifetime of the
+ * containing chip info object. Such pointers remain valid until
+ * ::gpiod_chip_info_free is called on the containing chip info object. They
+ * must not be freed by the caller.
+ */
+
+/**
+ * @brief Free a chip info object and release all associated resources.
+ * @param info GPIO chip info object to free.
+ */
+void gpiod_chip_info_free(struct gpiod_chip_info *info);
+
+/**
+ * @brief Get the name of the chip as represented in the kernel.
+ * @param info GPIO chip info object.
+ * @return Valid pointer to a human-readable string containing the chip name.
+ * The string lifetime is tied to the chip info object so the pointer
+ * must not be freed by the caller.
+ */
+const char *gpiod_chip_info_get_name(struct gpiod_chip_info *info);
+
+/**
+ * @brief Get the label of the chip as represented in the kernel.
+ * @param info GPIO chip info object.
+ * @return Valid pointer to a human-readable string containing the chip label.
+ * The string lifetime is tied to the chip info object so the pointer
+ * must not be freed by the caller.
+ */
+const char *gpiod_chip_info_get_label(struct gpiod_chip_info *info);
+
+/**
+ * @brief Get the number of lines exposed by the chip.
+ * @param info GPIO chip info object.
+ * @return Number of GPIO lines.
+ */
+size_t gpiod_chip_info_get_num_lines(struct gpiod_chip_info *info);
+
+/**
+ * @}
+ *
+ * @defgroup line_defs Line definitions
+ * @{
+ *
+ * These defines are used across the API.
+ */
+
+/**
+ * @brief Logical line state.
+ */
+enum gpiod_line_value {
+ GPIOD_LINE_VALUE_ERROR = -1,
+ /**< Returned to indicate an error when reading the value. */
+ GPIOD_LINE_VALUE_INACTIVE = 0,
+ /**< Line is logically inactive. */
+ GPIOD_LINE_VALUE_ACTIVE = 1,
+ /**< Line is logically active. */
+};
+
+/**
+ * @brief Direction settings.
+ */
+enum gpiod_line_direction {
+ GPIOD_LINE_DIRECTION_AS_IS = 1,
+ /**< Request the line(s), but don't change direction. */
+ GPIOD_LINE_DIRECTION_INPUT,
+ /**< Direction is input - for reading the value of an externally driven
+ * GPIO line. */
+ GPIOD_LINE_DIRECTION_OUTPUT,
+ /**< Direction is output - for driving the GPIO line. */
+};
+
+/**
+ * @brief Edge detection settings.
+ */
+enum gpiod_line_edge {
+ GPIOD_LINE_EDGE_NONE = 1,
+ /**< Line edge detection is disabled. */
+ GPIOD_LINE_EDGE_RISING,
+ /**< Line detects rising edge events. */
+ GPIOD_LINE_EDGE_FALLING,
+ /**< Line detects falling edge events. */
+ GPIOD_LINE_EDGE_BOTH,
+ /**< Line detects both rising and falling edge events. */
+};
+
+/**
+ * @brief Internal bias settings.
+ */
+enum gpiod_line_bias {
+ GPIOD_LINE_BIAS_AS_IS = 1,
+ /**< Don't change the bias setting when applying line config. */
+ GPIOD_LINE_BIAS_UNKNOWN,
+ /**< The internal bias state is unknown. */
+ GPIOD_LINE_BIAS_DISABLED,
+ /**< The internal bias is disabled. */
+ GPIOD_LINE_BIAS_PULL_UP,
+ /**< The internal pull-up bias is enabled. */
+ GPIOD_LINE_BIAS_PULL_DOWN,
+ /**< The internal pull-down bias is enabled. */
+};
+
+/**
+ * @brief Drive settings.
+ */
+enum gpiod_line_drive {
+ GPIOD_LINE_DRIVE_PUSH_PULL = 1,
+ /**< Drive setting is push-pull. */
+ GPIOD_LINE_DRIVE_OPEN_DRAIN,
+ /**< Line output is open-drain. */
+ GPIOD_LINE_DRIVE_OPEN_SOURCE,
+ /**< Line output is open-source. */
+};
+
+/**
+ * @brief Clock settings.
+ */
+enum gpiod_line_clock {
+ GPIOD_LINE_CLOCK_MONOTONIC = 1,
+ /**< Line uses the monotonic clock for edge event timestamps. */
+ GPIOD_LINE_CLOCK_REALTIME,
+ /**< Line uses the realtime clock for edge event timestamps. */
+ GPIOD_LINE_CLOCK_HTE,
+ /**< Line uses the hardware timestamp engine for event timestamps. */
+};
+
+/**
+ * @}
+ *
+ * @defgroup line_info Line info
+ * @{
+ *
+ * Functions for retrieving kernel information about both requested and free
+ * lines.
+ *
+ * Line info object contains an immutable snapshot of a line's status.
+ *
+ * The line info contains all the publicly available information about a
+ * line, which does not include the line value. The line must be requested
+ * to access the line value.
+ *
+ * Some accessor methods return pointers. Those pointers refer to internal
+ * fields. The lifetimes of those fields are tied to the lifetime of the
+ * containing line info object. Such pointers remain valid until
+ * ::gpiod_line_info_free is called on the containing line info object. They
+ * must not be freed by the caller.
+ */
+
+/**
+ * @brief Free a line info object and release all associated resources.
+ * @param info GPIO line info object to free.
+ */
+void gpiod_line_info_free(struct gpiod_line_info *info);
+
+/**
+ * @brief Copy a line info object.
+ * @param info Line info to copy.
+ * @return Copy of the line info or NULL on error. The returned object must
+ * be freed by the caller using :gpiod_line_info_free.
+ */
+struct gpiod_line_info *gpiod_line_info_copy(struct gpiod_line_info *info);
+
+/**
+ * @brief Get the offset of the line.
+ * @param info GPIO line info object.
+ * @return Offset of the line within the parent chip.
+ *
+ * The offset uniquely identifies the line on the chip. The combination of the
+ * chip and offset uniquely identifies the line within the system.
+ */
+unsigned int gpiod_line_info_get_offset(struct gpiod_line_info *info);
+
+/**
+ * @brief Get the name of the line.
+ * @param info GPIO line info object.
+ * @return Name of the GPIO line as it is represented in the kernel.
+ * This function returns a valid pointer to a null-terminated string
+ * or NULL if the line is unnamed. The string lifetime is tied to the
+ * line info object so the pointer must not be freed.
+ */
+const char *gpiod_line_info_get_name(struct gpiod_line_info *info);
+
+/**
+ * @brief Check if the line is in use.
+ * @param info GPIO line object.
+ * @return True if the line is in use, false otherwise.
+ *
+ * The exact reason a line is busy cannot be determined from user space.
+ * It may have been requested by another process or hogged by the kernel.
+ * It only matters that the line is used and can't be requested until
+ * released by the existing consumer.
+ */
+bool gpiod_line_info_is_used(struct gpiod_line_info *info);
+
+/**
+ * @brief Get the name of the consumer of the line.
+ * @param info GPIO line info object.
+ * @return Name of the GPIO consumer as it is represented in the kernel.
+ * This function returns a valid pointer to a null-terminated string
+ * or NULL if the consumer name is not set.
+ * The string lifetime is tied to the line info object so the pointer
+ * must not be freed.
+ */
+const char *gpiod_line_info_get_consumer(struct gpiod_line_info *info);
+
+/**
+ * @brief Get the direction setting of the line.
+ * @param info GPIO line info object.
+ * @return Returns ::GPIOD_LINE_DIRECTION_INPUT or
+ * ::GPIOD_LINE_DIRECTION_OUTPUT.
+ */
+enum gpiod_line_direction
+gpiod_line_info_get_direction(struct gpiod_line_info *info);
+
+/**
+ * @brief Get the edge detection setting of the line.
+ * @param info GPIO line info object.
+ * @return Returns ::GPIOD_LINE_EDGE_NONE, ::GPIOD_LINE_EDGE_RISING,
+ * ::GPIOD_LINE_EDGE_FALLING or ::GPIOD_LINE_EDGE_BOTH.
+ */
+enum gpiod_line_edge
+gpiod_line_info_get_edge_detection(struct gpiod_line_info *info);
+
+/**
+ * @brief Get the bias setting of the line.
+ * @param info GPIO line object.
+ * @return Returns ::GPIOD_LINE_BIAS_PULL_UP, ::GPIOD_LINE_BIAS_PULL_DOWN,
+ * ::GPIOD_LINE_BIAS_DISABLED or ::GPIOD_LINE_BIAS_UNKNOWN.
+ */
+enum gpiod_line_bias
+gpiod_line_info_get_bias(struct gpiod_line_info *info);
+
+/**
+ * @brief Get the drive setting of the line.
+ * @param info GPIO line info object.
+ * @return Returns ::GPIOD_LINE_DRIVE_PUSH_PULL, ::GPIOD_LINE_DRIVE_OPEN_DRAIN
+ * or ::GPIOD_LINE_DRIVE_OPEN_SOURCE.
+ */
+enum gpiod_line_drive
+gpiod_line_info_get_drive(struct gpiod_line_info *info);
+
+/**
+ * @brief Check if the logical value of the line is inverted compared to the
+ * physical.
+ * @param info GPIO line object.
+ * @return True if the line is "active-low", false otherwise.
+ */
+bool gpiod_line_info_is_active_low(struct gpiod_line_info *info);
+
+/**
+ * @brief Check if the line is debounced (either by hardware or by the kernel
+ * software debouncer).
+ * @param info GPIO line info object.
+ * @return True if the line is debounced, false otherwise.
+ */
+bool gpiod_line_info_is_debounced(struct gpiod_line_info *info);
+
+/**
+ * @brief Get the debounce period of the line, in microseconds.
+ * @param info GPIO line info object.
+ * @return Debounce period in microseconds.
+ * 0 if the line is not debounced.
+ */
+unsigned long
+gpiod_line_info_get_debounce_period_us(struct gpiod_line_info *info);
+
+/**
+ * @brief Get the event clock setting used for edge event timestamps for the
+ * line.
+ * @param info GPIO line info object.
+ * @return Returns ::GPIOD_LINE_CLOCK_MONOTONIC, ::GPIOD_LINE_CLOCK_HTE or
+ * ::GPIOD_LINE_CLOCK_REALTIME.
+ */
+enum gpiod_line_clock
+gpiod_line_info_get_event_clock(struct gpiod_line_info *info);
+
+/**
+ * @}
+ *
+ * @defgroup line_watch Line status watch events
+ * @{
+ *
+ * Accessors for the info event objects allowing to monitor changes in GPIO
+ * line status.
+ *
+ * Callers are notified about changes in a line's status due to GPIO uAPI
+ * calls. Each info event contains information about the event itself
+ * (timestamp, type) as well as a snapshot of line's status in the form
+ * of a line-info object.
+ */
+
+/**
+ * @brief Line status change event types.
+ */
+enum gpiod_info_event_type {
+ GPIOD_INFO_EVENT_LINE_REQUESTED = 1,
+ /**< Line has been requested. */
+ GPIOD_INFO_EVENT_LINE_RELEASED,
+ /**< Previously requested line has been released. */
+ GPIOD_INFO_EVENT_LINE_CONFIG_CHANGED,
+ /**< Line configuration has changed. */
+};
+
+/**
+ * @brief Free the info event object and release all associated resources.
+ * @param event Info event to free.
+ */
+void gpiod_info_event_free(struct gpiod_info_event *event);
+
+/**
+ * @brief Get the event type of the status change event.
+ * @param event Line status watch event.
+ * @return One of ::GPIOD_INFO_EVENT_LINE_REQUESTED,
+ * ::GPIOD_INFO_EVENT_LINE_RELEASED or
+ * ::GPIOD_INFO_EVENT_LINE_CONFIG_CHANGED.
+ */
+enum gpiod_info_event_type
+gpiod_info_event_get_event_type(struct gpiod_info_event *event);
+
+/**
+ * @brief Get the timestamp of the event.
+ * @param event Line status watch event.
+ * @return Timestamp in nanoseconds, read from the monotonic clock.
+ */
+uint64_t gpiod_info_event_get_timestamp_ns(struct gpiod_info_event *event);
+
+/**
+ * @brief Get the snapshot of line-info associated with the event.
+ * @param event Line info event object.
+ * @return Returns a pointer to the line-info object associated with the event.
+ * The object lifetime is tied to the event object, so the pointer must
+ * be not be freed by the caller.
+ * @warning Thread-safety:
+ * Since the line-info object is tied to the event, different threads
+ * may not operate on the event and line-info at the same time. The
+ * line-info can be copied using ::gpiod_line_info_copy in order to
+ * create a standalone object - which then may safely be used from a
+ * different thread concurrently.
+ */
+struct gpiod_line_info *
+gpiod_info_event_get_line_info(struct gpiod_info_event *event);
+
+/**
+ * @}
+ *
+ * @defgroup line_settings Line settings objects
+ * @{
+ *
+ * Functions for manipulating line settings objects.
+ *
+ * Line settings object contains a set of line properties that can be used
+ * when requesting lines or reconfiguring an existing request.
+ *
+ * Mutators in general can only fail if the new property value is invalid. The
+ * return values can be safely ignored - the object remains valid even after
+ * a mutator fails and simply uses the sane default appropriate for given
+ * property.
+ */
+
+/**
+ * @brief Create a new line settings object.
+ * @return New line settings object or NULL on error. The returned object must
+ * be freed by the caller using ::gpiod_line_settings_free.
+ */
+struct gpiod_line_settings *gpiod_line_settings_new(void);
+
+/**
+ * @brief Free the line settings object and release all associated resources.
+ * @param settings Line settings object.
+ */
+void gpiod_line_settings_free(struct gpiod_line_settings *settings);
+
+/**
+ * @brief Reset the line settings object to its default values.
+ * @param settings Line settings object.
+ */
+void gpiod_line_settings_reset(struct gpiod_line_settings *settings);
+
+/**
+ * @brief Copy the line settings object.
+ * @param settings Line settings object to copy.
+ * @return New line settings object that must be freed using
+ * ::gpiod_line_settings_free or NULL on failure.
+ */
+struct gpiod_line_settings *
+gpiod_line_settings_copy(struct gpiod_line_settings *settings);
+
+/**
+ * @brief Set direction.
+ * @param settings Line settings object.
+ * @param direction New direction.
+ * @return 0 on success, -1 on error.
+ */
+int gpiod_line_settings_set_direction(struct gpiod_line_settings *settings,
+ enum gpiod_line_direction direction);
+
+/**
+ * @brief Get direction.
+ * @param settings Line settings object.
+ * @return Current direction.
+ */
+enum gpiod_line_direction
+gpiod_line_settings_get_direction(struct gpiod_line_settings *settings);
+
+/**
+ * @brief Set edge detection.
+ * @param settings Line settings object.
+ * @param edge New edge detection setting.
+ * @return 0 on success, -1 on failure.
+ */
+int gpiod_line_settings_set_edge_detection(struct gpiod_line_settings *settings,
+ enum gpiod_line_edge edge);
+
+/**
+ * @brief Get edge detection.
+ * @param settings Line settings object.
+ * @return Current edge detection setting.
+ */
+enum gpiod_line_edge
+gpiod_line_settings_get_edge_detection(struct gpiod_line_settings *settings);
+
+/**
+ * @brief Set bias.
+ * @param settings Line settings object.
+ * @param bias New bias.
+ * @return 0 on success, -1 on failure.
+ */
+int gpiod_line_settings_set_bias(struct gpiod_line_settings *settings,
+ enum gpiod_line_bias bias);
+
+/**
+ * @brief Get bias.
+ * @param settings Line settings object.
+ * @return Current bias setting.
+ */
+enum gpiod_line_bias
+gpiod_line_settings_get_bias(struct gpiod_line_settings *settings);
+
+/**
+ * @brief Set drive.
+ * @param settings Line settings object.
+ * @param drive New drive setting.
+ * @return 0 on success, -1 on failure.
+ */
+int gpiod_line_settings_set_drive(struct gpiod_line_settings *settings,
+ enum gpiod_line_drive drive);
+
+/**
+ * @brief Get drive.
+ * @param settings Line settings object.
+ * @return Current drive setting.
+ */
+enum gpiod_line_drive
+gpiod_line_settings_get_drive(struct gpiod_line_settings *settings);
+
+/**
+ * @brief Set active-low setting.
+ * @param settings Line settings object.
+ * @param active_low New active-low setting.
+ */
+void gpiod_line_settings_set_active_low(struct gpiod_line_settings *settings,
+ bool active_low);
+
+/**
+ * @brief Get active-low setting.
+ * @param settings Line settings object.
+ * @return True if active-low is enabled, false otherwise.
+ */
+bool gpiod_line_settings_get_active_low(struct gpiod_line_settings *settings);
+
+/**
+ * @brief Set debounce period.
+ * @param settings Line settings object.
+ * @param period New debounce period in microseconds.
+ */
+void
+gpiod_line_settings_set_debounce_period_us(struct gpiod_line_settings *settings,
+ unsigned long period);
+
+/**
+ * @brief Get debounce period.
+ * @param settings Line settings object.
+ * @return Current debounce period in microseconds.
+ */
+unsigned long
+gpiod_line_settings_get_debounce_period_us(
+ struct gpiod_line_settings *settings);
+
+/**
+ * @brief Set event clock.
+ * @param settings Line settings object.
+ * @param event_clock New event clock.
+ * @return 0 on success, -1 on failure.
+ */
+int gpiod_line_settings_set_event_clock(struct gpiod_line_settings *settings,
+ enum gpiod_line_clock event_clock);
+
+/**
+ * @brief Get event clock setting.
+ * @param settings Line settings object.
+ * @return Current event clock setting.
+ */
+enum gpiod_line_clock
+gpiod_line_settings_get_event_clock(struct gpiod_line_settings *settings);
+
+/**
+ * @brief Set the output value.
+ * @param settings Line settings object.
+ * @param value New output value.
+ * @return 0 on success, -1 on failure.
+ */
+int gpiod_line_settings_set_output_value(struct gpiod_line_settings *settings,
+ enum gpiod_line_value value);
+
+/**
+ * @brief Get the output value.
+ * @param settings Line settings object.
+ * @return Current output value.
+ */
+enum gpiod_line_value
+gpiod_line_settings_get_output_value(struct gpiod_line_settings *settings);
+
+/**
+ * @}
+ *
+ * @defgroup line_config Line configuration objects
+ * @{
+ *
+ * Functions for manipulating line configuration objects.
+ *
+ * The line-config object contains the configuration for lines that can be
+ * used in two cases:
+ * - when making a line request
+ * - when reconfiguring a set of already requested lines.
+ *
+ * A new line-config object is empty. Using it in a request will lead to an
+ * error. In order to a line-config to become useful, it needs to be assigned
+ * at least one offset-to-settings mapping by calling
+ * ::gpiod_line_config_add_line_settings.
+ *
+ * When calling ::gpiod_chip_request_lines, the library will request all
+ * offsets that were assigned settings in the order that they were assigned.
+ * If any of the offsets was duplicated, the last one will take precedence.
+ */
+
+/**
+ * @brief Create a new line config object.
+ * @return New line config object or NULL on error. The returned object must
+ * be freed by the caller using ::gpiod_line_config_free.
+ */
+struct gpiod_line_config *gpiod_line_config_new(void);
+
+/**
+ * @brief Free the line config object and release all associated resources.
+ * @param config Line config object to free.
+ */
+void gpiod_line_config_free(struct gpiod_line_config *config);
+
+/**
+ * @brief Reset the line config object.
+ * @param config Line config object to free.
+ *
+ * Resets the entire configuration stored in the object. This is useful if
+ * the user wants to reuse the object without reallocating it.
+ */
+void gpiod_line_config_reset(struct gpiod_line_config *config);
+
+/**
+ * @brief Add line settings for a set of offsets.
+ * @param config Line config object.
+ * @param offsets Array of offsets for which to apply the settings.
+ * @param num_offsets Number of offsets stored in the offsets array.
+ * @param settings Line settings to apply.
+ * @return 0 on success, -1 on failure.
+ */
+int gpiod_line_config_add_line_settings(struct gpiod_line_config *config,
+ const unsigned int *offsets,
+ size_t num_offsets,
+ struct gpiod_line_settings *settings);
+
+/**
+ * @brief Get line settings for offset.
+ * @param config Line config object.
+ * @param offset Offset for which to get line settings.
+ * @return New line settings object (must be freed by the caller) or NULL on
+ * error.
+ */
+struct gpiod_line_settings *
+gpiod_line_config_get_line_settings(struct gpiod_line_config *config,
+ unsigned int offset);
+
+/**
+ * @brief Set output values for a number of lines.
+ * @param config Line config object.
+ * @param values Buffer containing the output values.
+ * @param num_values Number of values in the buffer.
+ * @return 0 on success, -1 on error.
+ *
+ * This is a helper that allows users to set multiple (potentially different)
+ * output values at once while using the same line settings object. Instead of
+ * modifying the output value in the settings object and calling
+ * ::gpiod_line_config_add_line_settings multiple times, we can specify the
+ * settings, add them for a set of offsets and then call this function to
+ * set the output values.
+ *
+ * Values set by this function override whatever values were specified in the
+ * regular line settings.
+ *
+ * Each value must be associated with the line identified by the corresponding
+ * entry in the offset array filled by
+ * ::gpiod_line_request_get_requested_offsets.
+ */
+int gpiod_line_config_set_output_values(struct gpiod_line_config *config,
+ const enum gpiod_line_value *values,
+ size_t num_values);
+
+/**
+ * @brief Get the number of configured line offsets.
+ * @param config Line config object.
+ * @return Number of offsets for which line settings have been added.
+ */
+size_t
+gpiod_line_config_get_num_configured_offsets(struct gpiod_line_config *config);
+
+/**
+ * @brief Get configured offsets.
+ * @param config Line config object.
+ * @param offsets Array to store offsets.
+ * @param max_offsets Number of offsets that can be stored in the offsets array.
+ * @return Number of offsets stored in the offsets array.
+ *
+ * If max_offsets is lower than the number of lines actually requested (this
+ * value can be retrieved using ::gpiod_line_config_get_num_configured_offsets),
+ * then only up to max_lines offsets will be stored in offsets.
+ */
+size_t
+gpiod_line_config_get_configured_offsets(struct gpiod_line_config *config,
+ unsigned int *offsets,
+ size_t max_offsets);
+
+/**
+ * @}
+ *
+ * @defgroup request_config Request configuration objects
+ * @{
+ *
+ * Functions for manipulating request configuration objects.
+ *
+ * Request config objects are used to pass a set of options to the kernel at
+ * the time of the line request. The mutators don't return error values. If the
+ * values are invalid, in general they are silently adjusted to acceptable
+ * ranges.
+ */
+
+/**
+ * @brief Create a new request config object.
+ * @return New request config object or NULL on error. The returned object must
+ * be freed by the caller using ::gpiod_request_config_free.
+ */
+struct gpiod_request_config *gpiod_request_config_new(void);
+
+/**
+ * @brief Free the request config object and release all associated resources.
+ * @param config Line config object.
+ */
+void gpiod_request_config_free(struct gpiod_request_config *config);
+
+/**
+ * @brief Set the consumer name for the request.
+ * @param config Request config object.
+ * @param consumer Consumer name.
+ * @note If the consumer string is too long, it will be truncated to the max
+ * accepted length.
+ */
+void gpiod_request_config_set_consumer(struct gpiod_request_config *config,
+ const char *consumer);
+
+/**
+ * @brief Get the consumer name configured in the request config.
+ * @param config Request config object.
+ * @return Consumer name stored in the request config.
+ */
+const char *
+gpiod_request_config_get_consumer(struct gpiod_request_config *config);
+
+/**
+ * @brief Set the size of the kernel event buffer for the request.
+ * @param config Request config object.
+ * @param event_buffer_size New event buffer size.
+ * @note The kernel may adjust the value if it's too high. If set to 0, the
+ * default value will be used.
+ * @note The kernel buffer is distinct from and independent of the user space
+ * buffer (::gpiod_edge_event_buffer_new).
+ */
+void
+gpiod_request_config_set_event_buffer_size(struct gpiod_request_config *config,
+ size_t event_buffer_size);
+
+/**
+ * @brief Get the edge event buffer size for the request config.
+ * @param config Request config object.
+ * @return Edge event buffer size setting from the request config.
+ */
+size_t
+gpiod_request_config_get_event_buffer_size(struct gpiod_request_config *config);
+
+/**
+ * @}
+ *
+ * @defgroup line_request Line request operations
+ * @{
+ *
+ * Functions allowing interactions with requested lines.
+ */
+
+/**
+ * @brief Release the requested lines and free all associated resources.
+ * @param request Line request object to release.
+ */
+void gpiod_line_request_release(struct gpiod_line_request *request);
+
+/**
+ * @brief Get the name of the chip this request was made on.
+ * @param request Line request object.
+ * @return Name the GPIO chip device. The returned pointer is valid for the
+ * lifetime of the request object and must not be freed by the caller.
+ */
+const char *
+gpiod_line_request_get_chip_name(struct gpiod_line_request *request);
+
+/**
+ * @brief Get the number of lines in the request.
+ * @param request Line request object.
+ * @return Number of requested lines.
+ */
+size_t
+gpiod_line_request_get_num_requested_lines(struct gpiod_line_request *request);
+
+/**
+ * @brief Get the offsets of the lines in the request.
+ * @param request Line request object.
+ * @param offsets Array to store offsets.
+ * @param max_offsets Number of offsets that can be stored in the offsets array.
+ * @return Number of offsets stored in the offsets array.
+ *
+ * If max_offsets is lower than the number of lines actually requested (this
+ * value can be retrieved using ::gpiod_line_request_get_num_requested_lines),
+ * then only up to max_lines offsets will be stored in offsets.
+ */
+size_t
+gpiod_line_request_get_requested_offsets(struct gpiod_line_request *request,
+ unsigned int *offsets,
+ size_t max_offsets);
+
+/**
+ * @brief Get the value of a single requested line.
+ * @param request Line request object.
+ * @param offset The offset of the line of which the value should be read.
+ * @return Returns 1 or 0 on success and -1 on error.
+ */
+enum gpiod_line_value
+gpiod_line_request_get_value(struct gpiod_line_request *request,
+ unsigned int offset);
+
+/**
+ * @brief Get the values of a subset of requested lines.
+ * @param request GPIO line request.
+ * @param num_values Number of lines for which to read values.
+ * @param offsets Array of offsets identifying the subset of requested lines
+ * from which to read values.
+ * @param values Array in which the values will be stored. Must be sized
+ * to hold \p num_values entries. Each value is associated with
+ * the line identified by the corresponding entry in \p offsets.
+ * @return 0 on success, -1 on failure.
+ */
+int gpiod_line_request_get_values_subset(struct gpiod_line_request *request,
+ size_t num_values,
+ const unsigned int *offsets,
+ enum gpiod_line_value *values);
+
+/**
+ * @brief Get the values of all requested lines.
+ * @param request GPIO line request.
+ * @param values Array in which the values will be stored. Must be sized to
+ * hold the number of lines filled by
+ * ::gpiod_line_request_get_num_requested_lines.
+ * Each value is associated with the line identified by the
+ * corresponding entry in the offset array filled by
+ * ::gpiod_line_request_get_requested_offsets.
+ * @return 0 on success, -1 on failure.
+ */
+int gpiod_line_request_get_values(struct gpiod_line_request *request,
+ enum gpiod_line_value *values);
+
+/**
+ * @brief Set the value of a single requested line.
+ * @param request Line request object.
+ * @param offset The offset of the line for which the value should be set.
+ * @param value Value to set.
+ * @return 0 on success, -1 on failure.
+ */
+int gpiod_line_request_set_value(struct gpiod_line_request *request,
+ unsigned int offset,
+ enum gpiod_line_value value);
+
+/**
+ * @brief Set the values of a subset of requested lines.
+ * @param request GPIO line request.
+ * @param num_values Number of lines for which to set values.
+ * @param offsets Array of offsets, containing the number of entries specified
+ * by \p num_values, identifying the requested lines for
+ * which to set values.
+ * @param values Array of values to set, containing the number of entries
+ * specified by \p num_values. Each value is associated with the
+ * line identified by the corresponding entry in \p offsets.
+ * @return 0 on success, -1 on failure.
+ */
+int gpiod_line_request_set_values_subset(struct gpiod_line_request *request,
+ size_t num_values,
+ const unsigned int *offsets,
+ const enum gpiod_line_value *values);
+
+/**
+ * @brief Set the values of all lines associated with a request.
+ * @param request GPIO line request.
+ * @param values Array containing the values to set. Must be sized to
+ * contain the number of lines filled by
+ * ::gpiod_line_request_get_num_requested_lines.
+ * Each value is associated with the line identified by the
+ * corresponding entry in the offset array filled by
+ * ::gpiod_line_request_get_requested_offsets.
+ * @return 0 on success, -1 on failure.
+ */
+int gpiod_line_request_set_values(struct gpiod_line_request *request,
+ const enum gpiod_line_value *values);
+
+/**
+ * @brief Update the configuration of lines associated with a line request.
+ * @param request GPIO line request.
+ * @param config New line config to apply.
+ * @return 0 on success, -1 on failure.
+ * @note The new line configuration completely replaces the old.
+ * @note Any requested lines without overrides are configured to the requested
+ * defaults.
+ * @note Any configured overrides for lines that have not been requested
+ * are silently ignored.
+ */
+int gpiod_line_request_reconfigure_lines(struct gpiod_line_request *request,
+ struct gpiod_line_config *config);
+
+/**
+ * @brief Get the file descriptor associated with a line request.
+ * @param request GPIO line request.
+ * @return The file descriptor associated with the request.
+ * This function never fails.
+ * The returned file descriptor must not be closed by the caller.
+ * Call ::gpiod_line_request_release to close the file.
+ */
+int gpiod_line_request_get_fd(struct gpiod_line_request *request);
+
+/**
+ * @brief Wait for edge events on any of the requested lines.
+ * @param request GPIO line request.
+ * @param timeout_ns Wait time limit in nanoseconds. If set to 0, the function
+ * returns immediatelly. If set to a negative number, the
+ * function blocks indefinitely until an event becomes
+ * available.
+ * @return 0 if wait timed out, -1 if an error occurred, 1 if an event is
+ * pending.
+ *
+ * Lines must have edge detection set for edge events to be emitted.
+ * By default edge detection is disabled.
+ */
+int gpiod_line_request_wait_edge_events(struct gpiod_line_request *request,
+ int64_t timeout_ns);
+
+/**
+ * @brief Read a number of edge events from a line request.
+ * @param request GPIO line request.
+ * @param buffer Edge event buffer, sized to hold at least \p max_events.
+ * @param max_events Maximum number of events to read.
+ * @return On success returns the number of events read from the file
+ * descriptor, on failure return -1.
+ * @note This function will block if no event was queued for the line request.
+ * @note Any exising events in the buffer are overwritten. This is not an
+ * append operation.
+ */
+int gpiod_line_request_read_edge_events(struct gpiod_line_request *request,
+ struct gpiod_edge_event_buffer *buffer,
+ size_t max_events);
+
+/**
+ * @}
+ *
+ * @defgroup edge_event Line edge events handling
+ * @{
+ *
+ * Functions and data types for handling edge events.
+ *
+ * An edge event object contains information about a single line edge event.
+ * It contains the event type, timestamp and the offset of the line on which
+ * the event occurred as well as two sequence numbers (global for all lines
+ * in the associated request and local for this line only).
+ *
+ * Edge events are stored into an edge-event buffer object to improve
+ * performance and to limit the number of memory allocations when a large
+ * number of events are being read.
+ */
+
+/**
+ * @brief Event types.
+ */
+enum gpiod_edge_event_type {
+ GPIOD_EDGE_EVENT_RISING_EDGE = 1,
+ /**< Rising edge event. */
+ GPIOD_EDGE_EVENT_FALLING_EDGE,
+ /**< Falling edge event. */
+};
+
+/**
+ * @brief Free the edge event object.
+ * @param event Edge event object to free.
+ */
+void gpiod_edge_event_free(struct gpiod_edge_event *event);
+
+/**
+ * @brief Copy the edge event object.
+ * @param event Edge event to copy.
+ * @return Copy of the edge event or NULL on error. The returned object must
+ * be freed by the caller using ::gpiod_edge_event_free.
+ */
+struct gpiod_edge_event *gpiod_edge_event_copy(struct gpiod_edge_event *event);
+
+/**
+ * @brief Get the event type.
+ * @param event GPIO edge event.
+ * @return The event type (::GPIOD_EDGE_EVENT_RISING_EDGE or
+ * ::GPIOD_EDGE_EVENT_FALLING_EDGE).
+ */
+enum gpiod_edge_event_type
+gpiod_edge_event_get_event_type(struct gpiod_edge_event *event);
+
+/**
+ * @brief Get the timestamp of the event.
+ * @param event GPIO edge event.
+ * @return Timestamp in nanoseconds.
+ * @note The source clock for the timestamp depends on the event_clock
+ * setting for the line.
+ */
+uint64_t gpiod_edge_event_get_timestamp_ns(struct gpiod_edge_event *event);
+
+/**
+ * @brief Get the offset of the line which triggered the event.
+ * @param event GPIO edge event.
+ * @return Line offset.
+ */
+unsigned int gpiod_edge_event_get_line_offset(struct gpiod_edge_event *event);
+
+/**
+ * @brief Get the global sequence number of the event.
+ * @param event GPIO edge event.
+ * @return Sequence number of the event in the series of events for all lines
+ * in the associated line request.
+ */
+unsigned long gpiod_edge_event_get_global_seqno(struct gpiod_edge_event *event);
+
+/**
+ * @brief Get the event sequence number specific to the line.
+ * @param event GPIO edge event.
+ * @return Sequence number of the event in the series of events only for this
+ * line within the lifetime of the associated line request.
+ */
+unsigned long gpiod_edge_event_get_line_seqno(struct gpiod_edge_event *event);
+
+/**
+ * @brief Create a new edge event buffer.
+ * @param capacity Number of events the buffer can store (min = 1, max = 1024).
+ * @return New edge event buffer or NULL on error.
+ * @note If capacity equals 0, it will be set to a default value of 64. If
+ * capacity is larger than 1024, it will be limited to 1024.
+ * @note The user space buffer is independent of the kernel buffer
+ * (::gpiod_request_config_set_event_buffer_size). As the user space
+ * buffer is filled from the kernel buffer, there is no benefit making
+ * the user space buffer larger than the kernel buffer.
+ * The default kernel buffer size for each request is (16 * num_lines).
+ */
+struct gpiod_edge_event_buffer *
+gpiod_edge_event_buffer_new(size_t capacity);
+
+/**
+ * @brief Get the capacity (the max number of events that can be stored) of
+ * the event buffer.
+ * @param buffer Edge event buffer.
+ * @return The capacity of the buffer.
+ */
+size_t
+gpiod_edge_event_buffer_get_capacity(struct gpiod_edge_event_buffer *buffer);
+
+/**
+ * @brief Free the edge event buffer and release all associated resources.
+ * @param buffer Edge event buffer to free.
+ */
+void gpiod_edge_event_buffer_free(struct gpiod_edge_event_buffer *buffer);
+
+/**
+ * @brief Get an event stored in the buffer.
+ * @param buffer Edge event buffer.
+ * @param index Index of the event in the buffer.
+ * @return Pointer to an event stored in the buffer. The lifetime of the
+ * event is tied to the buffer object. Users must not free the event
+ * returned by this function.
+ * @warning Thread-safety:
+ * Since events are tied to the buffer instance, different threads
+ * may not operate on the buffer and any associated events at the same
+ * time. Events can be copied using ::gpiod_edge_event_copy in order
+ * to create a standalone objects - which each may safely be used from
+ * a different thread concurrently.
+ */
+struct gpiod_edge_event *
+gpiod_edge_event_buffer_get_event(struct gpiod_edge_event_buffer *buffer,
+ unsigned long index);
+
+/**
+ * @brief Get the number of events a buffer has stored.
+ * @param buffer Edge event buffer.
+ * @return Number of events stored in the buffer.
+ */
+size_t
+gpiod_edge_event_buffer_get_num_events(struct gpiod_edge_event_buffer *buffer);
+
+/**
+ * @}
+ *
+ * @defgroup misc Stuff that didn't fit anywhere else
+ * @{
+ *
+ * Various libgpiod-related functions.
+ */
+
+/**
+ * @brief Check if the file pointed to by path is a GPIO chip character device.
+ * @param path Path to check.
+ * @return True if the file exists and is either a GPIO chip character device
+ * or a symbolic link to one.
+ */
+bool gpiod_is_gpiochip_device(const char *path);
+
+/**
+ * @brief Get the API version of the library as a human-readable string.
+ * @return A valid pointer to a human-readable string containing the library
+ * version. The pointer is valid for the lifetime of the program and
+ * must not be freed by the caller.
+ */
+const char *gpiod_api_version(void);
+
+/**
+ * @}
+ */
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* __LIBGPIOD_GPIOD_H__ */
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+
+lib_LTLIBRARIES = libgpiod.la
+libgpiod_la_SOURCES = \
+ chip.c \
+ chip-info.c \
+ edge-event.c \
+ info-event.c \
+ internal.h \
+ internal.c \
+ line-config.c \
+ line-info.c \
+ line-request.c \
+ line-settings.c \
+ misc.c \
+ request-config.c \
+ uapi/gpio.h
+
+libgpiod_la_CFLAGS = -Wall -Wextra -g -std=gnu89
+libgpiod_la_CFLAGS += -fvisibility=hidden -I$(top_srcdir)/include/
+libgpiod_la_CFLAGS += -include $(top_builddir)/config.h
+libgpiod_la_CFLAGS += $(PROFILING_CFLAGS)
+libgpiod_la_LDFLAGS = -version-info $(subst .,:,$(ABI_VERSION))
+libgpiod_la_LDFLAGS+= $(PROFILING_LDFLAGS)
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = libgpiod.pc
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <assert.h>
+#include <gpiod.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "internal.h"
+
+struct gpiod_chip_info {
+ size_t num_lines;
+ char name[GPIO_MAX_NAME_SIZE + 1];
+ char label[GPIO_MAX_NAME_SIZE + 1];
+};
+
+GPIOD_API void gpiod_chip_info_free(struct gpiod_chip_info *info)
+{
+ free(info);
+}
+
+GPIOD_API const char *gpiod_chip_info_get_name(struct gpiod_chip_info *info)
+{
+ assert(info);
+
+ return info->name;
+}
+
+GPIOD_API const char *gpiod_chip_info_get_label(struct gpiod_chip_info *info)
+{
+ assert(info);
+
+ return info->label;
+}
+
+GPIOD_API size_t gpiod_chip_info_get_num_lines(struct gpiod_chip_info *info)
+{
+ assert(info);
+
+ return info->num_lines;
+}
+
+struct gpiod_chip_info *
+gpiod_chip_info_from_uapi(struct gpiochip_info *uapi_info)
+{
+ struct gpiod_chip_info *info;
+
+ info = malloc(sizeof(*info));
+ if (!info)
+ return NULL;
+
+ memset(info, 0, sizeof(*info));
+
+ info->num_lines = uapi_info->lines;
+
+ /*
+ * GPIO device must have a name - don't bother checking this field. In
+ * the worst case (would have to be a weird kernel bug) it'll be empty.
+ */
+ strncpy(info->name, uapi_info->name, sizeof(info->name));
+
+ /*
+ * The kernel sets the label of a GPIO device to "unknown" if it
+ * hasn't been defined in DT, board file etc. On the off-chance that
+ * we got an empty string, do the same.
+ */
+ if (uapi_info->label[0] == '\0')
+ strncpy(info->label, "unknown", sizeof(info->label));
+ else
+ strncpy(info->label, uapi_info->label, sizeof(info->label));
+
+ return info;
+}
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <gpiod.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "internal.h"
+
+struct gpiod_chip {
+ int fd;
+ char *path;
+};
+
+GPIOD_API struct gpiod_chip *gpiod_chip_open(const char *path)
+{
+ struct gpiod_chip *chip;
+ int fd;
+
+ if (!path) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ if (!gpiod_check_gpiochip_device(path, true))
+ return NULL;
+
+ fd = open(path, O_RDWR | O_CLOEXEC);
+ if (fd < 0)
+ return NULL;
+
+ chip = malloc(sizeof(*chip));
+ if (!chip)
+ goto err_close_fd;
+
+ memset(chip, 0, sizeof(*chip));
+
+ chip->path = strdup(path);
+ if (!chip->path)
+ goto err_free_chip;
+
+ chip->fd = fd;
+
+ return chip;
+
+err_free_chip:
+ free(chip);
+err_close_fd:
+ close(fd);
+
+ return NULL;
+}
+
+GPIOD_API void gpiod_chip_close(struct gpiod_chip *chip)
+{
+ if (!chip)
+ return;
+
+ close(chip->fd);
+ free(chip->path);
+ free(chip);
+}
+
+static int read_chip_info(int fd, struct gpiochip_info *info)
+{
+ int ret;
+
+ memset(info, 0, sizeof(*info));
+
+ ret = gpiod_ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, info);
+ if (ret)
+ return -1;
+
+ return 0;
+}
+
+GPIOD_API struct gpiod_chip_info *gpiod_chip_get_info(struct gpiod_chip *chip)
+{
+ struct gpiochip_info info;
+ int ret;
+
+ assert(chip);
+
+ ret = read_chip_info(chip->fd, &info);
+ if (ret)
+ return NULL;
+
+ return gpiod_chip_info_from_uapi(&info);
+}
+
+GPIOD_API const char *gpiod_chip_get_path(struct gpiod_chip *chip)
+{
+ assert(chip);
+
+ return chip->path;
+}
+
+static int chip_read_line_info(int fd, unsigned int offset,
+ struct gpio_v2_line_info *info, bool watch)
+{
+ int ret, cmd;
+
+ memset(info, 0, sizeof(*info));
+ info->offset = offset;
+
+ cmd = watch ? GPIO_V2_GET_LINEINFO_WATCH_IOCTL :
+ GPIO_V2_GET_LINEINFO_IOCTL;
+
+ ret = gpiod_ioctl(fd, cmd, info);
+ if (ret)
+ return -1;
+
+ return 0;
+}
+
+static struct gpiod_line_info *
+chip_get_line_info(struct gpiod_chip *chip, unsigned int offset, bool watch)
+{
+ struct gpio_v2_line_info info;
+ int ret;
+
+ assert(chip);
+
+ ret = chip_read_line_info(chip->fd, offset, &info, watch);
+ if (ret)
+ return NULL;
+
+ return gpiod_line_info_from_uapi(&info);
+}
+
+GPIOD_API struct gpiod_line_info *
+gpiod_chip_get_line_info(struct gpiod_chip *chip, unsigned int offset)
+{
+ return chip_get_line_info(chip, offset, false);
+}
+
+GPIOD_API struct gpiod_line_info *
+gpiod_chip_watch_line_info(struct gpiod_chip *chip, unsigned int offset)
+{
+ return chip_get_line_info(chip, offset, true);
+}
+
+GPIOD_API int gpiod_chip_unwatch_line_info(struct gpiod_chip *chip,
+ unsigned int offset)
+{
+ assert(chip);
+
+ return gpiod_ioctl(chip->fd, GPIO_GET_LINEINFO_UNWATCH_IOCTL, &offset);
+}
+
+GPIOD_API int gpiod_chip_get_fd(struct gpiod_chip *chip)
+{
+ assert(chip);
+
+ return chip->fd;
+}
+
+GPIOD_API int gpiod_chip_wait_info_event(struct gpiod_chip *chip,
+ int64_t timeout_ns)
+{
+ assert(chip);
+
+ return gpiod_poll_fd(chip->fd, timeout_ns);
+}
+
+GPIOD_API struct gpiod_info_event *
+gpiod_chip_read_info_event(struct gpiod_chip *chip)
+{
+ assert(chip);
+
+ return gpiod_info_event_read_fd(chip->fd);
+}
+
+GPIOD_API int gpiod_chip_get_line_offset_from_name(struct gpiod_chip *chip,
+ const char *name)
+{
+ struct gpio_v2_line_info linfo;
+ struct gpiochip_info chinfo;
+ unsigned int offset;
+ int ret;
+
+ assert(chip);
+
+ if (!name) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ ret = read_chip_info(chip->fd, &chinfo);
+ if (ret)
+ return -1;
+
+ for (offset = 0; offset < chinfo.lines; offset++) {
+ ret = chip_read_line_info(chip->fd, offset, &linfo, false);
+ if (ret)
+ return -1;
+
+ if (strcmp(name, linfo.name) == 0)
+ return offset;
+ }
+
+ errno = ENOENT;
+ return -1;
+}
+
+GPIOD_API struct gpiod_line_request *
+gpiod_chip_request_lines(struct gpiod_chip *chip,
+ struct gpiod_request_config *req_cfg,
+ struct gpiod_line_config *line_cfg)
+{
+ struct gpio_v2_line_request uapi_req;
+ struct gpiod_line_request *request;
+ struct gpiochip_info info;
+ int ret;
+
+ assert(chip);
+
+ if (!line_cfg) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ memset(&uapi_req, 0, sizeof(uapi_req));
+
+ if (req_cfg)
+ gpiod_request_config_to_uapi(req_cfg, &uapi_req);
+
+ ret = gpiod_line_config_to_uapi(line_cfg, &uapi_req);
+ if (ret)
+ return NULL;
+
+ ret = read_chip_info(chip->fd, &info);
+ if (ret)
+ return NULL;
+
+ ret = gpiod_ioctl(chip->fd, GPIO_V2_GET_LINE_IOCTL, &uapi_req);
+ if (ret)
+ return NULL;
+
+ request = gpiod_line_request_from_uapi(&uapi_req, info.name);
+ if (!request) {
+ close(uapi_req.fd);
+ return NULL;
+ }
+
+ return request;
+}
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <assert.h>
+#include <errno.h>
+#include <gpiod.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "internal.h"
+
+/* As defined in the kernel. */
+#define EVENT_BUFFER_MAX_CAPACITY (GPIO_V2_LINES_MAX * 16)
+
+struct gpiod_edge_event {
+ enum gpiod_edge_event_type event_type;
+ uint64_t timestamp;
+ unsigned int line_offset;
+ unsigned long global_seqno;
+ unsigned long line_seqno;
+};
+
+struct gpiod_edge_event_buffer {
+ size_t capacity;
+ size_t num_events;
+ struct gpiod_edge_event *events;
+ struct gpio_v2_line_event *event_data;
+};
+
+GPIOD_API void gpiod_edge_event_free(struct gpiod_edge_event *event)
+{
+ free(event);
+}
+
+GPIOD_API struct gpiod_edge_event *
+gpiod_edge_event_copy(struct gpiod_edge_event *event)
+{
+ struct gpiod_edge_event *copy;
+
+ assert(event);
+
+ copy = malloc(sizeof(*event));
+ if (!copy)
+ return NULL;
+
+ memcpy(copy, event, sizeof(*event));
+
+ return copy;
+}
+
+GPIOD_API enum gpiod_edge_event_type
+gpiod_edge_event_get_event_type(struct gpiod_edge_event *event)
+{
+ assert(event);
+
+ return event->event_type;
+}
+
+GPIOD_API uint64_t
+gpiod_edge_event_get_timestamp_ns(struct gpiod_edge_event *event)
+{
+ assert(event);
+
+ return event->timestamp;
+}
+
+GPIOD_API unsigned int
+gpiod_edge_event_get_line_offset(struct gpiod_edge_event *event)
+{
+ assert(event);
+
+ return event->line_offset;
+}
+
+GPIOD_API unsigned long
+gpiod_edge_event_get_global_seqno(struct gpiod_edge_event *event)
+{
+ assert(event);
+
+ return event->global_seqno;
+}
+
+GPIOD_API unsigned long
+gpiod_edge_event_get_line_seqno(struct gpiod_edge_event *event)
+{
+ assert(event);
+
+ return event->line_seqno;
+}
+
+GPIOD_API struct gpiod_edge_event_buffer *
+gpiod_edge_event_buffer_new(size_t capacity)
+{
+ struct gpiod_edge_event_buffer *buf;
+
+ if (capacity == 0)
+ capacity = 64;
+ if (capacity > EVENT_BUFFER_MAX_CAPACITY)
+ capacity = EVENT_BUFFER_MAX_CAPACITY;
+
+ buf = malloc(sizeof(*buf));
+ if (!buf)
+ return NULL;
+
+ memset(buf, 0, sizeof(*buf));
+ buf->capacity = capacity;
+
+ buf->events = calloc(capacity, sizeof(*buf->events));
+ if (!buf->events) {
+ free(buf);
+ return NULL;
+ }
+
+ buf->event_data = calloc(capacity, sizeof(*buf->event_data));
+ if (!buf->event_data) {
+ free(buf->events);
+ free(buf);
+ return NULL;
+ }
+
+ return buf;
+}
+
+GPIOD_API size_t
+gpiod_edge_event_buffer_get_capacity(struct gpiod_edge_event_buffer *buffer)
+{
+ assert(buffer);
+
+ return buffer->capacity;
+}
+
+GPIOD_API void
+gpiod_edge_event_buffer_free(struct gpiod_edge_event_buffer *buffer)
+{
+ if (!buffer)
+ return;
+
+ free(buffer->events);
+ free(buffer->event_data);
+ free(buffer);
+}
+
+GPIOD_API struct gpiod_edge_event *
+gpiod_edge_event_buffer_get_event(struct gpiod_edge_event_buffer *buffer,
+ unsigned long index)
+{
+ assert(buffer);
+
+ if (index >= buffer->num_events) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ return &buffer->events[index];
+}
+
+GPIOD_API size_t
+gpiod_edge_event_buffer_get_num_events(struct gpiod_edge_event_buffer *buffer)
+{
+ assert(buffer);
+
+ return buffer->num_events;
+}
+
+int gpiod_edge_event_buffer_read_fd(int fd,
+ struct gpiod_edge_event_buffer *buffer,
+ size_t max_events)
+{
+ struct gpio_v2_line_event *curr;
+ struct gpiod_edge_event *event;
+ size_t i;
+ ssize_t rd;
+
+ if (!buffer) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ memset(buffer->event_data, 0,
+ sizeof(*buffer->event_data) * buffer->capacity);
+ memset(buffer->events, 0, sizeof(*buffer->events) * buffer->capacity);
+
+ if (max_events > buffer->capacity)
+ max_events = buffer->capacity;
+
+ rd = read(fd, buffer->event_data,
+ max_events * sizeof(*buffer->event_data));
+ if (rd < 0) {
+ return -1;
+ } else if ((unsigned int)rd < sizeof(*buffer->event_data)) {
+ errno = EIO;
+ return -1;
+ }
+
+ buffer->num_events = rd / sizeof(*buffer->event_data);
+
+ for (i = 0; i < buffer->num_events; i++) {
+ curr = &buffer->event_data[i];
+ event = &buffer->events[i];
+
+ event->line_offset = curr->offset;
+ event->event_type = curr->id == GPIO_V2_LINE_EVENT_RISING_EDGE ?
+ GPIOD_EDGE_EVENT_RISING_EDGE :
+ GPIOD_EDGE_EVENT_FALLING_EDGE;
+ event->timestamp = curr->timestamp_ns;
+ event->global_seqno = curr->seqno;
+ event->line_seqno = curr->line_seqno;
+ }
+
+ return i;
+}
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "internal.h"
+
+struct gpiod_info_event {
+ enum gpiod_info_event_type event_type;
+ uint64_t timestamp;
+ struct gpiod_line_info *info;
+};
+
+struct gpiod_info_event *
+gpiod_info_event_from_uapi(struct gpio_v2_line_info_changed *uapi_evt)
+{
+ struct gpiod_info_event *event;
+
+ event = malloc(sizeof(*event));
+ if (!event)
+ return NULL;
+
+ memset(event, 0, sizeof(*event));
+ event->timestamp = uapi_evt->timestamp_ns;
+
+ switch (uapi_evt->event_type) {
+ case GPIOLINE_CHANGED_REQUESTED:
+ event->event_type = GPIOD_INFO_EVENT_LINE_REQUESTED;
+ break;
+ case GPIOLINE_CHANGED_RELEASED:
+ event->event_type = GPIOD_INFO_EVENT_LINE_RELEASED;
+ break;
+ case GPIOLINE_CHANGED_CONFIG:
+ event->event_type = GPIOD_INFO_EVENT_LINE_CONFIG_CHANGED;
+ break;
+ default:
+ /* Can't happen unless there's a bug in the kernel. */
+ errno = ENOMSG;
+ free(event);
+ return NULL;
+ }
+
+ event->info = gpiod_line_info_from_uapi(&uapi_evt->info);
+ if (!event->info) {
+ free(event);
+ return NULL;
+ }
+
+ return event;
+}
+
+GPIOD_API void gpiod_info_event_free(struct gpiod_info_event *event)
+{
+ if (!event)
+ return;
+
+ gpiod_line_info_free(event->info);
+ free(event);
+}
+
+GPIOD_API enum gpiod_info_event_type
+gpiod_info_event_get_event_type(struct gpiod_info_event *event)
+{
+ assert(event);
+
+ return event->event_type;
+}
+
+GPIOD_API uint64_t
+gpiod_info_event_get_timestamp_ns(struct gpiod_info_event *event)
+{
+ assert(event);
+
+ return event->timestamp;
+}
+
+GPIOD_API struct gpiod_line_info *
+gpiod_info_event_get_line_info(struct gpiod_info_event *event)
+{
+ assert(event);
+
+ return event->info;
+}
+
+struct gpiod_info_event *gpiod_info_event_read_fd(int fd)
+{
+ struct gpio_v2_line_info_changed uapi_evt;
+ ssize_t rd;
+
+ memset(&uapi_evt, 0, sizeof(uapi_evt));
+
+ rd = read(fd, &uapi_evt, sizeof(uapi_evt));
+ if (rd < 0) {
+ return NULL;
+ } else if ((unsigned int)rd < sizeof(uapi_evt)) {
+ errno = EIO;
+ return NULL;
+ }
+
+ return gpiod_info_event_from_uapi(&uapi_evt);
+}
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <errno.h>
+#include <poll.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "internal.h"
+
+bool gpiod_check_gpiochip_device(const char *path, bool set_errno)
+{
+ char *realname, *sysfsp, devpath[64];
+ struct stat statbuf;
+ bool ret = false;
+ int rv;
+
+ if (!path) {
+ errno = EINVAL;
+ goto out;
+ }
+
+ rv = lstat(path, &statbuf);
+ if (rv)
+ goto out;
+
+ /*
+ * Is it a symbolic link? We have to resolve it before checking
+ * the rest.
+ */
+ realname = S_ISLNK(statbuf.st_mode) ? realpath(path, NULL) :
+ strdup(path);
+ if (realname == NULL)
+ goto out;
+
+ rv = stat(realname, &statbuf);
+ if (rv)
+ goto out_free_realname;
+
+ /* Is it a character device? */
+ if (!S_ISCHR(statbuf.st_mode)) {
+ errno = ENOTTY;
+ goto out_free_realname;
+ }
+
+ /* Is the device associated with the GPIO subsystem? */
+ snprintf(devpath, sizeof(devpath), "/sys/dev/char/%u:%u/subsystem",
+ major(statbuf.st_rdev), minor(statbuf.st_rdev));
+
+ sysfsp = realpath(devpath, NULL);
+ if (!sysfsp)
+ goto out_free_realname;
+
+ /*
+ * In glibc, if any of the underlying readlink() calls fail (which is
+ * perfectly normal when resolving paths), errno is not cleared.
+ */
+ errno = 0;
+
+ if (strcmp(sysfsp, "/sys/bus/gpio") != 0) {
+ /* This is a character device but not the one we're after. */
+ errno = ENODEV;
+ goto out_free_sysfsp;
+ }
+
+ ret = true;
+
+out_free_sysfsp:
+ free(sysfsp);
+out_free_realname:
+ free(realname);
+out:
+ if (!set_errno)
+ errno = 0;
+ return ret;
+}
+
+int gpiod_poll_fd(int fd, int64_t timeout_ns)
+{
+ struct timespec ts;
+ struct pollfd pfd;
+ int ret;
+
+ memset(&pfd, 0, sizeof(pfd));
+ pfd.fd = fd;
+ pfd.events = POLLIN | POLLPRI;
+
+ if (timeout_ns >= 0) {
+ ts.tv_sec = timeout_ns / 1000000000ULL;
+ ts.tv_nsec = timeout_ns % 1000000000ULL;
+ }
+
+ ret = ppoll(&pfd, 1, timeout_ns < 0 ? NULL : &ts, NULL);
+ if (ret < 0)
+ return -1;
+ else if (ret == 0)
+ return 0;
+
+ return 1;
+}
+
+int gpiod_set_output_value(enum gpiod_line_value in, enum gpiod_line_value *out)
+{
+ switch (in) {
+ case GPIOD_LINE_VALUE_INACTIVE:
+ case GPIOD_LINE_VALUE_ACTIVE:
+ *out = in;
+ break;
+ default:
+ *out = GPIOD_LINE_VALUE_INACTIVE;
+ errno = EINVAL;
+ return -1;
+ }
+
+ return 0;
+}
+
+int gpiod_ioctl(int fd, unsigned long request, void *arg)
+{
+ int ret;
+
+ ret = ioctl(fd, request, arg);
+ if (ret <= 0)
+ return ret;
+
+ errno = EBADE;
+ return -1;
+}
+
+void gpiod_line_mask_zero(uint64_t *mask)
+{
+ *mask = 0ULL;
+}
+
+bool gpiod_line_mask_test_bit(const uint64_t *mask, int nr)
+{
+ return *mask & (1ULL << nr);
+}
+
+void gpiod_line_mask_set_bit(uint64_t *mask, unsigned int nr)
+{
+ *mask |= (1ULL << nr);
+}
+
+void gpiod_line_mask_assign_bit(uint64_t *mask, unsigned int nr, bool value)
+{
+ if (value)
+ gpiod_line_mask_set_bit(mask, nr);
+ else
+ *mask &= ~(1ULL << nr);
+}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/* SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <bgolaszewski@baylibre.com> */
+
+#ifndef __LIBGPIOD_GPIOD_INTERNAL_H__
+#define __LIBGPIOD_GPIOD_INTERNAL_H__
+
+#include <gpiod.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "uapi/gpio.h"
+
+/* For internal library use only. */
+
+#define GPIOD_API __attribute__((visibility("default")))
+#define GPIOD_BIT(nr) (1UL << (nr))
+
+bool gpiod_check_gpiochip_device(const char *path, bool set_errno);
+
+struct gpiod_chip_info *
+gpiod_chip_info_from_uapi(struct gpiochip_info *uapi_info);
+struct gpiod_line_info *
+gpiod_line_info_from_uapi(struct gpio_v2_line_info *uapi_info);
+void gpiod_request_config_to_uapi(struct gpiod_request_config *config,
+ struct gpio_v2_line_request *uapi_req);
+int gpiod_line_config_to_uapi(struct gpiod_line_config *config,
+ struct gpio_v2_line_request *uapi_cfg);
+struct gpiod_line_request *
+gpiod_line_request_from_uapi(struct gpio_v2_line_request *uapi_req,
+ const char *chip_name);
+int gpiod_edge_event_buffer_read_fd(int fd,
+ struct gpiod_edge_event_buffer *buffer,
+ size_t max_events);
+struct gpiod_info_event *
+gpiod_info_event_from_uapi(struct gpio_v2_line_info_changed *uapi_evt);
+struct gpiod_info_event *gpiod_info_event_read_fd(int fd);
+
+int gpiod_poll_fd(int fd, int64_t timeout);
+int gpiod_set_output_value(enum gpiod_line_value in,
+ enum gpiod_line_value *out);
+int gpiod_ioctl(int fd, unsigned long request, void *arg);
+
+void gpiod_line_mask_zero(uint64_t *mask);
+bool gpiod_line_mask_test_bit(const uint64_t *mask, int nr);
+void gpiod_line_mask_set_bit(uint64_t *mask, unsigned int nr);
+void gpiod_line_mask_assign_bit(uint64_t *mask, unsigned int nr, bool value);
+
+#endif /* __LIBGPIOD_GPIOD_INTERNAL_H__ */
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+# SPDX-FileCopyrightText: 2018 Clemens Gruber <clemens.gruber@pqgruber.com>
+
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libgpiod
+Description: Library and tools for the Linux GPIO chardev
+URL: @PACKAGE_URL@
+Version: @PACKAGE_VERSION@
+Libs: -L${libdir} -lgpiod
+Cflags: -I${includedir}
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <assert.h>
+#include <errno.h>
+#include <gpiod.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/param.h>
+
+#include "internal.h"
+
+#define LINES_MAX (GPIO_V2_LINES_MAX)
+
+struct settings_node {
+ struct settings_node *next;
+ struct gpiod_line_settings *settings;
+};
+
+struct per_line_config {
+ unsigned int offset;
+ struct settings_node *node;
+};
+
+struct gpiod_line_config {
+ struct per_line_config line_configs[LINES_MAX];
+ size_t num_configs;
+ enum gpiod_line_value output_values[LINES_MAX];
+ size_t num_output_values;
+ struct settings_node *sref_list;
+};
+
+GPIOD_API struct gpiod_line_config *gpiod_line_config_new(void)
+{
+ struct gpiod_line_config *config;
+
+ config = malloc(sizeof(*config));
+ if (!config)
+ return NULL;
+
+ memset(config, 0, sizeof(*config));
+
+ return config;
+}
+
+static void free_refs(struct gpiod_line_config *config)
+{
+ struct settings_node *node, *tmp;
+
+ for (node = config->sref_list; node;) {
+ tmp = node->next;
+ gpiod_line_settings_free(node->settings);
+ free(node);
+ node = tmp;
+ }
+}
+
+GPIOD_API void gpiod_line_config_free(struct gpiod_line_config *config)
+{
+ if (!config)
+ return;
+
+ free_refs(config);
+ free(config);
+}
+
+GPIOD_API void gpiod_line_config_reset(struct gpiod_line_config *config)
+{
+ assert(config);
+
+ free_refs(config);
+ memset(config, 0, sizeof(*config));
+}
+
+static struct per_line_config *find_config(struct gpiod_line_config *config,
+ unsigned int offset)
+{
+ struct per_line_config *per_line;
+ size_t i;
+
+ for (i = 0; i < config->num_configs; i++) {
+ per_line = &config->line_configs[i];
+
+ if (offset == per_line->offset)
+ return per_line;
+ }
+
+ return &config->line_configs[config->num_configs++];
+}
+
+GPIOD_API int gpiod_line_config_add_line_settings(
+ struct gpiod_line_config *config, const unsigned int *offsets,
+ size_t num_offsets, struct gpiod_line_settings *settings)
+{
+ struct per_line_config *per_line;
+ struct settings_node *node;
+ size_t i;
+
+ assert(config);
+
+ if (!offsets || num_offsets == 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if ((config->num_configs + num_offsets) > LINES_MAX) {
+ errno = E2BIG;
+ return -1;
+ }
+
+ node = malloc(sizeof(*node));
+ if (!node)
+ return -1;
+
+ if (!settings)
+ node->settings = gpiod_line_settings_new();
+ else
+ node->settings = gpiod_line_settings_copy(settings);
+ if (!node->settings) {
+ free(node);
+ return -1;
+ }
+
+ node->next = config->sref_list;
+ config->sref_list = node;
+
+ for (i = 0; i < num_offsets; i++) {
+ per_line = find_config(config, offsets[i]);
+
+ per_line->offset = offsets[i];
+ per_line->node = node;
+ }
+
+ return 0;
+}
+
+GPIOD_API struct gpiod_line_settings *
+gpiod_line_config_get_line_settings(struct gpiod_line_config *config,
+ unsigned int offset)
+{
+ struct gpiod_line_settings *settings;
+ struct per_line_config *per_line;
+ size_t i;
+ int ret;
+
+ assert(config);
+
+ for (i = 0; i < config->num_configs; i++) {
+ per_line = &config->line_configs[i];
+
+ if (per_line->offset == offset) {
+ settings = gpiod_line_settings_copy(
+ per_line->node->settings);
+ if (!settings)
+ return NULL;
+
+ /*
+ * If a global output value was set for this line - use
+ * it and override the one stored in settings.
+ */
+ if (config->num_output_values > i) {
+ ret = gpiod_line_settings_set_output_value(
+ settings,
+ config->output_values[i]);
+ if (ret) {
+ gpiod_line_settings_free(settings);
+ return NULL;
+ }
+ }
+
+ return settings;
+ }
+ }
+
+ errno = ENOENT;
+ return NULL;
+}
+
+GPIOD_API int
+gpiod_line_config_set_output_values(struct gpiod_line_config *config,
+ const enum gpiod_line_value *values,
+ size_t num_values)
+{
+ size_t i;
+ int ret;
+
+ assert(config);
+
+ if (!num_values || num_values > LINES_MAX || !values) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ for (i = 0; i < num_values; i++) {
+ ret = gpiod_set_output_value(values[i],
+ &config->output_values[i]);
+ if (ret) {
+ config->num_output_values = 0;
+ return ret;
+ }
+ }
+
+ config->num_output_values = num_values;
+
+ return 0;
+}
+
+GPIOD_API size_t
+gpiod_line_config_get_num_configured_offsets(struct gpiod_line_config *config)
+{
+ assert(config);
+
+ return config->num_configs;
+}
+
+GPIOD_API size_t
+gpiod_line_config_get_configured_offsets(struct gpiod_line_config *config,
+ unsigned int *offsets,
+ size_t max_offsets)
+{
+ size_t num_offsets, i;
+
+ assert(config);
+
+ if (!offsets || !max_offsets || !config->num_configs)
+ return 0;
+
+ num_offsets = MIN(config->num_configs, max_offsets);
+
+ for (i = 0; i < num_offsets; i++)
+ offsets[i] = config->line_configs[i].offset;
+
+ return num_offsets;
+}
+
+static void set_offsets(struct gpiod_line_config *config,
+ struct gpio_v2_line_request *uapi_cfg)
+{
+ size_t i;
+
+ uapi_cfg->num_lines = config->num_configs;
+
+ for (i = 0; i < config->num_configs; i++)
+ uapi_cfg->offsets[i] = config->line_configs[i].offset;
+}
+
+static bool has_at_least_one_output_direction(struct gpiod_line_config *config)
+{
+ size_t i;
+
+ for (i = 0; i < config->num_configs; i++) {
+ if (gpiod_line_settings_get_direction(
+ config->line_configs[i].node->settings) ==
+ GPIOD_LINE_DIRECTION_OUTPUT)
+ return true;
+ }
+
+ return false;
+}
+
+static void set_output_value(uint64_t *vals, size_t bit,
+ enum gpiod_line_value value)
+{
+ gpiod_line_mask_assign_bit(vals, bit,
+ value == GPIOD_LINE_VALUE_ACTIVE ? 1 : 0);
+}
+
+static void set_kernel_output_values(uint64_t *mask, uint64_t *vals,
+ struct gpiod_line_config *config)
+{
+ struct per_line_config *per_line;
+ enum gpiod_line_value value;
+ size_t i;
+
+ gpiod_line_mask_zero(mask);
+ gpiod_line_mask_zero(vals);
+
+ for (i = 0; i < config->num_configs; i++) {
+ per_line = &config->line_configs[i];
+
+ if (gpiod_line_settings_get_direction(
+ per_line->node->settings) !=
+ GPIOD_LINE_DIRECTION_OUTPUT)
+ continue;
+
+ gpiod_line_mask_set_bit(mask, i);
+ value = gpiod_line_settings_get_output_value(
+ per_line->node->settings);
+ set_output_value(vals, i, value);
+ }
+
+ /* "Global" output values override the ones from per-line settings. */
+ for (i = 0; i < config->num_output_values; i++) {
+ gpiod_line_mask_set_bit(mask, i);
+ value = config->output_values[i];
+ set_output_value(vals, i, value);
+ }
+}
+
+static void set_output_values(struct gpiod_line_config *config,
+ struct gpio_v2_line_request *uapi_cfg,
+ unsigned int *attr_idx)
+{
+ struct gpio_v2_line_config_attribute *attr;
+ uint64_t mask, values;
+
+ if (!has_at_least_one_output_direction(config))
+ return;
+
+ attr = &uapi_cfg->config.attrs[(*attr_idx)++];
+ attr->attr.id = GPIO_V2_LINE_ATTR_ID_OUTPUT_VALUES;
+ set_kernel_output_values(&mask, &values, config);
+ attr->attr.values = values;
+ attr->mask = mask;
+}
+
+static int set_debounce_periods(struct gpiod_line_config *config,
+ struct gpio_v2_line_config *uapi_cfg,
+ unsigned int *attr_idx)
+{
+ struct gpio_v2_line_config_attribute *attr;
+ unsigned long period_i, period_j;
+ uint64_t done, mask;
+ size_t i, j;
+
+ gpiod_line_mask_zero(&done);
+
+ for (i = 0; i < config->num_configs; i++) {
+ if (gpiod_line_mask_test_bit(&done, i))
+ continue;
+
+ gpiod_line_mask_set_bit(&done, i);
+ gpiod_line_mask_zero(&mask);
+
+ period_i = gpiod_line_settings_get_debounce_period_us(
+ config->line_configs[i].node->settings);
+ if (!period_i)
+ continue;
+
+ if (*attr_idx == GPIO_V2_LINE_NUM_ATTRS_MAX) {
+ errno = E2BIG;
+ return -1;
+ }
+
+ attr = &uapi_cfg->attrs[(*attr_idx)++];
+ attr->attr.id = GPIO_V2_LINE_ATTR_ID_DEBOUNCE;
+ attr->attr.debounce_period_us = period_i;
+ gpiod_line_mask_set_bit(&mask, i);
+
+ for (j = i; j < config->num_configs; j++) {
+ period_j = gpiod_line_settings_get_debounce_period_us(
+ config->line_configs[j].node->settings);
+ if (period_i == period_j) {
+ gpiod_line_mask_set_bit(&mask, j);
+ gpiod_line_mask_set_bit(&done, j);
+ }
+ }
+
+ attr->mask = mask;
+ }
+
+ return 0;
+}
+
+static uint64_t make_kernel_flags(struct gpiod_line_settings *settings)
+{
+ uint64_t flags = 0;
+
+ switch (gpiod_line_settings_get_direction(settings)) {
+ case GPIOD_LINE_DIRECTION_INPUT:
+ flags |= GPIO_V2_LINE_FLAG_INPUT;
+ break;
+ case GPIOD_LINE_DIRECTION_OUTPUT:
+ flags |= GPIO_V2_LINE_FLAG_OUTPUT;
+ break;
+ default:
+ break;
+ }
+
+ switch (gpiod_line_settings_get_edge_detection(settings)) {
+ case GPIOD_LINE_EDGE_FALLING:
+ flags |= (GPIO_V2_LINE_FLAG_EDGE_FALLING |
+ GPIO_V2_LINE_FLAG_INPUT);
+ break;
+ case GPIOD_LINE_EDGE_RISING:
+ flags |= (GPIO_V2_LINE_FLAG_EDGE_RISING |
+ GPIO_V2_LINE_FLAG_INPUT);
+ break;
+ case GPIOD_LINE_EDGE_BOTH:
+ flags |= (GPIO_V2_LINE_FLAG_EDGE_FALLING |
+ GPIO_V2_LINE_FLAG_EDGE_RISING |
+ GPIO_V2_LINE_FLAG_INPUT);
+ break;
+ default:
+ break;
+ }
+
+ switch (gpiod_line_settings_get_drive(settings)) {
+ case GPIOD_LINE_DRIVE_OPEN_DRAIN:
+ flags |= GPIO_V2_LINE_FLAG_OPEN_DRAIN;
+ break;
+ case GPIOD_LINE_DRIVE_OPEN_SOURCE:
+ flags |= GPIO_V2_LINE_FLAG_OPEN_SOURCE;
+ break;
+ default:
+ break;
+ }
+
+ switch (gpiod_line_settings_get_bias(settings)) {
+ case GPIOD_LINE_BIAS_DISABLED:
+ flags |= GPIO_V2_LINE_FLAG_BIAS_DISABLED;
+ break;
+ case GPIOD_LINE_BIAS_PULL_UP:
+ flags |= GPIO_V2_LINE_FLAG_BIAS_PULL_UP;
+ break;
+ case GPIOD_LINE_BIAS_PULL_DOWN:
+ flags |= GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN;
+ break;
+ default:
+ break;
+ }
+
+ if (gpiod_line_settings_get_active_low(settings))
+ flags |= GPIO_V2_LINE_FLAG_ACTIVE_LOW;
+
+ switch (gpiod_line_settings_get_event_clock(settings)) {
+ case GPIOD_LINE_CLOCK_REALTIME:
+ flags |= GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME;
+ break;
+ case GPIOD_LINE_CLOCK_HTE:
+ flags |= GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE;
+ break;
+ default:
+ break;
+ }
+
+ return flags;
+}
+
+static bool settings_equal(struct gpiod_line_settings *left,
+ struct gpiod_line_settings *right)
+{
+ if (gpiod_line_settings_get_direction(left) !=
+ gpiod_line_settings_get_direction(right))
+ return false;
+
+ if (gpiod_line_settings_get_edge_detection(left) !=
+ gpiod_line_settings_get_edge_detection(right))
+ return false;
+
+ if (gpiod_line_settings_get_bias(left) !=
+ gpiod_line_settings_get_bias(right))
+ return false;
+
+ if (gpiod_line_settings_get_drive(left) !=
+ gpiod_line_settings_get_drive(right))
+ return false;
+
+ if (gpiod_line_settings_get_active_low(left) !=
+ gpiod_line_settings_get_active_low(right))
+ return false;
+
+ if (gpiod_line_settings_get_event_clock(left) !=
+ gpiod_line_settings_get_event_clock(right))
+ return false;
+
+ return true;
+}
+
+static int set_flags(struct gpiod_line_config *config,
+ struct gpio_v2_line_config *uapi_cfg,
+ unsigned int *attr_idx)
+{
+ struct gpiod_line_settings *settings_i, *settings_j;
+ struct gpio_v2_line_config_attribute *attr;
+ bool globals_taken = false;
+ uint64_t done, mask;
+ size_t i, j;
+
+ gpiod_line_mask_zero(&done);
+
+ for (i = 0; i < config->num_configs; i++) {
+ if (gpiod_line_mask_test_bit(&done, i))
+ continue;
+
+ gpiod_line_mask_set_bit(&done, i);
+
+ settings_i = config->line_configs[i].node->settings;
+
+ if (!globals_taken) {
+ globals_taken = true;
+ uapi_cfg->flags = make_kernel_flags(settings_i);
+
+ for (j = i; j < config->num_configs; j++) {
+ settings_j =
+ config->line_configs[j].node->settings;
+ if (settings_equal(settings_i, settings_j))
+ gpiod_line_mask_set_bit(&done, j);
+ }
+ } else {
+ gpiod_line_mask_zero(&mask);
+ gpiod_line_mask_set_bit(&mask, i);
+
+ if (*attr_idx == GPIO_V2_LINE_NUM_ATTRS_MAX) {
+ errno = E2BIG;
+ return -1;
+ }
+
+ attr = &uapi_cfg->attrs[(*attr_idx)++];
+ attr->attr.id = GPIO_V2_LINE_ATTR_ID_FLAGS;
+ attr->attr.flags = make_kernel_flags(settings_i);
+
+ for (j = i; j < config->num_configs; j++) {
+ settings_j =
+ config->line_configs[j].node->settings;
+ if (settings_equal(settings_i, settings_j)) {
+ gpiod_line_mask_set_bit(&done, j);
+ gpiod_line_mask_set_bit(&mask, j);
+ }
+ }
+
+ attr->mask = mask;
+ }
+ }
+
+ return 0;
+}
+
+int gpiod_line_config_to_uapi(struct gpiod_line_config *config,
+ struct gpio_v2_line_request *uapi_cfg)
+{
+ unsigned int attr_idx = 0;
+ int ret;
+
+ set_offsets(config, uapi_cfg);
+ set_output_values(config, uapi_cfg, &attr_idx);
+
+ ret = set_debounce_periods(config, &uapi_cfg->config, &attr_idx);
+ if (ret)
+ return -1;
+
+ ret = set_flags(config, &uapi_cfg->config, &attr_idx);
+ if (ret)
+ return -1;
+
+ uapi_cfg->config.num_attrs = attr_idx;
+
+ return 0;
+}
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <assert.h>
+#include <gpiod.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "internal.h"
+
+struct gpiod_line_info {
+ unsigned int offset;
+ char name[GPIO_MAX_NAME_SIZE + 1];
+ bool used;
+ char consumer[GPIO_MAX_NAME_SIZE + 1];
+ enum gpiod_line_direction direction;
+ bool active_low;
+ enum gpiod_line_bias bias;
+ enum gpiod_line_drive drive;
+ enum gpiod_line_edge edge;
+ enum gpiod_line_clock event_clock;
+ bool debounced;
+ unsigned long debounce_period_us;
+};
+
+GPIOD_API void gpiod_line_info_free(struct gpiod_line_info *info)
+{
+ free(info);
+}
+
+GPIOD_API struct gpiod_line_info *
+gpiod_line_info_copy(struct gpiod_line_info *info)
+{
+ struct gpiod_line_info *copy;
+
+ assert(info);
+
+ copy = malloc(sizeof(*info));
+ if (!copy)
+ return NULL;
+
+ memcpy(copy, info, sizeof(*info));
+
+ return copy;
+}
+
+GPIOD_API unsigned int gpiod_line_info_get_offset(struct gpiod_line_info *info)
+{
+ assert(info);
+
+ return info->offset;
+}
+
+GPIOD_API const char *gpiod_line_info_get_name(struct gpiod_line_info *info)
+{
+ assert(info);
+
+ return info->name[0] == '\0' ? NULL : info->name;
+}
+
+GPIOD_API bool gpiod_line_info_is_used(struct gpiod_line_info *info)
+{
+ assert(info);
+
+ return info->used;
+}
+
+GPIOD_API const char *gpiod_line_info_get_consumer(struct gpiod_line_info *info)
+{
+ assert(info);
+
+ return info->consumer[0] == '\0' ? NULL : info->consumer;
+}
+
+GPIOD_API enum gpiod_line_direction
+gpiod_line_info_get_direction(struct gpiod_line_info *info)
+{
+ assert(info);
+
+ return info->direction;
+}
+
+GPIOD_API bool gpiod_line_info_is_active_low(struct gpiod_line_info *info)
+{
+ assert(info);
+
+ return info->active_low;
+}
+
+GPIOD_API enum gpiod_line_bias
+gpiod_line_info_get_bias(struct gpiod_line_info *info)
+{
+ assert(info);
+
+ return info->bias;
+}
+
+GPIOD_API enum gpiod_line_drive
+gpiod_line_info_get_drive(struct gpiod_line_info *info)
+{
+ assert(info);
+
+ return info->drive;
+}
+
+GPIOD_API enum gpiod_line_edge
+gpiod_line_info_get_edge_detection(struct gpiod_line_info *info)
+{
+ assert(info);
+
+ return info->edge;
+}
+
+GPIOD_API enum gpiod_line_clock
+gpiod_line_info_get_event_clock(struct gpiod_line_info *info)
+{
+ assert(info);
+
+ return info->event_clock;
+}
+
+GPIOD_API bool gpiod_line_info_is_debounced(struct gpiod_line_info *info)
+{
+ assert(info);
+
+ return info->debounced;
+}
+
+GPIOD_API unsigned long
+gpiod_line_info_get_debounce_period_us(struct gpiod_line_info *info)
+{
+ assert(info);
+
+ return info->debounce_period_us;
+}
+
+struct gpiod_line_info *
+gpiod_line_info_from_uapi(struct gpio_v2_line_info *uapi_info)
+{
+ struct gpio_v2_line_attribute *attr;
+ struct gpiod_line_info *info;
+ size_t i;
+
+ info = malloc(sizeof(*info));
+ if (!info)
+ return NULL;
+
+ memset(info, 0, sizeof(*info));
+
+ info->offset = uapi_info->offset;
+ strncpy(info->name, uapi_info->name, GPIO_MAX_NAME_SIZE);
+
+ info->used = !!(uapi_info->flags & GPIO_V2_LINE_FLAG_USED);
+ strncpy(info->consumer, uapi_info->consumer, GPIO_MAX_NAME_SIZE);
+
+ if (uapi_info->flags & GPIO_V2_LINE_FLAG_OUTPUT)
+ info->direction = GPIOD_LINE_DIRECTION_OUTPUT;
+ else
+ info->direction = GPIOD_LINE_DIRECTION_INPUT;
+
+ if (uapi_info->flags & GPIO_V2_LINE_FLAG_ACTIVE_LOW)
+ info->active_low = true;
+
+ if (uapi_info->flags & GPIO_V2_LINE_FLAG_BIAS_PULL_UP)
+ info->bias = GPIOD_LINE_BIAS_PULL_UP;
+ else if (uapi_info->flags & GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN)
+ info->bias = GPIOD_LINE_BIAS_PULL_DOWN;
+ else if (uapi_info->flags & GPIO_V2_LINE_FLAG_BIAS_DISABLED)
+ info->bias = GPIOD_LINE_BIAS_DISABLED;
+ else
+ info->bias = GPIOD_LINE_BIAS_UNKNOWN;
+
+ if (uapi_info->flags & GPIO_V2_LINE_FLAG_OPEN_DRAIN)
+ info->drive = GPIOD_LINE_DRIVE_OPEN_DRAIN;
+ else if (uapi_info->flags & GPIO_V2_LINE_FLAG_OPEN_SOURCE)
+ info->drive = GPIOD_LINE_DRIVE_OPEN_SOURCE;
+ else
+ info->drive = GPIOD_LINE_DRIVE_PUSH_PULL;
+
+ if ((uapi_info->flags & GPIO_V2_LINE_FLAG_EDGE_RISING) &&
+ (uapi_info->flags & GPIO_V2_LINE_FLAG_EDGE_FALLING))
+ info->edge = GPIOD_LINE_EDGE_BOTH;
+ else if (uapi_info->flags & GPIO_V2_LINE_FLAG_EDGE_RISING)
+ info->edge = GPIOD_LINE_EDGE_RISING;
+ else if (uapi_info->flags & GPIO_V2_LINE_FLAG_EDGE_FALLING)
+ info->edge = GPIOD_LINE_EDGE_FALLING;
+ else
+ info->edge = GPIOD_LINE_EDGE_NONE;
+
+ if (uapi_info->flags & GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME)
+ info->event_clock = GPIOD_LINE_CLOCK_REALTIME;
+ else if (uapi_info->flags & GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE)
+ info->event_clock = GPIOD_LINE_CLOCK_HTE;
+ else
+ info->event_clock = GPIOD_LINE_CLOCK_MONOTONIC;
+
+ /*
+ * We assume that the kernel returns correct configuration and that no
+ * attributes repeat.
+ */
+ for (i = 0; i < uapi_info->num_attrs; i++) {
+ attr = &uapi_info->attrs[i];
+
+ if (attr->id == GPIO_V2_LINE_ATTR_ID_DEBOUNCE) {
+ info->debounced = true;
+ info->debounce_period_us = attr->debounce_period_us;
+ }
+ }
+
+ return info;
+}
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <assert.h>
+#include <errno.h>
+#include <gpiod.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/param.h>
+#include <unistd.h>
+
+#include "internal.h"
+
+struct gpiod_line_request {
+ char *chip_name;
+ unsigned int offsets[GPIO_V2_LINES_MAX];
+ size_t num_lines;
+ int fd;
+};
+
+struct gpiod_line_request *
+gpiod_line_request_from_uapi(struct gpio_v2_line_request *uapi_req,
+ const char *chip_name)
+{
+ struct gpiod_line_request *request;
+
+ request = malloc(sizeof(*request));
+ if (!request)
+ return NULL;
+
+ memset(request, 0, sizeof(*request));
+
+ request->chip_name = strdup(chip_name);
+ if (!request->chip_name) {
+ free(request);
+ return NULL;
+ }
+
+ request->fd = uapi_req->fd;
+ request->num_lines = uapi_req->num_lines;
+ memcpy(request->offsets, uapi_req->offsets,
+ sizeof(*request->offsets) * request->num_lines);
+
+ return request;
+}
+
+GPIOD_API void gpiod_line_request_release(struct gpiod_line_request *request)
+{
+ if (!request)
+ return;
+
+ close(request->fd);
+ free(request->chip_name);
+ free(request);
+}
+
+GPIOD_API const char *
+gpiod_line_request_get_chip_name(struct gpiod_line_request *request)
+{
+ assert(request);
+
+ return request->chip_name;
+}
+
+GPIOD_API size_t
+gpiod_line_request_get_num_requested_lines(struct gpiod_line_request *request)
+{
+ assert(request);
+
+ return request->num_lines;
+}
+
+GPIOD_API size_t
+gpiod_line_request_get_requested_offsets(struct gpiod_line_request *request,
+ unsigned int *offsets,
+ size_t max_offsets)
+{
+ size_t num_offsets;
+
+ assert(request);
+
+ if (!offsets || !max_offsets)
+ return 0;
+
+ num_offsets = MIN(request->num_lines, max_offsets);
+
+ memcpy(offsets, request->offsets, sizeof(*offsets) * num_offsets);
+
+ return num_offsets;
+}
+
+GPIOD_API enum gpiod_line_value
+gpiod_line_request_get_value(struct gpiod_line_request *request,
+ unsigned int offset)
+{
+ enum gpiod_line_value val;
+ unsigned int ret;
+
+ assert(request);
+
+ ret = gpiod_line_request_get_values_subset(request, 1, &offset, &val);
+ if (ret)
+ return GPIOD_LINE_VALUE_ERROR;
+
+ return val;
+}
+
+static int offset_to_bit(struct gpiod_line_request *request,
+ unsigned int offset)
+{
+ size_t i;
+
+ assert(request);
+
+ for (i = 0; i < request->num_lines; i++) {
+ if (offset == request->offsets[i])
+ return i;
+ }
+
+ return -1;
+}
+
+GPIOD_API int
+gpiod_line_request_get_values_subset(struct gpiod_line_request *request,
+ size_t num_values,
+ const unsigned int *offsets,
+ enum gpiod_line_value *values)
+{
+ struct gpio_v2_line_values uapi_values;
+ uint64_t mask = 0, bits = 0;
+ size_t i;
+ int bit, ret;
+
+ assert(request);
+
+ if (!offsets || !values) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ uapi_values.bits = 0;
+
+ for (i = 0; i < num_values; i++) {
+ bit = offset_to_bit(request, offsets[i]);
+ if (bit < 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ gpiod_line_mask_set_bit(&mask, bit);
+ }
+
+ uapi_values.mask = mask;
+
+ ret = gpiod_ioctl(request->fd, GPIO_V2_LINE_GET_VALUES_IOCTL,
+ &uapi_values);
+ if (ret)
+ return -1;
+
+ bits = uapi_values.bits;
+ memset(values, 0, sizeof(*values) * num_values);
+
+ for (i = 0; i < num_values; i++) {
+ bit = offset_to_bit(request, offsets[i]);
+ values[i] = gpiod_line_mask_test_bit(&bits, bit) ? 1 : 0;
+ }
+
+ return 0;
+}
+
+GPIOD_API int gpiod_line_request_get_values(struct gpiod_line_request *request,
+ enum gpiod_line_value *values)
+{
+ assert(request);
+
+ return gpiod_line_request_get_values_subset(request, request->num_lines,
+ request->offsets, values);
+}
+
+GPIOD_API int gpiod_line_request_set_value(struct gpiod_line_request *request,
+ unsigned int offset,
+ enum gpiod_line_value value)
+{
+ return gpiod_line_request_set_values_subset(request, 1,
+ &offset, &value);
+}
+
+GPIOD_API int
+gpiod_line_request_set_values_subset(struct gpiod_line_request *request,
+ size_t num_values,
+ const unsigned int *offsets,
+ const enum gpiod_line_value *values)
+{
+ struct gpio_v2_line_values uapi_values;
+ uint64_t mask = 0, bits = 0;
+ size_t i;
+ int bit;
+
+ assert(request);
+
+ if (!offsets || !values) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ for (i = 0; i < num_values; i++) {
+ bit = offset_to_bit(request, offsets[i]);
+ if (bit < 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ gpiod_line_mask_set_bit(&mask, bit);
+ gpiod_line_mask_assign_bit(&bits, bit, values[i]);
+ }
+
+ memset(&uapi_values, 0, sizeof(uapi_values));
+ uapi_values.mask = mask;
+ uapi_values.bits = bits;
+
+ return gpiod_ioctl(request->fd, GPIO_V2_LINE_SET_VALUES_IOCTL,
+ &uapi_values);
+}
+
+GPIOD_API int gpiod_line_request_set_values(struct gpiod_line_request *request,
+ const enum gpiod_line_value *values)
+{
+ assert(request);
+
+ return gpiod_line_request_set_values_subset(request, request->num_lines,
+ request->offsets, values);
+}
+
+static bool offsets_equal(struct gpiod_line_request *request,
+ struct gpio_v2_line_request *uapi_cfg)
+{
+ size_t i;
+
+ if (request->num_lines != uapi_cfg->num_lines)
+ return false;
+
+ for (i = 0; i < request->num_lines; i++) {
+ if (request->offsets[i] != uapi_cfg->offsets[i])
+ return false;
+ }
+
+ return true;
+}
+
+GPIOD_API int
+gpiod_line_request_reconfigure_lines(struct gpiod_line_request *request,
+ struct gpiod_line_config *config)
+{
+ struct gpio_v2_line_request uapi_cfg;
+ int ret;
+
+ assert(request);
+
+ if (!config) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ memset(&uapi_cfg, 0, sizeof(uapi_cfg));
+
+ ret = gpiod_line_config_to_uapi(config, &uapi_cfg);
+ if (ret)
+ return ret;
+
+ if (!offsets_equal(request, &uapi_cfg)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ ret = gpiod_ioctl(request->fd, GPIO_V2_LINE_SET_CONFIG_IOCTL,
+ &uapi_cfg.config);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+GPIOD_API int gpiod_line_request_get_fd(struct gpiod_line_request *request)
+{
+ assert(request);
+
+ return request->fd;
+}
+
+GPIOD_API int
+gpiod_line_request_wait_edge_events(struct gpiod_line_request *request,
+ int64_t timeout_ns)
+{
+ assert(request);
+
+ return gpiod_poll_fd(request->fd, timeout_ns);
+}
+
+GPIOD_API int
+gpiod_line_request_read_edge_events(struct gpiod_line_request *request,
+ struct gpiod_edge_event_buffer *buffer,
+ size_t max_events)
+{
+ assert(request);
+
+ return gpiod_edge_event_buffer_read_fd(request->fd, buffer, max_events);
+}
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <assert.h>
+#include <errno.h>
+#include <gpiod.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "internal.h"
+
+struct gpiod_line_settings {
+ enum gpiod_line_direction direction;
+ enum gpiod_line_edge edge_detection;
+ enum gpiod_line_drive drive;
+ enum gpiod_line_bias bias;
+ bool active_low;
+ enum gpiod_line_clock event_clock;
+ long debounce_period_us;
+ enum gpiod_line_value output_value;
+};
+
+GPIOD_API struct gpiod_line_settings *gpiod_line_settings_new(void)
+{
+ struct gpiod_line_settings *settings;
+
+ settings = malloc(sizeof(*settings));
+ if (!settings)
+ return NULL;
+
+ gpiod_line_settings_reset(settings);
+
+ return settings;
+}
+
+GPIOD_API void gpiod_line_settings_free(struct gpiod_line_settings *settings)
+{
+ free(settings);
+}
+
+GPIOD_API void gpiod_line_settings_reset(struct gpiod_line_settings *settings)
+{
+ assert(settings);
+
+ settings->direction = GPIOD_LINE_DIRECTION_AS_IS;
+ settings->edge_detection = GPIOD_LINE_EDGE_NONE;
+ settings->bias = GPIOD_LINE_BIAS_AS_IS;
+ settings->drive = GPIOD_LINE_DRIVE_PUSH_PULL;
+ settings->active_low = false;
+ settings->debounce_period_us = 0;
+ settings->event_clock = GPIOD_LINE_CLOCK_MONOTONIC;
+ settings->output_value = GPIOD_LINE_VALUE_INACTIVE;
+}
+
+GPIOD_API struct gpiod_line_settings *
+gpiod_line_settings_copy(struct gpiod_line_settings *settings)
+{
+ assert(settings);
+
+ struct gpiod_line_settings *copy;
+
+ copy = malloc(sizeof(*copy));
+ if (!copy)
+ return NULL;
+
+ memcpy(copy, settings, sizeof(*copy));
+
+ return copy;
+}
+
+GPIOD_API int
+gpiod_line_settings_set_direction(struct gpiod_line_settings *settings,
+ enum gpiod_line_direction direction)
+{
+ assert(settings);
+
+ switch (direction) {
+ case GPIOD_LINE_DIRECTION_INPUT:
+ case GPIOD_LINE_DIRECTION_OUTPUT:
+ case GPIOD_LINE_DIRECTION_AS_IS:
+ settings->direction = direction;
+ break;
+ default:
+ settings->direction = GPIOD_LINE_DIRECTION_AS_IS;
+ errno = EINVAL;
+ return -1;
+ }
+
+ return 0;
+}
+
+GPIOD_API enum gpiod_line_direction
+gpiod_line_settings_get_direction(struct gpiod_line_settings *settings)
+{
+ assert(settings);
+
+ return settings->direction;
+}
+
+GPIOD_API int
+gpiod_line_settings_set_edge_detection(struct gpiod_line_settings *settings,
+ enum gpiod_line_edge edge)
+{
+ assert(settings);
+
+ switch (edge) {
+ case GPIOD_LINE_EDGE_NONE:
+ case GPIOD_LINE_EDGE_RISING:
+ case GPIOD_LINE_EDGE_FALLING:
+ case GPIOD_LINE_EDGE_BOTH:
+ settings->edge_detection = edge;
+ break;
+ default:
+ settings->edge_detection = GPIOD_LINE_EDGE_NONE;
+ errno = EINVAL;
+ return -1;
+ }
+
+ return 0;
+}
+
+GPIOD_API enum gpiod_line_edge
+gpiod_line_settings_get_edge_detection(struct gpiod_line_settings *settings)
+{
+ assert(settings);
+
+ return settings->edge_detection;
+}
+
+GPIOD_API int gpiod_line_settings_set_bias(struct gpiod_line_settings *settings,
+ enum gpiod_line_bias bias)
+{
+ assert(settings);
+
+ switch (bias) {
+ case GPIOD_LINE_BIAS_AS_IS:
+ case GPIOD_LINE_BIAS_DISABLED:
+ case GPIOD_LINE_BIAS_PULL_UP:
+ case GPIOD_LINE_BIAS_PULL_DOWN:
+ settings->bias = bias;
+ break;
+ default:
+ settings->bias = GPIOD_LINE_BIAS_AS_IS;
+ errno = EINVAL;
+ return -1;
+ }
+
+ return 0;
+}
+
+GPIOD_API enum gpiod_line_bias
+gpiod_line_settings_get_bias(struct gpiod_line_settings *settings)
+{
+ assert(settings);
+
+ return settings->bias;
+}
+
+GPIOD_API int
+gpiod_line_settings_set_drive(struct gpiod_line_settings *settings,
+ enum gpiod_line_drive drive)
+{
+ assert(settings);
+
+ switch (drive) {
+ case GPIOD_LINE_DRIVE_PUSH_PULL:
+ case GPIOD_LINE_DRIVE_OPEN_DRAIN:
+ case GPIOD_LINE_DRIVE_OPEN_SOURCE:
+ settings->drive = drive;
+ break;
+ default:
+ settings->drive = GPIOD_LINE_DRIVE_PUSH_PULL;
+ errno = EINVAL;
+ return -1;
+ }
+
+ return 0;
+}
+
+GPIOD_API enum gpiod_line_drive
+gpiod_line_settings_get_drive(struct gpiod_line_settings *settings)
+{
+ assert(settings);
+
+ return settings->drive;
+}
+
+GPIOD_API void
+gpiod_line_settings_set_active_low(struct gpiod_line_settings *settings,
+ bool active_low)
+{
+ assert(settings);
+
+ settings->active_low = active_low;
+}
+
+GPIOD_API bool
+gpiod_line_settings_get_active_low(struct gpiod_line_settings *settings)
+{
+ assert(settings);
+
+ return settings->active_low;
+}
+
+GPIOD_API void
+gpiod_line_settings_set_debounce_period_us(struct gpiod_line_settings *settings,
+ unsigned long period)
+{
+ assert(settings);
+
+ settings->debounce_period_us = period;
+}
+
+GPIOD_API unsigned long
+gpiod_line_settings_get_debounce_period_us(struct gpiod_line_settings *settings)
+{
+ assert(settings);
+
+ return settings->debounce_period_us;
+}
+
+GPIOD_API int
+gpiod_line_settings_set_event_clock(struct gpiod_line_settings *settings,
+ enum gpiod_line_clock event_clock)
+{
+ assert(settings);
+
+ switch (event_clock) {
+ case GPIOD_LINE_CLOCK_MONOTONIC:
+ case GPIOD_LINE_CLOCK_REALTIME:
+ case GPIOD_LINE_CLOCK_HTE:
+ settings->event_clock = event_clock;
+ break;
+ default:
+ settings->event_clock = GPIOD_LINE_CLOCK_MONOTONIC;
+ errno = EINVAL;
+ return -1;
+ }
+
+ return 0;
+}
+
+GPIOD_API enum gpiod_line_clock
+gpiod_line_settings_get_event_clock(struct gpiod_line_settings *settings)
+{
+ assert(settings);
+
+ return settings->event_clock;
+}
+
+GPIOD_API int
+gpiod_line_settings_set_output_value(struct gpiod_line_settings *settings,
+ enum gpiod_line_value value)
+{
+ assert(settings);
+
+ return gpiod_set_output_value(value, &settings->output_value);
+}
+
+GPIOD_API enum gpiod_line_value
+gpiod_line_settings_get_output_value(struct gpiod_line_settings *settings)
+{
+ assert(settings);
+
+ return settings->output_value;
+}
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2017-2022 Bartosz Golaszewski <bartekgola@gmail.com>
+
+#include <gpiod.h>
+
+#include "internal.h"
+
+GPIOD_API bool gpiod_is_gpiochip_device(const char *path)
+{
+ return gpiod_check_gpiochip_device(path, false);
+}
+
+GPIOD_API const char *gpiod_api_version(void)
+{
+ return GPIOD_VERSION_STR;
+}
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <assert.h>
+#include <errno.h>
+#include <gpiod.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "internal.h"
+
+struct gpiod_request_config {
+ char consumer[GPIO_MAX_NAME_SIZE];
+ size_t event_buffer_size;
+};
+
+GPIOD_API struct gpiod_request_config *gpiod_request_config_new(void)
+{
+ struct gpiod_request_config *config;
+
+ config = malloc(sizeof(*config));
+ if (!config)
+ return NULL;
+
+ memset(config, 0, sizeof(*config));
+
+ return config;
+}
+
+GPIOD_API void gpiod_request_config_free(struct gpiod_request_config *config)
+{
+ free(config);
+}
+
+GPIOD_API void
+gpiod_request_config_set_consumer(struct gpiod_request_config *config,
+ const char *consumer)
+{
+ assert(config);
+
+ if (!consumer) {
+ config->consumer[0] = '\0';
+ } else {
+ strncpy(config->consumer, consumer, GPIO_MAX_NAME_SIZE - 1);
+ config->consumer[GPIO_MAX_NAME_SIZE - 1] = '\0';
+ }
+}
+
+GPIOD_API const char *
+gpiod_request_config_get_consumer(struct gpiod_request_config *config)
+{
+ assert(config);
+
+ return config->consumer[0] == '\0' ? NULL : config->consumer;
+}
+
+GPIOD_API void
+gpiod_request_config_set_event_buffer_size(struct gpiod_request_config *config,
+ size_t event_buffer_size)
+{
+ assert(config);
+
+ config->event_buffer_size = event_buffer_size;
+}
+
+GPIOD_API size_t
+gpiod_request_config_get_event_buffer_size(struct gpiod_request_config *config)
+{
+ assert(config);
+
+ return config->event_buffer_size;
+}
+
+void gpiod_request_config_to_uapi(struct gpiod_request_config *config,
+ struct gpio_v2_line_request *uapi_req)
+{
+ strcpy(uapi_req->consumer, config->consumer);
+ uapi_req->event_buffer_size = config->event_buffer_size;
+}
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */
+/*
+ * <linux/gpio.h> - userspace ABI for the GPIO character devices
+ *
+ * Copyright (C) 2016 Linus Walleij
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ */
+#ifndef _UAPI_GPIO_H_
+#define _UAPI_GPIO_H_
+
+#include <linux/const.h>
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+/*
+ * The maximum size of name and label arrays.
+ *
+ * Must be a multiple of 8 to ensure 32/64-bit alignment of structs.
+ */
+#define GPIO_MAX_NAME_SIZE 32
+
+/**
+ * struct gpiochip_info - Information about a certain GPIO chip
+ * @name: the Linux kernel name of this GPIO chip
+ * @label: a functional name for this GPIO chip, such as a product
+ * number, may be empty (i.e. label[0] == '\0')
+ * @lines: number of GPIO lines on this chip
+ */
+struct gpiochip_info {
+ char name[GPIO_MAX_NAME_SIZE];
+ char label[GPIO_MAX_NAME_SIZE];
+ __u32 lines;
+};
+
+/*
+ * Maximum number of requested lines.
+ *
+ * Must be no greater than 64, as bitmaps are restricted here to 64-bits
+ * for simplicity, and a multiple of 2 to ensure 32/64-bit alignment of
+ * structs.
+ */
+#define GPIO_V2_LINES_MAX 64
+
+/*
+ * The maximum number of configuration attributes associated with a line
+ * request.
+ */
+#define GPIO_V2_LINE_NUM_ATTRS_MAX 10
+
+/**
+ * enum gpio_v2_line_flag - &struct gpio_v2_line_attribute.flags values
+ * @GPIO_V2_LINE_FLAG_USED: line is not available for request
+ * @GPIO_V2_LINE_FLAG_ACTIVE_LOW: line active state is physical low
+ * @GPIO_V2_LINE_FLAG_INPUT: line is an input
+ * @GPIO_V2_LINE_FLAG_OUTPUT: line is an output
+ * @GPIO_V2_LINE_FLAG_EDGE_RISING: line detects rising (inactive to active)
+ * edges
+ * @GPIO_V2_LINE_FLAG_EDGE_FALLING: line detects falling (active to
+ * inactive) edges
+ * @GPIO_V2_LINE_FLAG_OPEN_DRAIN: line is an open drain output
+ * @GPIO_V2_LINE_FLAG_OPEN_SOURCE: line is an open source output
+ * @GPIO_V2_LINE_FLAG_BIAS_PULL_UP: line has pull-up bias enabled
+ * @GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN: line has pull-down bias enabled
+ * @GPIO_V2_LINE_FLAG_BIAS_DISABLED: line has bias disabled
+ * @GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME: line events contain REALTIME timestamps
+ * @GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE: line events contain timestamps from
+ * hardware timestamp engine
+ */
+enum gpio_v2_line_flag {
+ GPIO_V2_LINE_FLAG_USED = _BITULL(0),
+ GPIO_V2_LINE_FLAG_ACTIVE_LOW = _BITULL(1),
+ GPIO_V2_LINE_FLAG_INPUT = _BITULL(2),
+ GPIO_V2_LINE_FLAG_OUTPUT = _BITULL(3),
+ GPIO_V2_LINE_FLAG_EDGE_RISING = _BITULL(4),
+ GPIO_V2_LINE_FLAG_EDGE_FALLING = _BITULL(5),
+ GPIO_V2_LINE_FLAG_OPEN_DRAIN = _BITULL(6),
+ GPIO_V2_LINE_FLAG_OPEN_SOURCE = _BITULL(7),
+ GPIO_V2_LINE_FLAG_BIAS_PULL_UP = _BITULL(8),
+ GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN = _BITULL(9),
+ GPIO_V2_LINE_FLAG_BIAS_DISABLED = _BITULL(10),
+ GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME = _BITULL(11),
+ GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE = _BITULL(12),
+};
+
+/**
+ * struct gpio_v2_line_values - Values of GPIO lines
+ * @bits: a bitmap containing the value of the lines, set to 1 for active
+ * and 0 for inactive.
+ * @mask: a bitmap identifying the lines to get or set, with each bit
+ * number corresponding to the index into &struct
+ * gpio_v2_line_request.offsets.
+ */
+struct gpio_v2_line_values {
+ __aligned_u64 bits;
+ __aligned_u64 mask;
+};
+
+/**
+ * enum gpio_v2_line_attr_id - &struct gpio_v2_line_attribute.id values
+ * identifying which field of the attribute union is in use.
+ * @GPIO_V2_LINE_ATTR_ID_FLAGS: flags field is in use
+ * @GPIO_V2_LINE_ATTR_ID_OUTPUT_VALUES: values field is in use
+ * @GPIO_V2_LINE_ATTR_ID_DEBOUNCE: debounce_period_us field is in use
+ */
+enum gpio_v2_line_attr_id {
+ GPIO_V2_LINE_ATTR_ID_FLAGS = 1,
+ GPIO_V2_LINE_ATTR_ID_OUTPUT_VALUES = 2,
+ GPIO_V2_LINE_ATTR_ID_DEBOUNCE = 3,
+};
+
+/**
+ * struct gpio_v2_line_attribute - a configurable attribute of a line
+ * @id: attribute identifier with value from &enum gpio_v2_line_attr_id
+ * @padding: reserved for future use and must be zero filled
+ * @flags: if id is %GPIO_V2_LINE_ATTR_ID_FLAGS, the flags for the GPIO
+ * line, with values from &enum gpio_v2_line_flag, such as
+ * %GPIO_V2_LINE_FLAG_ACTIVE_LOW, %GPIO_V2_LINE_FLAG_OUTPUT etc, added
+ * together. This overrides the default flags contained in the &struct
+ * gpio_v2_line_config for the associated line.
+ * @values: if id is %GPIO_V2_LINE_ATTR_ID_OUTPUT_VALUES, a bitmap
+ * containing the values to which the lines will be set, with each bit
+ * number corresponding to the index into &struct
+ * gpio_v2_line_request.offsets.
+ * @debounce_period_us: if id is %GPIO_V2_LINE_ATTR_ID_DEBOUNCE, the
+ * desired debounce period, in microseconds
+ */
+struct gpio_v2_line_attribute {
+ __u32 id;
+ __u32 padding;
+ union {
+ __aligned_u64 flags;
+ __aligned_u64 values;
+ __u32 debounce_period_us;
+ };
+};
+
+/**
+ * struct gpio_v2_line_config_attribute - a configuration attribute
+ * associated with one or more of the requested lines.
+ * @attr: the configurable attribute
+ * @mask: a bitmap identifying the lines to which the attribute applies,
+ * with each bit number corresponding to the index into &struct
+ * gpio_v2_line_request.offsets.
+ */
+struct gpio_v2_line_config_attribute {
+ struct gpio_v2_line_attribute attr;
+ __aligned_u64 mask;
+};
+
+/**
+ * struct gpio_v2_line_config - Configuration for GPIO lines
+ * @flags: flags for the GPIO lines, with values from &enum
+ * gpio_v2_line_flag, such as %GPIO_V2_LINE_FLAG_ACTIVE_LOW,
+ * %GPIO_V2_LINE_FLAG_OUTPUT etc, added together. This is the default for
+ * all requested lines but may be overridden for particular lines using
+ * @attrs.
+ * @num_attrs: the number of attributes in @attrs
+ * @padding: reserved for future use and must be zero filled
+ * @attrs: the configuration attributes associated with the requested
+ * lines. Any attribute should only be associated with a particular line
+ * once. If an attribute is associated with a line multiple times then the
+ * first occurrence (i.e. lowest index) has precedence.
+ */
+struct gpio_v2_line_config {
+ __aligned_u64 flags;
+ __u32 num_attrs;
+ /* Pad to fill implicit padding and reserve space for future use. */
+ __u32 padding[5];
+ struct gpio_v2_line_config_attribute attrs[GPIO_V2_LINE_NUM_ATTRS_MAX];
+};
+
+/**
+ * struct gpio_v2_line_request - Information about a request for GPIO lines
+ * @offsets: an array of desired lines, specified by offset index for the
+ * associated GPIO chip
+ * @consumer: a desired consumer label for the selected GPIO lines such as
+ * "my-bitbanged-relay"
+ * @config: requested configuration for the lines.
+ * @num_lines: number of lines requested in this request, i.e. the number
+ * of valid fields in the %GPIO_V2_LINES_MAX sized arrays, set to 1 to
+ * request a single line
+ * @event_buffer_size: a suggested minimum number of line events that the
+ * kernel should buffer. This is only relevant if edge detection is
+ * enabled in the configuration. Note that this is only a suggested value
+ * and the kernel may allocate a larger buffer or cap the size of the
+ * buffer. If this field is zero then the buffer size defaults to a minimum
+ * of @num_lines * 16.
+ * @padding: reserved for future use and must be zero filled
+ * @fd: if successful this field will contain a valid anonymous file handle
+ * after a %GPIO_GET_LINE_IOCTL operation, zero or negative value means
+ * error
+ */
+struct gpio_v2_line_request {
+ __u32 offsets[GPIO_V2_LINES_MAX];
+ char consumer[GPIO_MAX_NAME_SIZE];
+ struct gpio_v2_line_config config;
+ __u32 num_lines;
+ __u32 event_buffer_size;
+ /* Pad to fill implicit padding and reserve space for future use. */
+ __u32 padding[5];
+ __s32 fd;
+};
+
+/**
+ * struct gpio_v2_line_info - Information about a certain GPIO line
+ * @name: 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, may be empty (i.e. name[0] == '\0')
+ * @consumer: a functional name for the consumer of this GPIO line as set
+ * by whatever is using it, will be empty if there is no current user but
+ * may also be empty if the consumer doesn't set this up
+ * @offset: the local offset on this GPIO chip, fill this in when
+ * requesting the line information from the kernel
+ * @num_attrs: the number of attributes in @attrs
+ * @flags: flags for this GPIO line, with values from &enum
+ * gpio_v2_line_flag, such as %GPIO_V2_LINE_FLAG_ACTIVE_LOW,
+ * %GPIO_V2_LINE_FLAG_OUTPUT etc, added together.
+ * @attrs: the configuration attributes associated with the line
+ * @padding: reserved for future use
+ */
+struct gpio_v2_line_info {
+ char name[GPIO_MAX_NAME_SIZE];
+ char consumer[GPIO_MAX_NAME_SIZE];
+ __u32 offset;
+ __u32 num_attrs;
+ __aligned_u64 flags;
+ struct gpio_v2_line_attribute attrs[GPIO_V2_LINE_NUM_ATTRS_MAX];
+ /* Space reserved for future use. */
+ __u32 padding[4];
+};
+
+/**
+ * enum gpio_v2_line_changed_type - &struct gpio_v2_line_changed.event_type
+ * values
+ * @GPIO_V2_LINE_CHANGED_REQUESTED: line has been requested
+ * @GPIO_V2_LINE_CHANGED_RELEASED: line has been released
+ * @GPIO_V2_LINE_CHANGED_CONFIG: line has been reconfigured
+ */
+enum gpio_v2_line_changed_type {
+ GPIO_V2_LINE_CHANGED_REQUESTED = 1,
+ GPIO_V2_LINE_CHANGED_RELEASED = 2,
+ GPIO_V2_LINE_CHANGED_CONFIG = 3,
+};
+
+/**
+ * struct gpio_v2_line_info_changed - Information about a change in status
+ * of a GPIO line
+ * @info: updated line information
+ * @timestamp_ns: estimate of time of status change occurrence, in nanoseconds
+ * @event_type: the type of change with a value from &enum
+ * gpio_v2_line_changed_type
+ * @padding: reserved for future use
+ */
+struct gpio_v2_line_info_changed {
+ struct gpio_v2_line_info info;
+ __aligned_u64 timestamp_ns;
+ __u32 event_type;
+ /* Pad struct to 64-bit boundary and reserve space for future use. */
+ __u32 padding[5];
+};
+
+/**
+ * enum gpio_v2_line_event_id - &struct gpio_v2_line_event.id values
+ * @GPIO_V2_LINE_EVENT_RISING_EDGE: event triggered by a rising edge
+ * @GPIO_V2_LINE_EVENT_FALLING_EDGE: event triggered by a falling edge
+ */
+enum gpio_v2_line_event_id {
+ GPIO_V2_LINE_EVENT_RISING_EDGE = 1,
+ GPIO_V2_LINE_EVENT_FALLING_EDGE = 2,
+};
+
+/**
+ * struct gpio_v2_line_event - The actual event being pushed to userspace
+ * @timestamp_ns: best estimate of time of event occurrence, in nanoseconds.
+ * @id: event identifier with value from &enum gpio_v2_line_event_id
+ * @offset: the offset of the line that triggered the event
+ * @seqno: the sequence number for this event in the sequence of events for
+ * all the lines in this line request
+ * @line_seqno: the sequence number for this event in the sequence of
+ * events on this particular line
+ * @padding: reserved for future use
+ *
+ * By default the @timestamp_ns is read from %CLOCK_MONOTONIC and is
+ * intended to allow the accurate measurement of the time between events.
+ * It does not provide the wall-clock time.
+ *
+ * If the %GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME flag is set then the
+ * @timestamp_ns is read from %CLOCK_REALTIME.
+ */
+struct gpio_v2_line_event {
+ __aligned_u64 timestamp_ns;
+ __u32 id;
+ __u32 offset;
+ __u32 seqno;
+ __u32 line_seqno;
+ /* Space reserved for future use. */
+ __u32 padding[6];
+};
+
+/*
+ * ABI v1
+ *
+ * This version of the ABI is deprecated.
+ * Use the latest version of the ABI, defined above, instead.
+ */
+
+/* Informational flags */
+#define GPIOLINE_FLAG_KERNEL (1UL << 0) /* Line used by the kernel */
+#define GPIOLINE_FLAG_IS_OUT (1UL << 1)
+#define GPIOLINE_FLAG_ACTIVE_LOW (1UL << 2)
+#define GPIOLINE_FLAG_OPEN_DRAIN (1UL << 3)
+#define GPIOLINE_FLAG_OPEN_SOURCE (1UL << 4)
+#define GPIOLINE_FLAG_BIAS_PULL_UP (1UL << 5)
+#define GPIOLINE_FLAG_BIAS_PULL_DOWN (1UL << 6)
+#define GPIOLINE_FLAG_BIAS_DISABLE (1UL << 7)
+
+/**
+ * struct gpioline_info - Information about a certain GPIO line
+ * @line_offset: the local offset on this GPIO device, fill this in when
+ * requesting the line information from the kernel
+ * @flags: various flags for this line
+ * @name: 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, may be empty (i.e. name[0] == '\0')
+ * @consumer: a functional name for the consumer of this GPIO line as set by
+ * whatever is using it, will be empty if there is no current user but may
+ * also be empty if the consumer doesn't set this up
+ *
+ * Note: This struct is part of ABI v1 and is deprecated.
+ * Use &struct gpio_v2_line_info instead.
+ */
+struct gpioline_info {
+ __u32 line_offset;
+ __u32 flags;
+ char name[GPIO_MAX_NAME_SIZE];
+ char consumer[GPIO_MAX_NAME_SIZE];
+};
+
+/* Maximum number of requested handles */
+#define GPIOHANDLES_MAX 64
+
+/* Possible line status change events */
+enum {
+ GPIOLINE_CHANGED_REQUESTED = 1,
+ GPIOLINE_CHANGED_RELEASED,
+ GPIOLINE_CHANGED_CONFIG,
+};
+
+/**
+ * struct gpioline_info_changed - Information about a change in status
+ * of a GPIO line
+ * @info: updated line information
+ * @timestamp: estimate of time of status change occurrence, in nanoseconds
+ * @event_type: one of %GPIOLINE_CHANGED_REQUESTED,
+ * %GPIOLINE_CHANGED_RELEASED and %GPIOLINE_CHANGED_CONFIG
+ * @padding: reserved for future use
+ *
+ * The &struct gpioline_info embedded here has 32-bit alignment on its own,
+ * but it works fine with 64-bit alignment too. With its 72 byte size, we can
+ * guarantee there are no implicit holes between it and subsequent members.
+ * The 20-byte padding at the end makes sure we don't add any implicit padding
+ * at the end of the structure on 64-bit architectures.
+ *
+ * Note: This struct is part of ABI v1 and is deprecated.
+ * Use &struct gpio_v2_line_info_changed instead.
+ */
+struct gpioline_info_changed {
+ struct gpioline_info info;
+ __u64 timestamp;
+ __u32 event_type;
+ __u32 padding[5]; /* for future use */
+};
+
+/* Linerequest flags */
+#define GPIOHANDLE_REQUEST_INPUT (1UL << 0)
+#define GPIOHANDLE_REQUEST_OUTPUT (1UL << 1)
+#define GPIOHANDLE_REQUEST_ACTIVE_LOW (1UL << 2)
+#define GPIOHANDLE_REQUEST_OPEN_DRAIN (1UL << 3)
+#define GPIOHANDLE_REQUEST_OPEN_SOURCE (1UL << 4)
+#define GPIOHANDLE_REQUEST_BIAS_PULL_UP (1UL << 5)
+#define GPIOHANDLE_REQUEST_BIAS_PULL_DOWN (1UL << 6)
+#define GPIOHANDLE_REQUEST_BIAS_DISABLE (1UL << 7)
+
+/**
+ * struct gpiohandle_request - Information about a GPIO handle request
+ * @lineoffsets: an array of desired lines, specified by offset index for the
+ * associated GPIO device
+ * @flags: desired flags for the desired GPIO lines, such as
+ * %GPIOHANDLE_REQUEST_OUTPUT, %GPIOHANDLE_REQUEST_ACTIVE_LOW etc, added
+ * together. Note that even if multiple lines are requested, the same flags
+ * must be applicable to all of them, if you want lines with individual
+ * flags set, request them one by one. It is possible to select
+ * a batch of input or output lines, but they must all have the same
+ * characteristics, i.e. all inputs or all outputs, all active low etc
+ * @default_values: if the %GPIOHANDLE_REQUEST_OUTPUT is set for a requested
+ * line, this specifies the default output value, should be 0 (low) or
+ * 1 (high), anything else than 0 or 1 will be interpreted as 1 (high)
+ * @consumer_label: a desired consumer label for the selected GPIO line(s)
+ * such as "my-bitbanged-relay"
+ * @lines: number of lines requested in this request, i.e. the number of
+ * valid fields in the above arrays, set to 1 to request a single line
+ * @fd: if successful this field will contain a valid anonymous file handle
+ * after a %GPIO_GET_LINEHANDLE_IOCTL operation, zero or negative value
+ * means error
+ *
+ * Note: This struct is part of ABI v1 and is deprecated.
+ * Use &struct gpio_v2_line_request instead.
+ */
+struct gpiohandle_request {
+ __u32 lineoffsets[GPIOHANDLES_MAX];
+ __u32 flags;
+ __u8 default_values[GPIOHANDLES_MAX];
+ char consumer_label[GPIO_MAX_NAME_SIZE];
+ __u32 lines;
+ int fd;
+};
+
+/**
+ * struct gpiohandle_config - Configuration for a GPIO handle request
+ * @flags: updated flags for the requested GPIO lines, such as
+ * %GPIOHANDLE_REQUEST_OUTPUT, %GPIOHANDLE_REQUEST_ACTIVE_LOW etc, added
+ * together
+ * @default_values: if the %GPIOHANDLE_REQUEST_OUTPUT is set in flags,
+ * this specifies the default output value, should be 0 (low) or
+ * 1 (high), anything else than 0 or 1 will be interpreted as 1 (high)
+ * @padding: reserved for future use and should be zero filled
+ *
+ * Note: This struct is part of ABI v1 and is deprecated.
+ * Use &struct gpio_v2_line_config instead.
+ */
+struct gpiohandle_config {
+ __u32 flags;
+ __u8 default_values[GPIOHANDLES_MAX];
+ __u32 padding[4]; /* padding for future use */
+};
+
+/**
+ * struct gpiohandle_data - Information of values on a GPIO handle
+ * @values: when getting the state of lines this contains the current
+ * state of a line, when setting the state of lines these should contain
+ * the desired target state
+ *
+ * Note: This struct is part of ABI v1 and is deprecated.
+ * Use &struct gpio_v2_line_values instead.
+ */
+struct gpiohandle_data {
+ __u8 values[GPIOHANDLES_MAX];
+};
+
+/* Eventrequest flags */
+#define GPIOEVENT_REQUEST_RISING_EDGE (1UL << 0)
+#define GPIOEVENT_REQUEST_FALLING_EDGE (1UL << 1)
+#define GPIOEVENT_REQUEST_BOTH_EDGES ((1UL << 0) | (1UL << 1))
+
+/**
+ * struct gpioevent_request - Information about a GPIO event request
+ * @lineoffset: the desired line to subscribe to events from, specified by
+ * offset index for the associated GPIO device
+ * @handleflags: desired handle flags for the desired GPIO line, such as
+ * %GPIOHANDLE_REQUEST_ACTIVE_LOW or %GPIOHANDLE_REQUEST_OPEN_DRAIN
+ * @eventflags: desired flags for the desired GPIO event line, such as
+ * %GPIOEVENT_REQUEST_RISING_EDGE or %GPIOEVENT_REQUEST_FALLING_EDGE
+ * @consumer_label: a desired consumer label for the selected GPIO line(s)
+ * such as "my-listener"
+ * @fd: if successful this field will contain a valid anonymous file handle
+ * after a %GPIO_GET_LINEEVENT_IOCTL operation, zero or negative value
+ * means error
+ *
+ * Note: This struct is part of ABI v1 and is deprecated.
+ * Use &struct gpio_v2_line_request instead.
+ */
+struct gpioevent_request {
+ __u32 lineoffset;
+ __u32 handleflags;
+ __u32 eventflags;
+ char consumer_label[GPIO_MAX_NAME_SIZE];
+ int fd;
+};
+
+/*
+ * GPIO event types
+ */
+#define GPIOEVENT_EVENT_RISING_EDGE 0x01
+#define GPIOEVENT_EVENT_FALLING_EDGE 0x02
+
+/**
+ * struct gpioevent_data - The actual event being pushed to userspace
+ * @timestamp: best estimate of time of event occurrence, in nanoseconds
+ * @id: event identifier
+ *
+ * Note: This struct is part of ABI v1 and is deprecated.
+ * Use &struct gpio_v2_line_event instead.
+ */
+struct gpioevent_data {
+ __u64 timestamp;
+ __u32 id;
+};
+
+/*
+ * v1 and v2 ioctl()s
+ */
+#define GPIO_GET_CHIPINFO_IOCTL _IOR(0xB4, 0x01, struct gpiochip_info)
+#define GPIO_GET_LINEINFO_UNWATCH_IOCTL _IOWR(0xB4, 0x0C, __u32)
+
+/*
+ * v2 ioctl()s
+ */
+#define GPIO_V2_GET_LINEINFO_IOCTL _IOWR(0xB4, 0x05, struct gpio_v2_line_info)
+#define GPIO_V2_GET_LINEINFO_WATCH_IOCTL _IOWR(0xB4, 0x06, struct gpio_v2_line_info)
+#define GPIO_V2_GET_LINE_IOCTL _IOWR(0xB4, 0x07, struct gpio_v2_line_request)
+#define GPIO_V2_LINE_SET_CONFIG_IOCTL _IOWR(0xB4, 0x0D, struct gpio_v2_line_config)
+#define GPIO_V2_LINE_GET_VALUES_IOCTL _IOWR(0xB4, 0x0E, struct gpio_v2_line_values)
+#define GPIO_V2_LINE_SET_VALUES_IOCTL _IOWR(0xB4, 0x0F, struct gpio_v2_line_values)
+
+/*
+ * v1 ioctl()s
+ *
+ * These ioctl()s are deprecated. Use the v2 equivalent instead.
+ */
+#define GPIO_GET_LINEINFO_IOCTL _IOWR(0xB4, 0x02, struct gpioline_info)
+#define GPIO_GET_LINEHANDLE_IOCTL _IOWR(0xB4, 0x03, struct gpiohandle_request)
+#define GPIO_GET_LINEEVENT_IOCTL _IOWR(0xB4, 0x04, struct gpioevent_request)
+#define GPIOHANDLE_GET_LINE_VALUES_IOCTL _IOWR(0xB4, 0x08, struct gpiohandle_data)
+#define GPIOHANDLE_SET_LINE_VALUES_IOCTL _IOWR(0xB4, 0x09, struct gpiohandle_data)
+#define GPIOHANDLE_SET_CONFIG_IOCTL _IOWR(0xB4, 0x0A, struct gpiohandle_config)
+#define GPIO_GET_LINEINFO_WATCH_IOCTL _IOWR(0xB4, 0x0B, struct gpioline_info)
+
+#endif /* _UAPI_GPIO_H_ */
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+
+*.man
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+
+if WITH_MANPAGES
+
+dist_man1_MANS = \
+ gpiodetect.man \
+ gpioinfo.man \
+ gpioget.man \
+ gpioset.man \
+ gpiomon.man \
+ gpionotify.man
+
+%.man: $(top_builddir)/tools/$(*F)
+ $(AM_V_GEN)help2man $(top_builddir)/tools/$(*F) --include=$(srcdir)/template --output=$(builddir)/$@ --no-info
+
+clean-local:
+ rm -f $(dist_man1_MANS)
+
+endif
+
+EXTRA_DIST = template
--- /dev/null
+# SPDX-License-Identifier: CC-BY-SA-4.0
+# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+
+[AUTHOR]
+Bartosz Golaszewski <brgl@bgdev.pl>
+
+[REPORTING BUGS]
+Report bugs to:
+ Bartosz Golaszewski <brgl@bgdev.pl>
+ linux-gpio <linux-gpio@vger.kernel.org>
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Kent Gibson <warthog618@gmail.com>
+
+# This file is part of libgpiod.
+#
+# Configuration file for the Sphinx documentation builder.
+#
+# This file only contains a selection of the most common options. For a full
+# list see the documentation:
+# https://www.sphinx-doc.org/en/master/usage/configuration.html
+
+import subprocess
+
+subprocess.run("cd .. ; ./autogen.sh ; make doc", shell=True)
+
+# -- Path setup --------------------------------------------------------------
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#
+# import os
+# import sys
+# sys.path.insert(0, os.path.abspath('.'))
+
+# -- Project information -----------------------------------------------------
+
+project = "libgpiod"
+copyright = "2022, Bartosz Golaszewski"
+author = "Bartosz Golaszewski"
+
+# The full version, including alpha/beta/rc tags
+release = (
+ subprocess.run(["git", "describe", "--dirty"], capture_output=True)
+ .stdout.decode()
+ .strip()
+)
+
+# -- General configuration ---------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = []
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = []
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This pattern also affects html_static_path and html_extra_path.
+exclude_patterns = []
+
+# -- Options for HTML output -------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+#
+html_theme = "alabaster"
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = []
+
+html_extra_path = ["../doc/html"]
--- /dev/null
+..
+ SPDX-License-Identifier: LGPL-2.1-or-later
+ SPDX-FileCopyrightText: 2022 Kent Gibson <warthog618@gmail.com>
+
+..
+ This file is part of libgpiod.
+
+ libgpiod documentation master file.
+
+Welcome to libgpiod's documentation!
+====================================
+
+.. toctree::
+ :maxdepth: 2
+ :caption: Contents:
+
+
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+
+gpiod-test
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2017-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+SUBDIRS = gpiosim
+
+AM_CFLAGS = -I$(top_srcdir)/include/ -I$(top_srcdir)/tests/gpiosim/
+AM_CFLAGS += -include $(top_builddir)/config.h
+AM_CFLAGS += -Wall -Wextra -g -std=gnu89 $(GLIB_CFLAGS) $(GIO_CFLAGS)
+AM_CFLAGS += -DG_LOG_DOMAIN=\"gpiod-test\"
+LDADD = $(top_builddir)/lib/libgpiod.la
+LDADD += $(top_builddir)/tests/gpiosim/libgpiosim.la
+LDADD += $(GLIB_LIBS) $(GIO_LIBS)
+
+noinst_PROGRAMS = gpiod-test
+
+gpiod_test_SOURCES = \
+ gpiod-test.c \
+ gpiod-test.h \
+ gpiod-test-helpers.c \
+ gpiod-test-helpers.h \
+ gpiod-test-sim.c \
+ gpiod-test-sim.h \
+ tests-chip.c \
+ tests-chip-info.c \
+ tests-edge-event.c \
+ tests-info-event.c \
+ tests-line-config.c \
+ tests-line-info.c \
+ tests-line-request.c \
+ tests-line-settings.c \
+ tests-misc.c \
+ tests-request-config.c
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* SPDX-FileCopyrightText: 2017-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+/*
+ * Testing framework for the core library.
+ *
+ * This file contains functions and definitions extending the GLib unit testing
+ * framework with functionalities necessary to test the libgpiod core C API as
+ * well as the kernel-to-user-space interface.
+ */
+
+#include "gpiod-test-helpers.h"
+
+GVariant *
+gpiod_test_package_line_names(const GPIOSimLineName *names)
+{
+ g_autoptr(GVariantBuilder) builder = NULL;
+ const GPIOSimLineName *name;
+
+ builder = g_variant_builder_new(G_VARIANT_TYPE("a(us)"));
+
+ for (name = &names[0]; name->name; name++)
+ g_variant_builder_add(builder, "(us)",
+ name->offset, name->name);
+
+ return g_variant_ref_sink(g_variant_new("a(us)", builder));
+}
+
+GVariant *gpiod_test_package_hogs(const GPIOSimHog *hogs)
+{
+ g_autoptr(GVariantBuilder) builder = NULL;
+ const GPIOSimHog *hog;
+
+ builder = g_variant_builder_new(G_VARIANT_TYPE("a(usi)"));
+
+ for (hog = &hogs[0]; hog->name; hog++)
+ g_variant_builder_add(builder, "(usi)",
+ hog->offset, hog->name, hog->direction);
+
+ return g_variant_ref_sink(g_variant_new("a(usi)", builder));
+}
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+#ifndef __GPIOD_TEST_HELPERS_H__
+#define __GPIOD_TEST_HELPERS_H__
+
+#include <errno.h>
+#include <glib.h>
+#include <gpiod.h>
+
+#include "gpiod-test-sim.h"
+
+/*
+ * These typedefs are needed to make g_autoptr work - it doesn't accept
+ * regular 'struct typename' syntax.
+ */
+
+typedef struct gpiod_chip struct_gpiod_chip;
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(struct_gpiod_chip, gpiod_chip_close);
+
+typedef struct gpiod_chip_info struct_gpiod_chip_info;
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(struct_gpiod_chip_info, gpiod_chip_info_free);
+
+typedef struct gpiod_line_info struct_gpiod_line_info;
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(struct_gpiod_line_info, gpiod_line_info_free);
+
+typedef struct gpiod_info_event struct_gpiod_info_event;
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(struct_gpiod_info_event, gpiod_info_event_free);
+
+typedef struct gpiod_line_config struct_gpiod_line_config;
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(struct_gpiod_line_config, gpiod_line_config_free);
+
+typedef struct gpiod_line_settings struct_gpiod_line_settings;
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(struct_gpiod_line_settings,
+ gpiod_line_settings_free);
+
+typedef struct gpiod_request_config struct_gpiod_request_config;
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(struct_gpiod_request_config,
+ gpiod_request_config_free);
+
+typedef struct gpiod_line_request struct_gpiod_line_request;
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(struct_gpiod_line_request,
+ gpiod_line_request_release);
+
+typedef struct gpiod_edge_event struct_gpiod_edge_event;
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(struct_gpiod_edge_event, gpiod_edge_event_free);
+
+typedef struct gpiod_edge_event_buffer struct_gpiod_edge_event_buffer;
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(struct_gpiod_edge_event_buffer,
+ gpiod_edge_event_buffer_free);
+
+#define gpiod_test_return_if_failed() \
+ do { \
+ if (g_test_failed()) \
+ return; \
+ } while (0)
+
+#define gpiod_test_join_thread_and_return_if_failed(_thread) \
+ do { \
+ if (g_test_failed()) { \
+ g_thread_join(_thread); \
+ return; \
+ } \
+ } while (0)
+
+#define gpiod_test_open_chip_or_fail(_path) \
+ ({ \
+ struct gpiod_chip *_chip = gpiod_chip_open(_path); \
+ g_assert_nonnull(_chip); \
+ gpiod_test_return_if_failed(); \
+ _chip; \
+ })
+
+#define gpiod_test_chip_get_info_or_fail(_chip) \
+ ({ \
+ struct gpiod_chip_info *_info = gpiod_chip_get_info(_chip); \
+ g_assert_nonnull(_info); \
+ gpiod_test_return_if_failed(); \
+ _info; \
+ })
+
+#define gpiod_test_chip_get_line_info_or_fail(_chip, _offset) \
+ ({ \
+ struct gpiod_line_info *_info = \
+ gpiod_chip_get_line_info(_chip, _offset); \
+ g_assert_nonnull(_info); \
+ gpiod_test_return_if_failed(); \
+ _info; \
+ })
+
+#define gpiod_test_chip_watch_line_info_or_fail(_chip, _offset) \
+ ({ \
+ struct gpiod_line_info *_info = \
+ gpiod_chip_watch_line_info(_chip, _offset); \
+ g_assert_nonnull(_info); \
+ gpiod_test_return_if_failed(); \
+ _info; \
+ })
+
+#define gpiod_test_create_line_settings_or_fail() \
+ ({ \
+ struct gpiod_line_settings *_settings = \
+ gpiod_line_settings_new(); \
+ g_assert_nonnull(_settings); \
+ gpiod_test_return_if_failed(); \
+ _settings; \
+ })
+
+#define gpiod_test_create_line_config_or_fail() \
+ ({ \
+ struct gpiod_line_config *_config = \
+ gpiod_line_config_new(); \
+ g_assert_nonnull(_config); \
+ gpiod_test_return_if_failed(); \
+ _config; \
+ })
+
+#define gpiod_test_create_edge_event_buffer_or_fail(_capacity) \
+ ({ \
+ struct gpiod_edge_event_buffer *_buffer = \
+ gpiod_edge_event_buffer_new(_capacity); \
+ g_assert_nonnull(_buffer); \
+ gpiod_test_return_if_failed(); \
+ _buffer; \
+ })
+
+#define gpiod_test_line_config_add_line_settings_or_fail(_line_cfg, _offsets, \
+ _num_offsets, \
+ _settings) \
+ do { \
+ gint _ret = gpiod_line_config_add_line_settings(_line_cfg, \
+ _offsets, \
+ _num_offsets, \
+ _settings); \
+ g_assert_cmpint(_ret, ==, 0); \
+ gpiod_test_return_if_failed(); \
+ } while (0)
+
+#define gpiod_test_line_config_get_line_settings_or_fail(_line_cfg, _offset) \
+ ({ \
+ struct gpiod_line_settings *_settings = \
+ gpiod_line_config_get_line_settings(_line_cfg, \
+ _offset); \
+ g_assert_nonnull(_settings); \
+ gpiod_test_return_if_failed(); \
+ _settings; \
+ })
+
+#define gpiod_test_line_config_set_output_values_or_fail(_line_cfg, _values, \
+ _num_values) \
+ do { \
+ gint _ret = gpiod_line_config_set_output_values(_line_cfg, \
+ _values, \
+ _num_values); \
+ g_assert_cmpint(_ret, ==, 0); \
+ gpiod_test_return_if_failed(); \
+ } while (0)
+
+#define gpiod_test_create_request_config_or_fail() \
+ ({ \
+ struct gpiod_request_config *_config = \
+ gpiod_request_config_new(); \
+ g_assert_nonnull(_config); \
+ gpiod_test_return_if_failed(); \
+ _config; \
+ })
+
+#define gpiod_test_chip_request_lines_or_fail(_chip, _req_cfg, _line_cfg) \
+ ({ \
+ struct gpiod_line_request *_request = \
+ gpiod_chip_request_lines(_chip, \
+ _req_cfg, _line_cfg); \
+ g_assert_nonnull(_request); \
+ gpiod_test_return_if_failed(); \
+ _request; \
+ })
+
+#define gpiod_test_line_request_reconfigure_lines_or_fail(_request, _line_cfg) \
+ do { \
+ gint _ret = gpiod_line_request_reconfigure_lines(_request, \
+ _line_cfg); \
+ g_assert_cmpint(_ret, ==, 0); \
+ gpiod_test_return_if_failed(); \
+ } while (0)
+
+#define gpiod_test_expect_errno(_expected) \
+ g_assert_cmpint(_expected, ==, errno)
+
+typedef struct {
+ guint offset;
+ const gchar *name;
+} GPIOSimLineName;
+
+typedef struct {
+ guint offset;
+ const gchar *name;
+ GPIOSimDirection direction;
+} GPIOSimHog;
+
+GVariant *gpiod_test_package_line_names(const GPIOSimLineName *names);
+GVariant *gpiod_test_package_hogs(const GPIOSimHog *hogs);
+
+#endif /* __GPIOD_TEST_HELPERS_H__ */
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/* SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+#include <errno.h>
+#include <gpiosim.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "gpiod-test-sim.h"
+
+G_DEFINE_QUARK(g-gpiosim-error, g_gpiosim_error);
+
+struct _GPIOSimChip {
+ GObject parent_instance;
+ struct gpiosim_bank *bank;
+ GError *construct_err;
+ guint num_lines;
+ gchar *label;
+ GVariant *line_names;
+ GVariant *hogs;
+};
+
+enum {
+ G_GPIOSIM_CHIP_PROP_DEV_PATH = 1,
+ G_GPIOSIM_CHIP_PROP_NAME,
+ G_GPIOSIM_CHIP_PROP_NUM_LINES,
+ G_GPIOSIM_CHIP_PROP_LABEL,
+ G_GPIOSIM_CHIP_PROP_LINE_NAMES,
+ G_GPIOSIM_CHIP_PROP_HOGS,
+};
+
+static struct gpiosim_ctx *sim_ctx;
+
+static gboolean
+g_gpiosim_chip_initable_init(GInitable *initable,
+ GCancellable *cancellable G_GNUC_UNUSED,
+ GError **err)
+{
+ GPIOSimChip *self = G_GPIOSIM_CHIP_OBJ(initable);
+
+ if (self->construct_err) {
+ g_propagate_error(err, self->construct_err);
+ self->construct_err = NULL;
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void g_gpiosim_chip_initable_iface_init(GInitableIface *iface)
+{
+ iface->init = g_gpiosim_chip_initable_init;
+}
+
+G_DEFINE_TYPE_WITH_CODE(GPIOSimChip, g_gpiosim_chip, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE(
+ G_TYPE_INITABLE,
+ g_gpiosim_chip_initable_iface_init));
+
+static void g_gpiosim_ctx_unref(void)
+{
+ gpiosim_ctx_unref(sim_ctx);
+}
+
+static gboolean g_gpiosim_chip_apply_line_names(GPIOSimChip *self)
+{
+ g_autoptr(GVariantIter) iter = NULL;
+ guint offset;
+ gchar *name;
+ int ret;
+
+ if (!self->line_names)
+ return TRUE;
+
+ iter = g_variant_iter_new(self->line_names);
+
+ while (g_variant_iter_loop(iter, "(us)", &offset, &name)) {
+ ret = gpiosim_bank_set_line_name(self->bank, offset, name);
+ if (ret) {
+ g_set_error(&self->construct_err, G_GPIOSIM_ERROR,
+ G_GPIOSIM_ERR_CHIP_INIT_FAILED,
+ "Unable to set the name of the simulated GPIO line: %s",
+ g_strerror(errno));
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean g_gpiosim_chip_apply_hogs(GPIOSimChip *self)
+{
+ g_autoptr(GVariantIter) iter = NULL;
+ enum gpiosim_direction dir;
+ guint offset;
+ gchar *name;
+ gint vdir;
+ int ret;
+
+ if (!self->hogs)
+ return TRUE;
+
+ iter = g_variant_iter_new(self->hogs);
+
+ while (g_variant_iter_loop(iter, "(usi)", &offset, &name, &vdir)) {
+ switch (vdir) {
+ case G_GPIOSIM_DIRECTION_INPUT:
+ dir = GPIOSIM_DIRECTION_INPUT;
+ break;
+ case G_GPIOSIM_DIRECTION_OUTPUT_HIGH:
+ dir = GPIOSIM_DIRECTION_OUTPUT_HIGH;
+ break;
+ case G_GPIOSIM_DIRECTION_OUTPUT_LOW:
+ dir = GPIOSIM_DIRECTION_OUTPUT_LOW;
+ break;
+ default:
+ g_error("Invalid hog direction value: %d", vdir);
+ }
+
+ ret = gpiosim_bank_hog_line(self->bank, offset, name, dir);
+ if (ret) {
+ g_set_error(&self->construct_err, G_GPIOSIM_ERROR,
+ G_GPIOSIM_ERR_CHIP_INIT_FAILED,
+ "Unable to hog the simulated GPIO line: %s",
+ g_strerror(errno));
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean g_gpiosim_chip_apply_properties(GPIOSimChip *self)
+{
+ int ret;
+
+ ret = gpiosim_bank_set_num_lines(self->bank, self->num_lines);
+ if (ret) {
+ g_set_error(&self->construct_err, G_GPIOSIM_ERROR,
+ G_GPIOSIM_ERR_CHIP_INIT_FAILED,
+ "Unable to set the number of lines exposed by the simulated chip: %s",
+ g_strerror(errno));
+ return FALSE;
+ }
+
+ if (self->label) {
+ ret = gpiosim_bank_set_label(self->bank, self->label);
+ if (ret) {
+ g_set_error(&self->construct_err, G_GPIOSIM_ERROR,
+ G_GPIOSIM_ERR_CHIP_INIT_FAILED,
+ "Unable to set the label of the simulated chip: %s",
+ g_strerror(errno));
+ return FALSE;
+ }
+ }
+
+ ret = g_gpiosim_chip_apply_line_names(self);
+ if (!ret)
+ return FALSE;
+
+ return g_gpiosim_chip_apply_hogs(self);
+}
+
+static gboolean g_gpiosim_chip_enable(GPIOSimChip *self)
+{
+ struct gpiosim_dev *dev;
+ int ret;
+
+ dev = gpiosim_bank_get_dev(self->bank);
+ ret = gpiosim_dev_enable(dev);
+ gpiosim_dev_unref(dev);
+ if (ret) {
+ g_set_error(&self->construct_err, G_GPIOSIM_ERROR,
+ G_GPIOSIM_ERR_CHIP_ENABLE_FAILED,
+ "Error while trying to enable the simulated GPIO device: %s",
+ g_strerror(errno));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean g_gpiosim_ctx_init(GError **err)
+{
+ sim_ctx = gpiosim_ctx_new();
+ if (!sim_ctx) {
+ g_set_error(err, G_GPIOSIM_ERROR,
+ G_GPIOSIM_ERR_CTX_INIT_FAILED,
+ "Unable to initialize libgpiosim: %s",
+ g_strerror(errno));
+ return FALSE;
+ }
+
+ atexit(g_gpiosim_ctx_unref);
+
+ return TRUE;
+}
+
+static void g_gpiosim_chip_constructed(GObject *obj)
+{
+ GPIOSimChip *self = G_GPIOSIM_CHIP(obj);
+ struct gpiosim_dev *dev;
+ gboolean ret;
+
+ if (!sim_ctx) {
+ ret = g_gpiosim_ctx_init(&self->construct_err);
+ if (!ret)
+ return;
+ }
+
+ dev = gpiosim_dev_new(sim_ctx);
+ if (!dev) {
+ g_set_error(&self->construct_err, G_GPIOSIM_ERROR,
+ G_GPIOSIM_ERR_CHIP_INIT_FAILED,
+ "Unable to instantiate new GPIO device: %s",
+ g_strerror(errno));
+ return;
+ }
+
+ self->bank = gpiosim_bank_new(dev);
+ gpiosim_dev_unref(dev);
+ if (!self->bank) {
+ g_set_error(&self->construct_err, G_GPIOSIM_ERROR,
+ G_GPIOSIM_ERR_CHIP_INIT_FAILED,
+ "Unable to instantiate new GPIO bank: %s",
+ g_strerror(errno));
+ return;
+ }
+
+ ret = g_gpiosim_chip_apply_properties(self);
+ if (!ret)
+ return;
+
+ ret = g_gpiosim_chip_enable(self);
+ if (!ret)
+ return;
+
+ G_OBJECT_CLASS(g_gpiosim_chip_parent_class)->constructed(obj);
+}
+
+static void g_gpiosim_chip_get_property(GObject *obj, guint prop_id,
+ GValue *val, GParamSpec *pspec)
+{
+ GPIOSimChip *self = G_GPIOSIM_CHIP(obj);
+
+ switch (prop_id) {
+ case G_GPIOSIM_CHIP_PROP_DEV_PATH:
+ g_value_set_static_string(val,
+ gpiosim_bank_get_dev_path(self->bank));
+ break;
+ case G_GPIOSIM_CHIP_PROP_NAME:
+ g_value_set_static_string(val,
+ gpiosim_bank_get_chip_name(self->bank));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
+ break;
+ }
+}
+
+static void set_variant_prop(GVariant **prop, const GValue *val)
+{
+ GVariant *variant = g_value_get_variant(val);
+
+ g_clear_pointer(prop, g_variant_unref);
+ if (variant)
+ *prop = g_variant_ref(variant);
+}
+
+static void g_gpiosim_chip_set_property(GObject *obj, guint prop_id,
+ const GValue *val, GParamSpec *pspec)
+{
+ GPIOSimChip *self = G_GPIOSIM_CHIP(obj);
+ const gchar *label;
+
+ switch (prop_id) {
+ case G_GPIOSIM_CHIP_PROP_NUM_LINES:
+ self->num_lines = g_value_get_uint(val);
+ break;
+ case G_GPIOSIM_CHIP_PROP_LABEL:
+ g_clear_pointer(&self->label, g_free);
+ label = g_value_get_string(val);
+ if (label)
+ self->label = g_strdup(label);
+ break;
+ case G_GPIOSIM_CHIP_PROP_LINE_NAMES:
+ set_variant_prop(&self->line_names, val);
+ break;
+ case G_GPIOSIM_CHIP_PROP_HOGS:
+ set_variant_prop(&self->hogs, val);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
+ break;
+ }
+}
+
+static void g_gpiosim_chip_dispose(GObject *obj)
+{
+ GPIOSimChip *self = G_GPIOSIM_CHIP(obj);
+
+ g_clear_pointer(&self->line_names, g_variant_unref);
+ g_clear_pointer(&self->hogs, g_variant_unref);
+
+ G_OBJECT_CLASS(g_gpiosim_chip_parent_class)->dispose(obj);
+}
+
+static void g_gpiosim_disable_and_cleanup(struct gpiosim_bank *bank)
+{
+ struct gpiosim_dev *dev;
+ gint ret;
+
+ dev = gpiosim_bank_get_dev(bank);
+
+ if (gpiosim_dev_is_live(dev)) {
+ ret = gpiosim_dev_disable(dev);
+ if (ret)
+ g_warning("Error while trying to disable the simulated GPIO device: %s",
+ g_strerror(errno));
+ }
+
+ gpiosim_dev_unref(dev);
+ gpiosim_bank_unref(bank);
+}
+
+static void g_gpiosim_chip_finalize(GObject *obj)
+{
+ GPIOSimChip *self = G_GPIOSIM_CHIP(obj);
+
+ g_clear_error(&self->construct_err);
+ g_clear_pointer(&self->label, g_free);
+ g_clear_pointer(&self->bank, g_gpiosim_disable_and_cleanup);
+
+ G_OBJECT_CLASS(g_gpiosim_chip_parent_class)->finalize(obj);
+}
+
+static void g_gpiosim_chip_class_init(GPIOSimChipClass *chip_class)
+{
+ GObjectClass *class = G_OBJECT_CLASS(chip_class);
+
+ class->constructed = g_gpiosim_chip_constructed;
+ class->get_property = g_gpiosim_chip_get_property;
+ class->set_property = g_gpiosim_chip_set_property;
+ class->dispose = g_gpiosim_chip_dispose;
+ class->finalize = g_gpiosim_chip_finalize;
+
+ g_object_class_install_property(
+ class, G_GPIOSIM_CHIP_PROP_DEV_PATH,
+ g_param_spec_string("dev-path", "Device path",
+ "Character device filesystem path.", NULL,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property(
+ class, G_GPIOSIM_CHIP_PROP_NAME,
+ g_param_spec_string(
+ "name", "Chip name",
+ "Name of this chip device as set by the kernel.", NULL,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property(
+ class, G_GPIOSIM_CHIP_PROP_NUM_LINES,
+ g_param_spec_uint("num-lines", "Number of lines",
+ "Number of lines this simulated chip exposes.",
+ 1, G_MAXUINT, 1,
+ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property(
+ class, G_GPIOSIM_CHIP_PROP_LABEL,
+ g_param_spec_string("label", "Chip label",
+ "Label of this simulated chip.", NULL,
+ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property(
+ class, G_GPIOSIM_CHIP_PROP_LINE_NAMES,
+ g_param_spec_variant(
+ "line-names", "Line names",
+ "List of names of the lines exposed by this chip",
+ (GVariantType *)"a(us)", NULL,
+ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property(
+ class, G_GPIOSIM_CHIP_PROP_HOGS,
+ g_param_spec_variant(
+ "hogs", "Line hogs",
+ "List of hogged lines and their directions.",
+ (GVariantType *)"a(usi)", NULL,
+ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void g_gpiosim_chip_init(GPIOSimChip *self)
+{
+ self->construct_err = NULL;
+ self->num_lines = 1;
+ self->label = NULL;
+ self->line_names = NULL;
+ self->hogs = NULL;
+}
+
+static const gchar *
+g_gpiosim_chip_get_string_prop(GPIOSimChip *self, const gchar *prop)
+{
+ GValue val = G_VALUE_INIT;
+ const gchar *str;
+
+ g_object_get_property(G_OBJECT(self), prop, &val);
+ str = g_value_get_string(&val);
+ g_value_unset(&val);
+
+ return str;
+}
+
+const gchar *g_gpiosim_chip_get_dev_path(GPIOSimChip *self)
+{
+ return g_gpiosim_chip_get_string_prop(self, "dev-path");
+}
+
+const gchar *g_gpiosim_chip_get_name(GPIOSimChip *self)
+{
+ return g_gpiosim_chip_get_string_prop(self, "name");
+}
+
+GPIOSimValue
+_g_gpiosim_chip_get_value(GPIOSimChip *chip, guint offset, GError **err)
+{
+ enum gpiosim_value val;
+
+ val = gpiosim_bank_get_value(chip->bank, offset);
+ switch (val) {
+ case GPIOSIM_VALUE_ERROR:
+ g_set_error(err, G_GPIOSIM_ERROR,
+ G_GPIOSIM_ERR_GET_VALUE_FAILED,
+ "Unable to read the line value: %s",
+ g_strerror(errno));
+ return G_GPIOSIM_VALUE_ERROR;
+ case GPIOSIM_VALUE_INACTIVE:
+ return G_GPIOSIM_VALUE_INACTIVE;
+ case GPIOSIM_VALUE_ACTIVE:
+ return G_GPIOSIM_VALUE_ACTIVE;
+ }
+
+ g_error("Invalid line value returned by gpiosim");
+}
+
+void g_gpiosim_chip_set_pull(GPIOSimChip *chip, guint offset, GPIOSimPull pull)
+{
+ enum gpiosim_pull sim_pull;
+ gint ret;
+
+ switch (pull) {
+ case G_GPIOSIM_PULL_DOWN:
+ sim_pull = GPIOSIM_PULL_DOWN;
+ break;
+ case G_GPIOSIM_PULL_UP:
+ sim_pull = GPIOSIM_PULL_UP;
+ break;
+ default:
+ g_error("invalid pull value");
+ }
+
+ ret = gpiosim_bank_set_pull(chip->bank, offset, sim_pull);
+ if (ret)
+ g_critical("Unable to set the pull setting for simulated line: %s",
+ g_strerror(errno));
+}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/* SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+#ifndef __GPIOD_TEST_SIM_H__
+#define __GPIOD_TEST_SIM_H__
+
+#include <gio/gio.h>
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+ G_GPIOSIM_VALUE_ERROR = -1,
+ G_GPIOSIM_VALUE_INACTIVE = 0,
+ G_GPIOSIM_VALUE_ACTIVE = 1,
+} GPIOSimValue;
+
+typedef enum {
+ G_GPIOSIM_PULL_UP = 1,
+ G_GPIOSIM_PULL_DOWN,
+} GPIOSimPull;
+
+typedef enum {
+ G_GPIOSIM_DIRECTION_INPUT = 1,
+ G_GPIOSIM_DIRECTION_OUTPUT_HIGH,
+ G_GPIOSIM_DIRECTION_OUTPUT_LOW,
+} GPIOSimDirection;
+
+#define G_GPIOSIM_ERROR g_gpiosim_error_quark()
+
+typedef enum {
+ G_GPIOSIM_ERR_CTX_INIT_FAILED = 1,
+ G_GPIOSIM_ERR_CHIP_INIT_FAILED,
+ G_GPIOSIM_ERR_CHIP_ENABLE_FAILED,
+ G_GPIOSIM_ERR_GET_VALUE_FAILED,
+} GPIOSimError;
+
+GQuark g_gpiosim_error_quark(void);
+
+G_DECLARE_FINAL_TYPE(GPIOSimChip, g_gpiosim_chip, G_GPIOSIM, CHIP, GObject);
+
+#define G_GPIOSIM_CHIP_TYPE (g_gpiosim_chip_get_type())
+#define G_GPIOSIM_CHIP_OBJ(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), G_GPIOSIM_CHIP_TYPE, GPIOSimChip))
+
+#define g_gpiosim_chip_new(...) \
+ ({ \
+ g_autoptr(GError) _err = NULL; \
+ GPIOSimChip *_chip = G_GPIOSIM_CHIP_OBJ( \
+ g_initable_new(G_GPIOSIM_CHIP_TYPE, \
+ NULL, &_err, \
+ __VA_ARGS__)); \
+ g_assert_no_error(_err); \
+ if (g_test_failed()) \
+ return; \
+ _chip; \
+ })
+
+const gchar *g_gpiosim_chip_get_dev_path(GPIOSimChip *self);
+const gchar *g_gpiosim_chip_get_name(GPIOSimChip *self);
+
+GPIOSimValue
+_g_gpiosim_chip_get_value(GPIOSimChip *self, guint offset, GError **err);
+void g_gpiosim_chip_set_pull(GPIOSimChip *self, guint offset, GPIOSimPull pull);
+
+#define g_gpiosim_chip_get_value(self, offset) \
+ ({ \
+ g_autoptr(GError) _err = NULL; \
+ gint _val = _g_gpiosim_chip_get_value(self, offset, &_err); \
+ g_assert_no_error(_err); \
+ if (g_test_failed()) \
+ return; \
+ _val; \
+ })
+
+G_END_DECLS
+
+#endif /* __GPIOD_TEST_SIM_H__ */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2017-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <errno.h>
+#include <linux/version.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/utsname.h>
+#include <unistd.h>
+
+#include "gpiod-test.h"
+
+#define MIN_KERNEL_MAJOR 5
+#define MIN_KERNEL_MINOR 19
+#define MIN_KERNEL_RELEASE 0
+#define MIN_KERNEL_VERSION KERNEL_VERSION(MIN_KERNEL_MAJOR, \
+ MIN_KERNEL_MINOR, \
+ MIN_KERNEL_RELEASE)
+
+static GList *tests;
+
+static gboolean check_kernel(void)
+{
+ guint major, minor, release;
+ struct utsname un;
+ gint ret;
+
+ g_debug("checking linux kernel version");
+
+ ret = uname(&un);
+ if (ret) {
+ g_critical("unable to read the kernel release version: %s",
+ g_strerror(errno));
+ return FALSE;
+ }
+
+ ret = sscanf(un.release, "%u.%u.%u", &major, &minor, &release);
+ if (ret != 3) {
+ g_critical("error reading kernel release version");
+ return FALSE;
+ }
+
+ if (KERNEL_VERSION(major, minor, release) < MIN_KERNEL_VERSION) {
+ g_critical("linux kernel version must be at least v%u.%u.%u - got v%u.%u.%u",
+ MIN_KERNEL_MAJOR, MIN_KERNEL_MINOR, MIN_KERNEL_RELEASE,
+ major, minor, release);
+ return FALSE;
+ }
+
+ g_debug("kernel release is v%u.%u.%u - ok to run tests",
+ major, minor, release);
+
+ return TRUE;
+}
+
+static void test_func_wrapper(gconstpointer data)
+{
+ const struct _gpiod_test_case *test = data;
+
+ test->func();
+}
+
+static void add_test_from_list(gpointer element, gpointer data G_GNUC_UNUSED)
+{
+ struct _gpiod_test_case *test = element;
+
+ g_test_add_data_func(test->path, test, test_func_wrapper);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+ g_test_set_nonfatal_assertions();
+
+ g_debug("running libgpiod test suite");
+ g_debug("%u tests registered", g_list_length(tests));
+
+ if (!check_kernel())
+ return EXIT_FAILURE;
+
+ g_list_foreach(tests, add_test_from_list, NULL);
+ g_list_free(tests);
+
+ return g_test_run();
+}
+
+void _gpiod_test_register(struct _gpiod_test_case *test)
+{
+ tests = g_list_append(tests, test);
+}
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* SPDX-FileCopyrightText: 2017-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+/*
+ * Testing framework for the core library.
+ *
+ * This file contains functions and definitions extending the GLib unit testing
+ * framework with functionalities necessary to test the libgpiod core C API as
+ * well as the kernel-to-user-space interface.
+ */
+
+#ifndef __GPIOD_TEST_H__
+#define __GPIOD_TEST_H__
+
+#include <glib.h>
+
+/* These are private definitions and should not be used directly. */
+
+struct _gpiod_test_case {
+ const gchar *path;
+ void (*func)(void);
+};
+
+void _gpiod_test_register(struct _gpiod_test_case *test);
+
+#define _GPIOD_TEST_PATH(_name) \
+ "/gpiod/" GPIOD_TEST_GROUP "/" G_STRINGIFY(_name)
+
+/*
+ * Register a test case function.
+ */
+#define GPIOD_TEST_CASE(_name) \
+ static void _gpiod_test_func_##_name(void); \
+ static struct _gpiod_test_case _##_name##_test_case = { \
+ .path = _GPIOD_TEST_PATH(_name), \
+ .func = _gpiod_test_func_##_name, \
+ }; \
+ static __attribute__((constructor)) void \
+ _##_name##_test_register(void) \
+ { \
+ _gpiod_test_register(&_##_name##_test_case); \
+ } \
+ static void _gpiod_test_func_##_name(void)
+
+#endif /* __GPIOD_TEST_H__ */
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2017-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+gpiosim-selftest
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+lib_LTLIBRARIES = libgpiosim.la
+noinst_PROGRAMS = gpiosim-selftest
+
+AM_CFLAGS = -Wall -Wextra -g -fvisibility=hidden -std=gnu89
+AM_CFLAGS += -include $(top_builddir)/config.h
+
+libgpiosim_la_SOURCES = gpiosim.c gpiosim.h
+libgpiosim_la_CFLAGS = $(AM_CFLAGS) $(KMOD_CFLAGS) $(MOUNT_CFLAGS)
+libgpiosim_la_LDFLAGS = -version-info $(subst .,:,$(ABI_GPIOSIM_VERSION))
+libgpiosim_la_LDFLAGS += $(KMOD_LIBS) $(MOUNT_LIBS) -pthread
+
+gpiosim_selftest_LDADD = libgpiosim.la
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "gpiosim.h"
+
+#define UNUSED __attribute__((unused))
+
+static const char *const line_names[] = {
+ "foo",
+ "bar",
+ "foobar",
+ NULL,
+ "barfoo",
+};
+
+int main(int argc UNUSED, char **argv UNUSED)
+{
+ struct gpiosim_bank *bank0, *bank1;
+ struct gpiosim_dev *dev;
+ struct gpiosim_ctx *ctx;
+ int ret, i;
+
+ printf("Creating gpiosim context\n");
+
+ ctx = gpiosim_ctx_new();
+ if (!ctx) {
+ perror("unable to create the gpios-sim context");
+ return EXIT_FAILURE;
+ }
+
+ printf("Creating a chip\n");
+
+ dev = gpiosim_dev_new(ctx);
+ if (!dev) {
+ perror("Unable to create a chip");
+ return EXIT_FAILURE;
+ }
+
+ printf("Creating a bank\n");
+
+ bank0 = gpiosim_bank_new(dev);
+ if (!bank0) {
+ perror("Unable to create a bank");
+ return EXIT_FAILURE;
+ }
+
+ printf("Creating a second bank\n");
+
+ bank1 = gpiosim_bank_new(dev);
+ if (!bank1) {
+ perror("Unable to create a bank");
+ return EXIT_FAILURE;
+ }
+
+ printf("Setting the label of bank #2 to foobar\n");
+
+ ret = gpiosim_bank_set_label(bank1, "foobar");
+ if (ret) {
+ perror("Unable to set the label of bank #2");
+ return EXIT_FAILURE;
+ }
+
+ printf("Setting the number of lines in bank #1 to 16\n");
+
+ ret = gpiosim_bank_set_num_lines(bank0, 16);
+ if (ret) {
+ perror("Unable to set the number of lines");
+ return EXIT_FAILURE;
+ }
+
+ printf("Setting the number of lines in bank #2 to 8\n");
+
+ ret = gpiosim_bank_set_num_lines(bank1, 8);
+ if (ret) {
+ perror("Unable to set the number of lines");
+ return EXIT_FAILURE;
+ }
+
+ printf("Setting names for some lines in bank #1\n");
+
+ for (i = 0; i < 5; i++) {
+ ret = gpiosim_bank_set_line_name(bank0, i, line_names[i]);
+ if (ret) {
+ perror("Unable to set line names");
+ return EXIT_FAILURE;
+ }
+ }
+
+ printf("Hog a line on bank #2\n");
+
+ ret = gpiosim_bank_hog_line(bank1, 3, "xyz",
+ GPIOSIM_DIRECTION_OUTPUT_HIGH);
+ if (ret) {
+ perror("Unable to hog a line");
+ return EXIT_FAILURE;
+ }
+
+ printf("Enabling the GPIO device\n");
+
+ ret = gpiosim_dev_enable(dev);
+ if (ret) {
+ perror("Unable to enable the device");
+ return EXIT_FAILURE;
+ }
+
+ printf("Setting the pull of a single line to pull-up\n");
+
+ ret = gpiosim_bank_set_pull(bank0, 6, GPIOSIM_PULL_UP);
+ if (ret) {
+ perror("Unable to set the pull");
+ return EXIT_FAILURE;
+ }
+
+ printf("Reading the pull back\n");
+
+ ret = gpiosim_bank_get_pull(bank0, 6);
+ if (ret < 0) {
+ perror("Unable to read the pull");
+ return EXIT_FAILURE;
+ }
+
+ if (ret != GPIOSIM_PULL_UP) {
+ fprintf(stderr, "Invalid pull value read\n");
+ return EXIT_FAILURE;
+ }
+
+ printf("Reading the value\n");
+
+ ret = gpiosim_bank_get_value(bank0, 6);
+ if (ret < 0) {
+ perror("Unable to read the value");
+ return EXIT_FAILURE;
+ }
+
+ if (ret != GPIOSIM_VALUE_ACTIVE) {
+ fprintf(stderr, "Invalid value read\n");
+ return EXIT_FAILURE;
+ }
+
+ printf("Disabling the GPIO device\n");
+
+ ret = gpiosim_dev_disable(dev);
+ if (ret) {
+ perror("Error while disabling the device");
+ return EXIT_FAILURE;
+ }
+
+ gpiosim_bank_unref(bank1);
+ gpiosim_bank_unref(bank0);
+ gpiosim_dev_unref(dev);
+ gpiosim_ctx_unref(ctx);
+
+ return EXIT_SUCCESS;
+}
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <libkmod.h>
+#include <libmount.h>
+#include <linux/version.h>
+#include <pthread.h>
+#include <search.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/prctl.h>
+#include <sys/random.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/utsname.h>
+#include <unistd.h>
+
+#include "gpiosim.h"
+
+#define GPIOSIM_API __attribute__((visibility("default")))
+#define UNUSED __attribute__((unused))
+#define MIN_KERNEL_VERSION KERNEL_VERSION(5, 17, 4)
+
+static pthread_mutex_t id_lock = PTHREAD_MUTEX_INITIALIZER;
+static pthread_once_t id_init_once = PTHREAD_ONCE_INIT;
+static void *id_root;
+
+struct {
+ int lowest;
+ bool found;
+} id_find_next_ctx;
+
+struct {
+ int id;
+ int *idp;
+} id_del_ctx;
+
+static void id_cleanup(void)
+{
+ tdestroy(id_root, free);
+}
+
+static void id_schedule_cleanup(void)
+{
+ atexit(id_cleanup);
+}
+
+static int id_compare(const void *p1, const void *p2)
+{
+ int id1 = *(int *)p1;
+ int id2 = *(int *)p2;
+
+ if (id1 < id2)
+ return -1;
+ if (id1 > id2)
+ return 1;
+ return 0;
+}
+
+static void id_find_next(const void *node, VISIT which, int depth UNUSED)
+{
+ int *id = *(int **)node;
+
+ if (id_find_next_ctx.found)
+ return;
+
+ switch (which) {
+ case postorder:
+ case leaf:
+ if (*id != id_find_next_ctx.lowest)
+ id_find_next_ctx.found = true;
+ else
+ id_find_next_ctx.lowest++;
+ break;
+ default:
+ break;
+ };
+}
+
+static void id_del(const void *node, VISIT which, int depth UNUSED)
+{
+ int *id = *(int **)node;
+
+ if (id_del_ctx.idp)
+ return;
+
+ switch (which) {
+ case postorder:
+ case leaf:
+ if (*id == id_del_ctx.id)
+ id_del_ctx.idp = id;
+ break;
+ default:
+ break;
+ }
+}
+
+static int id_alloc(void)
+{
+ void *ret;
+ int *id;
+
+ pthread_once(&id_init_once, id_schedule_cleanup);
+
+ pthread_mutex_lock(&id_lock);
+
+ id_find_next_ctx.lowest = 0;
+ id_find_next_ctx.found = false;
+
+ twalk(id_root, id_find_next);
+
+ id = malloc(sizeof(*id));
+ if (!id) {
+ pthread_mutex_unlock(&id_lock);
+ return -1;
+ }
+
+ *id = id_find_next_ctx.lowest;
+
+ ret = tsearch(id, &id_root, id_compare);
+ if (!ret) {
+ pthread_mutex_unlock(&id_lock);
+ /* tsearch() doesn't set errno. */
+ errno = ENOMEM;
+ return -1;
+ }
+
+ pthread_mutex_unlock(&id_lock);
+
+ return *id;
+}
+
+static void id_free(int id)
+{
+ pthread_mutex_lock(&id_lock);
+
+ id_del_ctx.id = id;
+ id_del_ctx.idp = NULL;
+
+ twalk(id_root, id_del);
+ if (id_del_ctx.idp) {
+ tdelete(id_del_ctx.idp, &id_root, id_compare);
+ free(id_del_ctx.idp);
+ }
+
+ pthread_mutex_unlock(&id_lock);
+}
+
+struct refcount {
+ unsigned int cnt;
+ void (*release)(struct refcount *);
+};
+
+static void refcount_init(struct refcount *ref,
+ void (*release)(struct refcount *))
+{
+ ref->cnt = 1;
+ ref->release = release;
+}
+
+static void refcount_inc(struct refcount *ref)
+{
+ ref->cnt++;
+}
+
+static void refcount_dec(struct refcount *ref)
+{
+ ref->cnt--;
+
+ if (!ref->cnt)
+ ref->release(ref);
+}
+
+struct list_head {
+ struct list_head *prev;
+ struct list_head *next;
+};
+
+static void list_init(struct list_head *list)
+{
+ list->next = list;
+ list->prev = list;
+}
+
+static void list_add(struct list_head *new, struct list_head *head)
+{
+ struct list_head *prev = head->prev;
+
+ head->prev = new;
+ new->next = head;
+ new->prev = prev;
+ prev->next = new;
+}
+
+static void list_del(struct list_head *entry)
+{
+ struct list_head *prev = entry->prev, *next = entry->next;
+
+ prev->next = next;
+ next->prev = prev;
+}
+
+#define container_of(ptr, type, member) ({ \
+ void *__mptr = (void *)(ptr); \
+ ((type *)(__mptr - offsetof(type, member))); \
+})
+
+#define list_entry(ptr, type, member) container_of(ptr, type, member)
+
+#define list_first_entry(ptr, type, member) \
+ list_entry((ptr)->next, type, member)
+
+#define list_next_entry(pos, member) \
+ list_entry((pos)->member.next, typeof(*(pos)), member)
+
+#define list_entry_is_head(pos, head, member) (&pos->member == (head))
+
+#define list_for_each_entry(pos, head, member) \
+ for (pos = list_first_entry(head, typeof(*pos), member); \
+ !list_entry_is_head(pos, head, member); \
+ pos = list_next_entry(pos, member))
+
+#define list_for_each_entry_safe(pos, next, head, member) \
+ for (pos = list_first_entry(head, typeof(*pos), member), \
+ next = list_next_entry(pos, member); \
+ !list_entry_is_head(pos, head, member); \
+ pos = next, next = list_next_entry(next, member))
+
+static int open_write_close(int base_fd, const char *where, const char *what)
+{
+ ssize_t written, size;
+ int fd;
+
+ if (what)
+ size = strlen(what) + 1;
+ else
+ size = 1;
+
+ fd = openat(base_fd, where, O_WRONLY);
+ if (fd < 0)
+ return -1;
+
+ written = write(fd, what ?: "", size);
+ close(fd);
+ if (written < 0) {
+ return -1;
+ } else if (written != size) {
+ errno = EIO;
+ return -1;
+ }
+
+ return 0;
+}
+
+static int open_read_close(int base_fd, const char *where,
+ char *buf, size_t bufsize)
+{
+ ssize_t rd;
+ int fd;
+
+ fd = openat(base_fd, where, O_RDONLY);
+ if (fd < 0)
+ return -1;
+
+ memset(buf, 0, bufsize);
+ rd = read(fd, buf, bufsize);
+ close(fd);
+ if (rd < 0)
+ return -1;
+
+ if (buf[rd - 1] == '\n')
+ buf[rd - 1] = '\0';
+
+ return 0;
+}
+
+static int check_kernel_version(void)
+{
+ unsigned int major, minor, release;
+ struct utsname un;
+ int ret;
+
+ ret = uname(&un);
+ if (ret)
+ return -1;
+
+ ret = sscanf(un.release, "%u.%u.%u", &major, &minor, &release);
+ if (ret != 3) {
+ errno = EFAULT;
+ return -1;
+ }
+
+ if (KERNEL_VERSION(major, minor, release) < MIN_KERNEL_VERSION) {
+ errno = EOPNOTSUPP;
+ return -1;
+ }
+
+ return 0;
+}
+
+static int check_gpiosim_module(void)
+{
+ struct kmod_module *module;
+ struct kmod_ctx *kmod;
+ const char *modpath;
+ int ret, initstate;
+
+ kmod = kmod_new(NULL, NULL);
+ if (!kmod)
+ return -1;
+
+ ret = kmod_module_new_from_name(kmod, "gpio-sim", &module);
+ if (ret)
+ goto out_unref_kmod;
+
+again:
+ /* First check if the module is already loaded or built-in. */
+ initstate = kmod_module_get_initstate(module);
+ if (initstate < 0) {
+ if (errno == ENOENT) {
+ /*
+ * It's not loaded, let's see if we can do it manually.
+ * See if we can find the module.
+ */
+ modpath = kmod_module_get_path(module);
+ if (!modpath) {
+ /* libkmod doesn't set errno. */
+ errno = ENOENT;
+ ret = -1;
+ goto out_unref_module;
+ }
+
+ ret = kmod_module_probe_insert_module(
+ module, KMOD_PROBE_IGNORE_LOADED, NULL, NULL,
+ NULL, NULL);
+ if (ret)
+ goto out_unref_module;
+
+ goto again;
+ } else {
+ if (errno == 0)
+ errno = EOPNOTSUPP;
+
+ goto out_unref_module;
+ }
+ }
+
+ if (initstate != KMOD_MODULE_BUILTIN &&
+ initstate != KMOD_MODULE_LIVE &&
+ initstate != KMOD_MODULE_COMING) {
+ errno = EPERM;
+ goto out_unref_module;
+ }
+
+ ret = 0;
+
+out_unref_module:
+ kmod_module_unref(module);
+out_unref_kmod:
+ kmod_unref(kmod);
+ return ret;
+}
+
+static char *configfs_make_item(int at, int id)
+{
+ char *item_name, prname[17];
+ int ret;
+
+ ret = prctl(PR_GET_NAME, prname);
+ if (ret)
+ return NULL;
+
+ ret = asprintf(&item_name, "%s.%u.%d", prname, getpid(), id);
+ if (ret < 0)
+ return NULL;
+
+ ret = mkdirat(at, item_name, 0600);
+ if (ret) {
+ free(item_name);
+ return NULL;
+ }
+
+ return item_name;
+}
+
+struct gpiosim_ctx {
+ struct refcount refcnt;
+ int cfs_dir_fd;
+ char *cfs_mnt_dir;
+};
+
+struct gpiosim_dev {
+ struct refcount refcnt;
+ struct gpiosim_ctx *ctx;
+ bool live;
+ char *item_name;
+ int id;
+ char *dev_name;
+ int cfs_dir_fd;
+ int sysfs_dir_fd;
+ struct list_head banks;
+};
+
+struct gpiosim_bank {
+ struct refcount refcnt;
+ struct gpiosim_dev *dev;
+ struct list_head siblings;
+ char *item_name;
+ int id;
+ char *chip_name;
+ char *dev_path;
+ int cfs_dir_fd;
+ int sysfs_dir_fd;
+ size_t num_lines;
+ struct list_head lines;
+};
+
+struct gpiosim_line {
+ struct list_head siblings;
+ unsigned int offset;
+};
+
+static inline struct gpiosim_ctx *to_gpiosim_ctx(struct refcount *ref)
+{
+ return container_of(ref, struct gpiosim_ctx, refcnt);
+}
+
+static inline struct gpiosim_dev *to_gpiosim_dev(struct refcount *ref)
+{
+ return container_of(ref, struct gpiosim_dev, refcnt);
+}
+
+static inline struct gpiosim_bank *to_gpiosim_bank(struct refcount *ref)
+{
+ return container_of(ref, struct gpiosim_bank, refcnt);
+}
+
+static int ctx_open_configfs_dir(struct gpiosim_ctx *ctx, const char *cfs_path)
+{
+ char *path;
+ int ret;
+
+ ret = asprintf(&path, "%s/gpio-sim", cfs_path);
+ if (ret < 0)
+ return -1;
+
+ ctx->cfs_dir_fd = open(path, O_RDONLY);
+ free(path);
+ if (ctx->cfs_dir_fd < 0)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * We don't need to check the configfs module as loading gpio-sim will pull it
+ * in but we need to find out if and where configfs was mounted. If it wasn't
+ * then as a last resort we'll try to mount it ourselves.
+ */
+static int ctx_get_configfs_fd(struct gpiosim_ctx *ctx)
+{
+ struct libmnt_context *mntctx;
+ struct libmnt_iter *iter;
+ struct libmnt_table *tb;
+ struct libmnt_fs *fs;
+ const char *type;
+ int ret;
+
+ /* Try to find out if and where configfs is mounted. */
+ mntctx = mnt_new_context();
+ if (!mntctx)
+ return -1;
+
+ ret = mnt_context_get_mtab(mntctx, &tb);
+ if (ret)
+ goto out_free_ctx;
+
+ iter = mnt_new_iter(MNT_ITER_FORWARD);
+ if (!iter)
+ goto out_free_ctx;
+
+ while (mnt_table_next_fs(tb, iter, &fs) == 0) {
+ type = mnt_fs_get_fstype(fs);
+
+ if (strcmp(type, "configfs") == 0) {
+ ret = ctx_open_configfs_dir(ctx, mnt_fs_get_target(fs));
+ if (ret)
+ goto out_free_iter;
+
+ ret = 0;
+ goto out_free_iter;
+ }
+ }
+
+ /* Didn't find any configfs mounts - let's try to do it ourselves. */
+ ctx->cfs_mnt_dir = strdup("/tmp/gpiosim-configfs-XXXXXX");
+ if (!ctx->cfs_mnt_dir)
+ goto out_free_iter;
+
+ ctx->cfs_mnt_dir = mkdtemp(ctx->cfs_mnt_dir);
+ if (!ctx->cfs_mnt_dir)
+ goto out_free_tmpdir;
+
+ ret = mount(NULL, ctx->cfs_mnt_dir, "configfs", MS_RELATIME, NULL);
+ if (ret)
+ goto out_rm_tmpdir;
+
+ ret = ctx_open_configfs_dir(ctx, ctx->cfs_mnt_dir);
+ if (ret == 0)
+ /* Skip unmounting & deleting the tmp directory on success. */
+ goto out_free_iter;
+
+ umount(ctx->cfs_mnt_dir);
+out_rm_tmpdir:
+ rmdir(ctx->cfs_mnt_dir);
+out_free_tmpdir:
+ free(ctx->cfs_mnt_dir);
+ ctx->cfs_mnt_dir = NULL;
+out_free_iter:
+ mnt_free_iter(iter);
+out_free_ctx:
+ mnt_free_context(mntctx);
+
+ return ret;
+}
+
+static void ctx_release(struct refcount *ref)
+{
+ struct gpiosim_ctx *ctx = to_gpiosim_ctx(ref);
+
+ close(ctx->cfs_dir_fd);
+
+ if (ctx->cfs_mnt_dir) {
+ umount(ctx->cfs_mnt_dir);
+ rmdir(ctx->cfs_mnt_dir);
+ free(ctx->cfs_mnt_dir);
+ }
+
+ free(ctx);
+}
+
+GPIOSIM_API struct gpiosim_ctx *gpiosim_ctx_new(void)
+{
+ struct gpiosim_ctx *ctx;
+ int ret;
+
+ ret = check_kernel_version();
+ if (ret)
+ return NULL;
+
+ ret = check_gpiosim_module();
+ if (ret)
+ return NULL;
+
+ ctx = malloc(sizeof(*ctx));
+ if (!ctx)
+ return NULL;
+
+ memset(ctx, 0, sizeof(*ctx));
+ refcount_init(&ctx->refcnt, ctx_release);
+
+ ret = ctx_get_configfs_fd(ctx);
+ if (ret) {
+ free(ctx);
+ return NULL;
+ }
+
+ return ctx;
+}
+
+GPIOSIM_API struct gpiosim_ctx *gpiosim_ctx_ref(struct gpiosim_ctx *ctx)
+{
+ refcount_inc(&ctx->refcnt);
+
+ return ctx;
+}
+
+GPIOSIM_API void gpiosim_ctx_unref(struct gpiosim_ctx *ctx)
+{
+ refcount_dec(&ctx->refcnt);
+}
+
+static void dev_release(struct refcount *ref)
+{
+ struct gpiosim_dev *dev = to_gpiosim_dev(ref);
+ struct gpiosim_ctx *ctx = dev->ctx;
+
+ if (dev->live)
+ gpiosim_dev_disable(dev);
+
+ unlinkat(ctx->cfs_dir_fd, dev->item_name, AT_REMOVEDIR);
+ close(dev->cfs_dir_fd);
+ free(dev->dev_name);
+ free(dev->item_name);
+ id_free(dev->id);
+ gpiosim_ctx_unref(ctx);
+ free(dev);
+}
+
+GPIOSIM_API struct gpiosim_dev *gpiosim_dev_new(struct gpiosim_ctx *ctx)
+{
+ int configfs_fd, ret, id;
+ struct gpiosim_dev *dev;
+ char devname[128];
+ char *item_name;
+
+ id = id_alloc();
+ if (id < 0)
+ return NULL;
+
+ item_name = configfs_make_item(ctx->cfs_dir_fd, id);
+ if (!item_name)
+ goto err_free_id;
+
+ configfs_fd = openat(ctx->cfs_dir_fd, item_name, O_RDONLY);
+ if (configfs_fd < 0)
+ goto err_unlink;
+
+ dev = malloc(sizeof(*dev));
+ if (!dev)
+ goto err_close_fd;
+
+ ret = open_read_close(configfs_fd, "dev_name",
+ devname, sizeof(devname));
+ if (ret)
+ goto err_free_dev;
+
+ memset(dev, 0, sizeof(*dev));
+ refcount_init(&dev->refcnt, dev_release);
+ list_init(&dev->banks);
+ dev->cfs_dir_fd = configfs_fd;
+ dev->sysfs_dir_fd = -1;
+ dev->item_name = item_name;
+ dev->id = id;
+
+ dev->dev_name = strdup(devname);
+ if (!dev->dev_name)
+ goto err_free_dev;
+
+ dev->ctx = gpiosim_ctx_ref(ctx);
+
+ return dev;
+
+err_free_dev:
+ free(dev);
+err_close_fd:
+ close(configfs_fd);
+err_unlink:
+ unlinkat(ctx->cfs_dir_fd, item_name, AT_REMOVEDIR);
+ free(item_name);
+err_free_id:
+ id_free(id);
+
+ return NULL;
+}
+
+GPIOSIM_API struct gpiosim_dev *gpiosim_dev_ref(struct gpiosim_dev *dev)
+{
+ refcount_inc(&dev->refcnt);
+
+ return dev;
+}
+
+GPIOSIM_API void gpiosim_dev_unref(struct gpiosim_dev *dev)
+{
+ refcount_dec(&dev->refcnt);
+}
+
+GPIOSIM_API struct gpiosim_ctx *gpiosim_dev_get_ctx(struct gpiosim_dev *dev)
+{
+ return gpiosim_ctx_ref(dev->ctx);
+}
+
+GPIOSIM_API const char *gpiosim_dev_get_name(struct gpiosim_dev *dev)
+{
+ return dev->dev_name;
+}
+
+static bool dev_check_pending(struct gpiosim_dev *dev)
+{
+ if (dev->live)
+ errno = EBUSY;
+
+ return !dev->live;
+}
+
+static bool dev_check_live(struct gpiosim_dev *dev)
+{
+ if (!dev->live)
+ errno = ENODEV;
+
+ return dev->live;
+}
+
+static int bank_set_chip_name(struct gpiosim_bank *bank)
+{
+ char chip_name[32];
+ int ret;
+
+ ret = open_read_close(bank->cfs_dir_fd, "chip_name",
+ chip_name, sizeof(chip_name));
+ if (ret)
+ return -1;
+
+ bank->chip_name = strdup(chip_name);
+ if (!bank->chip_name)
+ return -1;
+
+ return 0;
+}
+
+static int bank_set_dev_path(struct gpiosim_bank *bank)
+{
+ char dev_path[64];
+
+ snprintf(dev_path, sizeof(dev_path), "/dev/%s", bank->chip_name);
+
+ bank->dev_path = strdup(dev_path);
+ if (!bank->dev_path)
+ return -1;
+
+ return 0;
+}
+
+static int bank_open_sysfs_dir(struct gpiosim_bank *bank)
+{
+ struct gpiosim_dev *dev = bank->dev;
+ int fd;
+
+ fd = openat(dev->sysfs_dir_fd, bank->chip_name, O_RDONLY);
+ if (fd < 0)
+ return -1;
+
+ bank->sysfs_dir_fd = fd;
+
+ return 0;
+}
+
+static int bank_enable(struct gpiosim_bank *bank)
+{
+ int ret;
+
+ ret = bank_set_chip_name(bank);
+ if (ret)
+ return -1;
+
+ ret = bank_set_dev_path(bank);
+ if (ret)
+ return -1;
+
+ return bank_open_sysfs_dir(bank);
+}
+
+static int dev_open_sysfs_dir(struct gpiosim_dev *dev)
+{
+ int ret, fd;
+ char *sysp;
+
+ ret = asprintf(&sysp, "/sys/devices/platform/%s", dev->dev_name);
+ if (ret < 0)
+ return -1;
+
+ fd = open(sysp, O_RDONLY);
+ free(sysp);
+ if (fd < 0)
+ return -1;
+
+ dev->sysfs_dir_fd = fd;
+
+ return 0;
+}
+
+/* Closes the sysfs dir for this device and all its child banks. */
+static void dev_close_sysfs_dirs(struct gpiosim_dev *dev)
+{
+ struct gpiosim_bank *bank;
+
+ list_for_each_entry(bank, &dev->banks, siblings) {
+ free(bank->chip_name);
+ free(bank->dev_path);
+ bank->chip_name = bank->dev_path = NULL;
+ close(bank->sysfs_dir_fd);
+ bank->sysfs_dir_fd = -1;
+ }
+
+ close(dev->sysfs_dir_fd);
+ dev->sysfs_dir_fd = -1;
+}
+
+GPIOSIM_API int gpiosim_dev_enable(struct gpiosim_dev *dev)
+{
+ struct gpiosim_bank *bank;
+ int ret;
+
+ if (!dev_check_pending(dev))
+ return -1;
+
+ ret = open_write_close(dev->cfs_dir_fd, "live", "1");
+ if (ret)
+ return -1;
+
+ ret = dev_open_sysfs_dir(dev);
+ if (ret) {
+ open_write_close(dev->cfs_dir_fd, "live", "0");
+ return -1;
+ }
+
+ bank = container_of(&dev->banks, struct gpiosim_bank, siblings);
+
+ list_for_each_entry(bank, &dev->banks, siblings) {
+ ret = bank_enable(bank);
+ if (ret) {
+ dev_close_sysfs_dirs(dev);
+ open_write_close(dev->cfs_dir_fd, "live", "0");
+ return -1;
+ }
+ }
+
+ dev->live = true;
+
+ return 0;
+}
+
+GPIOSIM_API int gpiosim_dev_disable(struct gpiosim_dev *dev)
+{
+ int ret;
+
+ if (!dev_check_live(dev))
+ return -1;
+
+ ret = open_write_close(dev->cfs_dir_fd, "live", "0");
+ if (ret)
+ return ret;
+
+ dev_close_sysfs_dirs(dev);
+
+ dev->live = false;
+
+ return 0;
+}
+
+GPIOSIM_API bool gpiosim_dev_is_live(struct gpiosim_dev *dev)
+{
+ return dev->live;
+}
+
+static void bank_release(struct refcount *ref)
+{
+ struct gpiosim_bank *bank = to_gpiosim_bank(ref);
+ struct gpiosim_dev *dev = bank->dev;
+ struct gpiosim_line *line, *tmp;
+ char buf[64];
+
+ list_for_each_entry_safe(line, tmp, &bank->lines, siblings) {
+ snprintf(buf, sizeof(buf), "line%u/hog", line->offset);
+ unlinkat(bank->cfs_dir_fd, buf, AT_REMOVEDIR);
+
+ snprintf(buf, sizeof(buf), "line%u", line->offset);
+ unlinkat(bank->cfs_dir_fd, buf, AT_REMOVEDIR);
+
+ list_del(&line->siblings);
+ free(line);
+ }
+
+ list_del(&bank->siblings);
+ close(bank->cfs_dir_fd);
+ unlinkat(dev->cfs_dir_fd, bank->item_name, AT_REMOVEDIR);
+ gpiosim_dev_unref(dev);
+ if (bank->sysfs_dir_fd >= 0)
+ /* If the device wasn't disabled yet, this fd is still open. */
+ close(bank->sysfs_dir_fd);
+ free(bank->item_name);
+ id_free(bank->id);
+ free(bank->chip_name);
+ free(bank->dev_path);
+ free(bank);
+}
+
+GPIOSIM_API struct gpiosim_bank *gpiosim_bank_new(struct gpiosim_dev *dev)
+{
+ struct gpiosim_bank *bank;
+ int configfs_fd, id;
+ char *item_name;
+
+ if (!dev_check_pending(dev))
+ return NULL;
+
+ id = id_alloc();
+ if (id < 0)
+ return NULL;
+
+ item_name = configfs_make_item(dev->cfs_dir_fd, id);
+ if (!item_name)
+ goto err_free_id;
+
+ configfs_fd = openat(dev->cfs_dir_fd, item_name, O_RDONLY);
+ if (configfs_fd < 0)
+ goto err_unlink;
+
+ bank = malloc(sizeof(*bank));
+ if (!bank)
+ goto err_close_cfs;
+
+ memset(bank, 0, sizeof(*bank));
+
+ refcount_init(&bank->refcnt, bank_release);
+ list_add(&bank->siblings, &dev->banks);
+ bank->cfs_dir_fd = configfs_fd;
+ bank->dev = gpiosim_dev_ref(dev);
+ bank->item_name = item_name;
+ bank->num_lines = 1;
+ bank->id = id;
+ list_init(&bank->lines);
+
+ return bank;
+
+err_close_cfs:
+ close(configfs_fd);
+err_unlink:
+ unlinkat(dev->cfs_dir_fd, item_name, AT_REMOVEDIR);
+err_free_id:
+ id_free(id);
+
+ return NULL;
+}
+
+GPIOSIM_API struct gpiosim_bank *gpiosim_bank_ref(struct gpiosim_bank *bank)
+{
+ refcount_inc(&bank->refcnt);
+
+ return bank;
+}
+
+GPIOSIM_API void gpiosim_bank_unref(struct gpiosim_bank *bank)
+{
+ refcount_dec(&bank->refcnt);
+}
+
+GPIOSIM_API struct gpiosim_dev *gpiosim_bank_get_dev(struct gpiosim_bank *bank)
+{
+ return gpiosim_dev_ref(bank->dev);
+}
+
+GPIOSIM_API const char *gpiosim_bank_get_chip_name(struct gpiosim_bank *bank)
+{
+ return bank->chip_name;
+}
+
+GPIOSIM_API const char *gpiosim_bank_get_dev_path(struct gpiosim_bank *bank)
+{
+ return bank->dev_path;
+}
+
+GPIOSIM_API int gpiosim_bank_set_label(struct gpiosim_bank *bank,
+ const char *label)
+{
+ if (!dev_check_pending(bank->dev))
+ return -1;
+
+ return open_write_close(bank->cfs_dir_fd, "label", label);
+}
+
+GPIOSIM_API int gpiosim_bank_set_num_lines(struct gpiosim_bank *bank,
+ size_t num_lines)
+{
+ char buf[32];
+ int ret;
+
+ if (!dev_check_pending(bank->dev))
+ return -1;
+
+ snprintf(buf, sizeof(buf), "%zu", num_lines);
+
+ ret = open_write_close(bank->cfs_dir_fd, "num_lines", buf);
+ if (ret)
+ return -1;
+
+ bank->num_lines = num_lines;
+
+ return 0;
+}
+
+static int bank_make_line_dir(struct gpiosim_bank *bank, unsigned int offset)
+{
+ struct gpiosim_line *line;
+ char buf[32];
+ int ret;
+
+ snprintf(buf, sizeof(buf), "line%u", offset);
+
+ ret = faccessat(bank->cfs_dir_fd, buf, W_OK, 0);
+ if (!ret)
+ return 0;
+ if (ret && errno != ENOENT)
+ return -1;
+
+ line = malloc(sizeof(*line));
+ if (!line)
+ return -1;
+
+ ret = mkdirat(bank->cfs_dir_fd, buf, O_RDONLY);
+ if (ret) {
+ free(line);
+ return -1;
+ }
+
+ memset(line, 0, sizeof(*line));
+ line->offset = offset;
+ list_add(&line->siblings, &bank->lines);
+
+ return 0;
+}
+
+GPIOSIM_API int gpiosim_bank_set_line_name(struct gpiosim_bank *bank,
+ unsigned int offset,
+ const char *name)
+{
+ char buf[32];
+ int ret;
+
+ if (!dev_check_pending(bank->dev))
+ return -1;
+
+ ret = bank_make_line_dir(bank, offset);
+ if (ret)
+ return -1;
+
+ snprintf(buf, sizeof(buf), "line%u/name", offset);
+
+ return open_write_close(bank->cfs_dir_fd, buf, name ?: "");
+}
+
+GPIOSIM_API int gpiosim_bank_hog_line(struct gpiosim_bank *bank,
+ unsigned int offset, const char *name,
+ enum gpiosim_direction direction)
+{
+ char buf[64], *dir;
+ int ret, fd;
+
+ switch (direction) {
+ case GPIOSIM_DIRECTION_INPUT:
+ dir = "input";
+ break;
+ case GPIOSIM_DIRECTION_OUTPUT_HIGH:
+ dir = "output-high";
+ break;
+ case GPIOSIM_DIRECTION_OUTPUT_LOW:
+ dir = "output-low";
+ break;
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (!dev_check_pending(bank->dev))
+ return -1;
+
+ ret = bank_make_line_dir(bank, offset);
+ if (ret)
+ return -1;
+
+ snprintf(buf, sizeof(buf), "line%u/hog", offset);
+
+ ret = faccessat(bank->cfs_dir_fd, buf, W_OK, 0);
+ if (ret) {
+ if (errno == ENOENT) {
+ ret = mkdirat(bank->cfs_dir_fd, buf, O_RDONLY);
+ if (ret)
+ return -1;
+ } else {
+ return -1;
+ }
+ }
+
+ fd = openat(bank->cfs_dir_fd, buf, O_RDONLY);
+ if (fd < 0)
+ return -1;
+
+ ret = open_write_close(fd, "name", name ?: "");
+ if (ret) {
+ close(fd);
+ return -1;
+ }
+
+ ret = open_write_close(fd, "direction", dir);
+ close(fd);
+ return ret;
+}
+
+GPIOSIM_API int gpiosim_bank_clear_hog(struct gpiosim_bank *bank,
+ unsigned int offset)
+{
+ char buf[64];
+
+ snprintf(buf, sizeof(buf), "line%u/hog", offset);
+
+ return unlinkat(bank->cfs_dir_fd, buf, AT_REMOVEDIR);
+}
+
+static int sysfs_read_bank_attr(struct gpiosim_bank *bank, unsigned int offset,
+ const char *attr, char *buf, size_t bufsize)
+{
+ struct gpiosim_dev *dev = bank->dev;
+ char where[32];
+
+ if (!dev_check_live(dev))
+ return -1;
+
+ snprintf(where, sizeof(where), "sim_gpio%u/%s", offset, attr);
+
+ return open_read_close(bank->sysfs_dir_fd, where, buf, bufsize);
+}
+
+GPIOSIM_API enum
+gpiosim_value gpiosim_bank_get_value(struct gpiosim_bank *bank,
+ unsigned int offset)
+{
+ char what[3];
+ int ret;
+
+ ret = sysfs_read_bank_attr(bank, offset, "value", what, sizeof(what));
+ if (ret)
+ return GPIOSIM_VALUE_ERROR;
+
+ if (what[0] == '0')
+ return GPIOSIM_VALUE_INACTIVE;
+ if (what[0] == '1')
+ return GPIOSIM_VALUE_ACTIVE;
+
+ errno = EIO;
+ return GPIOSIM_VALUE_ERROR;
+}
+
+GPIOSIM_API enum gpiosim_pull
+gpiosim_bank_get_pull(struct gpiosim_bank *bank, unsigned int offset)
+{
+ char what[16];
+ int ret;
+
+ ret = sysfs_read_bank_attr(bank, offset, "pull", what, sizeof(what));
+ if (ret)
+ return GPIOSIM_PULL_ERROR;
+
+ if (strcmp(what, "pull-down") == 0)
+ return GPIOSIM_PULL_DOWN;
+ if (strcmp(what, "pull-up") == 0)
+ return GPIOSIM_PULL_UP;
+
+ errno = EIO;
+ return GPIOSIM_PULL_ERROR;
+}
+
+GPIOSIM_API int
+gpiosim_bank_set_pull(struct gpiosim_bank *bank,
+ unsigned int offset, enum gpiosim_pull pull)
+{
+ struct gpiosim_dev *dev = bank->dev;
+ char where[32], what[16];
+
+ if (!dev_check_live(dev))
+ return -1;
+
+ if (pull != GPIOSIM_PULL_DOWN && pull != GPIOSIM_PULL_UP) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ snprintf(where, sizeof(where), "sim_gpio%u/pull", offset);
+ snprintf(what, sizeof(what),
+ pull == GPIOSIM_PULL_DOWN ? "pull-down" : "pull-up");
+
+ return open_write_close(bank->sysfs_dir_fd, where, what);
+}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+#ifndef __GPIOD_GPIOSIM_H__
+#define __GPIOD_GPIOSIM_H__
+
+#include <stdbool.h>
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct gpiosim_ctx;
+struct gpiosim_dev;
+struct gpiosim_bank;
+
+enum gpiosim_value {
+ GPIOSIM_VALUE_ERROR = -1,
+ GPIOSIM_VALUE_INACTIVE = 0,
+ GPIOSIM_VALUE_ACTIVE = 1,
+};
+
+enum gpiosim_pull {
+ GPIOSIM_PULL_ERROR = -1,
+ GPIOSIM_PULL_DOWN = 1,
+ GPIOSIM_PULL_UP,
+};
+
+enum gpiosim_direction {
+ GPIOSIM_DIRECTION_INPUT = 1,
+ GPIOSIM_DIRECTION_OUTPUT_HIGH,
+ GPIOSIM_DIRECTION_OUTPUT_LOW,
+};
+
+struct gpiosim_ctx *gpiosim_ctx_new(void);
+struct gpiosim_ctx *gpiosim_ctx_ref(struct gpiosim_ctx *ctx);
+void gpiosim_ctx_unref(struct gpiosim_ctx *ctx);
+
+struct gpiosim_dev *gpiosim_dev_new(struct gpiosim_ctx *ctx);
+struct gpiosim_dev *gpiosim_dev_ref(struct gpiosim_dev *dev);
+void gpiosim_dev_unref(struct gpiosim_dev *dev);
+struct gpiosim_ctx *gpiosim_dev_get_ctx(struct gpiosim_dev *dev);
+const char *gpiosim_dev_get_name(struct gpiosim_dev *dev);
+
+int gpiosim_dev_enable(struct gpiosim_dev *dev);
+int gpiosim_dev_disable(struct gpiosim_dev *dev);
+bool gpiosim_dev_is_live(struct gpiosim_dev *dev);
+
+struct gpiosim_bank *gpiosim_bank_new(struct gpiosim_dev *dev);
+struct gpiosim_bank *gpiosim_bank_ref(struct gpiosim_bank *bank);
+void gpiosim_bank_unref(struct gpiosim_bank *bank);
+struct gpiosim_dev *gpiosim_bank_get_dev(struct gpiosim_bank *bank);
+const char *gpiosim_bank_get_chip_name(struct gpiosim_bank *bank);
+const char *gpiosim_bank_get_dev_path(struct gpiosim_bank *bank);
+
+int gpiosim_bank_set_label(struct gpiosim_bank *bank, const char *label);
+int gpiosim_bank_set_num_lines(struct gpiosim_bank *bank, size_t num_lines);
+int gpiosim_bank_set_line_name(struct gpiosim_bank *bank,
+ unsigned int offset, const char *name);
+int gpiosim_bank_hog_line(struct gpiosim_bank *bank, unsigned int offset,
+ const char *name, enum gpiosim_direction direction);
+int gpiosim_bank_clear_hog(struct gpiosim_bank *bank, unsigned int offset);
+
+enum gpiosim_value
+gpiosim_bank_get_value(struct gpiosim_bank *bank, unsigned int offset);
+enum gpiosim_pull
+gpiosim_bank_get_pull(struct gpiosim_bank *bank, unsigned int offset);
+int gpiosim_bank_set_pull(struct gpiosim_bank *bank,
+ unsigned int offset, enum gpiosim_pull pull);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* __GPIOD_GPIOSIM_H__ */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2017-2022 Bartosz Golaszewski <bartekgola@gmail.com>
+
+#include <errno.h>
+#include <glib.h>
+#include <gpiod.h>
+
+#include "gpiod-test.h"
+#include "gpiod-test-helpers.h"
+#include "gpiod-test-sim.h"
+
+#define GPIOD_TEST_GROUP "chip-info"
+
+GPIOD_TEST_CASE(get_chip_info_name)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new(NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_chip_info) info = NULL;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ info = gpiod_test_chip_get_info_or_fail(chip);
+
+ g_assert_cmpstr(gpiod_chip_info_get_name(info), ==,
+ g_gpiosim_chip_get_name(sim));
+}
+
+GPIOD_TEST_CASE(get_chip_info_label)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("label", "foobar",
+ NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_chip_info) info = NULL;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ info = gpiod_test_chip_get_info_or_fail(chip);
+
+ g_assert_cmpstr(gpiod_chip_info_get_label(info), ==, "foobar");
+}
+
+GPIOD_TEST_CASE(get_num_lines)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 16, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_chip_info) info = NULL;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ info = gpiod_test_chip_get_info_or_fail(chip);
+
+ g_assert_cmpuint(gpiod_chip_info_get_num_lines(info), ==, 16);
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+
+#include <errno.h>
+#include <glib.h>
+#include <gpiod.h>
+
+#include "gpiod-test.h"
+#include "gpiod-test-helpers.h"
+#include "gpiod-test-sim.h"
+
+#define GPIOD_TEST_GROUP "chip"
+
+GPIOD_TEST_CASE(open_chip_good)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new(NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+
+ chip = gpiod_chip_open(g_gpiosim_chip_get_dev_path(sim));
+ g_assert_nonnull(chip);
+}
+
+GPIOD_TEST_CASE(open_chip_nonexistent)
+{
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+
+ chip = gpiod_chip_open("/dev/nonexistent");
+ g_assert_null(chip);
+ gpiod_test_expect_errno(ENOENT);
+}
+
+GPIOD_TEST_CASE(open_chip_not_a_character_device)
+{
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+
+ chip = gpiod_chip_open("/tmp");
+ g_assert_null(chip);
+ gpiod_test_expect_errno(ENOTTY);
+}
+
+GPIOD_TEST_CASE(open_chip_not_a_gpio_device)
+{
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+
+ chip = gpiod_chip_open("/dev/null");
+ g_assert_null(chip);
+ gpiod_test_expect_errno(ENODEV);
+}
+
+GPIOD_TEST_CASE(open_chip_null_path)
+{
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+
+ chip = gpiod_chip_open(NULL);
+ g_assert_null(chip);
+ gpiod_test_expect_errno(EINVAL);
+}
+
+GPIOD_TEST_CASE(get_chip_path)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new(NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ const gchar *path = g_gpiosim_chip_get_dev_path(sim);
+
+ chip = gpiod_test_open_chip_or_fail(path);
+
+ g_assert_cmpstr(gpiod_chip_get_path(chip), ==, path);
+}
+
+GPIOD_TEST_CASE(get_fd)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new(NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+
+ g_assert_cmpint(gpiod_chip_get_fd(chip), >=, 0);
+}
+
+GPIOD_TEST_CASE(find_line_bad)
+{
+ static const GPIOSimLineName names[] = {
+ { .offset = 1, .name = "foo", },
+ { .offset = 2, .name = "bar", },
+ { .offset = 4, .name = "baz", },
+ { .offset = 5, .name = "xyz", },
+ { }
+ };
+
+ g_autoptr(GPIOSimChip) sim = NULL;
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(GVariant) vnames = gpiod_test_package_line_names(names);
+
+ sim = g_gpiosim_chip_new(
+ "num-lines", 8,
+ "line-names", vnames,
+ NULL);
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+
+ g_assert_cmpint(
+ gpiod_chip_get_line_offset_from_name(chip,
+ "nonexistent"), ==, -1);
+ gpiod_test_expect_errno(ENOENT);
+}
+
+GPIOD_TEST_CASE(find_line_good)
+{
+ static const GPIOSimLineName names[] = {
+ { .offset = 1, .name = "foo", },
+ { .offset = 2, .name = "bar", },
+ { .offset = 4, .name = "baz", },
+ { .offset = 5, .name = "xyz", },
+ { }
+ };
+
+ g_autoptr(GPIOSimChip) sim = NULL;
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(GVariant) vnames = gpiod_test_package_line_names(names);
+
+ sim = g_gpiosim_chip_new(
+ "num-lines", 8,
+ "line-names", vnames,
+ NULL);
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+
+ g_assert_cmpint(gpiod_chip_get_line_offset_from_name(chip, "baz"),
+ ==, 4);
+}
+
+/* Verify that for duplicated line names, the first one is returned. */
+GPIOD_TEST_CASE(find_line_duplicate)
+{
+ static const GPIOSimLineName names[] = {
+ { .offset = 1, .name = "foo", },
+ { .offset = 2, .name = "baz", },
+ { .offset = 4, .name = "baz", },
+ { .offset = 5, .name = "xyz", },
+ { }
+ };
+
+ g_autoptr(GPIOSimChip) sim = NULL;
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(GVariant) vnames = gpiod_test_package_line_names(names);
+
+ sim = g_gpiosim_chip_new(
+ "num-lines", 8,
+ "line-names", vnames,
+ NULL);
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+
+ g_assert_cmpint(gpiod_chip_get_line_offset_from_name(chip, "baz"),
+ ==, 2);
+}
+
+GPIOD_TEST_CASE(find_line_non_standard_names)
+{
+ static const GPIOSimLineName names[] = {
+ { .offset = 1, .name = "with whitespace", },
+ { .offset = 2, .name = "[:-]chars", },
+ { .offset = 3, .name = "l", },
+ { .offset = 6, .name = "ALLCAPS", },
+ { }
+ };
+
+ g_autoptr(GVariant) vnames = gpiod_test_package_line_names(names);
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8,
+ "line-names", vnames,
+ NULL);
+
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+
+ g_assert_cmpint(gpiod_chip_get_line_offset_from_name(chip,
+ "with whitespace"),
+ ==, 1);
+ g_assert_cmpint(gpiod_chip_get_line_offset_from_name(chip, "[:-]chars"),
+ ==, 2);
+ g_assert_cmpint(gpiod_chip_get_line_offset_from_name(chip, "l"),
+ ==, 3);
+ g_assert_cmpint(gpiod_chip_get_line_offset_from_name(chip, "ALLCAPS"),
+ ==, 6);
+}
+
+GPIOD_TEST_CASE(find_line_null_name)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new(NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ gint ret;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+
+ ret = gpiod_chip_get_line_offset_from_name(chip, NULL);
+ g_assert_cmpint(ret, ==, -1);
+ gpiod_test_expect_errno(EINVAL);
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+
+#include <glib.h>
+#include <gpiod.h>
+#include <poll.h>
+
+#include "gpiod-test.h"
+#include "gpiod-test-helpers.h"
+#include "gpiod-test-sim.h"
+
+#define GPIOD_TEST_GROUP "edge-event"
+
+GPIOD_TEST_CASE(edge_event_buffer_capacity)
+{
+ g_autoptr(struct_gpiod_edge_event_buffer) buffer = NULL;
+
+ buffer = gpiod_test_create_edge_event_buffer_or_fail(32);
+
+ g_assert_cmpuint(gpiod_edge_event_buffer_get_capacity(buffer), ==, 32);
+}
+
+GPIOD_TEST_CASE(edge_event_buffer_max_capacity)
+{
+ g_autoptr(struct_gpiod_edge_event_buffer) buffer = NULL;
+
+ buffer = gpiod_test_create_edge_event_buffer_or_fail(16 * 64 * 2);
+
+ g_assert_cmpuint(gpiod_edge_event_buffer_get_capacity(buffer),
+ ==, 16 * 64);
+}
+
+GPIOD_TEST_CASE(edge_event_wait_timeout)
+{
+ static const guint offset = 4;
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ gint ret;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_test_chip_request_lines_or_fail(chip, NULL, line_cfg);
+
+ ret = gpiod_line_request_wait_edge_events(request, 1000000);
+ g_assert_cmpint(ret, ==, 0);
+}
+
+GPIOD_TEST_CASE(cannot_request_lines_in_output_mode_with_edge_detection)
+{
+ static const guint offset = 4;
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_chip_request_lines(chip, NULL, line_cfg);
+ g_assert_null(request);
+ gpiod_test_expect_errno(EINVAL);
+}
+
+static gpointer falling_and_rising_edge_events(gpointer data)
+{
+ GPIOSimChip *sim = data;
+
+ g_usleep(1000);
+
+ g_gpiosim_chip_set_pull(sim, 2, G_GPIOSIM_PULL_UP);
+
+ g_usleep(1000);
+
+ g_gpiosim_chip_set_pull(sim, 2, G_GPIOSIM_PULL_DOWN);
+
+ return NULL;
+}
+
+GPIOD_TEST_CASE(read_both_events)
+{
+ static const guint offset = 2;
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ g_autoptr(GThread) thread = NULL;
+ g_autoptr(struct_gpiod_edge_event_buffer) buffer = NULL;
+ struct gpiod_edge_event *event;
+ guint64 ts_rising, ts_falling;
+ gint ret;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+ buffer = gpiod_test_create_edge_event_buffer_or_fail(64);
+
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_test_chip_request_lines_or_fail(chip, NULL, line_cfg);
+
+ thread = g_thread_new("request-release",
+ falling_and_rising_edge_events, sim);
+ g_thread_ref(thread);
+
+ /* First event. */
+
+ ret = gpiod_line_request_wait_edge_events(request, 1000000000);
+ g_assert_cmpint(ret, >, 0);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ ret = gpiod_line_request_read_edge_events(request, buffer, 1);
+ g_assert_cmpint(ret, ==, 1);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpuint(gpiod_edge_event_buffer_get_num_events(buffer), ==, 1);
+ event = gpiod_edge_event_buffer_get_event(buffer, 0);
+ g_assert_nonnull(event);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpint(gpiod_edge_event_get_event_type(event), ==,
+ GPIOD_EDGE_EVENT_RISING_EDGE);
+ g_assert_cmpuint(gpiod_edge_event_get_line_offset(event), ==, 2);
+ ts_rising = gpiod_edge_event_get_timestamp_ns(event);
+
+ /* Second event. */
+
+ ret = gpiod_line_request_wait_edge_events(request, 1000000000);
+ g_assert_cmpint(ret, >, 0);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ ret = gpiod_line_request_read_edge_events(request, buffer, 1);
+ g_assert_cmpint(ret, ==, 1);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpuint(gpiod_edge_event_buffer_get_num_events(buffer), ==, 1);
+ event = gpiod_edge_event_buffer_get_event(buffer, 0);
+ g_assert_nonnull(event);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpint(gpiod_edge_event_get_event_type(event), ==,
+ GPIOD_EDGE_EVENT_FALLING_EDGE);
+ g_assert_cmpuint(gpiod_edge_event_get_line_offset(event), ==, 2);
+ ts_falling = gpiod_edge_event_get_timestamp_ns(event);
+
+ g_thread_join(thread);
+
+ g_assert_cmpuint(ts_falling, >, ts_rising);
+}
+
+GPIOD_TEST_CASE(read_rising_edge_event)
+{
+ static const guint offset = 2;
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ g_autoptr(GThread) thread = NULL;
+ g_autoptr(struct_gpiod_edge_event_buffer) buffer = NULL;
+ struct gpiod_edge_event *event;
+ gint ret;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+ buffer = gpiod_test_create_edge_event_buffer_or_fail(64);
+
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_line_settings_set_edge_detection(settings,
+ GPIOD_LINE_EDGE_RISING);
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_test_chip_request_lines_or_fail(chip, NULL, line_cfg);
+
+ thread = g_thread_new("edge-generator",
+ falling_and_rising_edge_events, sim);
+ g_thread_ref(thread);
+
+ /* First event. */
+
+ ret = gpiod_line_request_wait_edge_events(request, 1000000000);
+ g_assert_cmpint(ret, >, 0);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ ret = gpiod_line_request_read_edge_events(request, buffer, 1);
+ g_assert_cmpint(ret, ==, 1);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpuint(gpiod_edge_event_buffer_get_num_events(buffer), ==, 1);
+ event = gpiod_edge_event_buffer_get_event(buffer, 0);
+ g_assert_nonnull(event);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpint(gpiod_edge_event_get_event_type(event), ==,
+ GPIOD_EDGE_EVENT_RISING_EDGE);
+ g_assert_cmpuint(gpiod_edge_event_get_line_offset(event), ==, 2);
+
+ /* Second event. */
+
+ ret = gpiod_line_request_wait_edge_events(request, 1000000);
+ g_assert_cmpint(ret, ==, 0); /* Time-out. */
+
+ g_thread_join(thread);
+}
+
+GPIOD_TEST_CASE(read_falling_edge_event)
+{
+ static const guint offset = 2;
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ g_autoptr(GThread) thread = NULL;
+ g_autoptr(struct_gpiod_edge_event_buffer) buffer = NULL;
+ struct gpiod_edge_event *event;
+ gint ret;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+ buffer = gpiod_test_create_edge_event_buffer_or_fail(64);
+
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_line_settings_set_edge_detection(settings,
+ GPIOD_LINE_EDGE_FALLING);
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_test_chip_request_lines_or_fail(chip, NULL, line_cfg);
+
+ thread = g_thread_new("request-release",
+ falling_and_rising_edge_events, sim);
+ g_thread_ref(thread);
+
+ /* First event is the second generated. */
+
+ ret = gpiod_line_request_wait_edge_events(request, 1000000000);
+ g_assert_cmpint(ret, >, 0);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ ret = gpiod_line_request_read_edge_events(request, buffer, 1);
+ g_assert_cmpint(ret, ==, 1);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpuint(gpiod_edge_event_buffer_get_num_events(buffer), ==, 1);
+ event = gpiod_edge_event_buffer_get_event(buffer, 0);
+ g_assert_nonnull(event);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpint(gpiod_edge_event_get_event_type(event), ==,
+ GPIOD_EDGE_EVENT_FALLING_EDGE);
+ g_assert_cmpuint(gpiod_edge_event_get_line_offset(event), ==, 2);
+
+ /* No more events. */
+
+ ret = gpiod_line_request_wait_edge_events(request, 1000000);
+ g_assert_cmpint(ret, ==, 0); /* Time-out. */
+
+ g_thread_join(thread);
+}
+
+GPIOD_TEST_CASE(read_rising_edge_event_polled)
+{
+ static const guint offset = 2;
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ g_autoptr(GThread) thread = NULL;
+ g_autoptr(struct_gpiod_edge_event_buffer) buffer = NULL;
+ struct gpiod_edge_event *event;
+ struct timespec ts;
+ struct pollfd pfd;
+ gint ret, fd;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+ buffer = gpiod_test_create_edge_event_buffer_or_fail(64);
+
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_line_settings_set_edge_detection(settings,
+ GPIOD_LINE_EDGE_RISING);
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_test_chip_request_lines_or_fail(chip, NULL, line_cfg);
+
+ thread = g_thread_new("edge-generator",
+ falling_and_rising_edge_events, sim);
+ g_thread_ref(thread);
+
+ /* First event. */
+
+ fd = gpiod_line_request_get_fd(request);
+
+ memset(&pfd, 0, sizeof(pfd));
+ pfd.fd = fd;
+ pfd.events = POLLIN | POLLPRI;
+
+ ts.tv_sec = 1;
+ ts.tv_nsec = 0;
+
+ ret = ppoll(&pfd, 1, &ts, NULL);
+ g_assert_cmpint(ret, >, 0);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ ret = gpiod_line_request_read_edge_events(request, buffer, 1);
+ g_assert_cmpint(ret, ==, 1);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpuint(gpiod_edge_event_buffer_get_num_events(buffer), ==, 1);
+ event = gpiod_edge_event_buffer_get_event(buffer, 0);
+ g_assert_nonnull(event);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpint(gpiod_edge_event_get_event_type(event), ==,
+ GPIOD_EDGE_EVENT_RISING_EDGE);
+ g_assert_cmpuint(gpiod_edge_event_get_line_offset(event), ==, 2);
+
+ /* Second event. */
+
+ ret = gpiod_line_request_wait_edge_events(request, 1000000);
+ g_assert_cmpint(ret, ==, 0); /* Time-out. */
+
+ g_thread_join(thread);
+}
+
+GPIOD_TEST_CASE(read_both_events_blocking)
+{
+ /*
+ * This time without polling so that the read gets a chance to block
+ * and we can make sure it doesn't immediately return an error.
+ */
+
+ static const guint offset = 2;
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ g_autoptr(GThread) thread = NULL;
+ g_autoptr(struct_gpiod_edge_event_buffer) buffer = NULL;
+ struct gpiod_edge_event *event;
+ gint ret;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+ buffer = gpiod_test_create_edge_event_buffer_or_fail(64);
+
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_test_chip_request_lines_or_fail(chip, NULL, line_cfg);
+
+ thread = g_thread_new("request-release",
+ falling_and_rising_edge_events, sim);
+ g_thread_ref(thread);
+
+ /* First event. */
+
+ ret = gpiod_line_request_read_edge_events(request, buffer, 1);
+ g_assert_cmpint(ret, ==, 1);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpuint(gpiod_edge_event_buffer_get_num_events(buffer), ==, 1);
+ event = gpiod_edge_event_buffer_get_event(buffer, 0);
+ g_assert_nonnull(event);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpint(gpiod_edge_event_get_event_type(event), ==,
+ GPIOD_EDGE_EVENT_RISING_EDGE);
+ g_assert_cmpuint(gpiod_edge_event_get_line_offset(event), ==, 2);
+
+ /* Second event. */
+
+ ret = gpiod_line_request_read_edge_events(request, buffer, 1);
+ g_assert_cmpint(ret, ==, 1);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpuint(gpiod_edge_event_buffer_get_num_events(buffer), ==, 1);
+ event = gpiod_edge_event_buffer_get_event(buffer, 0);
+ g_assert_nonnull(event);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpint(gpiod_edge_event_get_event_type(event), ==,
+ GPIOD_EDGE_EVENT_FALLING_EDGE);
+ g_assert_cmpuint(gpiod_edge_event_get_line_offset(event), ==, 2);
+
+ g_thread_join(thread);
+}
+
+static gpointer rising_edge_events_on_two_offsets(gpointer data)
+{
+ GPIOSimChip *sim = data;
+
+ g_usleep(1000);
+
+ g_gpiosim_chip_set_pull(sim, 2, G_GPIOSIM_PULL_UP);
+
+ g_usleep(1000);
+
+ g_gpiosim_chip_set_pull(sim, 3, G_GPIOSIM_PULL_UP);
+
+ return NULL;
+}
+
+GPIOD_TEST_CASE(seqno)
+{
+ static const guint offsets[] = { 2, 3 };
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ g_autoptr(GThread) thread = NULL;
+ g_autoptr(struct_gpiod_edge_event_buffer) buffer = NULL;
+ struct gpiod_edge_event *event;
+ gint ret;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+ buffer = gpiod_test_create_edge_event_buffer_or_fail(64);
+
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 2,
+ settings);
+
+ request = gpiod_test_chip_request_lines_or_fail(chip, NULL, line_cfg);
+
+ thread = g_thread_new("request-release",
+ rising_edge_events_on_two_offsets, sim);
+ g_thread_ref(thread);
+
+ /* First event. */
+
+ ret = gpiod_line_request_wait_edge_events(request, 1000000000);
+ g_assert_cmpint(ret, >, 0);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ ret = gpiod_line_request_read_edge_events(request, buffer, 1);
+ g_assert_cmpint(ret, ==, 1);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpuint(gpiod_edge_event_buffer_get_num_events(buffer), ==, 1);
+ event = gpiod_edge_event_buffer_get_event(buffer, 0);
+ g_assert_nonnull(event);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpuint(gpiod_edge_event_get_line_offset(event), ==, 2);
+ g_assert_cmpuint(gpiod_edge_event_get_global_seqno(event), ==, 1);
+ g_assert_cmpuint(gpiod_edge_event_get_line_seqno(event), ==, 1);
+
+ /* Second event. */
+
+ ret = gpiod_line_request_wait_edge_events(request, 1000000000);
+ g_assert_cmpint(ret, >, 0);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ ret = gpiod_line_request_read_edge_events(request, buffer, 1);
+ g_assert_cmpint(ret, ==, 1);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpuint(gpiod_edge_event_buffer_get_num_events(buffer), ==, 1);
+ event = gpiod_edge_event_buffer_get_event(buffer, 0);
+ g_assert_nonnull(event);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpuint(gpiod_edge_event_get_line_offset(event), ==, 3);
+ g_assert_cmpuint(gpiod_edge_event_get_global_seqno(event), ==, 2);
+ g_assert_cmpuint(gpiod_edge_event_get_line_seqno(event), ==, 1);
+
+ g_thread_join(thread);
+}
+
+GPIOD_TEST_CASE(event_copy)
+{
+ static const guint offset = 2;
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ g_autoptr(struct_gpiod_edge_event_buffer) buffer = NULL;
+ g_autoptr(struct_gpiod_edge_event) copy = NULL;
+ struct gpiod_edge_event *event;
+ gint ret;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+ buffer = gpiod_test_create_edge_event_buffer_or_fail(64);
+
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_test_chip_request_lines_or_fail(chip, NULL, line_cfg);
+
+ g_gpiosim_chip_set_pull(sim, 2, G_GPIOSIM_PULL_UP);
+
+ ret = gpiod_line_request_wait_edge_events(request, 1000000000);
+ g_assert_cmpint(ret, >, 0);
+ gpiod_test_return_if_failed();
+
+ ret = gpiod_line_request_read_edge_events(request, buffer, 1);
+ g_assert_cmpint(ret, ==, 1);
+ gpiod_test_return_if_failed();
+
+ event = gpiod_edge_event_buffer_get_event(buffer, 0);
+ g_assert_nonnull(event);
+ gpiod_test_return_if_failed();
+
+ copy = gpiod_edge_event_copy(event);
+ g_assert_nonnull(copy);
+ g_assert_true(copy != event);
+}
+
+GPIOD_TEST_CASE(reading_more_events_than_the_queue_contains_doesnt_block)
+{
+ static const guint offset = 2;
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ g_autoptr(struct_gpiod_edge_event_buffer) buffer = NULL;
+ gint ret;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+ buffer = gpiod_test_create_edge_event_buffer_or_fail(64);
+
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_test_chip_request_lines_or_fail(chip, NULL, line_cfg);
+
+ g_gpiosim_chip_set_pull(sim, 2, G_GPIOSIM_PULL_UP);
+ g_usleep(500);
+ g_gpiosim_chip_set_pull(sim, 2, G_GPIOSIM_PULL_DOWN);
+ g_usleep(500);
+ g_gpiosim_chip_set_pull(sim, 2, G_GPIOSIM_PULL_UP);
+ g_usleep(500);
+ g_gpiosim_chip_set_pull(sim, 2, G_GPIOSIM_PULL_DOWN);
+ g_usleep(500);
+ g_gpiosim_chip_set_pull(sim, 2, G_GPIOSIM_PULL_UP);
+ g_usleep(500);
+ g_gpiosim_chip_set_pull(sim, 2, G_GPIOSIM_PULL_DOWN);
+ g_usleep(500);
+ g_gpiosim_chip_set_pull(sim, 2, G_GPIOSIM_PULL_UP);
+ g_usleep(500);
+
+ ret = gpiod_line_request_read_edge_events(request, buffer, 12);
+ g_assert_cmpint(ret, ==, 7);
+ gpiod_test_return_if_failed();
+
+ ret = gpiod_line_request_wait_edge_events(request, 1000);
+ g_assert_cmpint(ret, ==, 0);
+ gpiod_test_return_if_failed();
+}
+
+GPIOD_TEST_CASE(null_buffer)
+{
+ static const guint offset = 2;
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ gint ret;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_test_chip_request_lines_or_fail(chip, NULL, line_cfg);
+
+ ret = gpiod_line_request_read_edge_events(request, NULL, 1);
+ g_assert_cmpint(ret, ==, -1);
+ gpiod_test_expect_errno(EINVAL);
+}
+
+GPIOD_TEST_CASE(get_edge_event_index_out_of_bounds)
+{
+ static const guint offset = 2;
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ g_autoptr(struct_gpiod_edge_event_buffer) buffer = NULL;
+ struct gpiod_edge_event *event;
+ gint ret;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+ buffer = gpiod_test_create_edge_event_buffer_or_fail(64);
+
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_test_chip_request_lines_or_fail(chip, NULL, line_cfg);
+
+ g_gpiosim_chip_set_pull(sim, 2, G_GPIOSIM_PULL_UP);
+ g_usleep(500);
+ g_gpiosim_chip_set_pull(sim, 2, G_GPIOSIM_PULL_DOWN);
+ g_usleep(500);
+ g_gpiosim_chip_set_pull(sim, 2, G_GPIOSIM_PULL_UP);
+ g_usleep(500);
+
+ ret = gpiod_line_request_read_edge_events(request, buffer, 3);
+ g_assert_cmpint(ret, ==, 3);
+ gpiod_test_return_if_failed();
+
+ event = gpiod_edge_event_buffer_get_event(buffer, 5);
+ g_assert_null(event);
+ gpiod_test_expect_errno(EINVAL);
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+
+#include <glib.h>
+#include <gpiod.h>
+#include <poll.h>
+
+#include "gpiod-test.h"
+#include "gpiod-test-helpers.h"
+#include "gpiod-test-sim.h"
+
+#define GPIOD_TEST_GROUP "info-event"
+
+GPIOD_TEST_CASE(watching_info_events_returns_line_info)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_info) info = NULL;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ info = gpiod_test_chip_watch_line_info_or_fail(chip, 3);
+ g_assert_cmpuint(gpiod_line_info_get_offset(info), ==, 3);
+}
+
+GPIOD_TEST_CASE(try_offset_out_of_range)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_info) info = NULL;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ info = gpiod_chip_watch_line_info(chip, 10);
+ g_assert_null(info);
+ gpiod_test_expect_errno(EINVAL);
+}
+
+GPIOD_TEST_CASE(event_timeout)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_info) info = NULL;
+ gint ret;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ info = gpiod_test_chip_watch_line_info_or_fail(chip, 6);
+
+ ret = gpiod_chip_wait_info_event(chip, 100000000);
+ g_assert_cmpint(ret, ==, 0);
+}
+
+struct request_ctx {
+ const char *path;
+ guint offset;
+};
+
+static gpointer request_reconfigure_release_line(gpointer data)
+{
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ struct request_ctx *ctx = data;
+ gint ret;
+
+ chip = gpiod_chip_open(ctx->path);
+ g_assert_nonnull(chip);
+ if (g_test_failed())
+ return NULL;
+
+ line_cfg = gpiod_line_config_new();
+ g_assert_nonnull(line_cfg);
+ if (g_test_failed())
+ return NULL;
+
+ settings = gpiod_line_settings_new();
+ g_assert_nonnull(settings);
+ if (g_test_failed())
+ return NULL;
+
+ g_usleep(1000);
+
+ ret = gpiod_line_config_add_line_settings(line_cfg, &ctx->offset,
+ 1, settings);
+ g_assert_cmpint(ret, ==, 0);
+ if (g_test_failed())
+ return NULL;
+
+ request = gpiod_chip_request_lines(chip, NULL, line_cfg);
+ g_assert_nonnull(request);
+ if (g_test_failed())
+ return NULL;
+
+ g_usleep(1000);
+
+ gpiod_line_config_reset(line_cfg);
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+ ret = gpiod_line_config_add_line_settings(line_cfg, &ctx->offset,
+ 1, settings);
+ g_assert_cmpint(ret, ==, 0);
+ if (g_test_failed())
+ return NULL;
+
+ ret = gpiod_line_request_reconfigure_lines(request, line_cfg);
+ g_assert_cmpint(ret, ==, 0);
+ if (g_test_failed())
+ return NULL;
+
+ g_usleep(1000);
+
+ gpiod_line_request_release(request);
+ request = NULL;
+
+ return NULL;
+}
+
+GPIOD_TEST_CASE(request_reconfigure_release_events)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_info) info = NULL;
+ g_autoptr(struct_gpiod_info_event) request_event = NULL;
+ g_autoptr(struct_gpiod_info_event) reconfigure_event = NULL;
+ g_autoptr(struct_gpiod_info_event) release_event = NULL;
+ g_autoptr(GThread) thread = NULL;
+ struct gpiod_line_info *request_info, *reconfigure_info, *release_info;
+ guint64 request_ts, reconfigure_ts, release_ts;
+ struct request_ctx ctx;
+ const char *chip_path = g_gpiosim_chip_get_dev_path(sim);
+ gint ret;
+
+ chip = gpiod_test_open_chip_or_fail(chip_path);
+ info = gpiod_test_chip_watch_line_info_or_fail(chip, 3);
+
+ g_assert_false(gpiod_line_info_is_used(info));
+
+ ctx.path = chip_path;
+ ctx.offset = 3;
+
+ thread = g_thread_new("request-release",
+ request_reconfigure_release_line, &ctx);
+ g_thread_ref(thread);
+
+ ret = gpiod_chip_wait_info_event(chip, 1000000000);
+ g_assert_cmpint(ret, >, 0);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ request_event = gpiod_chip_read_info_event(chip);
+ g_assert_nonnull(request_event);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpint(gpiod_info_event_get_event_type(request_event), ==,
+ GPIOD_INFO_EVENT_LINE_REQUESTED);
+
+ request_info = gpiod_info_event_get_line_info(request_event);
+
+ g_assert_cmpuint(gpiod_line_info_get_offset(request_info), ==, 3);
+ g_assert_true(gpiod_line_info_is_used(request_info));
+ g_assert_cmpint(gpiod_line_info_get_direction(request_info), ==,
+ GPIOD_LINE_DIRECTION_INPUT);
+
+ ret = gpiod_chip_wait_info_event(chip, 1000000000);
+ g_assert_cmpint(ret, >, 0);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ reconfigure_event = gpiod_chip_read_info_event(chip);
+ g_assert_nonnull(reconfigure_event);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpint(gpiod_info_event_get_event_type(reconfigure_event), ==,
+ GPIOD_INFO_EVENT_LINE_CONFIG_CHANGED);
+
+ reconfigure_info = gpiod_info_event_get_line_info(reconfigure_event);
+
+ g_assert_cmpuint(gpiod_line_info_get_offset(reconfigure_info), ==, 3);
+ g_assert_true(gpiod_line_info_is_used(reconfigure_info));
+ g_assert_cmpint(gpiod_line_info_get_direction(reconfigure_info), ==,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+
+ ret = gpiod_chip_wait_info_event(chip, 1000000000);
+ g_assert_cmpint(ret, >, 0);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ release_event = gpiod_chip_read_info_event(chip);
+ g_assert_nonnull(release_event);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpint(gpiod_info_event_get_event_type(release_event), ==,
+ GPIOD_INFO_EVENT_LINE_RELEASED);
+
+ release_info = gpiod_info_event_get_line_info(release_event);
+
+ g_assert_cmpuint(gpiod_line_info_get_offset(release_info), ==, 3);
+ g_assert_false(gpiod_line_info_is_used(release_info));
+
+ g_thread_join(thread);
+
+ request_ts = gpiod_info_event_get_timestamp_ns(request_event);
+ reconfigure_ts = gpiod_info_event_get_timestamp_ns(reconfigure_event);
+ release_ts = gpiod_info_event_get_timestamp_ns(release_event);
+
+ g_assert_cmpuint(request_ts, <, reconfigure_ts);
+ g_assert_cmpuint(reconfigure_ts, <, release_ts);
+}
+
+GPIOD_TEST_CASE(chip_fd_can_be_polled)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_info) info = NULL;
+ g_autoptr(struct_gpiod_info_event) event = NULL;
+ g_autoptr(GThread) thread = NULL;
+ const char *chip_path = g_gpiosim_chip_get_dev_path(sim);
+ struct gpiod_line_info *evinfo;
+ struct request_ctx ctx;
+ struct timespec ts;
+ struct pollfd pfd;
+ gint ret, fd;
+
+ chip = gpiod_test_open_chip_or_fail(chip_path);
+ info = gpiod_test_chip_watch_line_info_or_fail(chip, 3);
+
+ g_assert_false(gpiod_line_info_is_used(info));
+
+ ctx.path = chip_path;
+ ctx.offset = 3;
+
+ thread = g_thread_new("request-release",
+ request_reconfigure_release_line, &ctx);
+ g_thread_ref(thread);
+
+ fd = gpiod_chip_get_fd(chip);
+
+ memset(&pfd, 0, sizeof(pfd));
+ pfd.fd = fd;
+ pfd.events = POLLIN | POLLPRI;
+
+ ts.tv_sec = 1;
+ ts.tv_nsec = 0;
+
+ ret = ppoll(&pfd, 1, &ts, NULL);
+ g_assert_cmpint(ret, >, 0);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ event = gpiod_chip_read_info_event(chip);
+ g_assert_nonnull(event);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpint(gpiod_info_event_get_event_type(event), ==,
+ GPIOD_INFO_EVENT_LINE_REQUESTED);
+
+ evinfo = gpiod_info_event_get_line_info(event);
+
+ g_assert_cmpuint(gpiod_line_info_get_offset(evinfo), ==, 3);
+ g_assert_true(gpiod_line_info_is_used(evinfo));
+
+ g_thread_join(thread);
+}
+
+GPIOD_TEST_CASE(unwatch_and_check_that_no_events_are_generated)
+{
+ static const guint offset = 3;
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_info) info = NULL;
+ g_autoptr(struct_gpiod_info_event) event = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ gint ret;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ NULL);
+
+ info = gpiod_test_chip_watch_line_info_or_fail(chip, 3);
+
+ request = gpiod_test_chip_request_lines_or_fail(chip, NULL, line_cfg);
+
+ ret = gpiod_chip_wait_info_event(chip, 100000000);
+ g_assert_cmpint(ret, >, 0);
+ gpiod_test_return_if_failed();
+
+ event = gpiod_chip_read_info_event(chip);
+ g_assert_nonnull(event);
+ gpiod_test_return_if_failed();
+
+ ret = gpiod_chip_unwatch_line_info(chip, 3);
+ g_assert_cmpint(ret, ==, 0);
+ gpiod_test_return_if_failed();
+
+ gpiod_line_request_release(request);
+ request = NULL;
+
+ ret = gpiod_chip_wait_info_event(chip, 100000000);
+ g_assert_cmpint(ret, ==, 0);
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+
+#include <errno.h>
+#include <glib.h>
+#include <gpiod.h>
+
+#include "gpiod-test.h"
+#include "gpiod-test-helpers.h"
+#include "gpiod-test-sim.h"
+
+#define GPIOD_TEST_GROUP "line-config"
+
+GPIOD_TEST_CASE(too_many_lines)
+{
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) config = NULL;
+ guint offsets[65], i;
+ gint ret;
+
+ settings = gpiod_test_create_line_settings_or_fail();
+ config = gpiod_test_create_line_config_or_fail();
+
+ for (i = 0; i < 65; i++)
+ offsets[i] = i;
+
+ ret = gpiod_line_config_add_line_settings(config, offsets, 65,
+ settings);
+ g_assert_cmpint(ret, <, 0);
+ g_assert_cmpint(errno, ==, E2BIG);
+}
+
+GPIOD_TEST_CASE(get_line_settings)
+{
+ static const guint offsets[] = { 0, 1, 2, 3 };
+
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_settings) retrieved = NULL;
+ g_autoptr(struct_gpiod_line_config) config = NULL;
+
+ settings = gpiod_test_create_line_settings_or_fail();
+ config = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_PULL_DOWN);
+ gpiod_test_line_config_add_line_settings_or_fail(config, offsets, 4,
+ settings);
+
+ retrieved = gpiod_test_line_config_get_line_settings_or_fail(config, 2);
+
+ g_assert_cmpint(gpiod_line_settings_get_direction(retrieved), ==,
+ GPIOD_LINE_DIRECTION_INPUT);
+ g_assert_cmpint(gpiod_line_settings_get_bias(retrieved), ==,
+ GPIOD_LINE_BIAS_PULL_DOWN);
+}
+
+GPIOD_TEST_CASE(too_many_attrs)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ guint offset;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+ gpiod_line_settings_set_output_value(settings, GPIOD_LINE_VALUE_ACTIVE);
+ offset = 0;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_line_settings_set_debounce_period_us(settings, 1000);
+ gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+ offset = 1;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_PULL_UP);
+ offset = 2;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_PULL_DOWN);
+ offset = 3;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_DISABLED);
+ offset = 4;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ gpiod_line_settings_set_active_low(settings, true);
+ offset = 5;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ gpiod_line_settings_set_edge_detection(settings,
+ GPIOD_LINE_EDGE_FALLING);
+ offset = 6;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ gpiod_line_settings_set_event_clock(settings,
+ GPIOD_LINE_CLOCK_REALTIME);
+ offset = 7;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ gpiod_line_settings_reset(settings);
+
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+ gpiod_line_settings_set_drive(settings, GPIOD_LINE_DRIVE_OPEN_DRAIN);
+ offset = 8;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ gpiod_line_settings_set_drive(settings, GPIOD_LINE_DRIVE_OPEN_SOURCE);
+ offset = 9;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_chip_request_lines(chip, NULL, line_cfg);
+ g_assert_null(request);
+ g_assert_cmpint(errno, ==, E2BIG);
+}
+
+GPIOD_TEST_CASE(null_settings)
+{
+ static const guint offsets[] = { 0, 1, 2, 3 };
+
+ g_autoptr(struct_gpiod_line_config) config = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+
+ config = gpiod_test_create_line_config_or_fail();
+
+ gpiod_test_line_config_add_line_settings_or_fail(config, offsets, 4,
+ NULL);
+
+ settings = gpiod_test_line_config_get_line_settings_or_fail(config, 2);
+
+ g_assert_cmpint(gpiod_line_settings_get_drive(settings), ==,
+ GPIOD_LINE_DIRECTION_AS_IS);
+}
+
+GPIOD_TEST_CASE(null_and_0_offsets)
+{
+ static const guint offsets[] = { 0, 1, 2, 3 };
+
+ g_autoptr(struct_gpiod_line_config) config = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ gint ret;
+
+ config = gpiod_test_create_line_config_or_fail();
+ settings = gpiod_test_create_line_settings_or_fail();
+
+ ret = gpiod_line_config_add_line_settings(config, NULL, 4, settings);
+ g_assert_cmpint(ret, ==, -1);
+ gpiod_test_expect_errno(EINVAL);
+
+ ret = gpiod_line_config_add_line_settings(config, offsets, 0, settings);
+ g_assert_cmpint(ret, ==, -1);
+ gpiod_test_expect_errno(EINVAL);
+}
+
+GPIOD_TEST_CASE(reset_config)
+{
+ static const guint offsets[] = { 0, 1, 2, 3 };
+
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_settings) retrieved0 = NULL;
+ g_autoptr(struct_gpiod_line_settings) retrieved1 = NULL;
+ g_autoptr(struct_gpiod_line_config) config = NULL;
+
+ settings = gpiod_test_create_line_settings_or_fail();
+ config = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_PULL_DOWN);
+ gpiod_test_line_config_add_line_settings_or_fail(config, offsets, 4,
+ settings);
+
+ retrieved0 = gpiod_test_line_config_get_line_settings_or_fail(config,
+ 2);
+
+ g_assert_cmpint(gpiod_line_settings_get_direction(retrieved0), ==,
+ GPIOD_LINE_DIRECTION_INPUT);
+ g_assert_cmpint(gpiod_line_settings_get_bias(retrieved0), ==,
+ GPIOD_LINE_BIAS_PULL_DOWN);
+
+ gpiod_line_config_reset(config);
+
+ retrieved1 = gpiod_line_config_get_line_settings(config, 2);
+ g_assert_null(retrieved1);
+}
+
+GPIOD_TEST_CASE(get_offsets)
+{
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) config = NULL;
+ guint offsets[8], offsets_in[4];
+ size_t num_offsets;
+
+ settings = gpiod_test_create_line_settings_or_fail();
+ config = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_PULL_DOWN);
+ offsets[0] = 2;
+ offsets[1] = 4;
+ gpiod_test_line_config_add_line_settings_or_fail(config, offsets, 2,
+ settings);
+
+ gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+ offsets[0] = 6;
+ offsets[1] = 7;
+ gpiod_test_line_config_add_line_settings_or_fail(config, offsets, 2,
+ settings);
+
+ num_offsets = gpiod_line_config_get_configured_offsets(config,
+ offsets_in, 4);
+ g_assert_cmpuint(num_offsets, ==, 4);
+ g_assert_cmpuint(offsets_in[0], ==, 2);
+ g_assert_cmpuint(offsets_in[1], ==, 4);
+ g_assert_cmpuint(offsets_in[2], ==, 6);
+ g_assert_cmpuint(offsets_in[3], ==, 7);
+}
+
+GPIOD_TEST_CASE(get_0_offsets)
+{
+ g_autoptr(struct_gpiod_line_config) config = NULL;
+ size_t num_offsets;
+ guint offsets[3];
+
+ config = gpiod_test_create_line_config_or_fail();
+
+ num_offsets = gpiod_line_config_get_configured_offsets(config,
+ offsets, 0);
+ g_assert_cmpuint(num_offsets, ==, 0);
+}
+
+GPIOD_TEST_CASE(get_null_offsets)
+{
+ g_autoptr(struct_gpiod_line_config) config = NULL;
+ size_t num_offsets;
+
+ config = gpiod_test_create_line_config_or_fail();
+
+ num_offsets = gpiod_line_config_get_configured_offsets(config,
+ NULL, 10);
+ g_assert_cmpuint(num_offsets, ==, 0);
+}
+
+GPIOD_TEST_CASE(get_less_offsets_than_configured)
+{
+ static const guint offsets[] = { 0, 1, 2, 3 };
+
+ g_autoptr(struct_gpiod_line_config) config = NULL;
+ size_t num_retrieved;
+ guint retrieved[3];
+
+ config = gpiod_test_create_line_config_or_fail();
+
+ gpiod_test_line_config_add_line_settings_or_fail(config, offsets, 4,
+ NULL);
+
+ num_retrieved = gpiod_line_config_get_configured_offsets(config,
+ retrieved, 3);
+ g_assert_cmpuint(num_retrieved, ==, 3);
+ g_assert_cmpuint(retrieved[0], ==, 0);
+ g_assert_cmpuint(retrieved[1], ==, 1);
+ g_assert_cmpuint(retrieved[2], ==, 2);
+}
+
+GPIOD_TEST_CASE(get_more_offsets_than_configured)
+{
+ static const guint offsets[] = { 0, 1, 2, 3 };
+
+ g_autoptr(struct_gpiod_line_config) config = NULL;
+ size_t num_retrieved;
+ guint retrieved[8];
+
+ config = gpiod_test_create_line_config_or_fail();
+
+ gpiod_test_line_config_add_line_settings_or_fail(config, offsets, 4,
+ NULL);
+
+ num_retrieved = gpiod_line_config_get_configured_offsets(config,
+ retrieved, 8);
+ g_assert_cmpuint(num_retrieved, ==, 4);
+ g_assert_cmpuint(retrieved[0], ==, 0);
+ g_assert_cmpuint(retrieved[1], ==, 1);
+ g_assert_cmpuint(retrieved[2], ==, 2);
+ g_assert_cmpuint(retrieved[3], ==, 3);
+}
+
+GPIOD_TEST_CASE(set_global_output_values)
+{
+ static const guint offsets[] = { 0, 1, 2, 3 };
+ static const enum gpiod_line_value values[] = {
+ GPIOD_LINE_VALUE_ACTIVE,
+ GPIOD_LINE_VALUE_INACTIVE,
+ GPIOD_LINE_VALUE_ACTIVE,
+ GPIOD_LINE_VALUE_INACTIVE,
+ };
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 4, NULL);
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) config = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ config = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+ gpiod_test_line_config_add_line_settings_or_fail(config, offsets, 4,
+ settings);
+ gpiod_test_line_config_set_output_values_or_fail(config, values, 4);
+
+ request = gpiod_test_chip_request_lines_or_fail(chip, NULL, config);
+
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 0), ==,
+ G_GPIOSIM_VALUE_ACTIVE);
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 1), ==,
+ G_GPIOSIM_VALUE_INACTIVE);
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 2), ==,
+ G_GPIOSIM_VALUE_ACTIVE);
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 3), ==,
+ G_GPIOSIM_VALUE_INACTIVE);
+}
+
+GPIOD_TEST_CASE(read_back_global_output_values)
+{
+ static const guint offsets[] = { 0, 1, 2, 3 };
+ static const enum gpiod_line_value values[] = {
+ GPIOD_LINE_VALUE_ACTIVE,
+ GPIOD_LINE_VALUE_INACTIVE,
+ GPIOD_LINE_VALUE_ACTIVE,
+ GPIOD_LINE_VALUE_INACTIVE,
+ };
+
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_settings) retrieved = NULL;
+ g_autoptr(struct_gpiod_line_config) config = NULL;
+
+ settings = gpiod_test_create_line_settings_or_fail();
+ config = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+ gpiod_line_settings_set_output_value(settings, GPIOD_LINE_VALUE_ACTIVE);
+ gpiod_test_line_config_add_line_settings_or_fail(config, offsets, 4,
+ settings);
+ gpiod_test_line_config_set_output_values_or_fail(config, values, 4);
+
+ retrieved = gpiod_test_line_config_get_line_settings_or_fail(config, 1);
+ g_assert_cmpint(gpiod_line_settings_get_output_value(retrieved), ==,
+ GPIOD_LINE_VALUE_INACTIVE);
+}
+
+GPIOD_TEST_CASE(set_output_values_invalid_value)
+{
+ static const enum gpiod_line_value values[] = {
+ GPIOD_LINE_VALUE_ACTIVE,
+ GPIOD_LINE_VALUE_INACTIVE,
+ 999,
+ GPIOD_LINE_VALUE_INACTIVE,
+ };
+
+ g_autoptr(struct_gpiod_line_config) config = NULL;
+
+ config = gpiod_test_create_line_config_or_fail();
+
+ g_assert_cmpint(gpiod_line_config_set_output_values(config, values, 4),
+ ==, -1);
+ gpiod_test_expect_errno(EINVAL);
+}
+
+GPIOD_TEST_CASE(set_output_values_bad_args)
+{
+ static const enum gpiod_line_value values[] = {
+ GPIOD_LINE_VALUE_ACTIVE,
+ GPIOD_LINE_VALUE_INACTIVE,
+ GPIOD_LINE_VALUE_ACTIVE,
+ GPIOD_LINE_VALUE_INACTIVE,
+ };
+
+ g_autoptr(struct_gpiod_line_config) config = NULL;
+ gint ret;
+
+ config = gpiod_test_create_line_config_or_fail();
+
+ ret = gpiod_line_config_set_output_values(config, NULL, 4);
+ g_assert_cmpint(ret, ==, -1);
+ gpiod_test_expect_errno(EINVAL);
+
+ ret = gpiod_line_config_set_output_values(config, values, 0);
+ g_assert_cmpint(ret, ==, -1);
+ gpiod_test_expect_errno(EINVAL);
+}
+
+GPIOD_TEST_CASE(set_output_values_too_many_values)
+{
+ static const gsize num_values = 65;
+
+ g_autoptr(struct_gpiod_line_config) config = NULL;
+ g_autofree enum gpiod_line_value *values = NULL;
+ gint ret;
+ gsize i;
+
+ config = gpiod_test_create_line_config_or_fail();
+ values = g_malloc0(sizeof(*values) * num_values);
+
+ for (i = 0; i < num_values; i++)
+ values[i] = GPIOD_LINE_VALUE_ACTIVE;
+
+ ret = gpiod_line_config_set_output_values(config, values, num_values);
+ g_assert_cmpint(ret, ==, -1);
+ gpiod_test_expect_errno(EINVAL);
+}
+
+GPIOD_TEST_CASE(get_num_configured_offsets)
+{
+ static const guint offsets[] = { 0, 1, 2, 3 };
+
+ g_autoptr(struct_gpiod_line_config) config = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+
+ settings = gpiod_test_create_line_settings_or_fail();
+ config = gpiod_test_create_line_config_or_fail();
+
+ gpiod_test_line_config_add_line_settings_or_fail(config, offsets, 4,
+ settings);
+
+ g_assert_cmpuint(gpiod_line_config_get_num_configured_offsets(config),
+ ==, 4);
+}
+
+GPIOD_TEST_CASE(handle_duplicate_offsets)
+{
+ static const guint offsets[] = { 0, 2, 2, 3 };
+
+ g_autoptr(struct_gpiod_line_config) config = NULL;
+ size_t num_retrieved;
+ guint retrieved[3];
+
+ config = gpiod_test_create_line_config_or_fail();
+
+ gpiod_test_line_config_add_line_settings_or_fail(config, offsets, 4,
+ NULL);
+
+ g_assert_cmpuint(gpiod_line_config_get_num_configured_offsets(config),
+ ==, 3);
+ num_retrieved = gpiod_line_config_get_configured_offsets(config,
+ retrieved, 3);
+ g_assert_cmpuint(num_retrieved, ==, 3);
+ g_assert_cmpuint(retrieved[0], ==, 0);
+ g_assert_cmpuint(retrieved[1], ==, 2);
+ g_assert_cmpuint(retrieved[2], ==, 3);
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+
+#include <errno.h>
+#include <glib.h>
+#include <gpiod.h>
+
+#include "gpiod-test.h"
+#include "gpiod-test-helpers.h"
+#include "gpiod-test-sim.h"
+
+#define GPIOD_TEST_GROUP "line-info"
+
+GPIOD_TEST_CASE(get_line_info_good)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_info) info = NULL;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+
+ info = gpiod_test_chip_get_line_info_or_fail(chip, 3);
+ g_assert_cmpuint(gpiod_line_info_get_offset(info), ==, 3);
+}
+
+GPIOD_TEST_CASE(get_line_info_offset_out_of_range)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_info) info = NULL;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+
+ info = gpiod_chip_get_line_info(chip, 8);
+ g_assert_null(info);
+ gpiod_test_expect_errno(EINVAL);
+}
+
+GPIOD_TEST_CASE(line_info_basic_properties)
+{
+ static const GPIOSimLineName names[] = {
+ { .offset = 1, .name = "foo", },
+ { .offset = 2, .name = "bar", },
+ { .offset = 4, .name = "baz", },
+ { .offset = 5, .name = "xyz", },
+ { }
+ };
+
+ static const GPIOSimHog hogs[] = {
+ {
+ .offset = 3,
+ .name = "hog3",
+ .direction = G_GPIOSIM_DIRECTION_OUTPUT_HIGH,
+ },
+ {
+ .offset = 4,
+ .name = "hog4",
+ .direction = G_GPIOSIM_DIRECTION_OUTPUT_LOW,
+ },
+ { }
+ };
+
+ g_autoptr(GPIOSimChip) sim = NULL;
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_info) info4 = NULL;
+ g_autoptr(struct_gpiod_line_info) info6 = NULL;
+ g_autoptr(GVariant) vnames = gpiod_test_package_line_names(names);
+ g_autoptr(GVariant) vhogs = gpiod_test_package_hogs(hogs);
+
+ sim = g_gpiosim_chip_new(
+ "num-lines", 8,
+ "line-names", vnames,
+ "hogs", vhogs,
+ NULL);
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ info4 = gpiod_test_chip_get_line_info_or_fail(chip, 4);
+ info6 = gpiod_test_chip_get_line_info_or_fail(chip, 6);
+
+ g_assert_cmpuint(gpiod_line_info_get_offset(info4), ==, 4);
+ g_assert_cmpstr(gpiod_line_info_get_name(info4), ==, "baz");
+ g_assert_cmpstr(gpiod_line_info_get_consumer(info4), ==, "hog4");
+ g_assert_true(gpiod_line_info_is_used(info4));
+ g_assert_cmpint(gpiod_line_info_get_direction(info4), ==,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+ g_assert_cmpint(gpiod_line_info_get_edge_detection(info4), ==,
+ GPIOD_LINE_EDGE_NONE);
+ g_assert_false(gpiod_line_info_is_active_low(info4));
+ g_assert_cmpint(gpiod_line_info_get_bias(info4), ==,
+ GPIOD_LINE_BIAS_UNKNOWN);
+ g_assert_cmpint(gpiod_line_info_get_drive(info4), ==,
+ GPIOD_LINE_DRIVE_PUSH_PULL);
+ g_assert_cmpint(gpiod_line_info_get_event_clock(info4), ==,
+ GPIOD_LINE_CLOCK_MONOTONIC);
+ g_assert_false(gpiod_line_info_is_debounced(info4));
+ g_assert_cmpuint(gpiod_line_info_get_debounce_period_us(info4), ==, 0);
+}
+
+GPIOD_TEST_CASE(copy_line_info)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_info) info = NULL;
+ g_autoptr(struct_gpiod_line_info) copy = NULL;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ info = gpiod_test_chip_get_line_info_or_fail(chip, 3);
+
+ copy = gpiod_line_info_copy(info);
+ g_assert_nonnull(copy);
+ g_assert_true(info != copy);
+ g_assert_cmpuint(gpiod_line_info_get_offset(info), ==,
+ gpiod_line_info_get_offset(copy));
+}
+
+GPIOD_TEST_CASE(direction_settings)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ g_autoptr(struct_gpiod_line_info) info0 = NULL;
+ g_autoptr(struct_gpiod_line_info) info1 = NULL;
+ g_autoptr(struct_gpiod_line_info) info2 = NULL;
+ guint offset;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+ offset = 0;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+ offset = 1;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_AS_IS);
+ offset = 2;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_test_chip_request_lines_or_fail(chip, NULL, line_cfg);
+ info0 = gpiod_test_chip_get_line_info_or_fail(chip, 0);
+ info1 = gpiod_test_chip_get_line_info_or_fail(chip, 1);
+ info2 = gpiod_test_chip_get_line_info_or_fail(chip, 2);
+
+ g_assert_cmpint(gpiod_line_info_get_direction(info0), ==,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+ g_assert_cmpint(gpiod_line_info_get_direction(info1), ==,
+ GPIOD_LINE_DIRECTION_INPUT);
+ g_assert_cmpint(gpiod_line_info_get_direction(info2), ==,
+ GPIOD_LINE_DIRECTION_INPUT);
+}
+
+GPIOD_TEST_CASE(active_high)
+{
+ static const guint offset = 5;
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ g_autoptr(struct_gpiod_line_info) info = NULL;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_active_low(settings, true);
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset,
+ 1, settings);
+
+ request = gpiod_test_chip_request_lines_or_fail(chip, NULL, line_cfg);
+ info = gpiod_test_chip_get_line_info_or_fail(chip, 5);
+
+ g_assert_true(gpiod_line_info_is_active_low(info));
+}
+
+GPIOD_TEST_CASE(edge_detection_settings)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_info) info0 = NULL;
+ g_autoptr(struct_gpiod_line_info) info1 = NULL;
+ g_autoptr(struct_gpiod_line_info) info2 = NULL;
+ g_autoptr(struct_gpiod_line_info) info3 = NULL;
+ guint offset;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ line_cfg = gpiod_test_create_line_config_or_fail();
+ settings = gpiod_test_create_line_settings_or_fail();
+
+ gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_NONE);
+ offset = 0;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+ gpiod_line_settings_set_edge_detection(settings,
+ GPIOD_LINE_EDGE_RISING);
+ offset = 1;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+ gpiod_line_settings_set_edge_detection(settings,
+ GPIOD_LINE_EDGE_FALLING);
+ offset = 2;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+ gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+ offset = 3;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_test_chip_request_lines_or_fail(chip, NULL, line_cfg);
+ info0 = gpiod_test_chip_get_line_info_or_fail(chip, 0);
+ info1 = gpiod_test_chip_get_line_info_or_fail(chip, 1);
+ info2 = gpiod_test_chip_get_line_info_or_fail(chip, 2);
+ info3 = gpiod_test_chip_get_line_info_or_fail(chip, 3);
+
+ g_assert_cmpint(gpiod_line_info_get_edge_detection(info0), ==,
+ GPIOD_LINE_EDGE_NONE);
+ g_assert_cmpint(gpiod_line_info_get_edge_detection(info1), ==,
+ GPIOD_LINE_EDGE_RISING);
+ g_assert_cmpint(gpiod_line_info_get_edge_detection(info2), ==,
+ GPIOD_LINE_EDGE_FALLING);
+ g_assert_cmpint(gpiod_line_info_get_edge_detection(info3), ==,
+ GPIOD_LINE_EDGE_BOTH);
+}
+
+GPIOD_TEST_CASE(bias_settings)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ g_autoptr(struct_gpiod_line_info) info0 = NULL;
+ g_autoptr(struct_gpiod_line_info) info1 = NULL;
+ g_autoptr(struct_gpiod_line_info) info2 = NULL;
+ g_autoptr(struct_gpiod_line_info) info3 = NULL;
+ guint offset;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+ offset = 0;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+ gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_DISABLED);
+ offset = 1;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+ gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_PULL_DOWN);
+ offset = 2;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+ gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_PULL_UP);
+ offset = 3;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_test_chip_request_lines_or_fail(chip, NULL, line_cfg);
+ info0 = gpiod_test_chip_get_line_info_or_fail(chip, 0);
+ info1 = gpiod_test_chip_get_line_info_or_fail(chip, 1);
+ info2 = gpiod_test_chip_get_line_info_or_fail(chip, 2);
+ info3 = gpiod_test_chip_get_line_info_or_fail(chip, 3);
+
+ g_assert_cmpint(gpiod_line_info_get_bias(info0), ==,
+ GPIOD_LINE_BIAS_UNKNOWN);
+ g_assert_cmpint(gpiod_line_info_get_bias(info1), ==,
+ GPIOD_LINE_BIAS_DISABLED);
+ g_assert_cmpint(gpiod_line_info_get_bias(info2), ==,
+ GPIOD_LINE_BIAS_PULL_DOWN);
+ g_assert_cmpint(gpiod_line_info_get_bias(info3), ==,
+ GPIOD_LINE_BIAS_PULL_UP);
+}
+
+GPIOD_TEST_CASE(drive_settings)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ g_autoptr(struct_gpiod_line_info) info0 = NULL;
+ g_autoptr(struct_gpiod_line_info) info1 = NULL;
+ g_autoptr(struct_gpiod_line_info) info2 = NULL;
+ guint offset;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ line_cfg = gpiod_test_create_line_config_or_fail();
+ settings = gpiod_test_create_line_settings_or_fail();
+
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+ offset = 0;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+ gpiod_line_settings_set_drive(settings, GPIOD_LINE_DRIVE_OPEN_DRAIN);
+ offset = 1;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+ gpiod_line_settings_set_drive(settings, GPIOD_LINE_DRIVE_OPEN_SOURCE);
+ offset = 2;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_test_chip_request_lines_or_fail(chip, NULL, line_cfg);
+ info0 = gpiod_test_chip_get_line_info_or_fail(chip, 0);
+ info1 = gpiod_test_chip_get_line_info_or_fail(chip, 1);
+ info2 = gpiod_test_chip_get_line_info_or_fail(chip, 2);
+
+ g_assert_cmpint(gpiod_line_info_get_drive(info0), ==,
+ GPIOD_LINE_DRIVE_PUSH_PULL);
+ g_assert_cmpint(gpiod_line_info_get_drive(info1), ==,
+ GPIOD_LINE_DRIVE_OPEN_DRAIN);
+ g_assert_cmpint(gpiod_line_info_get_drive(info2), ==,
+ GPIOD_LINE_DRIVE_OPEN_SOURCE);
+}
+
+GPIOD_TEST_CASE(debounce_period)
+{
+ static const guint offset = 5;
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ g_autoptr(struct_gpiod_line_info) info = NULL;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ line_cfg = gpiod_test_create_line_config_or_fail();
+ settings = gpiod_test_create_line_settings_or_fail();
+
+ gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+ gpiod_line_settings_set_debounce_period_us(settings, 1000);
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_test_chip_request_lines_or_fail(chip, NULL, line_cfg);
+ info = gpiod_test_chip_get_line_info_or_fail(chip, 5);
+
+ g_assert_cmpuint(gpiod_line_info_get_debounce_period_us(info), ==,
+ 1000);
+}
+
+GPIOD_TEST_CASE(event_clock)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ g_autoptr(struct_gpiod_line_info) info0 = NULL;
+ g_autoptr(struct_gpiod_line_info) info1 = NULL;
+ g_autoptr(struct_gpiod_line_info) info2 = NULL;
+ guint offset;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ line_cfg = gpiod_test_create_line_config_or_fail();
+ settings = gpiod_test_create_line_settings_or_fail();
+
+ offset = 0;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+ gpiod_line_settings_set_event_clock(settings,
+ GPIOD_LINE_CLOCK_REALTIME);
+ offset = 1;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ gpiod_line_settings_set_event_clock(settings,
+ GPIOD_LINE_CLOCK_HTE);
+ offset = 2;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_chip_request_lines(chip, NULL, line_cfg);
+ if (!request && errno == EOPNOTSUPP) {
+ g_test_skip("HTE support not available");
+ return;
+ }
+
+ gpiod_test_return_if_failed();
+
+ info0 = gpiod_test_chip_get_line_info_or_fail(chip, 0);
+ info1 = gpiod_test_chip_get_line_info_or_fail(chip, 1);
+ info2 = gpiod_test_chip_get_line_info_or_fail(chip, 2);
+
+ g_assert_cmpint(gpiod_line_info_get_event_clock(info0), ==,
+ GPIOD_LINE_CLOCK_MONOTONIC);
+ g_assert_cmpint(gpiod_line_info_get_event_clock(info1), ==,
+ GPIOD_LINE_CLOCK_REALTIME);
+ g_assert_cmpint(gpiod_line_info_get_event_clock(info2), ==,
+ GPIOD_LINE_CLOCK_HTE);
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+
+#include <glib.h>
+#include <gpiod.h>
+
+#include "gpiod-test.h"
+#include "gpiod-test-helpers.h"
+#include "gpiod-test-sim.h"
+
+#define GPIOD_TEST_GROUP "line-request"
+
+GPIOD_TEST_CASE(request_fails_with_no_offsets)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 4, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+
+ request = gpiod_chip_request_lines(chip, NULL, line_cfg);
+ g_assert_null(request);
+ gpiod_test_expect_errno(EINVAL);
+}
+
+GPIOD_TEST_CASE(request_fails_with_no_line_config)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 4, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+
+ request = gpiod_chip_request_lines(chip, NULL, NULL);
+ g_assert_null(request);
+ gpiod_test_expect_errno(EINVAL);
+}
+
+GPIOD_TEST_CASE(request_fails_with_offset_out_of_bounds)
+{
+ static const guint offsets[] = { 2, 6 };
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 4, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 2,
+ NULL);
+
+ request = gpiod_chip_request_lines(chip, NULL, line_cfg);
+ g_assert_null(request);
+ gpiod_test_expect_errno(EINVAL);
+}
+
+GPIOD_TEST_CASE(set_consumer)
+{
+ static const guint offset = 2;
+ static const gchar *const consumer = "foobar";
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 4, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ g_autoptr(struct_gpiod_line_info) info = NULL;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ req_cfg = gpiod_test_create_request_config_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_request_config_set_consumer(req_cfg, consumer);
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ NULL);
+
+ request = gpiod_test_chip_request_lines_or_fail(chip,
+ req_cfg, line_cfg);
+
+ info = gpiod_test_chip_get_line_info_or_fail(chip, offset);
+
+ g_assert_true(gpiod_line_info_is_used(info));
+ g_assert_cmpstr(gpiod_line_info_get_consumer(info), ==, consumer);
+}
+
+GPIOD_TEST_CASE(empty_consumer)
+{
+ static const guint offset = 2;
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 4, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ g_autoptr(struct_gpiod_line_info) info = NULL;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ NULL);
+
+ request = gpiod_test_chip_request_lines_or_fail(chip, NULL, line_cfg);
+
+ info = gpiod_test_chip_get_line_info_or_fail(chip, offset);
+
+ g_assert_cmpstr(gpiod_line_info_get_consumer(info), ==, "?");
+}
+
+GPIOD_TEST_CASE(default_output_value)
+{
+ /*
+ * Have a hole in offsets on purpose - make sure it's not set by
+ * accident.
+ */
+ static const guint offsets[] = { 0, 1, 3, 4 };
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ guint i;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+ gpiod_line_settings_set_output_value(settings, GPIOD_LINE_VALUE_ACTIVE);
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 4,
+ settings);
+
+ g_gpiosim_chip_set_pull(sim, 2, G_GPIOSIM_PULL_DOWN);
+
+ request = gpiod_test_chip_request_lines_or_fail(chip, NULL, line_cfg);
+
+ for (i = 0; i < 4; i++)
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, offsets[i]), ==,
+ G_GPIOSIM_VALUE_ACTIVE);
+
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 2), ==,
+ G_GPIOSIM_VALUE_INACTIVE);
+}
+
+GPIOD_TEST_CASE(read_all_values)
+{
+ static const guint offsets[] = { 0, 2, 4, 5, 7 };
+ static const gint pulls[] = { 0, 1, 0, 1, 1 };
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ enum gpiod_line_value values[5];
+ gint ret;
+ guint i;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 5,
+ settings);
+
+ request = gpiod_test_chip_request_lines_or_fail(chip, NULL, line_cfg);
+
+ for (i = 0; i < 5; i++)
+ g_gpiosim_chip_set_pull(sim, offsets[i],
+ pulls[i] ? G_GPIOSIM_PULL_UP :
+ G_GPIOSIM_PULL_DOWN);
+
+ ret = gpiod_line_request_get_values(request, values);
+ g_assert_cmpint(ret, ==, 0);
+ gpiod_test_return_if_failed();
+
+ for (i = 0; i < 5; i++)
+ g_assert_cmpint(values[i], ==, pulls[i]);
+}
+
+GPIOD_TEST_CASE(request_multiple_values_but_read_one)
+{
+ static const guint offsets[] = { 0, 2, 4, 5, 7 };
+ static const gint pulls[] = { 0, 1, 0, 1, 1 };
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ gint ret;
+ guint i;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 5,
+ settings);
+
+ request = gpiod_test_chip_request_lines_or_fail(chip, NULL, line_cfg);
+
+ for (i = 0; i < 5; i++)
+ g_gpiosim_chip_set_pull(sim, offsets[i],
+ pulls[i] ? G_GPIOSIM_PULL_UP :
+ G_GPIOSIM_PULL_DOWN);
+
+ ret = gpiod_line_request_get_value(request, 5);
+ g_assert_cmpint(ret, ==, 1);
+}
+
+GPIOD_TEST_CASE(set_all_values)
+{
+ static const guint offsets[] = { 0, 2, 4, 5, 6 };
+ static const enum gpiod_line_value values[] = {
+ GPIOD_LINE_VALUE_ACTIVE,
+ GPIOD_LINE_VALUE_INACTIVE,
+ GPIOD_LINE_VALUE_ACTIVE,
+ GPIOD_LINE_VALUE_ACTIVE,
+ GPIOD_LINE_VALUE_ACTIVE
+ };
+ static const GPIOSimValue sim_values[] = {
+ G_GPIOSIM_VALUE_ACTIVE,
+ G_GPIOSIM_VALUE_INACTIVE,
+ G_GPIOSIM_VALUE_ACTIVE,
+ G_GPIOSIM_VALUE_ACTIVE,
+ G_GPIOSIM_VALUE_ACTIVE
+ };
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ gint ret;
+ guint i;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 5,
+ settings);
+
+ request = gpiod_test_chip_request_lines_or_fail(chip, NULL, line_cfg);
+
+ ret = gpiod_line_request_set_values(request, values);
+ g_assert_cmpint(ret, ==, 0);
+ gpiod_test_return_if_failed();
+
+ for (i = 0; i < 5; i++)
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, offsets[i]), ==,
+ sim_values[i]);
+}
+
+GPIOD_TEST_CASE(set_values_subset_of_lines)
+{
+ static const guint offsets[] = { 0, 1, 2, 3 };
+ static const guint offsets_to_set[] = { 0, 1, 3 };
+ static const enum gpiod_line_value values[] = {
+ GPIOD_LINE_VALUE_ACTIVE,
+ GPIOD_LINE_VALUE_INACTIVE,
+ GPIOD_LINE_VALUE_ACTIVE
+ };
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 4, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ gint ret;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 4,
+ settings);
+
+ request = gpiod_test_chip_request_lines_or_fail(chip, NULL, line_cfg);
+
+ ret = gpiod_line_request_set_values_subset(request, 3, offsets_to_set,
+ values);
+ g_assert_cmpint(ret, ==, 0);
+ gpiod_test_return_if_failed();
+
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 0), ==,
+ G_GPIOSIM_VALUE_ACTIVE);
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 1), ==,
+ G_GPIOSIM_VALUE_INACTIVE);
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 3), ==,
+ G_GPIOSIM_VALUE_ACTIVE);
+}
+
+GPIOD_TEST_CASE(set_line_after_requesting)
+{
+ static const guint offsets[] = { 0, 1, 3, 4 };
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+ gpiod_line_settings_set_output_value(settings,
+ GPIOD_LINE_VALUE_INACTIVE);
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 4,
+ settings);
+
+ request = gpiod_test_chip_request_lines_or_fail(chip, NULL, line_cfg);
+
+ gpiod_line_request_set_value(request, 1, GPIOD_LINE_VALUE_ACTIVE);
+
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 0), ==,
+ G_GPIOSIM_VALUE_INACTIVE);
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 1), ==,
+ G_GPIOSIM_VALUE_ACTIVE);
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 3), ==,
+ G_GPIOSIM_VALUE_INACTIVE);
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 4), ==,
+ G_GPIOSIM_VALUE_INACTIVE);
+}
+
+GPIOD_TEST_CASE(request_survives_parent_chip)
+{
+ static const guint offset = 0;
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 4, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ gint ret;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+ gpiod_line_settings_set_output_value(settings, GPIOD_LINE_VALUE_ACTIVE);
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_test_chip_request_lines_or_fail(chip, NULL, line_cfg);
+
+ g_assert_cmpint(gpiod_line_request_get_value(request, offset), ==,
+ GPIOD_LINE_VALUE_ACTIVE);
+
+ gpiod_chip_close(chip);
+ chip = NULL;
+
+ ret = gpiod_line_request_set_value(request, offset,
+ GPIOD_LINE_VALUE_ACTIVE);
+ g_assert_cmpint(ret, ==, 0);
+ gpiod_test_return_if_failed();
+
+ ret = gpiod_line_request_set_value(request, offset,
+ GPIOD_LINE_VALUE_ACTIVE);
+ g_assert_cmpint(ret, ==, 0);
+ gpiod_test_return_if_failed();
+}
+
+GPIOD_TEST_CASE(num_lines_and_offsets)
+{
+ static const guint offsets[] = { 0, 1, 2, 3, 7, 8, 11, 14 };
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 16, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ guint read_back[8], i;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 8,
+ NULL);
+
+ request = gpiod_test_chip_request_lines_or_fail(chip, NULL, line_cfg);
+
+ g_assert_cmpuint(gpiod_line_request_get_num_requested_lines(request),
+ ==, 8);
+ gpiod_test_return_if_failed();
+ gpiod_line_request_get_requested_offsets(request, read_back, 8);
+ for (i = 0; i < 8; i++)
+ g_assert_cmpuint(read_back[i], ==, offsets[i]);
+}
+
+GPIOD_TEST_CASE(active_low_read_value)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ guint offset;
+ gint value;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_active_low(settings, true);
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+ offset = 2;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+ gpiod_line_settings_set_output_value(settings, GPIOD_LINE_VALUE_ACTIVE);
+ offset = 3;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_test_chip_request_lines_or_fail(chip, NULL, line_cfg);
+
+ g_gpiosim_chip_set_pull(sim, 2, G_GPIOSIM_PULL_DOWN);
+ value = gpiod_line_request_get_value(request, 2);
+ g_assert_cmpint(value, ==, GPIOD_LINE_VALUE_ACTIVE);
+
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 3), ==,
+ G_GPIOSIM_VALUE_INACTIVE);
+}
+
+GPIOD_TEST_CASE(reconfigure_lines)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 4, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ guint offsets[2];
+ gint ret;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+
+ gpiod_line_settings_set_output_value(settings, GPIOD_LINE_VALUE_ACTIVE);
+ offsets[0] = 0;
+ offsets[1] = 2;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 2,
+ settings);
+ gpiod_line_settings_set_output_value(settings,
+ GPIOD_LINE_VALUE_INACTIVE);
+ offsets[0] = 1;
+ offsets[1] = 3;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 2,
+ settings);
+
+ request = gpiod_test_chip_request_lines_or_fail(chip, NULL, line_cfg);
+
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 0), ==,
+ G_GPIOSIM_VALUE_ACTIVE);
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 1), ==,
+ G_GPIOSIM_VALUE_INACTIVE);
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 2), ==,
+ G_GPIOSIM_VALUE_ACTIVE);
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 3), ==,
+ G_GPIOSIM_VALUE_INACTIVE);
+
+ gpiod_line_config_reset(line_cfg);
+
+ gpiod_line_settings_set_output_value(settings,
+ GPIOD_LINE_VALUE_INACTIVE);
+ offsets[0] = 0;
+ offsets[1] = 2;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 2,
+ settings);
+ gpiod_line_settings_set_output_value(settings, GPIOD_LINE_VALUE_ACTIVE);
+ offsets[0] = 1;
+ offsets[1] = 3;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 2,
+ settings);
+
+ ret = gpiod_line_request_reconfigure_lines(request, line_cfg);
+ g_assert_cmpint(ret, ==, 0);
+ gpiod_test_return_if_failed();
+
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 0), ==,
+ G_GPIOSIM_VALUE_INACTIVE);
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 1), ==,
+ G_GPIOSIM_VALUE_ACTIVE);
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 2), ==,
+ G_GPIOSIM_VALUE_INACTIVE);
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 3), ==,
+ G_GPIOSIM_VALUE_ACTIVE);
+}
+
+GPIOD_TEST_CASE(reconfigure_lines_null_config)
+{
+ static const guint offsets[] = { 0, 1, 2, 3 };
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ gint ret;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 4,
+ NULL);
+
+ request = gpiod_test_chip_request_lines_or_fail(chip, NULL, line_cfg);
+
+ ret = gpiod_line_request_reconfigure_lines(request, NULL);
+ g_assert_cmpint(ret, ==, -1);
+ gpiod_test_expect_errno(EINVAL);
+}
+
+GPIOD_TEST_CASE(reconfigure_lines_different_offsets)
+{
+ static const guint offsets0[] = { 0, 1, 2, 3 };
+ static const guint offsets1[] = { 2, 4, 5 };
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ gint ret;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets0, 4,
+ NULL);
+
+ request = gpiod_test_chip_request_lines_or_fail(chip, NULL, line_cfg);
+
+ gpiod_line_config_reset(line_cfg);
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets1, 3,
+ NULL);
+
+ ret = gpiod_line_request_reconfigure_lines(request, line_cfg);
+ g_assert_cmpint(ret, ==, -1);
+ gpiod_test_expect_errno(EINVAL);
+}
+
+GPIOD_TEST_CASE(request_lines_with_unordered_offsets)
+{
+ static const guint offsets[] = { 5, 1, 7, 2, 0, 6 };
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ enum gpiod_line_value values[4];
+ guint set_offsets[4];
+ gint ret;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+ gpiod_line_settings_set_output_value(settings, GPIOD_LINE_VALUE_ACTIVE);
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 6,
+ settings);
+
+ request = gpiod_test_chip_request_lines_or_fail(chip, NULL, line_cfg);
+
+ values[0] = 0;
+ values[1] = 1;
+ values[2] = 0;
+ values[3] = 0;
+ set_offsets[0] = 7;
+ set_offsets[1] = 1;
+ set_offsets[2] = 6;
+ set_offsets[3] = 0;
+ ret = gpiod_line_request_set_values_subset(request, 4, set_offsets, values);
+ g_assert_cmpint(ret, ==, 0);
+ gpiod_test_return_if_failed();
+
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 0), ==,
+ G_GPIOSIM_VALUE_INACTIVE);
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 1), ==,
+ G_GPIOSIM_VALUE_ACTIVE);
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 2), ==,
+ G_GPIOSIM_VALUE_ACTIVE);
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 5), ==,
+ G_GPIOSIM_VALUE_ACTIVE);
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 6), ==,
+ G_GPIOSIM_VALUE_INACTIVE);
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 7), ==,
+ G_GPIOSIM_VALUE_INACTIVE);
+}
+
+GPIOD_TEST_CASE(request_with_bias_set_to_pull_up)
+{
+ static const guint offset = 3;
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_PULL_UP);
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_test_chip_request_lines_or_fail(chip, NULL, line_cfg);
+
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 3), ==,
+ G_GPIOSIM_VALUE_ACTIVE);
+}
+
+GPIOD_TEST_CASE(get_requested_offsets_less_and_more)
+{
+ static const guint offsets[] = { 0, 1, 2, 3 };
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ size_t num_retrieved;
+ guint retrieved[6];
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 4,
+ NULL);
+
+ request = gpiod_test_chip_request_lines_or_fail(chip, NULL, line_cfg);
+
+ num_retrieved = gpiod_line_request_get_requested_offsets(request,
+ retrieved, 3);
+
+ g_assert_cmpuint(num_retrieved, ==, 3);
+ g_assert_cmpuint(retrieved[0], ==, 0);
+ g_assert_cmpuint(retrieved[1], ==, 1);
+ g_assert_cmpuint(retrieved[2], ==, 2);
+
+ memset(retrieved, 0, sizeof(retrieved));
+
+ num_retrieved = gpiod_line_request_get_requested_offsets(request,
+ retrieved, 6);
+
+ g_assert_cmpuint(num_retrieved, ==, 4);
+ g_assert_cmpuint(retrieved[0], ==, 0);
+ g_assert_cmpuint(retrieved[1], ==, 1);
+ g_assert_cmpuint(retrieved[2], ==, 2);
+ g_assert_cmpuint(retrieved[3], ==, 3);
+}
+
+GPIOD_TEST_CASE(get_chip_name)
+{
+ static const guint offset = 4;
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ NULL);
+
+ request = gpiod_test_chip_request_lines_or_fail(chip, NULL, line_cfg);
+
+ g_assert_cmpstr(g_gpiosim_chip_get_name(sim), ==,
+ gpiod_line_request_get_chip_name(request));
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <errno.h>
+#include <glib.h>
+#include <gpiod.h>
+
+#include "gpiod-test.h"
+#include "gpiod-test-helpers.h"
+
+#define GPIOD_TEST_GROUP "line-settings"
+
+GPIOD_TEST_CASE(default_config)
+{
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+
+ settings = gpiod_test_create_line_settings_or_fail();
+
+ g_assert_cmpint(gpiod_line_settings_get_direction(settings), ==,
+ GPIOD_LINE_DIRECTION_AS_IS);
+ g_assert_cmpint(gpiod_line_settings_get_edge_detection(settings), ==,
+ GPIOD_LINE_EDGE_NONE);
+ g_assert_cmpint(gpiod_line_settings_get_bias(settings), ==,
+ GPIOD_LINE_BIAS_AS_IS);
+ g_assert_cmpint(gpiod_line_settings_get_drive(settings), ==,
+ GPIOD_LINE_DRIVE_PUSH_PULL);
+ g_assert_false(gpiod_line_settings_get_active_low(settings));
+ g_assert_cmpuint(gpiod_line_settings_get_debounce_period_us(settings),
+ ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_event_clock(settings), ==,
+ GPIOD_LINE_CLOCK_MONOTONIC);
+ g_assert_cmpint(gpiod_line_settings_get_output_value(settings), ==,
+ GPIOD_LINE_VALUE_INACTIVE);
+}
+
+GPIOD_TEST_CASE(set_direction)
+{
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ gint ret;
+
+ settings = gpiod_test_create_line_settings_or_fail();
+
+ ret = gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_INPUT);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_direction(settings), ==,
+ GPIOD_LINE_DIRECTION_INPUT);
+
+ ret = gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_AS_IS);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_direction(settings), ==,
+ GPIOD_LINE_DIRECTION_AS_IS);
+
+ ret = gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_direction(settings), ==,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+
+ ret = gpiod_line_settings_set_direction(settings, 999);
+ g_assert_cmpint(ret, <, 0);
+ g_assert_cmpint(errno, ==, EINVAL);
+ g_assert_cmpint(gpiod_line_settings_get_direction(settings), ==,
+ GPIOD_LINE_DIRECTION_AS_IS);
+}
+
+GPIOD_TEST_CASE(set_edge_detection)
+{
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ gint ret;
+
+ settings = gpiod_test_create_line_settings_or_fail();
+
+ ret = gpiod_line_settings_set_edge_detection(settings,
+ GPIOD_LINE_EDGE_BOTH);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_edge_detection(settings), ==,
+ GPIOD_LINE_EDGE_BOTH);
+
+ ret = gpiod_line_settings_set_edge_detection(settings,
+ GPIOD_LINE_EDGE_NONE);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_edge_detection(settings), ==,
+ GPIOD_LINE_EDGE_NONE);
+
+ ret = gpiod_line_settings_set_edge_detection(settings,
+ GPIOD_LINE_EDGE_FALLING);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_edge_detection(settings), ==,
+ GPIOD_LINE_EDGE_FALLING);
+
+ ret = gpiod_line_settings_set_edge_detection(settings,
+ GPIOD_LINE_EDGE_RISING);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_edge_detection(settings), ==,
+ GPIOD_LINE_EDGE_RISING);
+
+ ret = gpiod_line_settings_set_edge_detection(settings, 999);
+ g_assert_cmpint(ret, <, 0);
+ g_assert_cmpint(errno, ==, EINVAL);
+ g_assert_cmpint(gpiod_line_settings_get_edge_detection(settings), ==,
+ GPIOD_LINE_EDGE_NONE);
+}
+
+GPIOD_TEST_CASE(set_bias)
+{
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ gint ret;
+
+ settings = gpiod_test_create_line_settings_or_fail();
+
+ ret = gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_DISABLED);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_bias(settings), ==,
+ GPIOD_LINE_BIAS_DISABLED);
+
+ ret = gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_AS_IS);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_bias(settings), ==,
+ GPIOD_LINE_BIAS_AS_IS);
+
+ ret = gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_PULL_DOWN);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_bias(settings), ==,
+ GPIOD_LINE_BIAS_PULL_DOWN);
+
+ ret = gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_PULL_UP);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_bias(settings), ==,
+ GPIOD_LINE_BIAS_PULL_UP);
+
+ ret = gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_UNKNOWN);
+ g_assert_cmpint(ret, <, 0);
+ g_assert_cmpint(errno, ==, EINVAL);
+ g_assert_cmpint(gpiod_line_settings_get_bias(settings), ==,
+ GPIOD_LINE_BIAS_AS_IS);
+
+ ret = gpiod_line_settings_set_bias(settings, 999);
+ g_assert_cmpint(ret, <, 0);
+ g_assert_cmpint(errno, ==, EINVAL);
+ g_assert_cmpint(gpiod_line_settings_get_bias(settings), ==,
+ GPIOD_LINE_BIAS_AS_IS);
+}
+
+GPIOD_TEST_CASE(set_drive)
+{
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ gint ret;
+
+ settings = gpiod_test_create_line_settings_or_fail();
+
+ ret = gpiod_line_settings_set_drive(settings,
+ GPIOD_LINE_DRIVE_OPEN_DRAIN);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_drive(settings), ==,
+ GPIOD_LINE_DRIVE_OPEN_DRAIN);
+
+ ret = gpiod_line_settings_set_drive(settings,
+ GPIOD_LINE_DRIVE_PUSH_PULL);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_drive(settings), ==,
+ GPIOD_LINE_DRIVE_PUSH_PULL);
+
+ ret = gpiod_line_settings_set_drive(settings,
+ GPIOD_LINE_DRIVE_OPEN_SOURCE);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_drive(settings), ==,
+ GPIOD_LINE_DRIVE_OPEN_SOURCE);
+
+ ret = gpiod_line_settings_set_drive(settings, 999);
+ g_assert_cmpint(ret, <, 0);
+ g_assert_cmpint(errno, ==, EINVAL);
+ g_assert_cmpint(gpiod_line_settings_get_drive(settings), ==,
+ GPIOD_LINE_DRIVE_PUSH_PULL);
+}
+
+GPIOD_TEST_CASE(set_active_low)
+{
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+
+ settings = gpiod_test_create_line_settings_or_fail();
+
+ gpiod_line_settings_set_active_low(settings, true);
+ g_assert_true(gpiod_line_settings_get_active_low(settings));
+
+ gpiod_line_settings_set_active_low(settings, false);
+ g_assert_false(gpiod_line_settings_get_active_low(settings));
+}
+
+GPIOD_TEST_CASE(set_debounce_period)
+{
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+
+ settings = gpiod_test_create_line_settings_or_fail();
+
+ gpiod_line_settings_set_debounce_period_us(settings, 4000);
+ g_assert_cmpint(gpiod_line_settings_get_debounce_period_us(settings),
+ ==, 4000);
+}
+
+GPIOD_TEST_CASE(set_event_clock)
+{
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ gint ret;
+
+ settings = gpiod_test_create_line_settings_or_fail();
+
+ ret = gpiod_line_settings_set_event_clock(settings,
+ GPIOD_LINE_CLOCK_MONOTONIC);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_event_clock(settings), ==,
+ GPIOD_LINE_CLOCK_MONOTONIC);
+
+ ret = gpiod_line_settings_set_event_clock(settings,
+ GPIOD_LINE_CLOCK_REALTIME);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_event_clock(settings), ==,
+ GPIOD_LINE_CLOCK_REALTIME);
+
+ ret = gpiod_line_settings_set_event_clock(settings,
+ GPIOD_LINE_CLOCK_HTE);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_event_clock(settings), ==,
+ GPIOD_LINE_CLOCK_HTE);
+
+ ret = gpiod_line_settings_set_event_clock(settings, 999);
+ g_assert_cmpint(ret, <, 0);
+ g_assert_cmpint(errno, ==, EINVAL);
+ g_assert_cmpint(gpiod_line_settings_get_event_clock(settings), ==,
+ GPIOD_LINE_CLOCK_MONOTONIC);
+}
+
+GPIOD_TEST_CASE(set_output_value)
+{
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ gint ret;
+
+ settings = gpiod_test_create_line_settings_or_fail();
+
+ ret = gpiod_line_settings_set_output_value(settings,
+ GPIOD_LINE_VALUE_ACTIVE);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_output_value(settings), ==,
+ GPIOD_LINE_VALUE_ACTIVE);
+
+ ret = gpiod_line_settings_set_output_value(settings,
+ GPIOD_LINE_VALUE_INACTIVE);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_output_value(settings), ==,
+ GPIOD_LINE_VALUE_INACTIVE);
+
+ ret = gpiod_line_settings_set_output_value(settings, 999);
+ g_assert_cmpint(ret, <, 0);
+ g_assert_cmpint(errno, ==, EINVAL);
+ g_assert_cmpint(gpiod_line_settings_get_output_value(settings), ==,
+ GPIOD_LINE_VALUE_INACTIVE);
+}
+
+GPIOD_TEST_CASE(copy_line_settings)
+{
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_settings) copy = NULL;
+
+ settings = gpiod_test_create_line_settings_or_fail();
+
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+ gpiod_line_settings_set_debounce_period_us(settings, 2000);
+ gpiod_line_settings_set_event_clock(settings,
+ GPIOD_LINE_CLOCK_REALTIME);
+
+ copy = gpiod_line_settings_copy(settings);
+ g_assert_nonnull(copy);
+ gpiod_test_return_if_failed();
+ g_assert_false(settings == copy);
+ g_assert_cmpint(gpiod_line_settings_get_direction(copy), ==,
+ GPIOD_LINE_DIRECTION_INPUT);
+ g_assert_cmpint(gpiod_line_settings_get_edge_detection(copy), ==,
+ GPIOD_LINE_EDGE_BOTH);
+ g_assert_cmpint(gpiod_line_settings_get_debounce_period_us(copy), ==,
+ 2000);
+ g_assert_cmpint(gpiod_line_settings_get_event_clock(copy), ==,
+ GPIOD_LINE_CLOCK_REALTIME);
+}
+
+GPIOD_TEST_CASE(reset_settings)
+{
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+
+ settings = gpiod_test_create_line_settings_or_fail();
+
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+ gpiod_line_settings_set_debounce_period_us(settings, 2000);
+ gpiod_line_settings_set_event_clock(settings,
+ GPIOD_LINE_CLOCK_REALTIME);
+
+ gpiod_line_settings_reset(settings);
+
+ g_assert_cmpint(gpiod_line_settings_get_direction(settings), ==,
+ GPIOD_LINE_DIRECTION_AS_IS);
+ g_assert_cmpint(gpiod_line_settings_get_edge_detection(settings), ==,
+ GPIOD_LINE_EDGE_NONE);
+ g_assert_cmpint(gpiod_line_settings_get_bias(settings), ==,
+ GPIOD_LINE_BIAS_AS_IS);
+ g_assert_cmpint(gpiod_line_settings_get_drive(settings), ==,
+ GPIOD_LINE_DRIVE_PUSH_PULL);
+ g_assert_false(gpiod_line_settings_get_active_low(settings));
+ g_assert_cmpuint(gpiod_line_settings_get_debounce_period_us(settings),
+ ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_event_clock(settings), ==,
+ GPIOD_LINE_CLOCK_MONOTONIC);
+ g_assert_cmpint(gpiod_line_settings_get_output_value(settings), ==,
+ GPIOD_LINE_VALUE_INACTIVE);
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+
+#include <errno.h>
+#include <glib.h>
+#include <gpiod.h>
+#include <unistd.h>
+
+#include "gpiod-test.h"
+#include "gpiod-test-helpers.h"
+#include "gpiod-test-sim.h"
+
+#define GPIOD_TEST_GROUP "misc"
+
+GPIOD_TEST_CASE(is_gpiochip_bad)
+{
+ g_assert_false(gpiod_is_gpiochip_device("/dev/null"));
+ g_assert_cmpint(errno, ==, 0);
+ g_assert_false(gpiod_is_gpiochip_device("/dev/nonexistent"));
+ g_assert_cmpint(errno, ==, 0);
+}
+
+GPIOD_TEST_CASE(is_gpiochip_good)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new(NULL);
+
+ g_assert_true(gpiod_is_gpiochip_device(
+ g_gpiosim_chip_get_dev_path(sim)));
+}
+
+GPIOD_TEST_CASE(is_gpiochip_link_bad)
+{
+ g_autofree gchar *link = NULL;
+ gint ret;
+
+ link = g_strdup_printf("/tmp/gpiod-test-link.%u", getpid());
+ ret = symlink("/dev/null", link);
+ g_assert_cmpint(ret, ==, 0);
+ gpiod_test_return_if_failed();
+
+ g_assert_false(gpiod_is_gpiochip_device(link));
+ ret = unlink(link);
+ g_assert_cmpint(ret, ==, 0);
+}
+
+GPIOD_TEST_CASE(is_gpiochip_link_good)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new(NULL);
+ g_autofree gchar *link = NULL;
+ gint ret;
+
+ link = g_strdup_printf("/tmp/gpiod-test-link.%u", getpid());
+ ret = symlink(g_gpiosim_chip_get_dev_path(sim), link);
+ g_assert_cmpint(ret, ==, 0);
+ gpiod_test_return_if_failed();
+
+ g_assert_true(gpiod_is_gpiochip_device(link));
+ ret = unlink(link);
+ g_assert_cmpint(ret, ==, 0);
+}
+
+GPIOD_TEST_CASE(is_gpiochip_null_path)
+{
+ g_assert_false(gpiod_is_gpiochip_device(NULL));
+ gpiod_test_expect_errno(0);
+}
+
+GPIOD_TEST_CASE(version_string)
+{
+ static const gchar *const pattern = "^\\d+\\.\\d+(\\.\\d+|\\-devel|\\-rc\\d+)?$";
+
+ g_autoptr(GError) err = NULL;
+ g_autoptr(GRegex) regex = NULL;
+ g_autoptr(GMatchInfo) match = NULL;
+ g_autofree gchar *res = NULL;
+ const gchar *ver;
+ gboolean ret;
+
+ ver = gpiod_api_version();
+ g_assert_nonnull(ver);
+ gpiod_test_return_if_failed();
+
+ regex = g_regex_new(pattern, 0, 0, &err);
+ g_assert_nonnull(regex);
+ g_assert_no_error(err);
+ gpiod_test_return_if_failed();
+
+ ret = g_regex_match(regex, ver, 0, &match);
+ g_assert_true(ret);
+ gpiod_test_return_if_failed();
+
+ g_assert_true(g_match_info_matches(match));
+ res = g_match_info_fetch(match, 0);
+ g_assert_nonnull(res);
+ g_assert_cmpstr(res, ==, ver);
+ g_match_info_next(match, &err);
+ g_assert_no_error(err);
+ g_assert_false(g_match_info_matches(match));
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+
+#include <glib.h>
+#include <gpiod.h>
+
+#include "gpiod-test.h"
+#include "gpiod-test-helpers.h"
+
+#define GPIOD_TEST_GROUP "request-config"
+
+GPIOD_TEST_CASE(default_config)
+{
+ g_autoptr(struct_gpiod_request_config) config = NULL;
+
+ config = gpiod_test_create_request_config_or_fail();
+
+ g_assert_null(gpiod_request_config_get_consumer(config));
+ g_assert_cmpuint(gpiod_request_config_get_event_buffer_size(config), ==,
+ 0);
+}
+
+GPIOD_TEST_CASE(set_consumer)
+{
+ g_autoptr(struct_gpiod_request_config) config = NULL;
+
+ config = gpiod_test_create_request_config_or_fail();
+
+ gpiod_request_config_set_consumer(config, "foobar");
+ g_assert_cmpstr(gpiod_request_config_get_consumer(config), ==,
+ "foobar");
+
+ gpiod_request_config_set_consumer(config, NULL);
+ g_assert_null(gpiod_request_config_get_consumer(config));
+}
+
+GPIOD_TEST_CASE(set_event_buffer_size)
+{
+ g_autoptr(struct_gpiod_request_config) config = NULL;
+
+ config = gpiod_test_create_request_config_or_fail();
+
+ gpiod_request_config_set_event_buffer_size(config, 128);
+ g_assert_cmpuint(gpiod_request_config_get_event_buffer_size(config), ==,
+ 128);
+}
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+
+gpiodetect
+gpioinfo
+gpioget
+gpioset
+gpiomon
+gpionotify
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+
+AM_CFLAGS = -I$(top_srcdir)/include/ -include $(top_builddir)/config.h
+AM_CFLAGS += -Wall -Wextra -g -std=gnu89
+
+noinst_LTLIBRARIES = libtools-common.la
+libtools_common_la_SOURCES = tools-common.c tools-common.h
+
+LDADD = libtools-common.la $(top_builddir)/lib/libgpiod.la
+
+if WITH_GPIOSET_INTERACTIVE
+
+AM_CFLAGS += -DGPIOSET_INTERACTIVE $(LIBEDIT_CFLAGS)
+LDADD += $(LIBEDIT_LIBS)
+
+endif
+
+bin_PROGRAMS = gpiodetect gpioinfo gpioget gpioset gpiomon gpionotify
+
+if WITH_TESTS
+
+dist_noinst_SCRIPTS = gpio-tools-test.bash
+
+endif
--- /dev/null
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+# SPDX-FileCopyrightText: 2022 Kent Gibson <warthog618@gmail.com>
+# SPDX-FileCopyrightText: 2023 Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
+
+# Simple test harness for the gpio-tools.
+
+# Where output from the dut is stored (must be used together
+# with SHUNIT_TMPDIR).
+DUT_OUTPUT=gpio-tools-test-output
+
+# Save the PID of coprocess - otherwise we won't be able to wait for it
+# once it exits as the COPROC_PID will be cleared.
+DUT_PID=""
+
+SOURCE_DIR="$(dirname ${BASH_SOURCE[0]})"
+
+# mappings from local name to system chip name, path, dev name
+declare -A GPIOSIM_CHIP_NAME
+declare -A GPIOSIM_CHIP_PATH
+declare -A GPIOSIM_DEV_NAME
+GPIOSIM_CONFIGFS="/sys/kernel/config/gpio-sim"
+GPIOSIM_SYSFS="/sys/devices/platform/"
+GPIOSIM_APP_NAME="gpio-tools-test"
+
+MIN_KERNEL_VERSION="5.17.4"
+MIN_SHUNIT_VERSION="2.1.8"
+
+# Run the command in $* and fail the test if the command succeeds.
+assert_fail() {
+ $* || return 0
+ fail " '$*': command did not fail as expected"
+}
+
+# Check if the string in $2 matches against the pattern in $1.
+regex_matches() {
+ [[ $2 =~ $1 ]]
+ assertEquals " '$2' did not match '$1':" "0" "$?"
+}
+
+output_contains_line() {
+ assertContains "$1" "$output"
+}
+
+output_is() {
+ assertEquals " output:" "$1" "$output"
+}
+
+num_lines_is() {
+ [ "$1" -eq "0" ] || [ -z "$output" ] && return 0
+ local NUM_LINES=$(echo "$output" | wc -l)
+ assertEquals " number of lines:" "$1" "$NUM_LINES"
+}
+
+status_is() {
+ assertEquals " status:" "$1" "$status"
+}
+
+# Same as above but match against the regex pattern in $1.
+output_regex_match() {
+ [[ "$output" =~ $1 ]]
+ assertEquals " '$output' did not match '$1'" "0" "$?"
+}
+
+gpiosim_chip() {
+ local VAR=$1
+ local NAME=${GPIOSIM_APP_NAME}-$$-${VAR}
+ local DEVPATH=$GPIOSIM_CONFIGFS/$NAME
+ local BANKPATH=$DEVPATH/bank0
+
+ mkdir -p $BANKPATH
+
+ for ARG in $*
+ do
+ local KEY=$(echo $ARG | cut -d"=" -f1)
+ local VAL=$(echo $ARG | cut -d"=" -f2)
+
+ if [ "$KEY" = "num_lines" ]
+ then
+ echo $VAL > $BANKPATH/num_lines
+ elif [ "$KEY" = "line_name" ]
+ then
+ local OFFSET=$(echo $VAL | cut -d":" -f1)
+ local LINENAME=$(echo $VAL | cut -d":" -f2)
+ local LINEPATH=$BANKPATH/line$OFFSET
+
+ mkdir -p $LINEPATH
+ echo $LINENAME > $LINEPATH/name
+ fi
+ done
+
+ echo 1 > $DEVPATH/live
+
+ local chip_name=$(<$BANKPATH/chip_name)
+ GPIOSIM_CHIP_NAME[$1]=$chip_name
+ GPIOSIM_CHIP_PATH[$1]="/dev/$chip_name"
+ GPIOSIM_DEV_NAME[$1]=$(<$DEVPATH/dev_name)
+}
+
+gpiosim_chip_number() {
+ local NAME=${GPIOSIM_CHIP_NAME[$1]}
+ echo ${NAME#"gpiochip"}
+}
+
+gpiosim_chip_symlink() {
+ GPIOSIM_CHIP_LINK="$2/${GPIOSIM_APP_NAME}-$$-lnk"
+ ln -s ${GPIOSIM_CHIP_PATH[$1]} "$GPIOSIM_CHIP_LINK"
+}
+
+gpiosim_chip_symlink_cleanup() {
+ if [ -n "$GPIOSIM_CHIP_LINK" ]
+ then
+ rm "$GPIOSIM_CHIP_LINK"
+ fi
+ unset GPIOSIM_CHIP_LINK
+}
+
+gpiosim_set_pull() {
+ local OFFSET=$2
+ local PULL=$3
+ local DEVNAME=${GPIOSIM_DEV_NAME[$1]}
+ local CHIPNAME=${GPIOSIM_CHIP_NAME[$1]}
+
+ echo $PULL > $GPIOSIM_SYSFS/$DEVNAME/$CHIPNAME/sim_gpio$OFFSET/pull
+}
+
+gpiosim_check_value() {
+ local OFFSET=$2
+ local EXPECTED=$3
+ local DEVNAME=${GPIOSIM_DEV_NAME[$1]}
+ local CHIPNAME=${GPIOSIM_CHIP_NAME[$1]}
+
+ VAL=$(<$GPIOSIM_SYSFS/$DEVNAME/$CHIPNAME/sim_gpio$OFFSET/value)
+ [ "$VAL" = "$EXPECTED" ]
+}
+
+gpiosim_wait_value() {
+ local OFFSET=$2
+ local EXPECTED=$3
+ local DEVNAME=${GPIOSIM_DEV_NAME[$1]}
+ local CHIPNAME=${GPIOSIM_CHIP_NAME[$1]}
+ local PORT=$GPIOSIM_SYSFS/$DEVNAME/$CHIPNAME/sim_gpio$OFFSET/value
+
+ for i in {1..30}; do
+ [ "$(<$PORT)" = "$EXPECTED" ] && return
+ sleep 0.01
+ done
+ return 1
+}
+
+gpiosim_cleanup() {
+ for CHIP in ${!GPIOSIM_CHIP_NAME[@]}
+ do
+ local NAME=${GPIOSIM_APP_NAME}-$$-$CHIP
+
+ local DEVPATH=$GPIOSIM_CONFIGFS/$NAME
+ local BANKPATH=$DEVPATH/bank0
+
+ echo 0 > $DEVPATH/live
+
+ ls $BANKPATH/line* > /dev/null 2>&1
+ if [ "$?" = "0" ]
+ then
+ for LINE in $(find $BANKPATH/ | grep -E "line[0-9]+$")
+ do
+ test -e $LINE/hog && rmdir $LINE/hog
+ rmdir $LINE
+ done
+ fi
+
+ rmdir $BANKPATH
+ rmdir $DEVPATH
+ done
+
+ gpiosim_chip_symlink_cleanup
+
+ GPIOSIM_CHIP_NAME=()
+ GPIOSIM_CHIP_PATH=()
+ GPIOSIM_DEV_NAME=()
+}
+
+run_tool() {
+ # Executables to test are expected to be in the same directory as the
+ # testing script.
+ output=$(timeout 10s $SOURCE_DIR/"$@" 2>&1)
+ status=$?
+}
+
+dut_run() {
+ coproc timeout 10s $SOURCE_DIR/"$@" 2>&1
+ DUT_PID=$COPROC_PID
+ read -t1 -n1 -u ${COPROC[0]} DUT_FIRST_CHAR
+}
+
+dut_run_redirect() {
+ coproc timeout 10s $SOURCE_DIR/"$@" > $SHUNIT_TMPDIR/$DUT_OUTPUT 2>&1
+ DUT_PID=$COPROC_PID
+ # give the process time to spin up
+ # FIXME - find a better solution
+ sleep 0.2
+}
+
+dut_read_redirect() {
+ output=$(<$SHUNIT_TMPDIR/$DUT_OUTPUT)
+ local ORIG_IFS="$IFS"
+ IFS=$'\n' lines=($output)
+ IFS="$ORIG_IFS"
+}
+
+dut_read() {
+ local LINE
+ lines=()
+ while read -t 0.2 -u ${COPROC[0]} LINE;
+ do
+ if [ -n "$DUT_FIRST_CHAR" ]
+ then
+ LINE=${DUT_FIRST_CHAR}${LINE}
+ unset DUT_FIRST_CHAR
+ fi
+ lines+=("$LINE")
+ done
+ output="${lines[@]}"
+}
+
+dut_readable() {
+ read -t 0 -u ${COPROC[0]} LINE
+}
+
+dut_flush() {
+ local JUNK
+ lines=()
+ output=
+ unset DUT_FIRST_CHAR
+ while read -t 0 -u ${COPROC[0]} JUNK;
+ do
+ read -t 0.1 -u ${COPROC[0]} JUNK || true
+ done
+}
+
+# check the next line of output matches the regex
+dut_regex_match() {
+ PATTERN=$1
+
+ read -t 0.2 -u ${COPROC[0]} LINE || (echo Timeout && false)
+ if [ -n "$DUT_FIRST_CHAR" ]
+ then
+ LINE=${DUT_FIRST_CHAR}${LINE}
+ unset DUT_FIRST_CHAR
+ fi
+ [[ $LINE =~ $PATTERN ]]
+ assertEquals "'$LINE' did not match '$PATTERN'" "0" "$?"
+}
+
+dut_write() {
+ echo $* >&${COPROC[1]}
+}
+
+dut_kill() {
+ SIGNUM=$1
+
+ kill $SIGNUM $DUT_PID
+}
+
+dut_wait() {
+ wait $DUT_PID
+ export status=$?
+ unset DUT_PID
+}
+
+dut_cleanup() {
+ if [ -n "$DUT_PID" ]
+ then
+ kill -SIGTERM $DUT_PID 2> /dev/null
+ wait $DUT_PID || false
+ fi
+ rm -f $SHUNIT_TMPDIR/$DUT_OUTPUT
+}
+
+tearDown() {
+ dut_cleanup
+ gpiosim_cleanup
+}
+
+request_release_line() {
+ $SOURCE_DIR/gpioget -c $* >/dev/null
+}
+
+#
+# gpiodetect test cases
+#
+
+test_gpiodetect_all_chips() {
+ gpiosim_chip sim0 num_lines=4
+ gpiosim_chip sim1 num_lines=8
+ gpiosim_chip sim2 num_lines=16
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+ local sim1=${GPIOSIM_CHIP_NAME[sim1]}
+ local sim2=${GPIOSIM_CHIP_NAME[sim2]}
+ local sim0dev=${GPIOSIM_DEV_NAME[sim0]}
+ local sim1dev=${GPIOSIM_DEV_NAME[sim1]}
+ local sim2dev=${GPIOSIM_DEV_NAME[sim2]}
+
+ run_tool gpiodetect
+
+ output_regex_match "$sim0 \[${sim0dev}[-:]node0\] \(4 lines\)"
+ output_regex_match "$sim1 \[${sim1dev}[-:]node0\] \(8 lines\)"
+ output_regex_match "$sim2 \[${sim2dev}[-:]node0\] \(16 lines\)"
+ status_is 0
+
+ # ignoring symlinks
+ local initial_output=$output
+ gpiosim_chip_symlink sim1 /dev
+
+ run_tool gpiodetect
+
+ output_is "$initial_output"
+ status_is 0
+}
+
+test_gpiodetect_a_chip() {
+ gpiosim_chip sim0 num_lines=4
+ gpiosim_chip sim1 num_lines=8
+ gpiosim_chip sim2 num_lines=16
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+ local sim1=${GPIOSIM_CHIP_NAME[sim1]}
+ local sim2=${GPIOSIM_CHIP_NAME[sim2]}
+ local sim0dev=${GPIOSIM_DEV_NAME[sim0]}
+ local sim1dev=${GPIOSIM_DEV_NAME[sim1]}
+ local sim2dev=${GPIOSIM_DEV_NAME[sim2]}
+
+ # by name
+ run_tool gpiodetect $sim0
+
+ output_regex_match "$sim0 \[${sim0dev}[-:]node0\] \(4 lines\)"
+ num_lines_is 1
+ status_is 0
+
+ # by path
+ run_tool gpiodetect ${GPIOSIM_CHIP_PATH[sim1]}
+
+ output_regex_match "$sim1 \[${sim1dev}[-:]node0\] \(8 lines\)"
+ num_lines_is 1
+ status_is 0
+
+ # by number
+ run_tool gpiodetect $(gpiosim_chip_number sim2)
+
+ output_regex_match "$sim2 \[${sim2dev}[-:]node0\] \(16 lines\)"
+ num_lines_is 1
+ status_is 0
+
+ # by symlink
+ gpiosim_chip_symlink sim2 .
+ run_tool gpiodetect $GPIOSIM_CHIP_LINK
+
+ output_regex_match "$sim2 \[${sim2dev}[-:]node0\] \(16 lines\)"
+ num_lines_is 1
+ status_is 0
+}
+
+test_gpiodetect_multiple_chips() {
+ gpiosim_chip sim0 num_lines=4
+ gpiosim_chip sim1 num_lines=8
+ gpiosim_chip sim2 num_lines=16
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+ local sim1=${GPIOSIM_CHIP_NAME[sim1]}
+ local sim2=${GPIOSIM_CHIP_NAME[sim2]}
+ local sim0dev=${GPIOSIM_DEV_NAME[sim0]}
+ local sim1dev=${GPIOSIM_DEV_NAME[sim1]}
+ local sim2dev=${GPIOSIM_DEV_NAME[sim2]}
+
+ run_tool gpiodetect $sim0 $sim1 $sim2
+
+ output_regex_match "$sim0 \[${sim0dev}[-:]node0\] \(4 lines\)"
+ output_regex_match "$sim1 \[${sim1dev}[-:]node0\] \(8 lines\)"
+ output_regex_match "$sim2 \[${sim2dev}[-:]node0\] \(16 lines\)"
+ num_lines_is 3
+ status_is 0
+}
+
+test_gpiodetect_with_nonexistent_chip() {
+ run_tool gpiodetect nonexistent-chip
+
+ status_is 1
+ output_regex_match \
+".*cannot find GPIO chip character device 'nonexistent-chip'"
+}
+
+#
+# gpioinfo test cases
+#
+
+test_gpioinfo_all_chips() {
+ gpiosim_chip sim0 num_lines=4
+ gpiosim_chip sim1 num_lines=8
+
+ run_tool gpioinfo
+
+ output_contains_line "${GPIOSIM_CHIP_NAME[sim0]} - 4 lines:"
+ output_contains_line "${GPIOSIM_CHIP_NAME[sim1]} - 8 lines:"
+ output_regex_match "\\s+line\\s+0:\\s+unnamed\\s+input"
+ output_regex_match "\\s+line\\s+7:\\s+unnamed\\s+input"
+ status_is 0
+
+ # ignoring symlinks
+ local initial_output=$output
+ gpiosim_chip_symlink sim1 /dev
+
+ run_tool gpioinfo
+
+ output_is "$initial_output"
+ status_is 0
+}
+
+test_gpioinfo_all_chips_with_some_used_lines() {
+ gpiosim_chip sim0 num_lines=4 line_name=1:foo line_name=2:bar
+ gpiosim_chip sim1 num_lines=8 line_name=3:baz line_name=4:xyz
+
+ dut_run gpioset --banner --active-low foo=1 baz=0
+
+ run_tool gpioinfo
+
+ output_contains_line "${GPIOSIM_CHIP_NAME[sim0]} - 4 lines:"
+ output_contains_line "${GPIOSIM_CHIP_NAME[sim1]} - 8 lines:"
+ output_regex_match "\\s+line\\s+0:\\s+unnamed\\s+input"
+ output_regex_match \
+"\\s+line\\s+1:\\s+\"foo\"\\s+output active-low consumer=\"gpioset\""
+ output_regex_match \
+"\\s+line\\s+3:\\s+\"baz\"\\s+output active-low consumer=\"gpioset\""
+ status_is 0
+}
+
+test_gpioinfo_a_chip() {
+ gpiosim_chip sim0 num_lines=8
+ gpiosim_chip sim1 num_lines=4
+
+ local sim1=${GPIOSIM_CHIP_NAME[sim1]}
+
+ # by name
+ run_tool gpioinfo --chip $sim1
+
+ output_contains_line "$sim1 - 4 lines:"
+ output_regex_match "\\s+line\\s+0:\\s+unnamed\\s+input"
+ output_regex_match "\\s+line\\s+1:\\s+unnamed\\s+input"
+ output_regex_match "\\s+line\\s+2:\\s+unnamed\\s+input"
+ output_regex_match "\\s+line\\s+3:\\s+unnamed\\s+input"
+ num_lines_is 5
+ status_is 0
+
+ # by path
+ run_tool gpioinfo --chip $sim1
+
+ output_contains_line "$sim1 - 4 lines:"
+ output_regex_match "\\s+line\\s+0:\\s+unnamed\\s+input"
+ output_regex_match "\\s+line\\s+1:\\s+unnamed\\s+input"
+ output_regex_match "\\s+line\\s+2:\\s+unnamed\\s+input"
+ output_regex_match "\\s+line\\s+3:\\s+unnamed\\s+input"
+ num_lines_is 5
+ status_is 0
+
+ # by number
+ run_tool gpioinfo --chip $sim1
+
+ output_contains_line "$sim1 - 4 lines:"
+ output_regex_match "\\s+line\\s+0:\\s+unnamed\\s+input"
+ output_regex_match "\\s+line\\s+1:\\s+unnamed\\s+input"
+ output_regex_match "\\s+line\\s+2:\\s+unnamed\\s+input"
+ output_regex_match "\\s+line\\s+3:\\s+unnamed\\s+input"
+ num_lines_is 5
+ status_is 0
+
+ # by symlink
+ gpiosim_chip_symlink sim1 .
+ run_tool gpioinfo --chip $GPIOSIM_CHIP_LINK
+
+ output_contains_line "$sim1 - 4 lines:"
+ output_regex_match "\\s+line\\s+0:\\s+unnamed\\s+input"
+ output_regex_match "\\s+line\\s+1:\\s+unnamed\\s+input"
+ output_regex_match "\\s+line\\s+2:\\s+unnamed\\s+input"
+ output_regex_match "\\s+line\\s+3:\\s+unnamed\\s+input"
+ num_lines_is 5
+ status_is 0
+}
+
+test_gpioinfo_a_line() {
+ gpiosim_chip sim0 num_lines=8 line_name=5:bar
+ gpiosim_chip sim1 num_lines=4 line_name=2:bar
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+ local sim1=${GPIOSIM_CHIP_NAME[sim1]}
+
+ # by offset
+ run_tool gpioinfo --chip $sim1 2
+
+ output_regex_match "$sim1 2\\s+\"bar\"\\s+input"
+ num_lines_is 1
+ status_is 0
+
+ # by name
+ run_tool gpioinfo bar
+
+ output_regex_match "$sim0 5\\s+\"bar\"\\s+input"
+ num_lines_is 1
+ status_is 0
+
+ # by chip and name
+ run_tool gpioinfo --chip $sim1 2
+
+ output_regex_match "$sim1 2\\s+\"bar\"\\s+input"
+ num_lines_is 1
+ status_is 0
+
+ # unquoted
+ run_tool gpioinfo --unquoted --chip $sim1 2
+
+ output_regex_match "$sim1 2\\s+bar\\s+input"
+ num_lines_is 1
+ status_is 0
+
+}
+
+test_gpioinfo_first_matching_named_line() {
+ gpiosim_chip sim0 num_lines=4 line_name=1:foo line_name=2:bar \
+ line_name=3:foobar
+ gpiosim_chip sim1 num_lines=8 line_name=0:baz line_name=2:foobar \
+ line_name=4:xyz line_name=7:foobar
+ gpiosim_chip sim2 num_lines=16
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ run_tool gpioinfo foobar
+
+ output_regex_match "$sim0 3\\s+\"foobar\"\\s+input"
+ num_lines_is 1
+ status_is 0
+}
+
+test_gpioinfo_multiple_lines() {
+ gpiosim_chip sim0 num_lines=8 line_name=5:bar
+ gpiosim_chip sim1 num_lines=4 line_name=2:baz
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+ local sim1=${GPIOSIM_CHIP_NAME[sim1]}
+
+ # by offset
+ run_tool gpioinfo --chip $sim1 1 2
+
+ output_regex_match "$sim1 1\\s+unnamed\\s+input"
+ output_regex_match "$sim1 2\\s+\"baz\"\\s+input"
+ num_lines_is 2
+ status_is 0
+
+ # by name
+ run_tool gpioinfo bar baz
+
+ output_regex_match "$sim0 5\\s+\"bar\"\\s+input"
+ output_regex_match "$sim1 2\\s+\"baz\"\\s+input"
+ num_lines_is 2
+ status_is 0
+
+ # by name and offset
+ run_tool gpioinfo --chip $sim0 bar 3
+
+ output_regex_match "$sim0 5\\s+\"bar\"\\s+input"
+ output_regex_match "$sim0 3\\s+unnamed\\s+input"
+ num_lines_is 2
+ status_is 0
+}
+
+test_gpioinfo_line_attribute_menagerie() {
+ gpiosim_chip sim0 num_lines=4 line_name=1:foo
+ gpiosim_chip sim1 num_lines=8 line_name=3:baz
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+ local sim1=${GPIOSIM_CHIP_NAME[sim1]}
+
+ dut_run gpioset --banner --active-low --bias=pull-up --drive=open-source foo=1 baz=0
+
+ run_tool gpioinfo foo baz
+
+ output_regex_match \
+"$sim0 1\\s+\"foo\"\\s+output active-low drive=open-source bias=pull-up consumer=\"gpioset\""
+ output_regex_match \
+"$sim1 3\\s+\"baz\"\\s+output active-low drive=open-source bias=pull-up consumer=\"gpioset\""
+ num_lines_is 2
+ status_is 0
+
+ dut_kill
+ dut_wait
+
+ dut_run gpioset --banner --bias=pull-down --drive=open-drain foo=1 baz=0
+
+ run_tool gpioinfo foo baz
+
+ output_regex_match \
+"$sim0 1\\s+\"foo\"\\s+output drive=open-drain bias=pull-down consumer=\"gpioset\""
+ output_regex_match \
+"$sim1 3\\s+\"baz\"\\s+output drive=open-drain bias=pull-down consumer=\"gpioset\""
+ num_lines_is 2
+ status_is 0
+
+ dut_kill
+ dut_wait
+
+ dut_run gpiomon --banner --bias=disabled --utc -p 10ms foo baz
+
+ run_tool gpioinfo foo baz
+
+ output_regex_match \
+"$sim0 1\\s+\"foo\"\\s+input bias=disabled edges=both event-clock=realtime debounce-period=10ms consumer=\"gpiomon\""
+ output_regex_match \
+"$sim1 3\\s+\"baz\"\\s+input bias=disabled edges=both event-clock=realtime debounce-period=10ms consumer=\"gpiomon\""
+ num_lines_is 2
+ status_is 0
+
+ dut_kill
+ dut_wait
+
+ dut_run gpiomon --banner --edges=rising --localtime foo baz
+
+ run_tool gpioinfo foo baz
+
+ output_regex_match \
+"$sim0 1\\s+\"foo\"\\s+input edges=rising event-clock=realtime consumer=\"gpiomon\""
+ output_regex_match \
+"$sim1 3\\s+\"baz\"\\s+input edges=rising event-clock=realtime consumer=\"gpiomon\""
+ num_lines_is 2
+ status_is 0
+
+ dut_kill
+ dut_wait
+
+ dut_run gpiomon --banner --edges=falling foo baz
+
+ run_tool gpioinfo foo baz
+
+ output_regex_match \
+"$sim0 1\\s+\"foo\"\\s+input edges=falling consumer=\"gpiomon\""
+ output_regex_match \
+"$sim1 3\\s+\"baz\"\\s+input edges=falling consumer=\"gpiomon\""
+ num_lines_is 2
+ status_is 0
+}
+
+test_gpioinfo_with_same_line_twice() {
+ gpiosim_chip sim0 num_lines=8 line_name=1:foo
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ # by offset
+ run_tool gpioinfo --chip $sim0 1 1
+
+ output_regex_match "$sim0 1\\s+\"foo\"\\s+input"
+ output_regex_match ".*lines '1' and '1' are the same line"
+ num_lines_is 2
+ status_is 1
+
+ # by name
+ run_tool gpioinfo foo foo
+
+ output_regex_match "$sim0 1\\s+\"foo\"\\s+input"
+ output_regex_match ".*lines 'foo' and 'foo' are the same line"
+ num_lines_is 2
+ status_is 1
+
+ # by name and offset
+ run_tool gpioinfo --chip $sim0 foo 1
+
+ output_regex_match "$sim0 1\\s+\"foo\"\\s+input"
+ output_regex_match ".*lines 'foo' and '1' are the same line"
+ num_lines_is 2
+ status_is 1
+}
+
+test_gpioinfo_all_lines_matching_name() {
+ gpiosim_chip sim0 num_lines=4 line_name=1:foo line_name=2:bar \
+ line_name=3:foobar
+ gpiosim_chip sim1 num_lines=8 line_name=0:baz line_name=2:foobar \
+ line_name=4:xyz line_name=7:foobar
+ gpiosim_chip sim2 num_lines=16
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+ local sim1=${GPIOSIM_CHIP_NAME[sim1]}
+
+ run_tool gpioinfo --strict foobar
+
+ output_regex_match "$sim0 3\\s+\"foobar\"\\s+input"
+ output_regex_match "$sim1 2\\s+\"foobar\"\\s+input"
+ output_regex_match "$sim1 7\\s+\"foobar\"\\s+input"
+ num_lines_is 3
+ status_is 1
+}
+
+test_gpioinfo_with_lines_strictly_by_name() {
+ # not suggesting this setup makes any sense
+ # - just test that we can deal with it
+ gpiosim_chip sim0 num_lines=8 line_name=1:6 line_name=6:1
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ # first by offset (to show offsets match first)
+ run_tool gpioinfo --chip $sim0 1 6
+
+ output_regex_match "$sim0 1\\s+\"6\"\\s+input"
+ output_regex_match "$sim0 6\\s+\"1\"\\s+input"
+ num_lines_is 2
+ status_is 0
+
+ # then strictly by name
+ run_tool gpioinfo --by-name --chip $sim0 1
+
+ output_regex_match "$sim0 6\\s+\"1\"\\s+input"
+ num_lines_is 1
+ status_is 0
+}
+
+test_gpioinfo_with_nonexistent_chip() {
+ run_tool gpioinfo --chip nonexistent-chip
+
+ output_regex_match \
+".*cannot find GPIO chip character device 'nonexistent-chip'"
+ status_is 1
+}
+
+test_gpioinfo_with_nonexistent_line() {
+ gpiosim_chip sim0 num_lines=8
+
+ run_tool gpioinfo nonexistent-line
+
+ output_regex_match ".*cannot find line 'nonexistent-line'"
+ status_is 1
+
+ run_tool gpioinfo --chip ${GPIOSIM_CHIP_NAME[sim0]} nonexistent-line
+
+ output_regex_match ".*cannot find line 'nonexistent-line'"
+ status_is 1
+}
+
+test_gpioinfo_with_offset_out_of_range() {
+ gpiosim_chip sim0 num_lines=4
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ run_tool gpioinfo --chip $sim0 0 1 2 3 4 5
+
+ output_regex_match "$sim0 0\\s+unnamed\\s+input"
+ output_regex_match "$sim0 1\\s+unnamed\\s+input"
+ output_regex_match "$sim0 2\\s+unnamed\\s+input"
+ output_regex_match "$sim0 3\\s+unnamed\\s+input"
+ output_regex_match ".*offset 4 is out of range on chip '$sim0'"
+ output_regex_match ".*offset 5 is out of range on chip '$sim0'"
+ num_lines_is 6
+ status_is 1
+}
+
+#
+# gpioget test cases
+#
+
+test_gpioget_by_name() {
+ gpiosim_chip sim0 num_lines=8 line_name=1:foo
+
+ gpiosim_set_pull sim0 1 pull-up
+
+ run_tool gpioget foo
+
+ output_is "\"foo\"=active"
+ status_is 0
+
+ run_tool gpioget --unquoted foo
+
+ output_is "foo=active"
+ status_is 0
+}
+
+test_gpioget_by_offset() {
+ gpiosim_chip sim0 num_lines=8
+
+ gpiosim_set_pull sim0 1 pull-up
+
+ run_tool gpioget --chip ${GPIOSIM_CHIP_NAME[sim0]} 1
+
+ output_is "\"1\"=active"
+ status_is 0
+
+ run_tool gpioget --unquoted --chip ${GPIOSIM_CHIP_NAME[sim0]} 1
+
+ output_is "1=active"
+ status_is 0
+}
+
+test_gpioget_by_symlink() {
+ gpiosim_chip sim0 num_lines=8
+ gpiosim_chip_symlink sim0 .
+
+ gpiosim_set_pull sim0 1 pull-up
+
+ run_tool gpioget --chip $GPIOSIM_CHIP_LINK 1
+
+ output_is "\"1\"=active"
+ status_is 0
+}
+
+test_gpioget_by_chip_and_name() {
+ gpiosim_chip sim0 num_lines=8 line_name=1:foo
+ gpiosim_chip sim1 num_lines=8 line_name=3:foo
+
+ gpiosim_set_pull sim1 3 pull-up
+
+ run_tool gpioget --chip ${GPIOSIM_CHIP_NAME[sim1]} foo
+
+ output_is "\"foo\"=active"
+ status_is 0
+
+ run_tool gpioget --unquoted --chip ${GPIOSIM_CHIP_NAME[sim1]} foo
+
+ output_is "foo=active"
+ status_is 0
+}
+
+test_gpioget_first_matching_named_line() {
+ gpiosim_chip sim0 num_lines=4 line_name=1:foo line_name=2:bar \
+ line_name=3:foobar line_name=7:foobar
+ gpiosim_chip sim1 num_lines=8 line_name=0:baz line_name=2:foobar \
+ line_name=4:xyz
+ gpiosim_chip sim2 num_lines=16
+
+ gpiosim_set_pull sim0 3 pull-up
+
+ run_tool gpioget foobar
+
+ output_is "\"foobar\"=active"
+ status_is 0
+}
+
+test_gpioget_multiple_lines() {
+ gpiosim_chip sim0 num_lines=8
+
+ gpiosim_set_pull sim0 2 pull-up
+ gpiosim_set_pull sim0 3 pull-up
+ gpiosim_set_pull sim0 5 pull-up
+ gpiosim_set_pull sim0 7 pull-up
+
+ run_tool gpioget --unquoted --chip ${GPIOSIM_CHIP_NAME[sim0]} 0 1 2 3 4 5 6 7
+
+ output_is \
+"0=inactive 1=inactive 2=active 3=active 4=inactive 5=active 6=inactive 7=active"
+ status_is 0
+}
+
+test_gpioget_multiple_lines_by_name_and_offset() {
+ gpiosim_chip sim0 num_lines=8 line_name=1:foo line_name=6:bar
+ gpiosim_chip sim1 num_lines=8 line_name=1:baz line_name=3:bar
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ gpiosim_set_pull sim0 1 pull-up
+ gpiosim_set_pull sim0 4 pull-up
+ gpiosim_set_pull sim0 6 pull-up
+
+ run_tool gpioget --chip $sim0 0 foo 4 bar
+
+ output_is "\"0\"=inactive \"foo\"=active \"4\"=active \"bar\"=active"
+ status_is 0
+}
+
+test_gpioget_multiple_lines_across_multiple_chips() {
+ gpiosim_chip sim0 num_lines=4 line_name=1:foo line_name=2:bar
+ gpiosim_chip sim1 num_lines=8 line_name=0:baz line_name=4:xyz
+
+ gpiosim_set_pull sim0 1 pull-up
+ gpiosim_set_pull sim1 4 pull-up
+
+ run_tool gpioget baz bar foo xyz
+
+ output_is "\"baz\"=inactive \"bar\"=inactive \"foo\"=active \"xyz\"=active"
+ status_is 0
+}
+
+test_gpioget_with_numeric_values() {
+ gpiosim_chip sim0 num_lines=8
+
+ gpiosim_set_pull sim0 2 pull-up
+ gpiosim_set_pull sim0 3 pull-up
+ gpiosim_set_pull sim0 5 pull-up
+ gpiosim_set_pull sim0 7 pull-up
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ run_tool gpioget --numeric --chip $sim0 0 1 2 3 4 5 6 7
+
+ output_is "0 0 1 1 0 1 0 1"
+ status_is 0
+}
+
+test_gpioget_with_active_low() {
+ gpiosim_chip sim0 num_lines=8
+
+ gpiosim_set_pull sim0 2 pull-up
+ gpiosim_set_pull sim0 3 pull-up
+ gpiosim_set_pull sim0 5 pull-up
+ gpiosim_set_pull sim0 7 pull-up
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ run_tool gpioget --active-low --unquoted --chip $sim0 0 1 2 3 4 5 6 7
+
+ output_is \
+"0=active 1=active 2=inactive 3=inactive 4=active 5=inactive 6=active 7=inactive"
+ status_is 0
+}
+
+test_gpioget_with_consumer() {
+ gpiosim_chip sim0 num_lines=4 line_name=1:foo line_name=2:bar
+ gpiosim_chip sim1 num_lines=8 line_name=3:baz line_name=4:xyz
+
+ dut_run gpionotify --banner -F "%l %E %C" foo baz
+
+ run_tool gpioget --consumer gpio-tools-tests foo baz
+ status_is 0
+
+ dut_read
+ output_regex_match "foo requested gpio-tools-tests"
+ output_regex_match "baz requested gpio-tools-tests"
+}
+
+test_gpioget_with_pull_up() {
+ gpiosim_chip sim0 num_lines=8
+
+ gpiosim_set_pull sim0 2 pull-up
+ gpiosim_set_pull sim0 3 pull-up
+ gpiosim_set_pull sim0 5 pull-up
+ gpiosim_set_pull sim0 7 pull-up
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ run_tool gpioget --bias=pull-up --unquoted --chip $sim0 0 1 2 3 4 5 6 7
+
+ output_is \
+"0=active 1=active 2=active 3=active 4=active 5=active 6=active 7=active"
+ status_is 0
+}
+
+test_gpioget_with_pull_down() {
+ gpiosim_chip sim0 num_lines=8
+
+ gpiosim_set_pull sim0 2 pull-up
+ gpiosim_set_pull sim0 3 pull-up
+ gpiosim_set_pull sim0 5 pull-up
+ gpiosim_set_pull sim0 7 pull-up
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ run_tool gpioget --bias=pull-down --unquoted --chip $sim0 0 1 2 3 4 5 6 7
+
+ output_is \
+"0=inactive 1=inactive 2=inactive 3=inactive 4=inactive 5=inactive 6=inactive 7=inactive"
+ status_is 0
+}
+
+test_gpioget_with_direction_as_is() {
+ gpiosim_chip sim0 num_lines=8 line_name=1:foo
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ # flip to output
+ run_tool gpioset -t0 foo=1
+
+ status_is 0
+
+ run_tool gpioinfo foo
+ output_regex_match "$sim0 1\\s+\"foo\"\\s+output"
+ status_is 0
+
+ run_tool gpioget --as-is foo
+ # note gpio-sim reverts line to its pull when released
+ output_is "\"foo\"=inactive"
+ status_is 0
+
+ run_tool gpioinfo foo
+ output_regex_match "$sim0 1\\s+\"foo\"\\s+output"
+ status_is 0
+
+ # whereas the default behaviour forces to input
+ run_tool gpioget foo
+ # note gpio-sim reverts line to its pull when released
+ # (defaults to pull-down)
+ output_is "\"foo\"=inactive"
+ status_is 0
+
+ run_tool gpioinfo foo
+ output_regex_match "$sim0 1\\s+\"foo\"\\s+input"
+ status_is 0
+}
+
+test_gpioget_with_hold_period() {
+ gpiosim_chip sim0 num_lines=8 line_name=1:foo
+
+ # only test parsing - testing the hold-period itself is tricky
+ run_tool gpioget --hold-period=100ms foo
+ output_is "\"foo\"=inactive"
+ status_is 0
+}
+
+test_gpioget_with_strict_named_line_check() {
+ gpiosim_chip sim0 num_lines=4 line_name=1:foo line_name=2:bar \
+ line_name=3:foobar
+ gpiosim_chip sim1 num_lines=8 line_name=0:baz line_name=2:foobar \
+ line_name=4:xyz line_name=7:foobar
+ gpiosim_chip sim2 num_lines=16
+
+ run_tool gpioget --strict foobar
+
+ output_regex_match ".*line 'foobar' is not unique"
+ status_is 1
+}
+
+test_gpioget_with_lines_by_offset() {
+ # not suggesting this setup makes any sense
+ # - just test that we can deal with it
+ gpiosim_chip sim0 num_lines=8 line_name=1:6 line_name=6:1
+
+ gpiosim_set_pull sim0 1 pull-up
+ gpiosim_set_pull sim0 6 pull-down
+
+ run_tool gpioget --chip ${GPIOSIM_CHIP_NAME[sim0]} 1 6
+
+ output_is "\"1\"=active \"6\"=inactive"
+ status_is 0
+
+ run_tool gpioget --unquoted --chip ${GPIOSIM_CHIP_NAME[sim0]} 1 6
+
+ output_is "1=active 6=inactive"
+ status_is 0
+}
+
+test_gpioget_with_lines_strictly_by_name() {
+ # not suggesting this setup makes any sense
+ # - just test that we can deal with it
+ gpiosim_chip sim0 num_lines=8 line_name=1:6 line_name=6:1
+
+ gpiosim_set_pull sim0 1 pull-up
+ gpiosim_set_pull sim0 6 pull-down
+
+ run_tool gpioget --by-name --chip ${GPIOSIM_CHIP_NAME[sim0]} 1 6
+
+ output_is "\"1\"=inactive \"6\"=active"
+ status_is 0
+
+ run_tool gpioget --by-name --unquoted --chip ${GPIOSIM_CHIP_NAME[sim0]} 1 6
+
+ output_is "1=inactive 6=active"
+ status_is 0
+}
+
+test_gpioget_with_no_arguments() {
+ run_tool gpioget
+
+ output_regex_match ".*at least one GPIO line must be specified"
+ status_is 1
+}
+
+test_gpioget_with_chip_but_no_line_specified() {
+ gpiosim_chip sim0 num_lines=8
+
+ run_tool gpioget --chip ${GPIOSIM_CHIP_NAME[sim0]}
+
+ output_regex_match ".*at least one GPIO line must be specified"
+ status_is 1
+}
+
+test_gpioget_with_offset_out_of_range() {
+ gpiosim_chip sim0 num_lines=4
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ run_tool gpioget --chip $sim0 0 1 2 3 4 5
+
+ output_regex_match ".*offset 4 is out of range on chip '$sim0'"
+ output_regex_match ".*offset 5 is out of range on chip '$sim0'"
+ status_is 1
+}
+
+test_gpioget_with_nonexistent_line() {
+ run_tool gpioget nonexistent-line
+
+ output_regex_match ".*cannot find line 'nonexistent-line'"
+ status_is 1
+}
+
+test_gpioget_with_same_line_twice() {
+ gpiosim_chip sim0 num_lines=8 line_name=1:foo
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ # by offset
+ run_tool gpioget --chip $sim0 0 0
+
+ output_regex_match ".*lines '0' and '0' are the same line"
+ status_is 1
+
+ # by name
+ run_tool gpioget foo foo
+
+ output_regex_match ".*lines 'foo' and 'foo' are the same line"
+ status_is 1
+
+ # by chip and name
+ run_tool gpioget --chip $sim0 foo foo
+
+ output_regex_match ".*lines 'foo' and 'foo' are the same line"
+ status_is 1
+
+ # by name and offset
+ run_tool gpioget --chip $sim0 foo 1
+
+ output_regex_match ".*lines 'foo' and '1' are the same line"
+ status_is 1
+
+ # by offset and name
+ run_tool gpioget --chip $sim0 1 foo
+
+ output_regex_match ".*lines '1' and 'foo' are the same line"
+ status_is 1
+}
+
+test_gpioget_with_invalid_bias() {
+ gpiosim_chip sim0 num_lines=8
+
+ run_tool gpioget --bias=bad --chip ${GPIOSIM_CHIP_NAME[sim0]} 0 1
+
+ output_regex_match ".*invalid bias.*"
+ status_is 1
+}
+
+test_gpioget_with_invalid_hold_period() {
+ gpiosim_chip sim0 num_lines=8
+
+ run_tool gpioget --hold-period=bad --chip ${GPIOSIM_CHIP_NAME[sim0]} 0
+
+ output_regex_match ".*invalid period.*"
+ status_is 1
+}
+
+#
+# gpioset test cases
+#
+
+test_gpioset_by_name() {
+ gpiosim_chip sim0 num_lines=8 line_name=1:foo
+
+ dut_run gpioset --banner foo=1
+
+ gpiosim_check_value sim0 1 1
+}
+
+test_gpioset_by_offset() {
+ gpiosim_chip sim0 num_lines=8
+
+ dut_run gpioset --banner --chip ${GPIOSIM_CHIP_NAME[sim0]} 1=1
+
+ gpiosim_check_value sim0 1 1
+}
+
+test_gpioset_by_symlink() {
+ gpiosim_chip sim0 num_lines=8
+ gpiosim_chip_symlink sim0 .
+
+ dut_run gpioset --banner --chip $GPIOSIM_CHIP_LINK 1=1
+
+ gpiosim_check_value sim0 1 1
+}
+
+test_gpioset_by_chip_and_name() {
+ gpiosim_chip sim0 num_lines=8 line_name=1:foo
+ gpiosim_chip sim1 num_lines=8 line_name=3:foo
+
+ dut_run gpioset --banner --chip ${GPIOSIM_CHIP_NAME[sim1]} foo=1
+
+ gpiosim_check_value sim1 3 1
+}
+
+test_gpioset_first_matching_named_line() {
+ gpiosim_chip sim0 num_lines=4 line_name=1:foo line_name=2:bar \
+ line_name=3:foobar
+ gpiosim_chip sim1 num_lines=8 line_name=0:baz line_name=2:foobar \
+ line_name=4:xyz line_name=7:foobar
+ gpiosim_chip sim2 num_lines=16
+
+ dut_run gpioset --banner foobar=1
+
+ gpiosim_check_value sim0 3 1
+}
+
+test_gpioset_multiple_lines() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpioset --banner --chip $sim0 0=0 1=0 2=1 3=1 4=1 5=1 6=0 7=1
+
+ gpiosim_check_value sim0 0 0
+ gpiosim_check_value sim0 1 0
+ gpiosim_check_value sim0 2 1
+ gpiosim_check_value sim0 3 1
+ gpiosim_check_value sim0 4 1
+ gpiosim_check_value sim0 5 1
+ gpiosim_check_value sim0 6 0
+ gpiosim_check_value sim0 7 1
+}
+
+test_gpioset_multiple_lines_by_name_and_offset() {
+ gpiosim_chip sim0 num_lines=4 line_name=1:foo line_name=2:bar
+
+ dut_run gpioset --banner --chip ${GPIOSIM_CHIP_NAME[sim0]} 0=1 foo=1 bar=1 3=1
+
+ gpiosim_check_value sim0 0 1
+ gpiosim_check_value sim0 1 1
+ gpiosim_check_value sim0 2 1
+ gpiosim_check_value sim0 3 1
+}
+
+
+test_gpioset_multiple_lines_across_multiple_chips() {
+ gpiosim_chip sim0 num_lines=4 line_name=1:foo line_name=2:bar
+ gpiosim_chip sim1 num_lines=8 line_name=0:baz line_name=4:xyz
+
+ dut_run gpioset --banner foo=1 bar=1 baz=1 xyz=1
+
+ gpiosim_check_value sim0 1 1
+ gpiosim_check_value sim0 2 1
+ gpiosim_check_value sim1 0 1
+ gpiosim_check_value sim1 4 1
+}
+
+test_gpioset_with_active_low() {
+ gpiosim_chip sim0 num_lines=8
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpioset --banner --active-low -c $sim0 \
+ 0=0 1=0 2=1 3=1 4=1 5=1 6=0 7=1
+
+ gpiosim_check_value sim0 0 1
+ gpiosim_check_value sim0 1 1
+ gpiosim_check_value sim0 2 0
+ gpiosim_check_value sim0 3 0
+ gpiosim_check_value sim0 4 0
+ gpiosim_check_value sim0 5 0
+ gpiosim_check_value sim0 6 1
+ gpiosim_check_value sim0 7 0
+}
+
+test_gpioset_with_consumer() {
+ gpiosim_chip sim0 num_lines=4 line_name=1:foo line_name=2:bar
+ gpiosim_chip sim1 num_lines=8 line_name=3:baz line_name=4:xyz
+
+ dut_run gpioset --banner --consumer gpio-tools-tests foo=1 baz=0
+
+ run_tool gpioinfo
+
+ output_regex_match "\\s+line\\s+0:\\s+unnamed\\s+input"
+ output_regex_match \
+"\\s+line\\s+1:\\s+\"foo\"\\s+output consumer=\"gpio-tools-tests\""
+ output_regex_match \
+"\\s+line\\s+3:\\s+\"baz\"\\s+output consumer=\"gpio-tools-tests\""
+ status_is 0
+}
+
+test_gpioset_with_push_pull() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpioset --banner --drive=push-pull --chip $sim0 \
+ 0=0 1=0 2=1 3=1 4=1 5=1 6=0 7=1
+
+ gpiosim_check_value sim0 0 0
+ gpiosim_check_value sim0 1 0
+ gpiosim_check_value sim0 2 1
+ gpiosim_check_value sim0 3 1
+ gpiosim_check_value sim0 4 1
+ gpiosim_check_value sim0 5 1
+ gpiosim_check_value sim0 6 0
+ gpiosim_check_value sim0 7 1
+}
+
+test_gpioset_with_open_drain() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ gpiosim_set_pull sim0 2 pull-up
+ gpiosim_set_pull sim0 3 pull-up
+ gpiosim_set_pull sim0 5 pull-up
+ gpiosim_set_pull sim0 7 pull-up
+
+ dut_run gpioset --banner --drive=open-drain --chip $sim0 \
+ 0=0 1=0 2=1 3=1 4=1 5=1 6=0 7=1
+
+ gpiosim_check_value sim0 0 0
+ gpiosim_check_value sim0 1 0
+ gpiosim_check_value sim0 2 1
+ gpiosim_check_value sim0 3 1
+ gpiosim_check_value sim0 4 0
+ gpiosim_check_value sim0 5 1
+ gpiosim_check_value sim0 6 0
+ gpiosim_check_value sim0 7 1
+}
+
+test_gpioset_with_open_source() {
+ gpiosim_chip sim0 num_lines=8
+
+ gpiosim_set_pull sim0 2 pull-up
+ gpiosim_set_pull sim0 3 pull-up
+ gpiosim_set_pull sim0 5 pull-up
+ gpiosim_set_pull sim0 7 pull-up
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpioset --banner --drive=open-source --chip $sim0 \
+ 0=0 1=0 2=1 3=0 4=1 5=1 6=0 7=1
+
+ gpiosim_check_value sim0 0 0
+ gpiosim_check_value sim0 1 0
+ gpiosim_check_value sim0 2 1
+ gpiosim_check_value sim0 3 1
+ gpiosim_check_value sim0 4 1
+ gpiosim_check_value sim0 5 1
+ gpiosim_check_value sim0 6 0
+ gpiosim_check_value sim0 7 1
+}
+
+test_gpioset_with_pull_up() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpioset --banner --bias=pull-up --drive=open-drain \
+ --chip $sim0 0=0 1=0 2=1 3=0 4=1 5=1 6=0 7=1
+
+ gpiosim_check_value sim0 0 0
+ gpiosim_check_value sim0 1 0
+ gpiosim_check_value sim0 2 1
+ gpiosim_check_value sim0 3 0
+ gpiosim_check_value sim0 4 1
+ gpiosim_check_value sim0 5 1
+ gpiosim_check_value sim0 6 0
+ gpiosim_check_value sim0 7 1
+}
+
+test_gpioset_with_pull_down() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpioset --banner --bias=pull-down --drive=open-source \
+ --chip $sim0 0=0 1=0 2=1 3=0 4=1 5=1 6=0 7=1
+
+ gpiosim_check_value sim0 0 0
+ gpiosim_check_value sim0 1 0
+ gpiosim_check_value sim0 2 1
+ gpiosim_check_value sim0 3 0
+ gpiosim_check_value sim0 4 1
+ gpiosim_check_value sim0 5 1
+ gpiosim_check_value sim0 6 0
+ gpiosim_check_value sim0 7 1
+}
+
+test_gpioset_with_value_variants() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ gpiosim_set_pull sim0 0 pull-up
+ gpiosim_set_pull sim0 1 pull-down
+ gpiosim_set_pull sim0 2 pull-down
+ gpiosim_set_pull sim0 3 pull-up
+ gpiosim_set_pull sim0 4 pull-down
+ gpiosim_set_pull sim0 5 pull-up
+ gpiosim_set_pull sim0 6 pull-up
+ gpiosim_set_pull sim0 7 pull-down
+
+ dut_run gpioset --banner --chip $sim0 0=0 1=1 2=active \
+ 3=inactive 4=on 5=off 6=false 7=true
+
+ gpiosim_check_value sim0 0 0
+ gpiosim_check_value sim0 1 1
+ gpiosim_check_value sim0 2 1
+ gpiosim_check_value sim0 3 0
+ gpiosim_check_value sim0 4 1
+ gpiosim_check_value sim0 5 0
+ gpiosim_check_value sim0 6 0
+ gpiosim_check_value sim0 7 1
+}
+
+test_gpioset_with_hold_period() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ gpiosim_set_pull sim0 5 pull-up
+
+ dut_run gpioset --banner --hold-period=1200ms -t0 --chip $sim0 0=1 5=0 7=1
+
+ gpiosim_check_value sim0 0 1
+ gpiosim_check_value sim0 5 0
+ gpiosim_check_value sim0 7 1
+
+ dut_wait
+
+ status_is 0
+}
+
+test_gpioset_interactive_exit() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpioset --interactive --chip $sim0 1=0 2=1 5=1 6=0 7=1
+
+ gpiosim_check_value sim0 1 0
+ gpiosim_check_value sim0 2 1
+ gpiosim_check_value sim0 5 1
+ gpiosim_check_value sim0 6 0
+ gpiosim_check_value sim0 7 1
+
+ dut_write "exit"
+ dut_wait
+
+ status_is 0
+}
+
+test_gpioset_interactive_help() {
+ gpiosim_chip sim0 num_lines=8 line_name=1:foo line_name=4:bar \
+ line_name=7:baz
+
+ dut_run gpioset --interactive foo=1 bar=0 baz=0
+
+ gpiosim_check_value sim0 1 1
+ gpiosim_check_value sim0 4 0
+ gpiosim_check_value sim0 7 0
+
+ dut_write "help"
+
+ dut_read
+ output_regex_match "COMMANDS:.*"
+ output_regex_match ".*get \[line\]\.\.\..*"
+ output_regex_match ".*set <line=value>\.\.\..*"
+ output_regex_match ".*toggle \[line\]\.\.\..*"
+ output_regex_match ".*sleep <period>.*"
+}
+
+test_gpioset_interactive_get() {
+ gpiosim_chip sim0 num_lines=8 line_name=1:foo line_name=4:bar \
+ line_name=7:baz
+
+ dut_run gpioset --interactive foo=1 bar=0 baz=0
+
+ gpiosim_check_value sim0 1 1
+ gpiosim_check_value sim0 4 0
+ gpiosim_check_value sim0 7 0
+
+ dut_write "get"
+
+ dut_read
+ output_regex_match "\"foo\"=active \"bar\"=inactive \"baz\"=inactive"
+
+ dut_write "get bar"
+
+ dut_read
+ output_regex_match "\"bar\"=inactive"
+}
+
+test_gpioset_interactive_get_unquoted() {
+ gpiosim_chip sim0 num_lines=8 line_name=1:foo line_name=4:bar \
+ line_name=7:baz
+
+ dut_run gpioset --interactive --unquoted foo=1 bar=0 baz=0
+
+ gpiosim_check_value sim0 1 1
+ gpiosim_check_value sim0 4 0
+ gpiosim_check_value sim0 7 0
+
+ dut_write "get"
+
+ dut_read
+ output_regex_match "foo=active bar=inactive baz=inactive"
+
+ dut_write "get bar"
+
+ dut_read
+ output_regex_match "bar=inactive"
+}
+
+test_gpioset_interactive_set() {
+ gpiosim_chip sim0 num_lines=8 line_name=1:foo line_name=4:bar \
+ line_name=7:baz
+
+ dut_run gpioset --interactive foo=1 bar=0 baz=0
+
+ gpiosim_check_value sim0 1 1
+ gpiosim_check_value sim0 4 0
+ gpiosim_check_value sim0 7 0
+
+ dut_write "set bar=active"
+
+ gpiosim_check_value sim0 1 1
+ gpiosim_check_value sim0 4 1
+ gpiosim_check_value sim0 7 0
+ dut_write "get"
+ dut_read
+ output_regex_match "\"foo\"=active \"bar\"=active \"baz\"=inactive"
+}
+
+test_gpioset_interactive_toggle() {
+ gpiosim_chip sim0 num_lines=8 line_name=1:foo line_name=4:bar \
+ line_name=7:baz
+
+ gpiosim_set_pull sim0 4 pull-up
+ gpiosim_set_pull sim0 7 pull-up
+
+ dut_run gpioset -i foo=1 bar=0 baz=0
+
+ gpiosim_check_value sim0 1 1
+ gpiosim_check_value sim0 4 0
+ gpiosim_check_value sim0 7 0
+
+ dut_write "toggle"
+
+ gpiosim_check_value sim0 1 0
+ gpiosim_check_value sim0 4 1
+ gpiosim_check_value sim0 7 1
+ dut_write "get"
+ dut_read
+ output_regex_match "\"foo\"=inactive\\s+\"bar\"=active\\s+\"baz\"=active\\s*"
+
+ dut_write "toggle baz"
+
+ gpiosim_check_value sim0 1 0
+ gpiosim_check_value sim0 4 1
+ gpiosim_check_value sim0 7 0
+ dut_write "get"
+ dut_read
+ output_regex_match "\"foo\"=inactive\\s+\"bar\"=active\\s+\"baz\"=inactive\\s*"
+}
+
+test_gpioset_interactive_sleep() {
+ gpiosim_chip sim0 num_lines=8 line_name=1:foo line_name=4:bar \
+ line_name=7:baz
+
+ dut_run gpioset --interactive foo=1 bar=0 baz=0
+
+ dut_write "sleep 500ms"
+ dut_flush
+
+ assert_fail dut_readable
+
+ sleep 1
+
+ # prompt, but not a full line...
+ dut_readable
+}
+
+test_gpioset_toggle_continuous() {
+ gpiosim_chip sim0 num_lines=8 line_name=1:foo line_name=4:bar \
+ line_name=7:baz
+
+ gpiosim_set_pull sim0 4 pull-up
+ gpiosim_set_pull sim0 7 pull-up
+
+ dut_run gpioset --banner --toggle 100ms foo=1 bar=0 baz=0
+
+ gpiosim_check_value sim0 1 1
+ gpiosim_check_value sim0 4 0
+ gpiosim_check_value sim0 7 0
+
+ gpiosim_wait_value sim0 1 0
+ gpiosim_check_value sim0 4 1
+ gpiosim_check_value sim0 7 1
+
+
+ gpiosim_wait_value sim0 1 1
+ gpiosim_check_value sim0 4 0
+ gpiosim_check_value sim0 7 0
+
+ gpiosim_wait_value sim0 1 0
+ gpiosim_check_value sim0 4 1
+ gpiosim_check_value sim0 7 1
+}
+
+test_gpioset_toggle_terminated() {
+ gpiosim_chip sim0 num_lines=8 line_name=1:foo line_name=4:bar \
+ line_name=7:baz
+
+ gpiosim_set_pull sim0 4 pull-up
+
+ # hold-period to allow test to sample before gpioset exits
+ dut_run gpioset --banner --toggle 1s,0 -p 600ms foo=1 bar=0 baz=1
+
+ gpiosim_check_value sim0 1 1
+ gpiosim_check_value sim0 4 0
+ gpiosim_check_value sim0 7 1
+
+ sleep 1
+
+ gpiosim_check_value sim0 1 0
+ gpiosim_check_value sim0 4 1
+ gpiosim_check_value sim0 7 0
+
+ dut_wait
+
+ status_is 0
+
+ # using --toggle 0 to exit
+ # hold-period to allow test to sample before gpioset exits
+ dut_run gpioset --banner -t0 -p 600ms foo=1 bar=0 baz=1
+
+ gpiosim_check_value sim0 1 1
+ gpiosim_check_value sim0 4 0
+ gpiosim_check_value sim0 7 1
+
+ dut_wait
+
+ status_is 0
+}
+
+test_gpioset_with_invalid_toggle_period() {
+ gpiosim_chip sim0 num_lines=8 line_name=1:foo line_name=4:bar \
+ line_name=7:baz
+
+ run_tool gpioset --toggle 1ns foo=1 bar=0 baz=0
+
+ output_regex_match ".*invalid period.*"
+ status_is 1
+}
+
+test_gpioset_with_strict_named_line_check() {
+ gpiosim_chip sim0 num_lines=4 line_name=1:foo line_name=2:bar \
+ line_name=3:foobar
+ gpiosim_chip sim1 num_lines=8 line_name=0:baz line_name=2:foobar \
+ line_name=4:xyz line_name=7:foobar
+ gpiosim_chip sim2 num_lines=16
+
+ run_tool gpioset --strict foobar=active
+
+ output_regex_match ".*line 'foobar' is not unique"
+ status_is 1
+}
+
+test_gpioset_with_lines_by_offset() {
+ # not suggesting this setup makes any sense
+ # - just test that we can deal with it
+ gpiosim_chip sim0 num_lines=8 line_name=1:6 line_name=6:1
+
+ gpiosim_set_pull sim0 1 pull-down
+ gpiosim_set_pull sim0 6 pull-up
+
+ dut_run gpioset --banner --chip ${GPIOSIM_CHIP_NAME[sim0]} 6=1 1=0
+
+ gpiosim_check_value sim0 1 0
+ gpiosim_check_value sim0 6 1
+}
+
+test_gpioset_with_lines_strictly_by_name() {
+ # not suggesting this setup makes any sense
+ # - just test that we can deal with it
+ gpiosim_chip sim0 num_lines=8 line_name=1:6 line_name=6:1
+
+ gpiosim_set_pull sim0 1 pull-down
+ gpiosim_set_pull sim0 6 pull-up
+
+ dut_run gpioset --banner --by-name --chip ${GPIOSIM_CHIP_NAME[sim0]} 6=1 1=0
+
+ gpiosim_check_value sim0 1 1
+ gpiosim_check_value sim0 6 0
+}
+
+test_gpioset_interactive_after_SIGINT() {
+ gpiosim_chip sim0 num_lines=8 line_name=1:foo
+
+ dut_run gpioset -i foo=1
+
+ dut_kill -SIGINT
+ dut_wait
+
+ status_is 130
+}
+
+test_gpioset_interactive_after_SIGTERM() {
+ gpiosim_chip sim0 num_lines=8 line_name=1:foo
+
+ dut_run gpioset -i foo=1
+
+ dut_kill -SIGTERM
+ dut_wait
+
+ status_is 143
+}
+
+test_gpioset_with_no_arguments() {
+ run_tool gpioset
+
+ status_is 1
+ output_regex_match ".*at least one GPIO line value must be specified"
+}
+
+test_gpioset_with_chip_but_no_line_specified() {
+ gpiosim_chip sim0 num_lines=8
+
+ run_tool gpioset --chip ${GPIOSIM_CHIP_NAME[sim0]}
+
+ output_regex_match ".*at least one GPIO line value must be specified"
+ status_is 1
+}
+
+test_gpioset_with_offset_out_of_range() {
+ gpiosim_chip sim0 num_lines=4
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ run_tool gpioset --chip $sim0 0=1 1=1 2=1 3=1 4=1 5=1
+
+ output_regex_match ".*offset 4 is out of range on chip '$sim0'"
+ output_regex_match ".*offset 5 is out of range on chip '$sim0'"
+ status_is 1
+}
+
+test_gpioset_with_invalid_hold_period() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ run_tool gpioset --hold-period=bad --chip $sim0 0=1
+
+ output_regex_match ".*invalid period.*"
+ status_is 1
+}
+
+test_gpioset_with_invalid_value() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ # by name
+ run_tool gpioset --chip $sim0 0=c
+
+ output_regex_match ".*invalid line value.*"
+ status_is 1
+
+ # by value
+ run_tool gpioset --chip $sim0 0=3
+
+ output_regex_match ".*invalid line value.*"
+ status_is 1
+}
+
+test_gpioset_with_invalid_offset() {
+ gpiosim_chip sim0 num_lines=8
+
+ run_tool gpioset --chip ${GPIOSIM_CHIP_NAME[sim0]} 4000000000=0
+
+ output_regex_match ".*cannot find line '4000000000'"
+ status_is 1
+}
+
+test_gpioset_with_invalid_bias() {
+ gpiosim_chip sim0 num_lines=8
+
+ run_tool gpioset --bias=bad --chip ${GPIOSIM_CHIP_NAME[sim0]} 0=1 1=1
+
+ output_regex_match ".*invalid bias.*"
+ status_is 1
+}
+
+test_gpioset_with_invalid_drive() {
+ gpiosim_chip sim0 num_lines=8
+
+ run_tool gpioset --drive=bad --chip ${GPIOSIM_CHIP_NAME[sim0]} 0=1 1=1
+
+ output_regex_match ".*invalid drive.*"
+ status_is 1
+}
+
+test_gpioset_with_interactive_and_toggle() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ run_tool gpioset --interactive --toggle 1s --chip $sim0 0=1
+
+ output_regex_match ".*can't combine interactive with toggle"
+ status_is 1
+}
+
+test_gpioset_with_nonexistent_line() {
+ run_tool gpioset nonexistent-line=0
+
+ output_regex_match ".*cannot find line 'nonexistent-line'"
+ status_is 1
+}
+
+test_gpioset_with_same_line_twice() {
+ gpiosim_chip sim0 num_lines=8 line_name=1:foo
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ # by offset
+ run_tool gpioset --chip $sim0 0=1 0=1
+
+ output_regex_match ".*lines '0' and '0' are the same line"
+ status_is 1
+
+ # by name
+ run_tool gpioset --chip $sim0 foo=1 foo=1
+
+ output_regex_match ".*lines 'foo' and 'foo' are the same line"
+ status_is 1
+
+ # by name and offset
+ run_tool gpioset --chip $sim0 foo=1 1=1
+
+ output_regex_match ".*lines 'foo' and '1' are the same line"
+ status_is 1
+
+ # by offset and name
+ run_tool gpioset --chip $sim0 1=1 foo=1
+
+ output_regex_match ".*lines '1' and 'foo' are the same line"
+ status_is 1
+}
+
+#
+# gpiomon test cases
+#
+
+test_gpiomon_by_name() {
+ gpiosim_chip sim0 num_lines=8 line_name=4:foo
+
+ gpiosim_set_pull sim0 4 pull-up
+
+ dut_run gpiomon --banner --edges=rising foo
+ dut_flush
+
+ gpiosim_set_pull sim0 4 pull-down
+ gpiosim_set_pull sim0 4 pull-up
+ gpiosim_set_pull sim0 4 pull-down
+ dut_regex_match "[0-9]+\.[0-9]+\\s+rising\\s+\"foo\""
+ assert_fail dut_readable
+}
+
+test_gpiomon_by_offset() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ gpiosim_set_pull sim0 4 pull-up
+
+ dut_run gpiomon --banner --edges=rising --chip $sim0 4
+ dut_regex_match "Monitoring line .*"
+
+ gpiosim_set_pull sim0 4 pull-down
+ gpiosim_set_pull sim0 4 pull-up
+ gpiosim_set_pull sim0 4 pull-down
+ dut_regex_match "[0-9]+\.[0-9]+\\s+rising\\s+$sim0 4"
+ assert_fail dut_readable
+}
+
+test_gpiomon_by_symlink() {
+ gpiosim_chip sim0 num_lines=8
+ gpiosim_chip_symlink sim0 .
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ gpiosim_set_pull sim0 4 pull-up
+
+ dut_run gpiomon --banner --edges=rising --chip $GPIOSIM_CHIP_LINK 4
+ dut_regex_match "Monitoring line .*"
+
+ gpiosim_set_pull sim0 4 pull-down
+ gpiosim_set_pull sim0 4 pull-up
+ gpiosim_set_pull sim0 4 pull-down
+ dut_regex_match "[0-9]+\.[0-9]+\\s+rising\\s+$sim0\\s+4"
+ assert_fail dut_readable
+}
+
+
+test_gpiomon_by_chip_and_name() {
+ gpiosim_chip sim0 num_lines=8 line_name=0:foo
+ gpiosim_chip sim1 num_lines=8 line_name=2:foo
+
+ local sim1=${GPIOSIM_CHIP_NAME[sim1]}
+
+ gpiosim_set_pull sim1 0 pull-up
+
+ dut_run gpiomon --banner --edges=rising --chip $sim1 foo
+ dut_regex_match "Monitoring line .*"
+
+ gpiosim_set_pull sim1 2 pull-down
+ gpiosim_set_pull sim1 2 pull-up
+ gpiosim_set_pull sim1 2 pull-down
+ dut_regex_match "[0-9]+\.[0-9]+\\s+rising\\s+$sim1 2 \"foo\""
+ assert_fail dut_readable
+}
+
+test_gpiomon_first_matching_named_line() {
+ gpiosim_chip sim0 num_lines=4 line_name=1:foo line_name=2:bar \
+ line_name=3:foobar
+ gpiosim_chip sim1 num_lines=8 line_name=0:baz line_name=2:foobar \
+ line_name=4:xyz line_name=7:foobar
+ gpiosim_chip sim2 num_lines=16
+
+ dut_run gpiomon --banner foobar
+ dut_regex_match "Monitoring line .*"
+
+ gpiosim_set_pull sim0 3 pull-up
+ dut_regex_match "[0-9]+\.[0-9]+\\s+rising\\s+\"foobar\""
+ assert_fail dut_readable
+}
+
+test_gpiomon_rising_edge() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ gpiosim_set_pull sim0 4 pull-up
+
+ dut_run gpiomon --banner --edges=rising --chip $sim0 4
+ dut_flush
+
+ gpiosim_set_pull sim0 4 pull-down
+ gpiosim_set_pull sim0 4 pull-up
+ gpiosim_set_pull sim0 4 pull-down
+ dut_regex_match "[0-9]+\.[0-9]+\\s+rising\\s+$sim0 4"
+ assert_fail dut_readable
+}
+
+test_gpiomon_falling_edge() {
+ gpiosim_chip sim0 num_lines=8
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ gpiosim_set_pull sim0 4 pull-down
+
+ dut_run gpiomon --banner --edges=falling --chip $sim0 4
+ dut_flush
+
+ gpiosim_set_pull sim0 4 pull-up
+ gpiosim_set_pull sim0 4 pull-down
+ gpiosim_set_pull sim0 4 pull-up
+ dut_regex_match "[0-9]+\.[0-9]+\\s+falling\\s+$sim0 4"
+ assert_fail dut_readable
+}
+
+test_gpiomon_both_edges() {
+ gpiosim_chip sim0 num_lines=8
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpiomon --banner --edges=both --chip $sim0 4
+ dut_regex_match "Monitoring line .*"
+
+ gpiosim_set_pull sim0 4 pull-up
+ dut_regex_match "[0-9]+\.[0-9]+\\s+rising\\s+$sim0 4"
+
+ gpiosim_set_pull sim0 4 pull-down
+ dut_regex_match "[0-9]+\.[0-9]+\\s+falling\\s+$sim0 4"
+}
+
+test_gpiomon_with_pull_up() {
+ gpiosim_chip sim0 num_lines=8
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ gpiosim_set_pull sim0 4 pull-down
+
+ dut_run gpiomon --banner --bias=pull-up --chip $sim0 4
+ dut_flush
+
+ gpiosim_set_pull sim0 4 pull-down
+ dut_regex_match "[0-9]+\.[0-9]+\\s+falling\\s+$sim0 4"
+
+ assert_fail dut_readable
+}
+
+test_gpiomon_with_pull_down() {
+ gpiosim_chip sim0 num_lines=8
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ gpiosim_set_pull sim0 4 pull-up
+
+ dut_run gpiomon --banner --bias=pull-down --chip $sim0 4
+ dut_flush
+
+ gpiosim_set_pull sim0 4 pull-up
+
+ dut_regex_match "[0-9]+\.[0-9]+\\s+rising\\s+$sim0 4"
+
+ assert_fail dut_readable
+}
+
+test_gpiomon_with_active_low() {
+ gpiosim_chip sim0 num_lines=8
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ gpiosim_set_pull sim0 4 pull-up
+
+ dut_run gpiomon --banner --active-low --chip $sim0 4
+ dut_flush
+
+ gpiosim_set_pull sim0 4 pull-down
+ dut_regex_match "[0-9]+\.[0-9]+\\s+rising\\s+$sim0 4"
+
+ gpiosim_set_pull sim0 4 pull-up
+ dut_regex_match "[0-9]+\.[0-9]+\\s+falling\\s+$sim0 4"
+
+ assert_fail dut_readable
+}
+
+test_gpiomon_with_consumer() {
+ gpiosim_chip sim0 num_lines=4 line_name=1:foo line_name=2:bar
+ gpiosim_chip sim1 num_lines=8 line_name=3:baz line_name=4:xyz
+
+ dut_run gpiomon --banner --consumer gpio-tools-tests foo baz
+
+ run_tool gpioinfo
+
+ output_regex_match "\\s+line\\s+0:\\s+unnamed\\s+input"
+ output_regex_match \
+"\\s+line\\s+1:\\s+\"foo\"\\s+input edges=both consumer=\"gpio-tools-tests\""
+ output_regex_match \
+"\\s+line\\s+3:\\s+\"baz\"\\s+input edges=both consumer=\"gpio-tools-tests\""
+ status_is 0
+}
+
+test_gpiomon_with_quiet_mode() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpiomon --banner --edges=rising --quiet --chip $sim0 4
+ dut_flush
+
+ gpiosim_set_pull sim0 4 pull-up
+ assert_fail dut_readable
+}
+
+test_gpiomon_with_unquoted() {
+ gpiosim_chip sim0 num_lines=8 line_name=4:foo
+
+ gpiosim_set_pull sim0 4 pull-up
+
+ dut_run gpiomon --banner --unquoted --edges=rising foo
+ dut_flush
+
+ gpiosim_set_pull sim0 4 pull-down
+ gpiosim_set_pull sim0 4 pull-up
+ gpiosim_set_pull sim0 4 pull-down
+ dut_regex_match "[0-9]+\.[0-9]+\\s+rising\\s+foo"
+ assert_fail dut_readable
+}
+
+test_gpiomon_with_num_events() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ # redirect, as gpiomon exits after 4 events
+ dut_run_redirect gpiomon --num-events=4 --chip $sim0 4
+
+ gpiosim_set_pull sim0 4 pull-up
+ sleep 0.01
+ gpiosim_set_pull sim0 4 pull-down
+ sleep 0.01
+ gpiosim_set_pull sim0 4 pull-up
+ sleep 0.01
+ gpiosim_set_pull sim0 4 pull-down
+ sleep 0.01
+
+ dut_wait
+ status_is 0
+ dut_read_redirect
+
+ regex_matches "[0-9]+\.[0-9]+\\s+rising\\s+$sim0 4" "${lines[0]}"
+ regex_matches "[0-9]+\.[0-9]+\\s+falling\\s+$sim0 4" "${lines[1]}"
+ regex_matches "[0-9]+\.[0-9]+\\s+rising\\s+$sim0 4" "${lines[2]}"
+ regex_matches "[0-9]+\.[0-9]+\\s+falling\\s+$sim0 4" "${lines[3]}"
+ num_lines_is 4
+}
+
+test_gpiomon_with_debounce_period() {
+ gpiosim_chip sim0 num_lines=4 line_name=1:foo line_name=2:bar
+ gpiosim_chip sim1 num_lines=8 line_name=3:baz line_name=4:xyz
+
+ dut_run gpiomon --banner --debounce-period 123us foo baz
+
+ run_tool gpioinfo
+
+ output_regex_match "\\s+line\\s+0:\\s+unnamed\\s+input"
+ output_regex_match \
+"\\s+line\\s+1:\\s+\"foo\"\\s+input edges=both debounce-period=123us"
+ output_regex_match \
+"\\s+line\\s+3:\\s+\"baz\"\\s+input edges=both debounce-period=123us"
+ status_is 0
+}
+
+test_gpiomon_with_idle_timeout() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ # redirect, as gpiomon exits
+ dut_run_redirect gpiomon --idle-timeout 10ms --chip $sim0 4
+
+ dut_wait
+ status_is 0
+ dut_read_redirect
+ num_lines_is 0
+}
+
+test_gpiomon_multiple_lines() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpiomon --banner --format=%o --chip $sim0 1 3 2 5 4
+ dut_regex_match "Monitoring lines .*"
+
+ gpiosim_set_pull sim0 2 pull-up
+ dut_regex_match "2"
+ gpiosim_set_pull sim0 3 pull-up
+ dut_regex_match "3"
+ gpiosim_set_pull sim0 4 pull-up
+ dut_regex_match "4"
+
+ assert_fail dut_readable
+}
+
+test_gpiomon_multiple_lines_by_name_and_offset() {
+ gpiosim_chip sim0 num_lines=4 line_name=1:foo line_name=2:bar
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpiomon --banner --format=%o --chip $sim0 foo bar 3
+ dut_regex_match "Monitoring lines .*"
+
+ gpiosim_set_pull sim0 2 pull-up
+ dut_regex_match "2"
+ gpiosim_set_pull sim0 3 pull-up
+ dut_regex_match "3"
+ gpiosim_set_pull sim0 1 pull-up
+ dut_regex_match "1"
+
+ assert_fail dut_readable
+}
+
+test_gpiomon_multiple_lines_across_multiple_chips() {
+ gpiosim_chip sim0 num_lines=4 line_name=1:foo line_name=2:bar
+ gpiosim_chip sim1 num_lines=8 line_name=0:baz line_name=4:xyz
+
+ dut_run gpiomon --banner --format=%l foo bar baz
+ dut_regex_match "Monitoring lines .*"
+
+ gpiosim_set_pull sim0 2 pull-up
+ dut_regex_match "bar"
+ gpiosim_set_pull sim1 0 pull-up
+ dut_regex_match "baz"
+ gpiosim_set_pull sim0 1 pull-up
+ dut_regex_match "foo"
+
+ assert_fail dut_readable
+}
+
+test_gpiomon_exit_after_SIGINT() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpiomon --banner --chip $sim0 4
+ dut_regex_match "Monitoring line .*"
+
+ dut_kill -SIGINT
+ dut_wait
+
+ status_is 130
+}
+
+test_gpiomon_exit_after_SIGTERM() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpiomon --banner --chip $sim0 4
+ dut_regex_match "Monitoring line .*"
+
+ dut_kill -SIGTERM
+ dut_wait
+
+ status_is 143
+}
+
+test_gpiomon_with_nonexistent_line() {
+ run_tool gpiomon nonexistent-line
+
+ status_is 1
+ output_regex_match ".*cannot find line 'nonexistent-line'"
+}
+
+test_gpiomon_with_same_line_twice() {
+ gpiosim_chip sim0 num_lines=8 line_name=1:foo
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ # by offset
+ run_tool gpiomon --chip $sim0 0 0
+
+ output_regex_match ".*lines '0' and '0' are the same line"
+ status_is 1
+
+ # by name
+ run_tool gpiomon foo foo
+
+ output_regex_match ".*lines 'foo' and 'foo' are the same line"
+ status_is 1
+
+ # by name and offset
+ run_tool gpiomon --chip $sim0 1 foo
+
+ output_regex_match ".*lines '1' and 'foo' are the same line"
+ status_is 1
+}
+
+test_gpiomon_with_strict_named_line_check() {
+ gpiosim_chip sim0 num_lines=4 line_name=1:foo line_name=2:bar \
+ line_name=3:foobar
+ gpiosim_chip sim1 num_lines=8 line_name=0:baz line_name=2:foobar \
+ line_name=4:xyz line_name=7:foobar
+ gpiosim_chip sim2 num_lines=16
+
+ run_tool gpiomon --strict foobar
+
+ output_regex_match ".*line 'foobar' is not unique"
+ status_is 1
+}
+test_gpiomon_with_lines_by_offset() {
+ # not suggesting this setup makes any sense
+ # - just test that we can deal with it
+ gpiosim_chip sim0 num_lines=8 line_name=1:6 line_name=6:1
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ gpiosim_set_pull sim0 1 pull-up
+
+ dut_run gpiomon --banner --chip $sim0 6 1
+ dut_flush
+
+ gpiosim_set_pull sim0 1 pull-down
+ dut_regex_match "[0-9]+\.[0-9]+\\s+falling\\s+$sim0 1"
+
+ gpiosim_set_pull sim0 1 pull-up
+ dut_regex_match "[0-9]+\.[0-9]+\\s+rising\\s+$sim0 1"
+
+ gpiosim_set_pull sim0 6 pull-up
+ dut_regex_match "[0-9]+\.[0-9]+\\s+rising\\s+$sim0 6"
+
+ gpiosim_set_pull sim0 6 pull-down
+ dut_regex_match "[0-9]+\.[0-9]+\\s+falling\\s+$sim0 6"
+
+ assert_fail dut_readable
+}
+
+test_gpiomon_with_lines_strictly_by_name() {
+ # not suggesting this setup makes sense
+ # - just test that we can deal with it
+ gpiosim_chip sim0 num_lines=8 line_name=1:42 line_name=6:13
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ gpiosim_set_pull sim0 1 pull-up
+
+ dut_run gpiomon --banner --by-name --chip $sim0 42 13
+ dut_flush
+
+ gpiosim_set_pull sim0 1 pull-down
+ dut_regex_match "[0-9]+\.[0-9]+\\s+falling\\s+$sim0 1"
+
+ gpiosim_set_pull sim0 1 pull-up
+ dut_regex_match "[0-9]+\.[0-9]+\\s+rising\\s+$sim0 1"
+
+ gpiosim_set_pull sim0 6 pull-up
+ dut_regex_match "[0-9]+\.[0-9]+\\s+rising\\s+$sim0 6"
+
+ gpiosim_set_pull sim0 6 pull-down
+ dut_regex_match "[0-9]+\.[0-9]+\\s+falling\\s+$sim0 6"
+
+ assert_fail dut_readable
+}
+
+test_gpiomon_with_no_arguments() {
+ run_tool gpiomon
+
+ output_regex_match ".*at least one GPIO line must be specified"
+ status_is 1
+}
+
+test_gpiomon_with_no_line_specified() {
+ gpiosim_chip sim0 num_lines=8
+
+ run_tool gpiomon --chip ${GPIOSIM_CHIP_NAME[sim0]}
+
+ output_regex_match ".*at least one GPIO line must be specified"
+ status_is 1
+}
+
+test_gpiomon_with_offset_out_of_range() {
+ gpiosim_chip sim0 num_lines=4
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ run_tool gpiomon --chip $sim0 5
+
+ output_regex_match ".*offset 5 is out of range on chip '$sim0'"
+ status_is 1
+}
+
+test_gpiomon_with_invalid_bias() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ run_tool gpiomon --bias=bad -c $sim0 0 1
+
+ output_regex_match ".*invalid bias.*"
+ status_is 1
+}
+
+test_gpiomon_with_invalid_debounce_period() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ run_tool gpiomon --debounce-period bad -c $sim0 0 1
+
+ output_regex_match ".*invalid period: bad"
+ status_is 1
+}
+
+test_gpiomon_with_invalid_idle_timeout() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ run_tool gpiomon --idle-timeout bad -c $sim0 0 1
+
+ output_regex_match ".*invalid period: bad"
+ status_is 1
+}
+
+test_gpiomon_with_custom_format_event_type_offset() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpiomon --banner "--format=%e %o" -c $sim0 4
+ dut_flush
+
+ gpiosim_set_pull sim0 4 pull-up
+ dut_read
+ output_is "1 4"
+}
+
+test_gpiomon_with_custom_format_event_type_offset_joined() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpiomon --banner "--format=%e%o" -c $sim0 4
+ dut_flush
+
+ gpiosim_set_pull sim0 4 pull-up
+ dut_read
+ output_is "14"
+}
+
+test_gpiomon_with_custom_format_edge_chip_and_line() {
+ gpiosim_chip sim0 num_lines=8 line_name=4:baz
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpiomon --banner "--format=%e %o %E %c %l" -c $sim0 baz
+ dut_flush
+
+ gpiosim_set_pull sim0 4 pull-up
+ dut_regex_match "1 4 rising $sim0 baz"
+}
+
+test_gpiomon_with_custom_format_seconds_timestamp() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpiomon --banner "--format=%e %o %S" -c $sim0 4
+ dut_flush
+
+ gpiosim_set_pull sim0 4 pull-up
+ dut_regex_match "1 4 [0-9]+\\.[0-9]+"
+}
+
+test_gpiomon_with_custom_format_UTC_timestamp() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpiomon --banner "--format=%U %e %o " --event-clock=realtime \
+ -c $sim0 4
+ dut_flush
+
+ gpiosim_set_pull sim0 4 pull-up
+ dut_regex_match \
+"[0-9][0-9][0-9][0-9]-[0-1][0-9]-[0-3][0-9]T[0-2][0-9]:[0-5][0-9]:[0-5][0-9]\\.[0-9]+Z 1 4"
+}
+
+test_gpiomon_with_custom_format_localtime_timestamp() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpiomon --banner "--format=%L %e %o" --event-clock=realtime \
+ -c $sim0 4
+ dut_flush
+
+ gpiosim_set_pull sim0 4 pull-up
+ dut_regex_match \
+"[0-9][0-9][0-9][0-9]-[0-1][0-9]-[0-3][0-9]T[0-2][0-9]:[0-5][0-9]:[0-5][0-9]\\.[0-9]+ 1 4"
+}
+
+test_gpiomon_with_custom_format_double_percent_sign() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpiomon --banner "--format=start%%end" -c $sim0 4
+ dut_flush
+
+ gpiosim_set_pull sim0 4 pull-up
+ dut_read
+ output_is "start%end"
+}
+
+test_gpiomon_with_custom_format_double_percent_sign_event_type_specifier() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpiomon --banner "--format=%%e" -c $sim0 4
+ dut_flush
+
+ gpiosim_set_pull sim0 4 pull-up
+ dut_read
+ output_is "%e"
+}
+
+test_gpiomon_with_custom_format_single_percent_sign() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpiomon --banner "--format=%" -c $sim0 4
+ dut_flush
+
+ gpiosim_set_pull sim0 4 pull-up
+ dut_read
+ output_is "%"
+}
+
+test_gpiomon_with_custom_format_single_percent_sign_between_other_characters() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpiomon --banner "--format=foo % bar" -c $sim0 4
+ dut_flush
+
+ gpiosim_set_pull sim0 4 pull-up
+ dut_read
+ output_is "foo % bar"
+}
+
+test_gpiomon_with_custom_format_unknown_specifier() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpiomon --banner "--format=%x" -c $sim0 4
+ dut_flush
+
+ gpiosim_set_pull sim0 4 pull-up
+ dut_read
+ output_is "%x"
+}
+
+#
+# gpionotify test cases
+#
+
+test_gpionotify_by_name() {
+ gpiosim_chip sim0 num_lines=8 line_name=4:foo
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpionotify --banner foo
+ dut_regex_match "Watching line .*"
+
+ request_release_line $sim0 4
+
+ dut_regex_match "[0-9]+\.[0-9]+\\s+requested\\s+\"foo\""
+ dut_regex_match "[0-9]+\.[0-9]+\\s+released\\s+\"foo\""
+ # tools currently have no way to generate a reconfig event
+}
+
+test_gpionotify_by_offset() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpionotify --banner --chip $sim0 4
+ dut_regex_match "Watching line .*"
+
+ request_release_line $sim0 4
+ dut_regex_match "[0-9]+\.[0-9]+\\s+requested\\s+$sim0 4"
+ dut_regex_match "[0-9]+\.[0-9]+\\s+released\\s+$sim0 4"
+
+ assert_fail dut_readable
+}
+
+test_gpionotify_by_symlink() {
+ gpiosim_chip sim0 num_lines=8
+ gpiosim_chip_symlink sim0 .
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpionotify --banner --chip $GPIOSIM_CHIP_LINK 4
+ dut_regex_match "Watching line .*"
+
+ request_release_line $sim0 4
+ dut_regex_match "[0-9]+\.[0-9]+\\s+requested\\s+$sim0\\s+4"
+ dut_regex_match "[0-9]+\.[0-9]+\\s+released\\s+$sim0\\s+4"
+
+ assert_fail dut_readable
+}
+
+test_gpionotify_by_chip_and_name() {
+ gpiosim_chip sim0 num_lines=8 line_name=4:foo
+ gpiosim_chip sim1 num_lines=8 line_name=2:foo
+
+ local sim1=${GPIOSIM_CHIP_NAME[sim1]}
+
+ dut_run gpionotify --banner --chip $sim1 foo
+ dut_regex_match "Watching line .*"
+
+ request_release_line $sim1 2
+ dut_regex_match "[0-9]+\.[0-9]+\\s+requested\\s+$sim1 2 \"foo\""
+ dut_regex_match "[0-9]+\.[0-9]+\\s+released\\s+$sim1 2 \"foo\""
+
+ assert_fail dut_readable
+}
+
+test_gpionotify_first_matching_named_line() {
+ gpiosim_chip sim0 num_lines=4 line_name=1:foo line_name=2:bar \
+ line_name=3:foobar
+ gpiosim_chip sim1 num_lines=8 line_name=0:baz line_name=2:foobar \
+ line_name=4:xyz line_name=7:foobar
+ gpiosim_chip sim2 num_lines=16
+
+ dut_run gpionotify --banner foobar
+ dut_regex_match "Watching line .*"
+
+ request_release_line ${GPIOSIM_CHIP_NAME[sim0]} 3
+ dut_regex_match "[0-9]+\.[0-9]+\\s+requested\\s+\"foobar\""
+ dut_regex_match "[0-9]+\.[0-9]+\\s+released\\s+\"foobar\""
+
+ assert_fail dut_readable
+}
+
+test_gpionotify_with_requested() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ gpiosim_set_pull sim0 4 pull-up
+
+ dut_run gpionotify --banner --event=requested --chip $sim0 4
+ dut_flush
+
+ request_release_line ${GPIOSIM_CHIP_NAME[sim0]} 4
+ dut_regex_match "[0-9]+\.[0-9]+\\s+requested\\s+$sim0 4"
+ assert_fail dut_readable
+}
+
+test_gpionotify_with_released() {
+ gpiosim_chip sim0 num_lines=8
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ gpiosim_set_pull sim0 4 pull-down
+
+ dut_run gpionotify --banner --event=released --chip $sim0 4
+ dut_flush
+
+ request_release_line ${GPIOSIM_CHIP_NAME[sim0]} 4
+ dut_regex_match "[0-9]+\.[0-9]+\\s+released\\s+$sim0 4"
+ assert_fail dut_readable
+}
+
+test_gpionotify_with_quiet_mode() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpionotify --banner --quiet --chip $sim0 4
+ dut_flush
+
+ request_release_line ${GPIOSIM_CHIP_NAME[sim0]} 4
+ assert_fail dut_readable
+}
+
+test_gpionotify_with_unquoted() {
+ gpiosim_chip sim0 num_lines=8 line_name=4:foo
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpionotify --banner --unquoted foo
+ dut_regex_match "Watching line .*"
+
+ request_release_line $sim0 4
+
+ dut_regex_match "[0-9]+\.[0-9]+\\s+requested\\s+foo"
+ dut_regex_match "[0-9]+\.[0-9]+\\s+released\\s+foo"
+}
+
+test_gpionotify_with_num_events() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ # redirect, as gpionotify exits after 4 events
+ dut_run_redirect gpionotify --num-events=4 --chip $sim0 3 4
+
+
+ request_release_line ${GPIOSIM_CHIP_NAME[sim0]} 4
+ request_release_line ${GPIOSIM_CHIP_NAME[sim0]} 3
+
+ dut_wait
+ status_is 0
+ dut_read_redirect
+
+ regex_matches "[0-9]+\.[0-9]+\\s+requested\\s+$sim0 4" "${lines[0]}"
+ regex_matches "[0-9]+\.[0-9]+\\s+released\\s+$sim0 4" "${lines[1]}"
+ regex_matches "[0-9]+\.[0-9]+\\s+requested\\s+$sim0 3" "${lines[2]}"
+ regex_matches "[0-9]+\.[0-9]+\\s+released\\s+$sim0 3" "${lines[3]}"
+ num_lines_is 4
+}
+
+test_gpionotify_with_idle_timeout() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ # redirect, as gpionotify exits
+ dut_run_redirect gpionotify --idle-timeout 10ms --chip $sim0 3 4
+
+ dut_wait
+ status_is 0
+ dut_read_redirect
+
+ num_lines_is 0
+}
+
+test_gpionotify_multiple_lines() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpionotify --banner --chip $sim0 1 2 3 4 5
+ dut_regex_match "Watching lines .*"
+
+ request_release_line $sim0 2
+ dut_regex_match "[0-9]+\.[0-9]+\\s+requested\\s+$sim0 2"
+ dut_regex_match "[0-9]+\.[0-9]+\\s+released\\s+$sim0 2"
+
+ request_release_line $sim0 3
+ dut_regex_match "[0-9]+\.[0-9]+\\s+requested\\s+$sim0 3"
+ dut_regex_match "[0-9]+\.[0-9]+\\s+released\\s+$sim0 3"
+
+ request_release_line $sim0 4
+ dut_regex_match "[0-9]+\.[0-9]+\\s+requested\\s+$sim0 4"
+ dut_regex_match "[0-9]+\.[0-9]+\\s+released\\s+$sim0 4"
+
+ assert_fail dut_readable
+}
+
+test_gpionotify_multiple_lines_by_name_and_offset() {
+ gpiosim_chip sim0 num_lines=4 line_name=1:foo line_name=2:bar
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpionotify --banner --chip $sim0 bar foo 3
+ dut_regex_match "Watching lines .*"
+
+ request_release_line $sim0 2
+ dut_regex_match "[0-9]+\.[0-9]+\\s+requested\\s+$sim0 2\\s+\"bar\""
+ dut_regex_match "[0-9]+\.[0-9]+\\s+released\\s+$sim0 2\\s+\"bar\""
+
+ request_release_line $sim0 1
+ dut_regex_match "[0-9]+\.[0-9]+\\s+requested\\s+$sim0 1\\s+\"foo\""
+ dut_regex_match "[0-9]+\.[0-9]+\\s+released\\s+$sim0 1\\s+\"foo\""
+
+ request_release_line $sim0 3
+ dut_regex_match "[0-9]+\.[0-9]+\\s+requested\\s+$sim0 3"
+ dut_regex_match "[0-9]+\.[0-9]+\\s+released\\s+$sim0 3"
+
+ assert_fail dut_readable
+}
+
+test_gpionotify_multiple_lines_across_multiple_chips() {
+ gpiosim_chip sim0 num_lines=4 line_name=1:foo line_name=2:bar
+ gpiosim_chip sim1 num_lines=8 line_name=0:baz line_name=4:xyz
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+ local sim1=${GPIOSIM_CHIP_NAME[sim1]}
+
+ dut_run gpionotify --banner baz bar foo xyz
+ dut_regex_match "Watching lines .*"
+
+ request_release_line $sim0 2
+ dut_regex_match "[0-9]+\.[0-9]+\\s+requested\\s+\"bar\""
+ dut_regex_match "[0-9]+\.[0-9]+\\s+released\\s+\"bar\""
+
+ request_release_line $sim0 1
+ dut_regex_match "[0-9]+\.[0-9]+\\s+requested\\s+\"foo\""
+ dut_regex_match "[0-9]+\.[0-9]+\\s+released\\s+\"foo\""
+
+ request_release_line $sim1 4
+ dut_regex_match "[0-9]+\.[0-9]+\\s+requested\\s+\"xyz\""
+ dut_regex_match "[0-9]+\.[0-9]+\\s+released\\s+\"xyz\""
+
+ request_release_line $sim1 0
+ dut_regex_match "[0-9]+\.[0-9]+\\s+requested\\s+\"baz\""
+ dut_regex_match "[0-9]+\.[0-9]+\\s+released\\s+\"baz\""
+
+ assert_fail dut_readable
+}
+
+test_gpionotify_exit_after_SIGINT() {
+ gpiosim_chip sim0 num_lines=8
+
+ dut_run gpionotify --banner --chip ${GPIOSIM_CHIP_NAME[sim0]} 4
+ dut_regex_match "Watching line .*"
+
+ dut_kill -SIGINT
+ dut_wait
+
+ status_is 130
+}
+
+test_gpionotify_exit_after_SIGTERM() {
+ gpiosim_chip sim0 num_lines=8
+
+ dut_run gpionotify --banner --chip ${GPIOSIM_CHIP_NAME[sim0]} 4
+ dut_regex_match "Watching line .*"
+
+ dut_kill -SIGTERM
+ dut_wait
+
+ status_is 143
+}
+
+test_gpionotify_with_nonexistent_line() {
+ run_tool gpionotify nonexistent-line
+
+ status_is 1
+ output_regex_match ".*cannot find line 'nonexistent-line'"
+}
+
+test_gpionotify_with_same_line_twice() {
+ gpiosim_chip sim0 num_lines=8 line_name=1:foo
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ # by offset
+ run_tool gpionotify --chip $sim0 0 0
+
+ output_regex_match ".*lines '0' and '0' are the same line"
+ num_lines_is 1
+ status_is 1
+
+ # by name
+ run_tool gpionotify foo foo
+
+ output_regex_match ".*lines 'foo' and 'foo' are the same line"
+ num_lines_is 1
+ status_is 1
+
+ # by name and offset
+ run_tool gpionotify --chip $sim0 1 foo
+
+ output_regex_match ".*lines '1' and 'foo' are the same line"
+ num_lines_is 1
+ status_is 1
+}
+
+test_gpionotify_with_strict_named_line_check() {
+ gpiosim_chip sim0 num_lines=4 line_name=1:foo line_name=2:bar \
+ line_name=3:foobar
+ gpiosim_chip sim1 num_lines=8 line_name=0:baz line_name=2:foobar \
+ line_name=4:xyz line_name=7:foobar
+ gpiosim_chip sim2 num_lines=16
+
+ run_tool gpionotify --strict foobar
+
+ output_regex_match ".*line 'foobar' is not unique"
+ status_is 1
+}
+
+test_gpionotify_with_lines_by_offset() {
+ # not suggesting this setup makes any sense
+ # - just test that we can deal with it
+ gpiosim_chip sim0 num_lines=8 line_name=1:6 line_name=6:1
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpionotify --banner --chip $sim0 1
+ dut_flush
+
+ request_release_line $sim0 1
+ dut_regex_match "[0-9]+\.[0-9]+\\s+requested\\s+$sim0 1"
+ dut_regex_match "[0-9]+\.[0-9]+\\s+released\\s+$sim0 1"
+
+ request_release_line $sim0 6
+
+ assert_fail dut_readable
+}
+
+test_gpionotify_with_lines_strictly_by_name() {
+ # not suggesting this setup makes any sense
+ # - just test that we can deal with it
+ gpiosim_chip sim0 num_lines=8 line_name=1:6 line_name=6:1
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpionotify --banner --by-name --chip $sim0 1
+ dut_flush
+
+ request_release_line $sim0 6
+ dut_regex_match "[0-9]+\.[0-9]+\\s+requested\\s+$sim0 6 \"1\""
+ dut_regex_match "[0-9]+\.[0-9]+\\s+released\\s+$sim0 6 \"1\""
+
+ request_release_line $sim0 1
+ assert_fail dut_readable
+}
+
+test_gpionotify_with_no_arguments() {
+ run_tool gpionotify
+
+ output_regex_match ".*at least one GPIO line must be specified"
+ status_is 1
+}
+
+test_gpionotify_with_no_line_specified() {
+ gpiosim_chip sim0 num_lines=8
+
+ run_tool gpionotify --chip ${GPIOSIM_CHIP_NAME[sim0]}
+
+ output_regex_match ".*at least one GPIO line must be specified"
+ status_is 1
+}
+
+test_gpionotify_with_offset_out_of_range() {
+ gpiosim_chip sim0 num_lines=4
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ run_tool gpionotify --chip $sim0 5
+
+ output_regex_match ".*offset 5 is out of range on chip '$sim0'"
+ status_is 1
+}
+
+test_gpionotify_with_invalid_idle_timeout() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ run_tool gpionotify --idle-timeout bad -c $sim0 0 1
+
+ output_regex_match ".*invalid period: bad"
+ status_is 1
+}
+
+test_gpionotify_with_custom_format_event_type_offset() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpionotify --banner --event=requested "--format=%e %o" -c $sim0 4
+ dut_flush
+
+ request_release_line $sim0 4
+ dut_read
+ output_is "1 4"
+}
+
+test_gpionotify_with_custom_format_event_type_offset_joined() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpionotify --banner --event=requested "--format=%e%o" -c $sim0 4
+ dut_flush
+
+ request_release_line $sim0 4
+ dut_read
+ output_is "14"
+}
+
+test_gpionotify_with_custom_format_event_chip_and_line() {
+ gpiosim_chip sim0 num_lines=8 line_name=4:baz
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpionotify --banner --event=released \
+ "--format=%e %o %E %c %l" -c $sim0 baz
+ dut_flush
+
+ request_release_line $sim0 4
+ dut_regex_match "2 4 released $sim0 baz"
+}
+
+test_gpionotify_with_custom_format_seconds_timestamp() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpionotify --banner --event=requested "--format=%e %o %S" \
+ -c $sim0 4
+ dut_flush
+
+ request_release_line $sim0 4
+ dut_regex_match "1 4 [0-9]+\\.[0-9]+"
+}
+
+test_gpionotify_with_custom_format_UTC_timestamp() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpionotify --banner --event=released \
+ "--format=%U %e %o" -c $sim0 4
+ dut_flush
+
+ request_release_line $sim0 4
+ dut_regex_match \
+"[0-9][0-9][0-9][0-9]-[0-1][0-9]-[0-3][0-9]T[0-2][0-9]:[0-5][0-9]:[0-5][0-9]\\.[0-9]+Z 2 4"
+}
+
+test_gpionotify_with_custom_format_localtime_timestamp() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpionotify --banner --event=released \
+ "--format=%L %e %o" -c $sim0 4
+ dut_flush
+
+ request_release_line $sim0 4
+ dut_regex_match \
+"[0-9][0-9][0-9][0-9]-[0-1][0-9]-[0-3][0-9]T[0-2][0-9]:[0-5][0-9]:[0-5][0-9]\\.[0-9]+ 2 4"
+}
+
+test_gpionotify_with_custom_format_double_percent_sign() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpionotify --banner --event=requested "--format=start%%end" \
+ -c $sim0 4
+ dut_flush
+
+ request_release_line $sim0 4
+ dut_read
+ output_is "start%end"
+}
+
+test_gpionotify_with_custom_format_double_percent_sign_event_type_specifier() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpionotify --banner --event=requested "--format=%%e" -c $sim0 4
+ dut_flush
+
+ request_release_line $sim0 4
+ dut_read
+ output_is "%e"
+}
+
+test_gpionotify_with_custom_format_single_percent_sign() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpionotify --banner --event=requested "--format=%" -c $sim0 4
+ dut_flush
+
+ request_release_line $sim0 4
+ dut_read
+ output_is "%"
+}
+
+test_gpionotify_with_custom_format_single_percent_sign_between_other_characters() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpionotify --banner --event=requested "--format=foo % bar" -c $sim0 4
+ dut_flush
+
+ request_release_line $sim0 4
+ dut_read
+ output_is "foo % bar"
+}
+
+test_gpionotify_with_custom_format_unknown_specifier() {
+ gpiosim_chip sim0 num_lines=8
+
+ local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+ dut_run gpionotify --banner --event=requested "--format=%x" -c $sim0 4
+ dut_flush
+
+ request_release_line $sim0 4
+ dut_read
+ output_is "%x"
+}
+
+die() {
+ echo "$@" 1>&2
+ exit 1
+}
+
+# Must be done after we sources shunit2 as we need SHUNIT_VERSION to be set.
+oneTimeSetUp() {
+ test "$SHUNIT_VERSION" = "$MIN_SHUNIT_VERSION" && return 0
+ local FIRST=$(printf "$SHUNIT_VERSION\n$MIN_SHUNIT_VERSION\n" | sort -Vr | head -1)
+ test "$FIRST" = "$MIN_SHUNIT_VERSION" && \
+ die "minimum shunit version required is $MIN_SHUNIT_VERSION (current version is $SHUNIT_VERSION"
+}
+
+check_kernel() {
+ local REQUIRED=$1
+ local CURRENT=$(uname -r)
+
+ SORTED=$(printf "$REQUIRED\n$CURRENT" | sort -V | head -n 1)
+
+ if [ "$SORTED" != "$REQUIRED" ]
+ then
+ die "linux kernel version must be at least: v$REQUIRED - got: v$CURRENT"
+ fi
+}
+
+check_prog() {
+ local PROG=$1
+
+ which "$PROG" > /dev/null
+ if [ "$?" -ne "0" ]
+ then
+ die "$PROG not found - needed to run the tests"
+ fi
+}
+
+# Check all required non-coreutils tools
+check_prog shunit2
+check_prog modprobe
+check_prog timeout
+check_prog grep
+
+# Check if we're running a kernel at the required version or later
+check_kernel $MIN_KERNEL_VERSION
+
+modprobe gpio-sim || die "unable to load the gpio-sim module"
+mountpoint /sys/kernel/config/ > /dev/null 2> /dev/null || \
+ die "configfs not mounted at /sys/kernel/config/"
+
+. shunit2
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+// SPDX-FileCopyrightText: 2022 Kent Gibson <warthog618@gmail.com>
+
+#include <getopt.h>
+#include <gpiod.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "tools-common.h"
+
+static void print_help(void)
+{
+ printf("Usage: %s [OPTIONS] [chip]...\n", get_prog_name());
+ printf("\n");
+ printf("List GPIO chips, print their labels and number of GPIO lines.\n");
+ printf("\n");
+ printf("Chips may be identified by number, name, or path.\n");
+ printf("e.g. '0', 'gpiochip0', and '/dev/gpiochip0' all refer to the same chip.\n");
+ printf("\n");
+ printf("If no chips are specified then all chips are listed.\n");
+ printf("\n");
+ printf("Options:\n");
+ printf(" -h, --help\t\tdisplay this help and exit\n");
+ printf(" -v, --version\t\toutput version information and exit\n");
+}
+
+static int parse_config(int argc, char **argv)
+{
+ static const struct option longopts[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'v' },
+ { GETOPT_NULL_LONGOPT },
+ };
+
+ static const char *const shortopts = "+hv";
+
+ int optc, opti;
+
+ for (;;) {
+ optc = getopt_long(argc, argv, shortopts, longopts, &opti);
+ if (optc < 0)
+ break;
+
+ switch (optc) {
+ case 'h':
+ print_help();
+ exit(EXIT_SUCCESS);
+ case 'v':
+ print_version();
+ exit(EXIT_SUCCESS);
+ case '?':
+ die("try %s --help", get_prog_name());
+ default:
+ abort();
+ }
+ }
+ return optind;
+}
+
+static int print_chip_info(const char *path)
+{
+ struct gpiod_chip_info *info;
+ struct gpiod_chip *chip;
+
+ chip = gpiod_chip_open(path);
+ if (!chip) {
+ print_perror("unable to open chip '%s'", path);
+ return 1;
+ }
+
+ info = gpiod_chip_get_info(chip);
+ if (!info)
+ die_perror("unable to read info for '%s'", path);
+
+ printf("%s [%s] (%zu lines)\n", gpiod_chip_info_get_name(info),
+ gpiod_chip_info_get_label(info),
+ gpiod_chip_info_get_num_lines(info));
+
+ gpiod_chip_info_free(info);
+ gpiod_chip_close(chip);
+
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ int num_chips, i, ret = EXIT_SUCCESS;
+ char **paths, *path;
+
+ set_prog_name(argv[0]);
+ i = parse_config(argc, argv);
+ argc -= i;
+ argv += i;
+
+ if (argc == 0) {
+ num_chips = all_chip_paths(&paths);
+ for (i = 0; i < num_chips; i++) {
+ if (print_chip_info(paths[i]))
+ ret = EXIT_FAILURE;
+
+ free(paths[i]);
+ }
+
+ free(paths);
+ }
+
+ for (i = 0; i < argc; i++) {
+ if (chip_path_lookup(argv[i], &path)) {
+ if (print_chip_info(path))
+ ret = EXIT_FAILURE;
+
+ free(path);
+ } else {
+ print_error(
+ "cannot find GPIO chip character device '%s'",
+ argv[i]);
+ ret = EXIT_FAILURE;
+ }
+ }
+
+ return ret;
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+// SPDX-FileCopyrightText: 2022 Kent Gibson <warthog618@gmail.com>
+
+#include <getopt.h>
+#include <gpiod.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "tools-common.h"
+
+struct config {
+ bool active_low;
+ bool by_name;
+ bool numeric;
+ bool strict;
+ bool unquoted;
+ enum gpiod_line_bias bias;
+ enum gpiod_line_direction direction;
+ unsigned int hold_period_us;
+ const char *chip_id;
+ const char *consumer;
+};
+
+static void print_help(void)
+{
+ printf("Usage: %s [OPTIONS] <line>...\n", get_prog_name());
+ printf("\n");
+ printf("Read values of GPIO lines.\n");
+ printf("\n");
+ printf("Lines are specified by name, or optionally by offset if the chip option\n");
+ printf("is provided.\n");
+ printf("\n");
+ printf("Options:\n");
+ printf(" -a, --as-is\t\tleave the line direction unchanged, not forced to input\n");
+ print_bias_help();
+ printf(" --by-name\t\ttreat lines as names even if they would parse as an offset\n");
+ printf(" -c, --chip <chip>\trestrict scope to a particular chip\n");
+ printf(" -C, --consumer <name>\tconsumer name applied to requested lines (default is 'gpioget')\n");
+ printf(" -h, --help\t\tdisplay this help and exit\n");
+ printf(" -l, --active-low\ttreat the line as active low\n");
+ printf(" -p, --hold-period <period>\n");
+ printf("\t\t\twait between requesting the lines and reading the values\n");
+ printf(" --numeric\t\tdisplay line values as '0' (inactive) or '1' (active)\n");
+ printf(" -s, --strict\t\tabort if requested line names are not unique\n");
+ printf(" --unquoted\tdon't quote line names\n");
+ printf(" -v, --version\t\toutput version information and exit\n");
+ print_chip_help();
+ print_period_help();
+}
+
+static int parse_config(int argc, char **argv, struct config *cfg)
+{
+ static const struct option longopts[] = {
+ { "active-low", no_argument, NULL, 'l' },
+ { "as-is", no_argument, NULL, 'a' },
+ { "bias", required_argument, NULL, 'b' },
+ { "by-name", no_argument, NULL, 'B' },
+ { "chip", required_argument, NULL, 'c' },
+ { "consumer", required_argument, NULL, 'C' },
+ { "help", no_argument, NULL, 'h' },
+ { "hold-period", required_argument, NULL, 'p' },
+ { "numeric", no_argument, NULL, 'N' },
+ { "strict", no_argument, NULL, 's' },
+ { "unquoted", no_argument, NULL, 'Q' },
+ { "version", no_argument, NULL, 'v' },
+ { GETOPT_NULL_LONGOPT },
+ };
+
+ static const char *const shortopts = "+ab:c:C:hlp:sv";
+
+ int opti, optc;
+
+ memset(cfg, 0, sizeof(*cfg));
+ cfg->direction = GPIOD_LINE_DIRECTION_INPUT;
+ cfg->consumer = "gpioget";
+
+ for (;;) {
+ optc = getopt_long(argc, argv, shortopts, longopts, &opti);
+ if (optc < 0)
+ break;
+
+ switch (optc) {
+ case 'a':
+ cfg->direction = GPIOD_LINE_DIRECTION_AS_IS;
+ break;
+ case 'b':
+ cfg->bias = parse_bias_or_die(optarg);
+ break;
+ case 'B':
+ cfg->by_name = true;
+ break;
+ case 'c':
+ cfg->chip_id = optarg;
+ break;
+ case 'C':
+ cfg->consumer = optarg;
+ break;
+ case 'l':
+ cfg->active_low = true;
+ break;
+ case 'N':
+ cfg->numeric = true;
+ break;
+ case 'p':
+ cfg->hold_period_us = parse_period_or_die(optarg);
+ break;
+ case 'Q':
+ cfg->unquoted = true;
+ break;
+ case 's':
+ cfg->strict = true;
+ break;
+ case 'h':
+ print_help();
+ exit(EXIT_SUCCESS);
+ case 'v':
+ print_version();
+ exit(EXIT_SUCCESS);
+ case '?':
+ die("try %s --help", get_prog_name());
+ case 0:
+ break;
+ default:
+ abort();
+ }
+ }
+
+ return optind;
+}
+
+int main(int argc, char **argv)
+{
+ struct gpiod_line_settings *settings;
+ struct gpiod_request_config *req_cfg;
+ struct gpiod_line_request *request;
+ struct gpiod_line_config *line_cfg;
+ struct line_resolver *resolver;
+ enum gpiod_line_value *values;
+ struct resolved_line *line;
+ struct gpiod_chip *chip;
+ unsigned int *offsets;
+ int i, num_lines, ret;
+ struct config cfg;
+ const char *fmt;
+
+ set_prog_name(argv[0]);
+ i = parse_config(argc, argv, &cfg);
+ argc -= i;
+ argv += i;
+
+ if (argc < 1)
+ die("at least one GPIO line must be specified");
+
+ resolver = resolve_lines(argc, argv, cfg.chip_id, cfg.strict,
+ cfg.by_name);
+ validate_resolution(resolver, cfg.chip_id);
+
+ offsets = calloc(resolver->num_lines, sizeof(*offsets));
+ values = calloc(resolver->num_lines, sizeof(*values));
+ if (!offsets || !values)
+ die("out of memory");
+
+ settings = gpiod_line_settings_new();
+ if (!settings)
+ die_perror("unable to allocate line settings");
+
+ gpiod_line_settings_set_direction(settings, cfg.direction);
+
+ if (cfg.bias)
+ gpiod_line_settings_set_bias(settings, cfg.bias);
+
+ if (cfg.active_low)
+ gpiod_line_settings_set_active_low(settings, true);
+
+ req_cfg = gpiod_request_config_new();
+ if (!req_cfg)
+ die_perror("unable to allocate the request config structure");
+
+ line_cfg = gpiod_line_config_new();
+ if (!line_cfg)
+ die_perror("unable to allocate the line config structure");
+
+ gpiod_request_config_set_consumer(req_cfg, cfg.consumer);
+
+ for (i = 0; i < resolver->num_chips; i++) {
+ chip = gpiod_chip_open(resolver->chips[i].path);
+ if (!chip)
+ die_perror("unable to open chip '%s'",
+ resolver->chips[i].path);
+
+ num_lines = get_line_offsets_and_values(resolver, i, offsets,
+ NULL);
+
+ gpiod_line_config_reset(line_cfg);
+ ret = gpiod_line_config_add_line_settings(line_cfg, offsets,
+ num_lines, settings);
+ if (ret)
+ die_perror("unable to add line settings");
+
+ request = gpiod_chip_request_lines(chip, req_cfg, line_cfg);
+ if (!request)
+ die_perror("unable to request lines");
+
+ if (cfg.hold_period_us)
+ usleep(cfg.hold_period_us);
+
+ ret = gpiod_line_request_get_values(request, values);
+ if (ret)
+ die_perror("unable to read GPIO line values");
+
+ set_line_values(resolver, i, values);
+
+ gpiod_line_request_release(request);
+ gpiod_chip_close(chip);
+ }
+
+ fmt = cfg.unquoted ? "%s=%s" : "\"%s\"=%s";
+
+ for (i = 0; i < resolver->num_lines; i++) {
+ line = &resolver->lines[i];
+ if (cfg.numeric)
+ printf("%d", line->value);
+ else
+ printf(fmt, line->id,
+ line->value ? "active" : "inactive");
+
+ if (i != resolver->num_lines - 1)
+ printf(" ");
+ }
+ printf("\n");
+
+ free_line_resolver(resolver);
+ gpiod_request_config_free(req_cfg);
+ gpiod_line_config_free(line_cfg);
+ gpiod_line_settings_free(settings);
+ free(offsets);
+ free(values);
+
+ return EXIT_SUCCESS;
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+// SPDX-FileCopyrightText: 2022 Kent Gibson <warthog618@gmail.com>
+
+#include <getopt.h>
+#include <gpiod.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "tools-common.h"
+
+struct config {
+ bool by_name;
+ bool strict;
+ bool unquoted_strings;
+ const char *chip_id;
+};
+
+static void print_help(void)
+{
+ printf("Usage: %s [OPTIONS] [line]...\n", get_prog_name());
+ printf("\n");
+ printf("Print information about GPIO lines.\n");
+ printf("\n");
+ printf("Lines are specified by name, or optionally by offset if the chip option\n");
+ printf("is provided.\n");
+ printf("\n");
+ printf("If no lines are specified then all lines are displayed.\n");
+ printf("\n");
+ printf("Options:\n");
+ printf(" --by-name\t\ttreat lines as names even if they would parse as an offset\n");
+ printf(" -c, --chip <chip>\trestrict scope to a particular chip\n");
+ printf(" -h, --help\t\tdisplay this help and exit\n");
+ printf(" -s, --strict\t\tcheck all lines - don't assume line names are unique\n");
+ printf(" --unquoted\tdon't quote line or consumer names\n");
+ printf(" -v, --version\t\toutput version information and exit\n");
+ print_chip_help();
+}
+
+static int parse_config(int argc, char **argv, struct config *cfg)
+{
+ static const struct option longopts[] = {
+ { "by-name", no_argument, NULL, 'B' },
+ { "chip", required_argument, NULL, 'c' },
+ { "help", no_argument, NULL, 'h' },
+ { "strict", no_argument, NULL, 's' },
+ { "unquoted", no_argument, NULL, 'Q' },
+ { "version", no_argument, NULL, 'v' },
+ { GETOPT_NULL_LONGOPT },
+ };
+
+
+ static const char *const shortopts = "+c:hsv";
+
+ int opti, optc;
+
+ memset(cfg, 0, sizeof(*cfg));
+
+ for (;;) {
+ optc = getopt_long(argc, argv, shortopts, longopts, &opti);
+ if (optc < 0)
+ break;
+
+ switch (optc) {
+ case 'B':
+ cfg->by_name = true;
+ break;
+ case 'c':
+ cfg->chip_id = optarg;
+ break;
+ case 's':
+ cfg->strict = true;
+ break;
+ case 'h':
+ print_help();
+ exit(EXIT_SUCCESS);
+ case 'Q':
+ cfg->unquoted_strings = true;
+ break;
+ case 'v':
+ print_version();
+ exit(EXIT_SUCCESS);
+ case '?':
+ die("try %s --help", get_prog_name());
+ case 0:
+ break;
+ default:
+ abort();
+ }
+ }
+
+ return optind;
+}
+
+/*
+ * Minimal version similar to tools-common that indicates if a line should be
+ * printed rather than storing details into the resolver.
+ * Does not die on non-unique lines.
+ */
+static bool resolve_line(struct line_resolver *resolver,
+ struct gpiod_line_info *info, int chip_num)
+{
+ struct resolved_line *line;
+ bool resolved = false;
+ unsigned int offset;
+ const char *name;
+ int i;
+
+ offset = gpiod_line_info_get_offset(info);
+
+ for (i = 0; i < resolver->num_lines; i++) {
+ line = &resolver->lines[i];
+
+ /* already resolved by offset? */
+ if (line->resolved && (line->offset == offset) &&
+ (line->chip_num == chip_num)) {
+ resolved = true;
+ }
+
+ if (line->resolved && !resolver->strict)
+ continue;
+
+ /* else resolve by name */
+ name = gpiod_line_info_get_name(info);
+ if (name && (strcmp(line->id, name) == 0)) {
+ line->resolved = true;
+ line->offset = offset;
+ line->chip_num = chip_num;
+ resolved = true;
+ }
+ }
+
+ return resolved;
+}
+
+static void print_line_info(struct gpiod_line_info *info, bool unquoted_strings)
+{
+ char quoted_name[17];
+ const char *name;
+ int len;
+
+ name = gpiod_line_info_get_name(info);
+ if (!name) {
+ name = "unnamed";
+ unquoted_strings = true;
+ }
+
+ if (unquoted_strings) {
+ printf("%-16s\t", name);
+ } else {
+ len = strlen(name);
+ if (len <= 14) {
+ quoted_name[0] = '"';
+ memcpy("ed_name[1], name, len);
+ quoted_name[len + 1] = '"';
+ quoted_name[len + 2] = '\0';
+ printf("%-16s\t", quoted_name);
+ } else {
+ printf("\"%s\"\t", name);
+ }
+ }
+
+ print_line_attributes(info, unquoted_strings);
+}
+
+/*
+ * based on resolve_lines, but prints lines immediately rather than collecting
+ * details in the resolver.
+ */
+static void list_lines(struct line_resolver *resolver, struct gpiod_chip *chip,
+ int chip_num, struct config *cfg)
+{
+ struct gpiod_chip_info *chip_info;
+ struct gpiod_line_info *info;
+ int offset, num_lines;
+
+ chip_info = gpiod_chip_get_info(chip);
+ if (!chip_info)
+ die_perror("unable to read info from chip %s",
+ gpiod_chip_get_path(chip));
+
+ num_lines = gpiod_chip_info_get_num_lines(chip_info);
+
+ if ((chip_num == 0) && (cfg->chip_id && !cfg->by_name))
+ resolve_lines_by_offset(resolver, num_lines);
+
+ for (offset = 0; ((offset < num_lines) &&
+ !(resolver->num_lines && resolve_done(resolver)));
+ offset++) {
+ info = gpiod_chip_get_line_info(chip, offset);
+ if (!info)
+ die_perror("unable to read info for line %d from %s",
+ offset, gpiod_chip_info_get_name(chip_info));
+
+ if (resolver->num_lines &&
+ !resolve_line(resolver, info, chip_num)) {
+ gpiod_line_info_free(info);
+ continue;
+ }
+
+ if (resolver->num_lines) {
+ printf("%s %u", gpiod_chip_info_get_name(chip_info),
+ offset);
+ } else {
+ if (offset == 0)
+ printf("%s - %u lines:\n",
+ gpiod_chip_info_get_name(chip_info),
+ num_lines);
+
+ printf("\tline %3u:", offset);
+ }
+
+ fputc('\t', stdout);
+ print_line_info(info, cfg->unquoted_strings);
+ fputc('\n', stdout);
+ gpiod_line_info_free(info);
+ resolver->num_found++;
+ }
+
+ gpiod_chip_info_free(chip_info);
+}
+
+int main(int argc, char **argv)
+{
+ struct line_resolver *resolver = NULL;
+ int num_chips, i, ret = EXIT_SUCCESS;
+ struct gpiod_chip *chip;
+ struct config cfg;
+ char **paths;
+
+ set_prog_name(argv[0]);
+ i = parse_config(argc, argv, &cfg);
+ argc -= i;
+ argv += i;
+
+ if (!cfg.chip_id)
+ cfg.by_name = true;
+
+ num_chips = chip_paths(cfg.chip_id, &paths);
+ if (cfg.chip_id && (num_chips == 0))
+ die("cannot find GPIO chip character device '%s'", cfg.chip_id);
+
+ resolver = resolver_init(argc, argv, num_chips, cfg.strict,
+ cfg.by_name);
+
+ for (i = 0; i < num_chips; i++) {
+ chip = gpiod_chip_open(paths[i]);
+ if (chip) {
+ list_lines(resolver, chip, i, &cfg);
+ gpiod_chip_close(chip);
+ } else {
+ print_perror("unable to open chip '%s'", paths[i]);
+
+ if (cfg.chip_id)
+ return EXIT_FAILURE;
+
+ ret = EXIT_FAILURE;
+ }
+ free(paths[i]);
+ }
+ free(paths);
+
+ validate_resolution(resolver, cfg.chip_id);
+ if (argc && resolver->num_found != argc)
+ ret = EXIT_FAILURE;
+ free(resolver);
+
+ return ret;
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+// SPDX-FileCopyrightText: 2022 Kent Gibson <warthog618@gmail.com>
+
+#include <getopt.h>
+#include <gpiod.h>
+#include <inttypes.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "tools-common.h"
+
+#define EVENT_BUF_SIZE 32
+
+struct config {
+ bool active_low;
+ bool banner;
+ bool by_name;
+ bool quiet;
+ bool strict;
+ bool unquoted;
+ enum gpiod_line_bias bias;
+ enum gpiod_line_edge edges;
+ int events_wanted;
+ unsigned int debounce_period_us;
+ const char *chip_id;
+ const char *consumer;
+ const char *fmt;
+ enum gpiod_line_clock event_clock;
+ int timestamp_fmt;
+ int timeout;
+};
+
+static void print_help(void)
+{
+ printf("Usage: %s [OPTIONS] <line>...\n", get_prog_name());
+ printf("\n");
+ printf("Wait for events on GPIO lines and print them to standard output.\n");
+ printf("\n");
+ printf("Lines are specified by name, or optionally by offset if the chip option\n");
+ printf("is provided.\n");
+ printf("\n");
+ printf("Options:\n");
+ printf(" --banner\t\tdisplay a banner on successful startup\n");
+ print_bias_help();
+ printf(" --by-name\t\ttreat lines as names even if they would parse as an offset\n");
+ printf(" -c, --chip <chip>\trestrict scope to a particular chip\n");
+ printf(" -C, --consumer <name>\tconsumer name applied to requested lines (default is 'gpiomon')\n");
+ printf(" -e, --edges <edges>\tspecify the edges to monitor\n");
+ printf("\t\t\tPossible values: 'falling', 'rising', 'both'.\n");
+ printf("\t\t\t(default is 'both')\n");
+ printf(" -E, --event-clock <clock>\n");
+ printf("\t\t\tspecify the source clock for event timestamps\n");
+ printf("\t\t\tPossible values: 'monotonic', 'realtime', 'hte'.\n");
+ printf("\t\t\t(default is 'monotonic')\n");
+ printf("\t\t\tBy default 'realtime' is formatted as UTC, others as raw u64.\n");
+ printf(" -h, --help\t\tdisplay this help and exit\n");
+ printf(" -F, --format <fmt>\tspecify a custom output format\n");
+ printf(" --idle-timeout <period>\n");
+ printf("\t\t\texit gracefully if no events occur for the period specified\n");
+ printf(" -l, --active-low\ttreat the line as active low, flipping the sense of\n");
+ printf("\t\t\trising and falling edges\n");
+ printf(" --localtime\tformat event timestamps as local time\n");
+ printf(" -n, --num-events <num>\n");
+ printf("\t\t\texit after processing num events\n");
+ printf(" -p, --debounce-period <period>\n");
+ printf("\t\t\tdebounce the line(s) with the specified period\n");
+ printf(" -q, --quiet\t\tdon't generate any output\n");
+ printf(" -s, --strict\t\tabort if requested line names are not unique\n");
+ printf(" --unquoted\tdon't quote line or consumer names\n");
+ printf(" --utc\t\tformat event timestamps as UTC (default for 'realtime')\n");
+ printf(" -v, --version\t\toutput version information and exit\n");
+ print_chip_help();
+ print_period_help();
+ printf("\n");
+ printf("Format specifiers:\n");
+ printf(" %%o GPIO line offset\n");
+ printf(" %%l GPIO line name\n");
+ printf(" %%c GPIO chip name\n");
+ printf(" %%e numeric edge event type ('1' - rising or '2' - falling)\n");
+ printf(" %%E edge event type ('rising' or 'falling')\n");
+ printf(" %%S event timestamp as seconds\n");
+ printf(" %%U event timestamp as UTC\n");
+ printf(" %%L event timestamp as local time\n");
+}
+
+static int parse_edges_or_die(const char *option)
+{
+ if (strcmp(option, "rising") == 0)
+ return GPIOD_LINE_EDGE_RISING;
+ if (strcmp(option, "falling") == 0)
+ return GPIOD_LINE_EDGE_FALLING;
+ if (strcmp(option, "both") != 0)
+ die("invalid edges: %s", option);
+
+ return GPIOD_LINE_EDGE_BOTH;
+}
+
+static int parse_event_clock_or_die(const char *option)
+{
+ if (strcmp(option, "realtime") == 0)
+ return GPIOD_LINE_CLOCK_REALTIME;
+ if (strcmp(option, "hte") == 0)
+ return GPIOD_LINE_CLOCK_HTE;
+ if (strcmp(option, "monotonic") != 0)
+ die("invalid event clock: %s", option);
+
+ return GPIOD_LINE_CLOCK_MONOTONIC;
+}
+
+static int parse_config(int argc, char **argv, struct config *cfg)
+{
+ static const char *const shortopts = "+b:c:C:e:E:hF:ln:p:qshv";
+
+ const struct option longopts[] = {
+ { "active-low", no_argument, NULL, 'l' },
+ { "banner", no_argument, NULL, '-'},
+ { "bias", required_argument, NULL, 'b' },
+ { "by-name", no_argument, NULL, 'B'},
+ { "chip", required_argument, NULL, 'c' },
+ { "consumer", required_argument, NULL, 'C' },
+ { "debounce-period", required_argument, NULL, 'p' },
+ { "edges", required_argument, NULL, 'e' },
+ { "event-clock", required_argument, NULL, 'E' },
+ { "format", required_argument, NULL, 'F' },
+ { "help", no_argument, NULL, 'h' },
+ { "idle-timeout", required_argument, NULL, 'i' },
+ { "localtime", no_argument, &cfg->timestamp_fmt, 2 },
+ { "num-events", required_argument, NULL, 'n' },
+ { "quiet", no_argument, NULL, 'q' },
+ { "silent", no_argument, NULL, 'q' },
+ { "strict", no_argument, NULL, 's' },
+ { "unquoted", no_argument, NULL, 'Q' },
+ { "utc", no_argument, &cfg->timestamp_fmt, 1 },
+ { "version", no_argument, NULL, 'v' },
+ { GETOPT_NULL_LONGOPT },
+ };
+
+ int opti, optc;
+
+ memset(cfg, 0, sizeof(*cfg));
+ cfg->edges = GPIOD_LINE_EDGE_BOTH;
+ cfg->consumer = "gpiomon";
+ cfg->timeout = -1;
+
+ for (;;) {
+ optc = getopt_long(argc, argv, shortopts, longopts, &opti);
+ if (optc < 0)
+ break;
+
+ switch (optc) {
+ case '-':
+ cfg->banner = true;
+ break;
+ case 'b':
+ cfg->bias = parse_bias_or_die(optarg);
+ break;
+ case 'B':
+ cfg->by_name = true;
+ break;
+ case 'c':
+ cfg->chip_id = optarg;
+ break;
+ case 'C':
+ cfg->consumer = optarg;
+ break;
+ case 'e':
+ cfg->edges = parse_edges_or_die(optarg);
+ break;
+ case 'E':
+ cfg->event_clock = parse_event_clock_or_die(optarg);
+ break;
+ case 'F':
+ cfg->fmt = optarg;
+ break;
+ case 'i':
+ cfg->timeout = parse_period_or_die(optarg) / 1000;
+ break;
+ case 'l':
+ cfg->active_low = true;
+ break;
+ case 'n':
+ cfg->events_wanted = parse_uint_or_die(optarg);
+ break;
+ case 'p':
+ cfg->debounce_period_us = parse_period_or_die(optarg);
+ break;
+ case 'q':
+ cfg->quiet = true;
+ break;
+ case 'Q':
+ cfg->unquoted = true;
+ break;
+ case 's':
+ cfg->strict = true;
+ break;
+ case 'h':
+ print_help();
+ exit(EXIT_SUCCESS);
+ case 'v':
+ print_version();
+ exit(EXIT_SUCCESS);
+ case '?':
+ die("try %s --help", get_prog_name());
+ case 0:
+ break;
+ default:
+ abort();
+ }
+ }
+
+ /* setup default clock/format combinations, where not overridden */
+ if (cfg->event_clock == 0) {
+ if (cfg->timestamp_fmt)
+ cfg->event_clock = GPIOD_LINE_CLOCK_REALTIME;
+ else
+ cfg->event_clock = GPIOD_LINE_CLOCK_MONOTONIC;
+ } else if ((cfg->event_clock == GPIOD_LINE_CLOCK_REALTIME) &&
+ (cfg->timestamp_fmt == 0)) {
+ cfg->timestamp_fmt = 1;
+ }
+
+ return optind;
+}
+
+static void print_banner(int num_lines, char **lines)
+{
+ int i;
+
+ if (num_lines > 1) {
+ printf("Monitoring lines ");
+
+ for (i = 0; i < num_lines - 1; i++)
+ printf("'%s', ", lines[i]);
+
+ printf("and '%s'...\n", lines[i]);
+ } else {
+ printf("Monitoring line '%s'...\n", lines[0]);
+ }
+}
+
+static void event_print_formatted(struct gpiod_edge_event *event,
+ struct line_resolver *resolver, int chip_num,
+ struct config *cfg)
+{
+ const char *lname, *prev, *curr;
+ unsigned int offset;
+ uint64_t evtime;
+ int evtype;
+ char fmt;
+
+ offset = gpiod_edge_event_get_line_offset(event);
+ evtime = gpiod_edge_event_get_timestamp_ns(event);
+ evtype = gpiod_edge_event_get_event_type(event);
+
+ for (prev = curr = cfg->fmt;;) {
+ curr = strchr(curr, '%');
+ if (!curr) {
+ fputs(prev, stdout);
+ break;
+ }
+
+ if (prev != curr)
+ fwrite(prev, curr - prev, 1, stdout);
+
+ fmt = *(curr + 1);
+
+ switch (fmt) {
+ case 'c':
+ fputs(get_chip_name(resolver, chip_num), stdout);
+ break;
+ case 'e':
+ printf("%d", evtype);
+ break;
+ case 'E':
+ if (evtype == GPIOD_EDGE_EVENT_RISING_EDGE)
+ fputs("rising", stdout);
+ else
+ fputs("falling", stdout);
+ break;
+ case 'l':
+ lname = get_line_name(resolver, chip_num, offset);
+ if (!lname)
+ lname = "unnamed";
+ fputs(lname, stdout);
+ break;
+ case 'L':
+ print_event_time(evtime, 2);
+ break;
+ case 'o':
+ printf("%u", offset);
+ break;
+ case 'S':
+ print_event_time(evtime, 0);
+ break;
+ case 'U':
+ print_event_time(evtime, 1);
+ break;
+ case '%':
+ fputc('%', stdout);
+ break;
+ case '\0':
+ fputc('%', stdout);
+ goto end;
+ default:
+ fwrite(curr, 2, 1, stdout);
+ break;
+ }
+
+ curr += 2;
+ prev = curr;
+ }
+
+end:
+ fputc('\n', stdout);
+}
+
+static void event_print_human_readable(struct gpiod_edge_event *event,
+ struct line_resolver *resolver,
+ int chip_num, struct config *cfg)
+{
+ unsigned int offset;
+ uint64_t evtime;
+
+ offset = gpiod_edge_event_get_line_offset(event);
+ evtime = gpiod_edge_event_get_timestamp_ns(event);
+
+ print_event_time(evtime, cfg->timestamp_fmt);
+
+ if (gpiod_edge_event_get_event_type(event) ==
+ GPIOD_EDGE_EVENT_RISING_EDGE)
+ fputs("\trising\t", stdout);
+ else
+ fputs("\tfalling\t", stdout);
+
+ print_line_id(resolver, chip_num, offset, cfg->chip_id, cfg->unquoted);
+ fputc('\n', stdout);
+}
+
+static void event_print(struct gpiod_edge_event *event,
+ struct line_resolver *resolver, int chip_num,
+ struct config *cfg)
+{
+ if (cfg->quiet)
+ return;
+
+ if (cfg->fmt)
+ event_print_formatted(event, resolver, chip_num, cfg);
+ else
+ event_print_human_readable(event, resolver, chip_num, cfg);
+}
+
+int main(int argc, char **argv)
+{
+ struct gpiod_edge_event_buffer *event_buffer;
+ struct gpiod_line_settings *settings;
+ struct gpiod_request_config *req_cfg;
+ struct gpiod_line_request **requests;
+ struct gpiod_line_config *line_cfg;
+ int num_lines, events_done = 0;
+ struct gpiod_edge_event *event;
+ struct line_resolver *resolver;
+ struct gpiod_chip *chip;
+ struct pollfd *pollfds;
+ unsigned int *offsets;
+ struct config cfg;
+ int ret, i, j;
+
+ set_prog_name(argv[0]);
+ i = parse_config(argc, argv, &cfg);
+ argc -= i;
+ argv += i;
+
+ if (argc < 1)
+ die("at least one GPIO line must be specified");
+
+ if (argc > 64)
+ die("too many lines given");
+
+ settings = gpiod_line_settings_new();
+ if (!settings)
+ die_perror("unable to allocate line settings");
+
+ if (cfg.bias)
+ gpiod_line_settings_set_bias(settings, cfg.bias);
+
+ if (cfg.active_low)
+ gpiod_line_settings_set_active_low(settings, true);
+
+ if (cfg.debounce_period_us)
+ gpiod_line_settings_set_debounce_period_us(
+ settings, cfg.debounce_period_us);
+
+ gpiod_line_settings_set_event_clock(settings, cfg.event_clock);
+ gpiod_line_settings_set_edge_detection(settings, cfg.edges);
+
+ line_cfg = gpiod_line_config_new();
+ if (!line_cfg)
+ die_perror("unable to allocate the line config structure");
+
+ req_cfg = gpiod_request_config_new();
+ if (!req_cfg)
+ die_perror("unable to allocate the request config structure");
+
+ gpiod_request_config_set_consumer(req_cfg, cfg.consumer);
+
+ event_buffer = gpiod_edge_event_buffer_new(EVENT_BUF_SIZE);
+ if (!event_buffer)
+ die_perror("unable to allocate the line event buffer");
+
+ resolver = resolve_lines(argc, argv, cfg.chip_id, cfg.strict,
+ cfg.by_name);
+ validate_resolution(resolver, cfg.chip_id);
+ requests = calloc(resolver->num_chips, sizeof(*requests));
+ pollfds = calloc(resolver->num_chips, sizeof(*pollfds));
+ offsets = calloc(resolver->num_lines, sizeof(*offsets));
+ if (!requests || !pollfds || !offsets)
+ die("out of memory");
+
+ for (i = 0; i < resolver->num_chips; i++) {
+ num_lines = get_line_offsets_and_values(resolver, i, offsets,
+ NULL);
+ gpiod_line_config_reset(line_cfg);
+ ret = gpiod_line_config_add_line_settings(line_cfg, offsets,
+ num_lines, settings);
+ if (ret)
+ die_perror("unable to add line settings");
+
+ chip = gpiod_chip_open(resolver->chips[i].path);
+ if (!chip)
+ die_perror("unable to open chip '%s'",
+ resolver->chips[i].path);
+
+ requests[i] = gpiod_chip_request_lines(chip, req_cfg, line_cfg);
+ if (!requests[i])
+ die_perror("unable to request lines on chip %s",
+ resolver->chips[i].path);
+
+ pollfds[i].fd = gpiod_line_request_get_fd(requests[i]);
+ pollfds[i].events = POLLIN;
+ gpiod_chip_close(chip);
+ }
+
+ gpiod_request_config_free(req_cfg);
+ gpiod_line_config_free(line_cfg);
+ gpiod_line_settings_free(settings);
+
+ if (cfg.banner)
+ print_banner(argc, argv);
+
+ for (;;) {
+ fflush(stdout);
+
+ ret = poll(pollfds, resolver->num_chips, cfg.timeout);
+ if (ret < 0)
+ die_perror("error polling for events");
+
+ if (ret == 0)
+ goto done;
+
+ for (i = 0; i < resolver->num_chips; i++) {
+ if (pollfds[i].revents == 0)
+ continue;
+
+ ret = gpiod_line_request_read_edge_events(requests[i],
+ event_buffer, EVENT_BUF_SIZE);
+ if (ret < 0)
+ die_perror("error reading line events");
+
+ for (j = 0; j < ret; j++) {
+ event = gpiod_edge_event_buffer_get_event(
+ event_buffer, j);
+ if (!event)
+ die_perror("unable to retrieve event from buffer");
+
+ event_print(event, resolver, i, &cfg);
+
+ events_done++;
+
+ if (cfg.events_wanted &&
+ events_done >= cfg.events_wanted)
+ goto done;
+ }
+ }
+ }
+
+done:
+ for (i = 0; i < resolver->num_chips; i++)
+ gpiod_line_request_release(requests[i]);
+
+ free(requests);
+ free_line_resolver(resolver);
+ gpiod_edge_event_buffer_free(event_buffer);
+ free(offsets);
+
+ return EXIT_SUCCESS;
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2022 Kent Gibson <warthog618@gmail.com>
+
+#include <getopt.h>
+#include <gpiod.h>
+#include <inttypes.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "tools-common.h"
+
+struct config {
+ bool banner;
+ bool by_name;
+ bool quiet;
+ bool strict;
+ bool unquoted;
+ int event_type;
+ int events_wanted;
+ const char *chip_id;
+ const char *fmt;
+ int timestamp_fmt;
+ int timeout;
+};
+
+static void print_help(void)
+{
+ printf("Usage: %s [OPTIONS] <line>...\n", get_prog_name());
+ printf("\n");
+ printf("Wait for changes to info on GPIO lines and print them to standard output.\n");
+ printf("\n");
+ printf("Lines are specified by name, or optionally by offset if the chip option\n");
+ printf("is provided.\n");
+ printf("\n");
+ printf("Options:\n");
+ printf(" --banner\t\tdisplay a banner on successful startup\n");
+ printf(" --by-name\t\ttreat lines as names even if they would parse as an offset\n");
+ printf(" -c, --chip <chip>\trestrict scope to a particular chip\n");
+ printf(" -e, --event <event>\tspecify the events to monitor\n");
+ printf("\t\t\tPossible values: 'requested', 'released', 'reconfigured'.\n");
+ printf("\t\t\t(default is all events)\n");
+ printf(" -h, --help\t\tdisplay this help and exit\n");
+ printf(" -F, --format <fmt>\tspecify a custom output format\n");
+ printf(" --idle-timeout <period>\n");
+ printf("\t\t\texit gracefully if no events occur for the period specified\n");
+ printf(" --localtime\tconvert event timestamps to local time\n");
+ printf(" -n, --num-events <num>\n");
+ printf("\t\t\texit after processing num events\n");
+ printf(" -q, --quiet\t\tdon't generate any output\n");
+ printf(" -s, --strict\t\tabort if requested line names are not unique\n");
+ printf(" --unquoted\tdon't quote line or consumer names\n");
+ printf(" --utc\t\tconvert event timestamps to UTC\n");
+ printf(" -v, --version\t\toutput version information and exit\n");
+ print_chip_help();
+ print_period_help();
+ printf("\n");
+ printf("Format specifiers:\n");
+ printf(" %%o GPIO line offset\n");
+ printf(" %%l GPIO line name\n");
+ printf(" %%c GPIO chip name\n");
+ printf(" %%e numeric info event type ('1' - requested, '2' - released or '3' - reconfigured)\n");
+ printf(" %%E info event type ('requested', 'released' or 'reconfigured')\n");
+ printf(" %%a line attributes\n");
+ printf(" %%C consumer\n");
+ printf(" %%S event timestamp as seconds\n");
+ printf(" %%U event timestamp as UTC\n");
+ printf(" %%L event timestamp as local time\n");
+}
+
+static int parse_event_type_or_die(const char *option)
+{
+ if (strcmp(option, "requested") == 0)
+ return GPIOD_INFO_EVENT_LINE_REQUESTED;
+ if (strcmp(option, "released") == 0)
+ return GPIOD_INFO_EVENT_LINE_RELEASED;
+ if (strcmp(option, "reconfigured") != 0)
+ die("invalid edge: %s", option);
+
+ return GPIOD_INFO_EVENT_LINE_CONFIG_CHANGED;
+}
+
+static int parse_config(int argc, char **argv, struct config *cfg)
+{
+ static const char *const shortopts = "+c:e:hF:n:qshv";
+
+ const struct option longopts[] = {
+ { "banner", no_argument, NULL, '-'},
+ { "by-name", no_argument, NULL, 'B'},
+ { "chip", required_argument, NULL, 'c' },
+ { "event", required_argument, NULL, 'e' },
+ { "format", required_argument, NULL, 'F' },
+ { "help", no_argument, NULL, 'h' },
+ { "idle-timeout", required_argument, NULL, 'i' },
+ { "localtime", no_argument, &cfg->timestamp_fmt, 2 },
+ { "num-events", required_argument, NULL, 'n' },
+ { "quiet", no_argument, NULL, 'q' },
+ { "silent", no_argument, NULL, 'q' },
+ { "strict", no_argument, NULL, 's' },
+ { "unquoted", no_argument, NULL, 'Q' },
+ { "utc", no_argument, &cfg->timestamp_fmt, 1 },
+ { "version", no_argument, NULL, 'v' },
+ { GETOPT_NULL_LONGOPT },
+ };
+
+ int opti, optc;
+
+ memset(cfg, 0, sizeof(*cfg));
+ cfg->timeout = -1;
+
+ for (;;) {
+ optc = getopt_long(argc, argv, shortopts, longopts, &opti);
+ if (optc < 0)
+ break;
+
+ switch (optc) {
+ case '-':
+ cfg->banner = true;
+ break;
+ case 'B':
+ cfg->by_name = true;
+ break;
+ case 'c':
+ cfg->chip_id = optarg;
+ break;
+ case 'e':
+ cfg->event_type = parse_event_type_or_die(optarg);
+ break;
+ case 'F':
+ cfg->fmt = optarg;
+ break;
+ case 'i':
+ cfg->timeout = parse_period_or_die(optarg) / 1000;
+ break;
+ case 'n':
+ cfg->events_wanted = parse_uint_or_die(optarg);
+ break;
+ case 'q':
+ cfg->quiet = true;
+ break;
+ case 'Q':
+ cfg->unquoted = true;
+ break;
+ case 's':
+ cfg->strict = true;
+ break;
+ case 'h':
+ print_help();
+ exit(EXIT_SUCCESS);
+ case 'v':
+ print_version();
+ exit(EXIT_SUCCESS);
+ case '?':
+ die("try %s --help", get_prog_name());
+ case 0:
+ break;
+ default:
+ abort();
+ }
+ }
+
+ return optind;
+}
+
+static void print_banner(int num_lines, char **lines)
+{
+ int i;
+
+ if (num_lines > 1) {
+ printf("Watching lines ");
+
+ for (i = 0; i < num_lines - 1; i++)
+ printf("'%s', ", lines[i]);
+
+ printf("and '%s'...\n", lines[i]);
+ } else {
+ printf("Watching line '%s'...\n", lines[0]);
+ }
+}
+
+static void print_event_type(int evtype)
+{
+ switch (evtype) {
+ case GPIOD_INFO_EVENT_LINE_REQUESTED:
+ fputs("requested", stdout);
+ break;
+ case GPIOD_INFO_EVENT_LINE_RELEASED:
+ fputs("released", stdout);
+ break;
+ case GPIOD_INFO_EVENT_LINE_CONFIG_CHANGED:
+ fputs("reconfigured", stdout);
+ break;
+ default:
+ fputs("unknown", stdout);
+ break;
+ }
+}
+
+/*
+ * A convenience function to map clock monotonic to realtime, as uAPI only
+ * supports CLOCK_MONOTONIC.
+ *
+ * Samples the realtime clock on either side of a monotonic sample and averages
+ * the realtime samples to estimate the offset between the two clocks.
+ * Any time shifts between the two realtime samples will result in the
+ * monotonic time being mapped to the average of the before and after, so
+ * half way between the old and new times.
+ *
+ * Any CPU suspension between the event being generated and converted will
+ * result in the returned time being shifted by the period of suspension.
+ */
+static uint64_t monotonic_to_realtime(uint64_t evtime)
+{
+ uint64_t before, after, mono;
+ struct timespec ts;
+
+ clock_gettime(CLOCK_REALTIME, &ts);
+ before = ts.tv_nsec + ((uint64_t)ts.tv_sec) * 1000000000;
+
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ mono = ts.tv_nsec + ((uint64_t)ts.tv_sec) * 1000000000;
+
+ clock_gettime(CLOCK_REALTIME, &ts);
+ after = ts.tv_nsec + ((uint64_t)ts.tv_sec) * 1000000000;
+
+ evtime += (after / 2 - mono + before / 2);
+
+ return evtime;
+}
+
+static void event_print_formatted(struct gpiod_info_event *event,
+ struct line_resolver *resolver, int chip_num,
+ struct config *cfg)
+{
+ const char *lname, *prev, *curr, *consumer;
+ struct gpiod_line_info *info;
+ unsigned int offset;
+ uint64_t evtime;
+ int evtype;
+ char fmt;
+
+ info = gpiod_info_event_get_line_info(event);
+ evtime = gpiod_info_event_get_timestamp_ns(event);
+ evtype = gpiod_info_event_get_event_type(event);
+ offset = gpiod_line_info_get_offset(info);
+
+ for (prev = curr = cfg->fmt;;) {
+ curr = strchr(curr, '%');
+ if (!curr) {
+ fputs(prev, stdout);
+ break;
+ }
+
+ if (prev != curr)
+ fwrite(prev, curr - prev, 1, stdout);
+
+ fmt = *(curr + 1);
+
+ switch (fmt) {
+ case 'a':
+ print_line_attributes(info, cfg->unquoted);
+ break;
+ case 'c':
+ fputs(get_chip_name(resolver, chip_num), stdout);
+ break;
+ case 'C':
+ if (!gpiod_line_info_is_used(info)) {
+ consumer = "unused";
+ } else {
+ consumer = gpiod_line_info_get_consumer(info);
+ if (!consumer)
+ consumer = "kernel";
+ }
+ fputs(consumer, stdout);
+ break;
+ case 'e':
+ printf("%d", evtype);
+ break;
+ case 'E':
+ print_event_type(evtype);
+ break;
+ case 'l':
+ lname = gpiod_line_info_get_name(info);
+ if (!lname)
+ lname = "unnamed";
+ fputs(lname, stdout);
+ break;
+ case 'L':
+ print_event_time(monotonic_to_realtime(evtime), 2);
+ break;
+ case 'o':
+ printf("%u", offset);
+ break;
+ case 'S':
+ print_event_time(evtime, 0);
+ break;
+ case 'U':
+ print_event_time(monotonic_to_realtime(evtime), 1);
+ break;
+ case '%':
+ fputc('%', stdout);
+ break;
+ case '\0':
+ fputc('%', stdout);
+ goto end;
+ default:
+ fwrite(curr, 2, 1, stdout);
+ break;
+ }
+
+ curr += 2;
+ prev = curr;
+ }
+
+end:
+ fputc('\n', stdout);
+}
+
+static void event_print_human_readable(struct gpiod_info_event *event,
+ struct line_resolver *resolver,
+ int chip_num, struct config *cfg)
+{
+ struct gpiod_line_info *info;
+ unsigned int offset;
+ uint64_t evtime;
+ char *evname;
+ int evtype;
+
+ info = gpiod_info_event_get_line_info(event);
+ evtime = gpiod_info_event_get_timestamp_ns(event);
+ evtype = gpiod_info_event_get_event_type(event);
+ offset = gpiod_line_info_get_offset(info);
+
+ switch (evtype) {
+ case GPIOD_INFO_EVENT_LINE_REQUESTED:
+ evname = "requested";
+ break;
+ case GPIOD_INFO_EVENT_LINE_RELEASED:
+ evname = "released";
+ break;
+ case GPIOD_INFO_EVENT_LINE_CONFIG_CHANGED:
+ evname = "reconfigured";
+ break;
+ default:
+ evname = "unknown";
+ }
+
+ if (cfg->timestamp_fmt)
+ evtime = monotonic_to_realtime(evtime);
+
+ print_event_time(evtime, cfg->timestamp_fmt);
+ printf("\t%s\t", evname);
+ print_line_id(resolver, chip_num, offset, cfg->chip_id, cfg->unquoted);
+ fputc('\n', stdout);
+}
+
+static void event_print(struct gpiod_info_event *event,
+ struct line_resolver *resolver, int chip_num,
+ struct config *cfg)
+{
+ if (cfg->quiet)
+ return;
+
+ if (cfg->fmt)
+ event_print_formatted(event, resolver, chip_num, cfg);
+ else
+ event_print_human_readable(event, resolver, chip_num, cfg);
+}
+
+int main(int argc, char **argv)
+{
+ int i, j, ret, events_done = 0, evtype;
+ struct line_resolver *resolver;
+ struct gpiod_info_event *event;
+ struct gpiod_chip **chips;
+ struct gpiod_chip *chip;
+ struct pollfd *pollfds;
+ struct config cfg;
+
+ set_prog_name(argv[0]);
+ i = parse_config(argc, argv, &cfg);
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1)
+ die("at least one GPIO line must be specified");
+
+ if (argc > 64)
+ die("too many lines given");
+
+ resolver = resolve_lines(argc, argv, cfg.chip_id, cfg.strict,
+ cfg.by_name);
+ validate_resolution(resolver, cfg.chip_id);
+ chips = calloc(resolver->num_chips, sizeof(*chips));
+ pollfds = calloc(resolver->num_chips, sizeof(*pollfds));
+ if (!pollfds)
+ die("out of memory");
+
+ for (i = 0; i < resolver->num_chips; i++) {
+ chip = gpiod_chip_open(resolver->chips[i].path);
+ if (!chip)
+ die_perror("unable to open chip '%s'",
+ resolver->chips[i].path);
+
+ for (j = 0; j < resolver->num_lines; j++)
+ if ((resolver->lines[j].chip_num == i) &&
+ !gpiod_chip_watch_line_info(
+ chip, resolver->lines[j].offset))
+ die_perror("unable to watch line on chip '%s'",
+ resolver->chips[i].path);
+
+ chips[i] = chip;
+ pollfds[i].fd = gpiod_chip_get_fd(chip);
+ pollfds[i].events = POLLIN;
+ }
+
+ if (cfg.banner)
+ print_banner(argc, argv);
+
+ for (;;) {
+ fflush(stdout);
+
+ ret = poll(pollfds, resolver->num_chips, cfg.timeout);
+ if (ret < 0)
+ die_perror("error polling for events");
+
+ if (ret == 0)
+ goto done;
+
+ for (i = 0; i < resolver->num_chips; i++) {
+ if (pollfds[i].revents == 0)
+ continue;
+
+ event = gpiod_chip_read_info_event(chips[i]);
+ if (!event)
+ die_perror("unable to retrieve chip event");
+
+ if (cfg.event_type) {
+ evtype = gpiod_info_event_get_event_type(event);
+ if (evtype != cfg.event_type)
+ continue;
+ }
+
+ event_print(event, resolver, i, &cfg);
+
+ events_done++;
+
+ if (cfg.events_wanted &&
+ events_done >= cfg.events_wanted)
+ goto done;
+ }
+ }
+done:
+ for (i = 0; i < resolver->num_chips; i++)
+ gpiod_chip_close(chips[i]);
+
+ free(chips);
+ free_line_resolver(resolver);
+
+ return EXIT_SUCCESS;
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+// SPDX-FileCopyrightText: 2022 Kent Gibson <warthog618@gmail.com>
+
+#include <ctype.h>
+#include <gpiod.h>
+#include <getopt.h>
+#include <limits.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#ifdef GPIOSET_INTERACTIVE
+#include <editline/readline.h>
+#endif
+
+#include "tools-common.h"
+
+struct config {
+ bool active_low;
+ bool banner;
+ bool by_name;
+ bool daemonize;
+ bool interactive;
+ bool strict;
+ bool unquoted;
+ enum gpiod_line_bias bias;
+ enum gpiod_line_drive drive;
+ int toggles;
+ unsigned int *toggle_periods;
+ unsigned int hold_period_us;
+ const char *chip_id;
+ const char *consumer;
+};
+
+static void print_help(void)
+{
+ printf("Usage: %s [OPTIONS] <line=value>...\n", get_prog_name());
+ printf("\n");
+ printf("Set values of GPIO lines.\n");
+ printf("\n");
+ printf("Lines are specified by name, or optionally by offset if the chip option\n");
+ printf("is provided.\n");
+ printf("Values may be '1' or '0', or equivalently 'active'/'inactive' or 'on'/'off'.\n");
+ printf("\n");
+ printf("The line output state is maintained until the process exits, but after that\n");
+ printf("is not guaranteed.\n");
+ printf("\n");
+ printf("Options:\n");
+ printf(" --banner\t\tdisplay a banner on successful startup\n");
+ print_bias_help();
+ printf(" --by-name\t\ttreat lines as names even if they would parse as an offset\n");
+ printf(" -c, --chip <chip>\trestrict scope to a particular chip\n");
+ printf(" -C, --consumer <name>\tconsumer name applied to requested lines (default is 'gpioset')\n");
+ printf(" -d, --drive <drive>\tspecify the line drive mode\n");
+ printf("\t\t\tPossible values: 'push-pull', 'open-drain', 'open-source'.\n");
+ printf("\t\t\t(default is 'push-pull')\n");
+ printf(" -h, --help\t\tdisplay this help and exit\n");
+#ifdef GPIOSET_INTERACTIVE
+ printf(" -i, --interactive\tset the lines then wait for additional set commands\n");
+ printf("\t\t\tUse the 'help' command at the interactive prompt to get help\n");
+ printf("\t\t\tfor the supported commands.\n");
+#endif
+ printf(" -l, --active-low\ttreat the line as active low\n");
+ printf(" -p, --hold-period <period>\n");
+ printf("\t\t\tthe minimum time period to hold lines at the requested values\n");
+ printf(" -s, --strict\t\tabort if requested line names are not unique\n");
+ printf(" -t, --toggle <period>[,period]...\n");
+ printf("\t\t\ttoggle the line(s) after the specified period(s)\n");
+ printf("\t\t\tIf the last period is non-zero then the sequence repeats.\n");
+ printf(" --unquoted\tdon't quote line names\n");
+ printf(" -v, --version\t\toutput version information and exit\n");
+ printf(" -z, --daemonize\tset values then detach from the controlling terminal\n");
+ print_chip_help();
+ print_period_help();
+ printf("\n");
+ printf("*Note*\n");
+ printf(" The state of a GPIO line controlled over the character device reverts to default\n");
+ printf(" when the last process referencing the file descriptor representing the device file exits.\n");
+ printf(" This means that it's wrong to run gpioset, have it exit and expect the line to continue\n");
+ printf(" being driven high or low. It may happen if given pin is floating but it must be interpreted\n");
+ printf(" as undefined behavior.\n");
+}
+
+static int parse_drive_or_die(const char *option)
+{
+ if (strcmp(option, "open-drain") == 0)
+ return GPIOD_LINE_DRIVE_OPEN_DRAIN;
+ if (strcmp(option, "open-source") == 0)
+ return GPIOD_LINE_DRIVE_OPEN_SOURCE;
+ if (strcmp(option, "push-pull") != 0)
+ die("invalid drive: %s", option);
+
+ return 0;
+}
+
+static int parse_periods_or_die(char *option, unsigned int **periods)
+{
+ int i, num_periods = 1;
+ unsigned int *pp;
+ char *end;
+
+ for (i = 0; option[i] != '\0'; i++)
+ if (option[i] == ',')
+ num_periods++;
+
+ pp = calloc(num_periods, sizeof(*pp));
+ if (pp == NULL)
+ die("out of memory");
+
+ for (i = 0; i < num_periods - 1; i++) {
+ for (end = option; *end != ','; end++)
+ ;
+
+ *end = '\0';
+ pp[i] = parse_period_or_die(option);
+ option = end + 1;
+ }
+ pp[i] = parse_period_or_die(option);
+ *periods = pp;
+
+ return num_periods;
+}
+
+static int parse_config(int argc, char **argv, struct config *cfg)
+{
+ static const struct option longopts[] = {
+ { "active-low", no_argument, NULL, 'l' },
+ { "banner", no_argument, NULL, '-'},
+ { "bias", required_argument, NULL, 'b' },
+ { "by-name", no_argument, NULL, 'B' },
+ { "chip", required_argument, NULL, 'c' },
+ { "consumer", required_argument, NULL, 'C' },
+ { "daemonize", no_argument, NULL, 'z' },
+ { "drive", required_argument, NULL, 'd' },
+ { "help", no_argument, NULL, 'h' },
+ { "hold-period", required_argument, NULL, 'p' },
+#ifdef GPIOSET_INTERACTIVE
+ { "interactive", no_argument, NULL, 'i' },
+#endif
+ { "strict", no_argument, NULL, 's' },
+ { "toggle", required_argument, NULL, 't' },
+ { "unquoted", no_argument, NULL, 'Q' },
+ { "version", no_argument, NULL, 'v' },
+ { GETOPT_NULL_LONGOPT },
+ };
+
+#ifdef GPIOSET_INTERACTIVE
+ static const char *const shortopts = "+b:c:C:d:hilp:st:vz";
+#else
+ static const char *const shortopts = "+b:c:C:d:hlp:st:vz";
+#endif
+
+ int opti, optc;
+
+ memset(cfg, 0, sizeof(*cfg));
+ cfg->consumer = "gpioset";
+
+ for (;;) {
+ optc = getopt_long(argc, argv, shortopts, longopts, &opti);
+ if (optc < 0)
+ break;
+
+ switch (optc) {
+ case '-':
+ cfg->banner = true;
+ break;
+ case 'b':
+ cfg->bias = parse_bias_or_die(optarg);
+ break;
+ case 'B':
+ cfg->by_name = true;
+ break;
+ case 'c':
+ cfg->chip_id = optarg;
+ break;
+ case 'C':
+ cfg->consumer = optarg;
+ break;
+ case 'd':
+ cfg->drive = parse_drive_or_die(optarg);
+ break;
+#ifdef GPIOSET_INTERACTIVE
+ case 'i':
+ cfg->interactive = true;
+ break;
+#endif
+ case 'l':
+ cfg->active_low = true;
+ break;
+ case 'p':
+ cfg->hold_period_us = parse_period_or_die(optarg);
+ break;
+ case 'Q':
+ cfg->unquoted = true;
+ break;
+ case 's':
+ cfg->strict = true;
+ break;
+ case 't':
+ cfg->toggles = parse_periods_or_die(optarg,
+ &cfg->toggle_periods);
+ break;
+ case 'z':
+ cfg->daemonize = true;
+ break;
+ case 'h':
+ print_help();
+ exit(EXIT_SUCCESS);
+ case 'v':
+ print_version();
+ exit(EXIT_SUCCESS);
+ case '?':
+ die("try %s --help", get_prog_name());
+ case 0:
+ break;
+ default:
+ abort();
+ }
+ }
+
+#ifdef GPIOSET_INTERACTIVE
+ if (cfg->toggles && cfg->interactive)
+ die("can't combine interactive with toggle");
+#endif
+
+ return optind;
+}
+
+static enum gpiod_line_value parse_value(const char *option)
+{
+ if (strcmp(option, "0") == 0)
+ return GPIOD_LINE_VALUE_INACTIVE;
+ if (strcmp(option, "1") == 0)
+ return GPIOD_LINE_VALUE_ACTIVE;
+ if (strcmp(option, "inactive") == 0)
+ return GPIOD_LINE_VALUE_INACTIVE;
+ if (strcmp(option, "active") == 0)
+ return GPIOD_LINE_VALUE_ACTIVE;
+ if (strcmp(option, "off") == 0)
+ return GPIOD_LINE_VALUE_INACTIVE;
+ if (strcmp(option, "on") == 0)
+ return GPIOD_LINE_VALUE_ACTIVE;
+ if (strcmp(option, "false") == 0)
+ return GPIOD_LINE_VALUE_INACTIVE;
+ if (strcmp(option, "true") == 0)
+ return GPIOD_LINE_VALUE_ACTIVE;
+
+ return GPIOD_LINE_VALUE_ERROR;
+}
+
+/*
+ * Parse line id and values from lvs into lines and values.
+ *
+ * Accepted forms:
+ * 'line=value'
+ * '"line"=value'
+ *
+ * If line id is quoted then it is returned unquoted.
+ */
+static bool parse_line_values(int num_lines, char **lvs, char **lines,
+ enum gpiod_line_value *values, bool interactive)
+{
+ char *value, *line;
+ int i;
+
+ for (i = 0; i < num_lines; i++) {
+ line = lvs[i];
+
+ if (*line != '"') {
+ value = strchr(line, '=');
+ } else {
+ line++;
+ value = strstr(line, "\"=");
+ if (value) {
+ *value = '\0';
+ value++;
+ }
+ }
+
+ if (!value) {
+ if (interactive)
+ printf("invalid line value: '%s'\n", lvs[i]);
+ else
+ print_error("invalid line value: '%s'", lvs[i]);
+
+ return false;
+ }
+
+ *value = '\0';
+ value++;
+ values[i] = parse_value(value);
+
+ if (values[i] == GPIOD_LINE_VALUE_ERROR) {
+ if (interactive)
+ printf("invalid line value: '%s'\n", value);
+ else
+ print_error("invalid line value: '%s'", value);
+
+ return false;
+ }
+
+ lines[i] = line;
+ }
+
+ return true;
+}
+
+/*
+ * Parse line id and values from lvs into lines and values, or die trying.
+ */
+static void parse_line_values_or_die(int num_lines, char **lvs, char **lines,
+ enum gpiod_line_value *values)
+{
+ if (!parse_line_values(num_lines, lvs, lines, values, false))
+ exit(EXIT_FAILURE);
+}
+
+static void print_banner(int num_lines, char **lines)
+{
+ int i;
+
+ if (num_lines > 1) {
+ printf("Setting lines ");
+
+ for (i = 0; i < num_lines - 1; i++)
+ printf("'%s', ", lines[i]);
+
+ printf("and '%s'...\n", lines[i]);
+ } else {
+ printf("Setting line '%s'...\n", lines[0]);
+ }
+ fflush(stdout);
+}
+
+static void wait_fd(int fd)
+{
+ struct pollfd pfd;
+
+ pfd.fd = fd;
+ pfd.events = POLLERR;
+
+ if (poll(&pfd, 1, -1) < 0)
+ die_perror("error waiting on request");
+}
+
+/*
+ * Apply values from the resolver to the requests.
+ * offset and values are scratch pads for working.
+ */
+static void apply_values(struct gpiod_line_request **requests,
+ struct line_resolver *resolver, unsigned int *offsets,
+ enum gpiod_line_value *values)
+{
+ int i;
+
+ for (i = 0; i < resolver->num_chips; i++) {
+ get_line_offsets_and_values(resolver, i, offsets, values);
+ if (gpiod_line_request_set_values(requests[i], values))
+ print_perror("unable to set values on '%s'",
+ get_chip_name(resolver, i));
+ }
+}
+
+/* Toggle the values of all lines in the resolver */
+static void toggle_all_lines(struct line_resolver *resolver)
+{
+ int i;
+
+ for (i = 0; i < resolver->num_lines; i++)
+ resolver->lines[i].value = !resolver->lines[i].value;
+}
+
+/*
+ * Toggle the resolved lines as specified by the toggle_periods,
+ * and apply the values to the requests.
+ * offset and values are scratch pads for working.
+ */
+static void toggle_sequence(int toggles, unsigned int *toggle_periods,
+ struct gpiod_line_request **requests,
+ struct line_resolver *resolver,
+ unsigned int *offsets,
+ enum gpiod_line_value *values)
+{
+ int i = 0;
+
+ if ((toggles == 1) && (toggle_periods[0] == 0))
+ return;
+
+ for (;;) {
+ usleep(toggle_periods[i]);
+ toggle_all_lines(resolver);
+ apply_values(requests, resolver, offsets, values);
+
+ i++;
+ if ((i == toggles - 1) && (toggle_periods[i] == 0))
+ return;
+
+ if (i == toggles)
+ i = 0;
+ }
+}
+
+#ifdef GPIOSET_INTERACTIVE
+
+/*
+ * Parse line id from words into lines.
+ *
+ * If line id is quoted then it is returned unquoted.
+ */
+static bool parse_line_ids(int num_lines, char **words, char **lines)
+{
+ int i, len;
+ char *line;
+
+ for (i = 0; i < num_lines; i++) {
+ line = words[i];
+ if (*line == '"') {
+ line++;
+ len = strlen(line);
+ if ((len == 0) || line[len - 1] != '"') {
+ printf("invalid line id: '%s'\n", words[i]);
+ return false;
+ }
+ line[len - 1] = '\0';
+ }
+ lines[i] = line;
+ }
+
+ return true;
+}
+
+/*
+ * Set the values in the resolver for the line values specified by
+ * the remaining parameters.
+ */
+static void set_line_values_subset(struct line_resolver *resolver,
+ int num_lines, char **lines,
+ enum gpiod_line_value *values)
+{
+ int l, i;
+
+ for (l = 0; l < num_lines; l++) {
+ for (i = 0; i < resolver->num_lines; i++) {
+ if (strcmp(lines[l], resolver->lines[i].id) == 0) {
+ resolver->lines[i].value = values[l];
+ break;
+ }
+ }
+ }
+}
+
+static void print_all_line_values(struct line_resolver *resolver, bool unquoted)
+{
+ char *fmt = unquoted ? "%s=%s " : "\"%s\"=%s ";
+ int i;
+
+ for (i = 0; i < resolver->num_lines; i++) {
+ if (i == resolver->num_lines - 1)
+ fmt = unquoted ? "%s=%s\n" : "\"%s\"=%s\n";
+
+ printf(fmt, resolver->lines[i].id,
+ resolver->lines[i].value ? "active" : "inactive");
+ }
+}
+
+/*
+ * Print the resovler line values for a subset of lines, specified by
+ * num_lines and lines.
+ */
+static void print_line_values(struct line_resolver *resolver, int num_lines,
+ char **lines, bool unquoted)
+{
+ char *fmt = unquoted ? "%s=%s " : "\"%s\"=%s ";
+ struct resolved_line *line;
+ int i, j;
+
+ for (i = 0; i < num_lines; i++) {
+ if (i == num_lines - 1)
+ fmt = unquoted ? "%s=%s\n" : "\"%s\"=%s\n";
+
+ for (j = 0; j < resolver->num_lines; j++) {
+ line = &resolver->lines[j];
+ if (strcmp(lines[i], line->id) == 0) {
+ printf(fmt, line->id,
+ line->value ? "active" : "inactive");
+ break;
+ }
+ }
+ }
+}
+
+/*
+ * Toggle a subset of lines, specified by num_lines and lines, in the resolver.
+ */
+static void toggle_lines(struct line_resolver *resolver, int num_lines,
+ char **lines)
+{
+ struct resolved_line *line;
+ int i, j;
+
+ for (i = 0; i < num_lines; i++)
+ for (j = 0; j < resolver->num_lines; j++) {
+ line = &resolver->lines[j];
+ if (strcmp(lines[i], line->id) == 0) {
+ line->value = !line->value;
+ break;
+ }
+ }
+}
+
+/*
+ * Check that a set of lines, specified by num_lines and lines, are all
+ * resolved lines.
+ */
+static bool valid_lines(struct line_resolver *resolver, int num_lines,
+ char **lines)
+{
+ bool ret = true, found;
+ int i, l;
+
+ for (l = 0; l < num_lines; l++) {
+ found = false;
+
+ for (i = 0; i < resolver->num_lines; i++) {
+ if (strcmp(lines[l], resolver->lines[i].id) == 0) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ printf("unknown line: '%s'\n", lines[l]);
+ ret = false;
+ }
+ }
+
+ return ret;
+}
+
+static void print_interactive_help(void)
+{
+ printf("COMMANDS:\n\n");
+ printf(" exit\n");
+ printf(" Exit the program\n");
+ printf(" get [line]...\n");
+ printf(" Display the output values of the given requested lines\n\n");
+ printf(" If no lines are specified then all requested lines are displayed\n\n");
+ printf(" help\n");
+ printf(" Print this help\n\n");
+ printf(" set <line=value>...\n");
+ printf(" Update the output values of the given requested lines\n\n");
+ printf(" sleep <period>\n");
+ printf(" Sleep for the specified period\n\n");
+ printf(" toggle [line]...\n");
+ printf(" Toggle the output values of the given requested lines\n\n");
+ printf(" If no lines are specified then all requested lines are toggled\n\n");
+}
+
+/*
+ * Split a line into words, returning the each of the words and the count.
+ *
+ * max_words specifies the max number of words that may be returned in words.
+ *
+ * Any escaping is ignored, on the assumption that the only escaped
+ * character of consequence is '"', and that names won't include quotes.
+ */
+static int split_words(char *line, int max_words, char **words)
+{
+ bool in_quotes = false, in_word = false;
+ int num_words = 0;
+
+ for (; (*line != '\0'); line++) {
+ if (!in_word) {
+ if (isspace(*line))
+ continue;
+
+ in_word = true;
+ in_quotes = (*line == '"');
+
+ /* count all words, but only store max_words */
+ if (num_words < max_words)
+ words[num_words] = line;
+ } else {
+ if (in_quotes) {
+ if (*line == '"')
+ in_quotes = false;
+ continue;
+ }
+ if (isspace(*line)) {
+ num_words++;
+ in_word = false;
+ *line = '\0';
+ }
+ }
+ }
+
+ if (in_word)
+ num_words++;
+
+ return num_words;
+}
+
+/* check if a line is specified somewhere in the rl_line_buffer */
+static bool in_line_buffer(const char *id)
+{
+ char *match = rl_line_buffer;
+ int len = strlen(id);
+
+ while ((match = strstr(match, id))) {
+ if ((match > rl_line_buffer && isspace(match[-1])) &&
+ ((match[len] == '=') || isspace(match[len])))
+ return true;
+
+ match += len;
+ }
+
+ return false;
+}
+
+/* context for complete_line_id, so it can provide valid line ids */
+static struct line_resolver *completion_context;
+
+/* tab completion helper for line ids */
+static char *complete_line_id(const char *text, int state)
+{
+ static int idx, len;
+ const char *id;
+
+ if (!state) {
+ idx = 0;
+ len = strlen(text);
+ }
+
+ while (idx < completion_context->num_lines) {
+ id = completion_context->lines[idx].id;
+ idx++;
+
+ if ((strncmp(id, text, len) == 0) && (!in_line_buffer(id)))
+ return strdup(id);
+ }
+
+ return NULL;
+}
+
+/* tab completion helper for line values (just the value component) */
+static char *complete_value(const char *text, int state)
+{
+ static const char * const values[] = {
+ "1", "0", "active", "inactive", "on", "off", "true", "false",
+ NULL
+ };
+
+ static int idx, len;
+
+ const char *value;
+
+ if (!state) {
+ idx = 0;
+ len = strlen(text);
+ }
+
+ while ((value = values[idx])) {
+ idx++;
+ if (strncmp(value, text, len) == 0)
+ return strdup(value);
+ }
+
+ return NULL;
+}
+
+/* tab completion help for interactive commands */
+static char *complete_command(const char *text, int state)
+{
+ static const char * const commands[] = {
+ "get", "set", "toggle", "sleep", "help", "exit", NULL
+ };
+
+ static int idx, len;
+
+ const char *cmd;
+
+ if (!state) {
+ idx = 0;
+ len = strlen(text);
+ }
+
+ while ((cmd = commands[idx])) {
+ idx++;
+ if (strncmp(cmd, text, len) == 0)
+ return strdup(cmd);
+ }
+ return NULL;
+}
+
+/* tab completion for interactive command lines */
+static char **tab_completion(const char *text, int start, int end)
+{
+ int cmd_start, cmd_end, len;
+ char **matches = NULL;
+
+ rl_attempted_completion_over = true;
+ rl_completion_type = '@';
+ rl_completion_append_character = ' ';
+
+ for (cmd_start = 0;
+ isspace(rl_line_buffer[cmd_start]) && cmd_start < end; cmd_start++)
+ ;
+
+ if (cmd_start == start)
+ matches = rl_completion_matches(text, complete_command);
+
+ for (cmd_end = cmd_start + 1;
+ !isspace(rl_line_buffer[cmd_end]) && cmd_end < end; cmd_end++)
+ ;
+
+ len = cmd_end - cmd_start;
+ if (len == 3 && strncmp("set ", &rl_line_buffer[cmd_start], 4) == 0) {
+ if (rl_line_buffer[start - 1] == '=') {
+ matches = rl_completion_matches(text, complete_value);
+ } else {
+ rl_completion_append_character = '=';
+ matches = rl_completion_matches(text, complete_line_id);
+ }
+ }
+
+ if ((len == 3 && strncmp("get ", &rl_line_buffer[cmd_start], 4) == 0) ||
+ (len == 6 &&
+ strncmp("toggle ", &rl_line_buffer[cmd_start], 7) == 0))
+ matches = rl_completion_matches(text, complete_line_id);
+
+ return matches;
+}
+
+#define PROMPT "gpioset> "
+
+static void interact(struct gpiod_line_request **requests,
+ struct line_resolver *resolver, char **lines,
+ unsigned int *offsets, enum gpiod_line_value *values,
+ bool unquoted)
+{
+ int num_words, num_lines, max_words, period_us, i;
+ char *line, **words, *line_buf;
+ bool done, stdout_is_tty;
+
+ stifle_history(20);
+ rl_attempted_completion_function = tab_completion;
+ rl_basic_word_break_characters = " =\"";
+ completion_context = resolver;
+ stdout_is_tty = isatty(1);
+
+ max_words = resolver->num_lines + 1;
+ words = calloc(max_words, sizeof(*words));
+ if (!words)
+ die("out of memory");
+
+ for (done = false; !done;) {
+ /*
+ * manually print the prompt, as libedit doesn't if stdout
+ * is not a tty. And fflush to ensure the prompt and any
+ * output buffered from the previous command is sent.
+ */
+ if (!stdout_is_tty)
+ printf(PROMPT);
+ fflush(stdout);
+
+ line = readline(PROMPT);
+ if (!line || line[0] == '\0') {
+ free(line);
+ continue;
+ }
+
+ for (i = strlen(line) - 1; (i > 0) && isspace(line[i]); i--)
+ line[i] = '\0';
+
+ line_buf = strdup(line);
+ num_words = split_words(line_buf, max_words, words);
+ if (num_words > max_words) {
+ printf("too many command parameters provided\n");
+ goto cmd_done;
+ }
+ num_lines = num_words - 1;
+ if (strcmp(words[0], "get") == 0) {
+ if (num_lines == 0)
+ print_all_line_values(resolver, unquoted);
+ else if (parse_line_ids(num_lines, &words[1], lines) &&
+ valid_lines(resolver, num_lines, lines))
+ print_line_values(resolver, num_lines, lines,
+ unquoted);
+ goto cmd_ok;
+ }
+ if (strcmp(words[0], "set") == 0) {
+ if (num_lines == 0)
+ printf("at least one GPIO line value must be specified\n");
+ else if (parse_line_values(num_lines, &words[1], lines,
+ values, true) &&
+ valid_lines(resolver, num_lines, lines)) {
+ set_line_values_subset(resolver, num_lines,
+ lines, values);
+ apply_values(requests, resolver, offsets,
+ values);
+ }
+ goto cmd_ok;
+ }
+ if (strcmp(words[0], "toggle") == 0) {
+ if (num_lines == 0)
+ toggle_all_lines(resolver);
+ else if (parse_line_ids(num_lines, &words[1], lines) &&
+ valid_lines(resolver, num_lines, lines))
+ toggle_lines(resolver, num_lines, lines);
+
+ apply_values(requests, resolver, offsets, values);
+ goto cmd_ok;
+ }
+ if (strcmp(words[0], "sleep") == 0) {
+ if (num_lines == 0) {
+ printf("a period must be specified\n");
+ goto cmd_ok;
+ }
+ if (num_lines > 1) {
+ printf("only one period can be specified\n");
+ goto cmd_ok;
+ }
+ period_us = parse_period(words[1]);
+ if (period_us < 0) {
+ printf("invalid period: '%s'\n", words[1]);
+ goto cmd_ok;
+ }
+ usleep(period_us);
+ goto cmd_ok;
+ }
+
+ if (strcmp(words[0], "exit") == 0) {
+ done = true;
+ goto cmd_done;
+ }
+
+ if (strcmp(words[0], "help") == 0) {
+ print_interactive_help();
+ goto cmd_done;
+ }
+
+ printf("unknown command: '%s'\n", words[0]);
+ printf("Try the 'help' command\n");
+
+cmd_ok:
+ for (i = 0; isspace(line[i]); i++)
+ ;
+
+ if ((history_length) == 0 ||
+ (strcmp(history_list()[history_length - 1]->line,
+ &line[i]) != 0))
+ add_history(&line[i]);
+
+cmd_done:
+ free(line);
+ free(line_buf);
+ }
+ free(words);
+}
+
+#endif /* GPIOSET_INTERACTIVE */
+
+int main(int argc, char **argv)
+{
+ struct gpiod_line_settings *settings;
+ struct gpiod_request_config *req_cfg;
+ struct gpiod_line_request **requests;
+ struct gpiod_line_config *line_cfg;
+ struct line_resolver *resolver;
+ enum gpiod_line_value *values;
+ struct gpiod_chip *chip;
+ unsigned int *offsets;
+ int i, num_lines, ret;
+ struct config cfg;
+ char **lines;
+
+ set_prog_name(argv[0]);
+ i = parse_config(argc, argv, &cfg);
+ argc -= i;
+ argv += i;
+
+ if (argc < 1)
+ die("at least one GPIO line value must be specified");
+
+ num_lines = argc;
+
+ lines = calloc(num_lines, sizeof(*lines));
+ values = calloc(num_lines, sizeof(*values));
+ if (!lines || !values)
+ die("out of memory");
+
+ parse_line_values_or_die(argc, argv, lines, values);
+
+ settings = gpiod_line_settings_new();
+ if (!settings)
+ die_perror("unable to allocate line settings");
+
+ if (cfg.bias)
+ gpiod_line_settings_set_bias(settings, cfg.bias);
+
+ if (cfg.drive)
+ gpiod_line_settings_set_drive(settings, cfg.drive);
+
+ if (cfg.active_low)
+ gpiod_line_settings_set_active_low(settings, true);
+
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+
+ req_cfg = gpiod_request_config_new();
+ if (!req_cfg)
+ die_perror("unable to allocate the request config structure");
+
+ gpiod_request_config_set_consumer(req_cfg, cfg.consumer);
+ resolver = resolve_lines(num_lines, lines, cfg.chip_id, cfg.strict,
+ cfg.by_name);
+ validate_resolution(resolver, cfg.chip_id);
+ for (i = 0; i < num_lines; i++)
+ resolver->lines[i].value = values[i];
+
+ requests = calloc(resolver->num_chips, sizeof(*requests));
+ offsets = calloc(num_lines, sizeof(*offsets));
+ if (!requests || !offsets)
+ die("out of memory");
+
+ line_cfg = gpiod_line_config_new();
+ if (!line_cfg)
+ die_perror("unable to allocate the line config structure");
+
+ for (i = 0; i < resolver->num_chips; i++) {
+ num_lines = get_line_offsets_and_values(resolver, i, offsets,
+ values);
+
+ gpiod_line_config_reset(line_cfg);
+
+ ret = gpiod_line_config_add_line_settings(line_cfg, offsets,
+ num_lines, settings);
+ if (ret)
+ die_perror("unable to add line settings");
+
+ ret = gpiod_line_config_set_output_values(line_cfg,
+ values, num_lines);
+ if (ret)
+ die_perror("unable to set output values");
+
+ chip = gpiod_chip_open(resolver->chips[i].path);
+ if (!chip)
+ die_perror("unable to open chip '%s'",
+ resolver->chips[i].path);
+
+ requests[i] = gpiod_chip_request_lines(chip, req_cfg, line_cfg);
+ if (!requests[i])
+ die_perror("unable to request lines on chip '%s'",
+ resolver->chips[i].path);
+
+ gpiod_chip_close(chip);
+ }
+
+ gpiod_request_config_free(req_cfg);
+ gpiod_line_config_free(line_cfg);
+ gpiod_line_settings_free(settings);
+
+ if (cfg.banner)
+ print_banner(argc, lines);
+
+ if (cfg.daemonize)
+ if (daemon(0, cfg.interactive) < 0)
+ die_perror("unable to daemonize");
+
+ if (cfg.toggles) {
+ for (i = 0; i < cfg.toggles; i++)
+ if ((cfg.hold_period_us > cfg.toggle_periods[i]) &&
+ ((i != cfg.toggles - 1) ||
+ cfg.toggle_periods[i] != 0))
+ cfg.toggle_periods[i] = cfg.hold_period_us;
+
+ toggle_sequence(cfg.toggles, cfg.toggle_periods, requests,
+ resolver, offsets, values);
+ free(cfg.toggle_periods);
+ }
+
+ if (cfg.hold_period_us)
+ usleep(cfg.hold_period_us);
+
+#ifdef GPIOSET_INTERACTIVE
+ if (cfg.interactive)
+ interact(requests, resolver, lines, offsets, values,
+ cfg.unquoted);
+ else if (!cfg.toggles)
+ wait_fd(gpiod_line_request_get_fd(requests[0]));
+#else
+ if (!cfg.toggles)
+ wait_fd(gpiod_line_request_get_fd(requests[0]));
+#endif
+
+ for (i = 0; i < resolver->num_chips; i++)
+ gpiod_line_request_release(requests[i]);
+
+ free(requests);
+ free_line_resolver(resolver);
+ free(lines);
+ free(values);
+ free(offsets);
+
+ return EXIT_SUCCESS;
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+// SPDX-FileCopyrightText: 2022 Kent Gibson <warthog618@gmail.com>
+
+/* Common code for GPIO tools. */
+
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <gpiod.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <time.h>
+
+#include "tools-common.h"
+
+static const char *prog_name = NULL;
+static const char *prog_short_name = NULL;
+
+void set_prog_name(const char *name)
+{
+ prog_name = name;
+ prog_short_name = name;
+ while (*name) {
+ if (*name++ == '/') {
+ prog_short_name = name;
+ }
+ }
+}
+
+const char *get_prog_name(void)
+{
+ return prog_name;
+}
+
+const char *get_prog_short_name(void)
+{
+ return prog_short_name;
+}
+
+void print_error(const char *fmt, ...)
+{
+ va_list va;
+
+ va_start(va, fmt);
+ fprintf(stderr, "%s: ", get_prog_name());
+ vfprintf(stderr, fmt, va);
+ fprintf(stderr, "\n");
+ va_end(va);
+}
+
+void print_perror(const char *fmt, ...)
+{
+ va_list va;
+
+ va_start(va, fmt);
+ fprintf(stderr, "%s: ", get_prog_name());
+ vfprintf(stderr, fmt, va);
+ fprintf(stderr, ": %s\n", strerror(errno));
+ va_end(va);
+}
+
+void die(const char *fmt, ...)
+{
+ va_list va;
+
+ va_start(va, fmt);
+ fprintf(stderr, "%s: ", get_prog_name());
+ vfprintf(stderr, fmt, va);
+ fprintf(stderr, "\n");
+ va_end(va);
+
+ exit(EXIT_FAILURE);
+}
+
+void die_perror(const char *fmt, ...)
+{
+ va_list va;
+
+ va_start(va, fmt);
+ fprintf(stderr, "%s: ", get_prog_name());
+ vfprintf(stderr, fmt, va);
+ fprintf(stderr, ": %s\n", strerror(errno));
+ va_end(va);
+
+ exit(EXIT_FAILURE);
+}
+
+void print_version(void)
+{
+ printf("%s (libgpiod) v%s\n", get_prog_short_name(), gpiod_api_version());
+ printf("Copyright (C) 2017-2023 Bartosz Golaszewski\n");
+ printf("License: GPL-2.0-or-later\n");
+ printf("This is free software: you are free to change and redistribute it.\n");
+ printf("There is NO WARRANTY, to the extent permitted by law.\n");
+}
+
+int parse_bias_or_die(const char *option)
+{
+ if (strcmp(option, "pull-down") == 0)
+ return GPIOD_LINE_BIAS_PULL_DOWN;
+ if (strcmp(option, "pull-up") == 0)
+ return GPIOD_LINE_BIAS_PULL_UP;
+ if (strcmp(option, "disabled") != 0)
+ die("invalid bias: %s", option);
+
+ return GPIOD_LINE_BIAS_DISABLED;
+}
+
+int parse_period(const char *option)
+{
+ unsigned long p, m = 0;
+ char *end;
+
+ p = strtoul(option, &end, 10);
+
+ switch (*end) {
+ case 'u':
+ m = 1;
+ end++;
+ break;
+ case 'm':
+ m = 1000;
+ end++;
+ break;
+ case 's':
+ m = 1000000;
+ break;
+ case '\0':
+ break;
+ default:
+ return -1;
+ }
+
+ if (m) {
+ if (*end != 's')
+ return -1;
+
+ end++;
+ } else {
+ m = 1000;
+ }
+
+ p *= m;
+ if (*end != '\0' || p > INT_MAX)
+ return -1;
+
+ return p;
+}
+
+unsigned int parse_period_or_die(const char *option)
+{
+ int period = parse_period(option);
+
+ if (period < 0)
+ die("invalid period: %s", option);
+
+ return period;
+}
+
+int parse_uint(const char *option)
+{
+ unsigned long o;
+ char *end;
+
+ o = strtoul(option, &end, 10);
+ if (*end == '\0' && o <= INT_MAX)
+ return o;
+
+ return -1;
+}
+
+unsigned int parse_uint_or_die(const char *option)
+{
+ int i = parse_uint(option);
+
+ if (i < 0)
+ die("invalid number: '%s'", option);
+
+ return i;
+}
+
+void print_bias_help(void)
+{
+ printf(" -b, --bias <bias>\tspecify the line bias\n");
+ printf("\t\t\tPossible values: 'pull-down', 'pull-up', 'disabled'.\n");
+ printf("\t\t\t(default is to leave bias unchanged)\n");
+}
+
+void print_chip_help(void)
+{
+ printf("\nChips:\n");
+ printf(" A GPIO chip may be identified by number, name, or path.\n");
+ printf(" e.g. '0', 'gpiochip0', and '/dev/gpiochip0' all refer to the same chip.\n");
+}
+
+void print_period_help(void)
+{
+ printf("\nPeriods:\n");
+ printf(" Periods are taken as milliseconds unless units are specified. e.g. 10us.\n");
+ printf(" Supported units are 's', 'ms', and 'us'.\n");
+}
+
+#define TIME_BUFFER_SIZE 20
+
+/*
+ * format:
+ * 0: raw seconds
+ * 1: utc time
+ * 2: local time
+ */
+void print_event_time(uint64_t evtime, int format)
+{
+ char tbuf[TIME_BUFFER_SIZE];
+ time_t evtsec;
+ struct tm t;
+ char *tz;
+
+ if (format) {
+ evtsec = evtime / 1000000000;
+ if (format == 2) {
+ localtime_r(&evtsec, &t);
+ tz = "";
+ } else {
+ gmtime_r(&evtsec, &t);
+ tz = "Z";
+ }
+ strftime(tbuf, TIME_BUFFER_SIZE, "%FT%T", &t);
+ printf("%s.%09" PRIu64 "%s", tbuf, evtime % 1000000000, tz);
+ } else {
+ printf("%" PRIu64 ".%09" PRIu64, evtime / 1000000000,
+ evtime % 1000000000);
+ }
+}
+
+static void print_bias(struct gpiod_line_info *info)
+{
+ const char *name;
+
+ switch (gpiod_line_info_get_bias(info)) {
+ case GPIOD_LINE_BIAS_PULL_UP:
+ name = "pull-up";
+ break;
+ case GPIOD_LINE_BIAS_PULL_DOWN:
+ name = "pull-down";
+ break;
+ case GPIOD_LINE_BIAS_DISABLED:
+ name = "disabled";
+ break;
+ default:
+ return;
+ }
+
+ printf(" bias=%s", name);
+}
+
+static void print_drive(struct gpiod_line_info *info)
+{
+ const char *name;
+
+ switch (gpiod_line_info_get_drive(info)) {
+ case GPIOD_LINE_DRIVE_OPEN_DRAIN:
+ name = "open-drain";
+ break;
+ case GPIOD_LINE_DRIVE_OPEN_SOURCE:
+ name = "open-source";
+ break;
+ default:
+ return;
+ }
+
+ printf(" drive=%s", name);
+}
+
+static void print_edge_detection(struct gpiod_line_info *info)
+{
+ const char *name;
+
+ switch (gpiod_line_info_get_edge_detection(info)) {
+ case GPIOD_LINE_EDGE_BOTH:
+ name = "both";
+ break;
+ case GPIOD_LINE_EDGE_RISING:
+ name = "rising";
+ break;
+ case GPIOD_LINE_EDGE_FALLING:
+ name = "falling";
+ break;
+ default:
+ return;
+ }
+
+ printf(" edges=%s", name);
+}
+
+static void print_event_clock(struct gpiod_line_info *info)
+{
+ const char *name;
+
+ switch (gpiod_line_info_get_event_clock(info)) {
+ case GPIOD_LINE_CLOCK_REALTIME:
+ name = "realtime";
+ break;
+ case GPIOD_LINE_CLOCK_HTE:
+ name = "hte";
+ break;
+ default:
+ return;
+ }
+
+ printf(" event-clock=%s", name);
+}
+
+static void print_debounce(struct gpiod_line_info *info)
+{
+ const char *units = "us";
+ unsigned long debounce;
+
+ debounce = gpiod_line_info_get_debounce_period_us(info);
+ if (!debounce)
+ return;
+ if (debounce % 1000000 == 0) {
+ debounce /= 1000000;
+ units = "s";
+ } else if (debounce % 1000 == 0) {
+ debounce /= 1000;
+ units = "ms";
+ }
+ printf(" debounce-period=%lu%s", debounce, units);
+}
+
+static void print_consumer(struct gpiod_line_info *info, bool unquoted)
+{
+ const char *consumer;
+ const char *fmt;
+
+ if (!gpiod_line_info_is_used(info))
+ return;
+
+ consumer = gpiod_line_info_get_consumer(info);
+ if (!consumer)
+ consumer = "kernel";
+
+ fmt = unquoted ? " consumer=%s" : " consumer=\"%s\"";
+
+ printf(fmt, consumer);
+}
+
+void print_line_attributes(struct gpiod_line_info *info, bool unquoted_strings)
+{
+ enum gpiod_line_direction direction;
+
+ direction = gpiod_line_info_get_direction(info);
+
+ printf("%s", direction == GPIOD_LINE_DIRECTION_INPUT ?
+ "input" : "output");
+
+ if (gpiod_line_info_is_active_low(info))
+ printf(" active-low");
+
+ print_drive(info);
+ print_bias(info);
+ print_edge_detection(info);
+ print_event_clock(info);
+ print_debounce(info);
+ print_consumer(info, unquoted_strings);
+}
+
+void print_line_id(struct line_resolver *resolver, int chip_num,
+ unsigned int offset, const char *chip_id, bool unquoted)
+{
+ const char *lname, *fmt;
+
+ lname = get_line_name(resolver, chip_num, offset);
+ if (!lname) {
+ printf("%s %u", get_chip_name(resolver, chip_num), offset);
+ return;
+ }
+ if (chip_id)
+ printf("%s %u ", get_chip_name(resolver, chip_num), offset);
+
+ fmt = unquoted ? "%s" : "\"%s\"";
+ printf(fmt, lname);
+}
+
+static int chip_dir_filter(const struct dirent *entry)
+{
+ struct stat sb;
+ int ret = 0;
+ char *path;
+
+ if (asprintf(&path, "/dev/%s", entry->d_name) < 0)
+ return 0;
+
+ if ((lstat(path, &sb) == 0) && (!S_ISLNK(sb.st_mode)) &&
+ gpiod_is_gpiochip_device(path))
+ ret = 1;
+
+ free(path);
+
+ return ret;
+}
+
+static bool isuint(const char *str)
+{
+ for (; *str && isdigit(*str); str++)
+ ;
+
+ return *str == '\0';
+}
+
+bool chip_path_lookup(const char *id, char **path_ptr)
+{
+ char *path;
+
+ if (isuint(id)) {
+ /* by number */
+ if (asprintf(&path, "/dev/gpiochip%s", id) < 0)
+ return false;
+ } else if (strchr(id, '/')) {
+ /* by path */
+ if (asprintf(&path, "%s", id) < 0)
+ return false;
+ } else {
+ /* by device name */
+ if (asprintf(&path, "/dev/%s", id) < 0)
+ return false;
+ }
+
+ if (!gpiod_is_gpiochip_device(path)) {
+ free(path);
+ return false;
+ }
+
+ *path_ptr = path;
+
+ return true;
+}
+
+int chip_paths(const char *id, char ***paths_ptr)
+{
+ char **paths;
+ char *path;
+
+ if (id == NULL)
+ return all_chip_paths(paths_ptr);
+
+ if (!chip_path_lookup(id, &path))
+ return 0;
+
+ paths = malloc(sizeof(*paths));
+ if (paths == NULL)
+ die("out of memory");
+
+ paths[0] = path;
+ *paths_ptr = paths;
+
+ return 1;
+}
+
+int all_chip_paths(char ***paths_ptr)
+{
+ int i, j, num_chips, ret = 0;
+ struct dirent **entries;
+ char **paths;
+
+ num_chips = scandir("/dev/", &entries, chip_dir_filter, versionsort);
+ if (num_chips < 0)
+ die_perror("unable to scan /dev");
+
+ paths = calloc(num_chips, sizeof(*paths));
+ if (paths == NULL)
+ die("out of memory");
+
+ for (i = 0; i < num_chips; i++) {
+ if (asprintf(&paths[i], "/dev/%s", entries[i]->d_name) < 0) {
+ for (j = 0; j < i; j++)
+ free(paths[j]);
+
+ free(paths);
+ return 0;
+ }
+ }
+
+ *paths_ptr = paths;
+ ret = num_chips;
+
+ for (i = 0; i < num_chips; i++)
+ free(entries[i]);
+
+ free(entries);
+ return ret;
+}
+
+static bool resolve_line(struct line_resolver *resolver,
+ struct gpiod_line_info *info, int chip_num)
+{
+ struct resolved_line *line;
+ bool resolved = false;
+ unsigned int offset;
+ const char *name;
+ int i;
+
+ offset = gpiod_line_info_get_offset(info);
+ for (i = 0; i < resolver->num_lines; i++) {
+ line = &resolver->lines[i];
+ /* already resolved by offset? */
+ if (line->resolved && (line->offset == offset) &&
+ (line->chip_num == chip_num)) {
+ line->info = info;
+ resolver->num_found++;
+ resolved = true;
+ }
+ if (line->resolved && !resolver->strict)
+ continue;
+
+ /* else resolve by name */
+ name = gpiod_line_info_get_name(info);
+ if (name && (strcmp(line->id, name) == 0)) {
+ if (resolver->strict && line->resolved)
+ die("line '%s' is not unique", line->id);
+ line->offset = offset;
+ line->info = info;
+ line->chip_num = resolver->num_chips;
+ line->resolved = true;
+ resolver->num_found++;
+ resolved = true;
+ }
+ }
+
+ return resolved;
+}
+
+/*
+ * check for lines that can be identified by offset
+ *
+ * This only applies to the first chip, as otherwise the lines must be
+ * identified by name.
+ */
+bool resolve_lines_by_offset(struct line_resolver *resolver,
+ unsigned int num_lines)
+{
+ struct resolved_line *line;
+ bool used = false;
+ int i;
+
+ for (i = 0; i < resolver->num_lines; i++) {
+ line = &resolver->lines[i];
+ if ((line->id_as_offset != -1) &&
+ (line->id_as_offset < (int)num_lines)) {
+ line->chip_num = 0;
+ line->offset = line->id_as_offset;
+ line->resolved = true;
+ used = true;
+ }
+ }
+ return used;
+}
+
+bool resolve_done(struct line_resolver *resolver)
+{
+ return (!resolver->strict &&
+ resolver->num_found >= resolver->num_lines);
+}
+
+struct line_resolver *resolver_init(int num_lines, char **lines, int num_chips,
+ bool strict, bool by_name)
+{
+ struct line_resolver *resolver;
+ struct resolved_line *line;
+ size_t resolver_size;
+ int i;
+
+ resolver_size = sizeof(*resolver) + num_lines * sizeof(*line);
+ resolver = malloc(resolver_size);
+ if (resolver == NULL)
+ die("out of memory");
+
+ memset(resolver, 0, resolver_size);
+
+ resolver->chips = calloc(num_chips, sizeof(struct resolved_chip));
+ if (resolver->chips == NULL)
+ die("out of memory");
+ memset(resolver->chips, 0, num_chips * sizeof(struct resolved_chip));
+
+ resolver->num_lines = num_lines;
+ resolver->strict = strict;
+ for (i = 0; i < num_lines; i++) {
+ line = &resolver->lines[i];
+ line->id = lines[i];
+ line->id_as_offset = by_name ? -1 : parse_uint(lines[i]);
+ line->chip_num = -1;
+ }
+
+ return resolver;
+}
+
+struct line_resolver *resolve_lines(int num_lines, char **lines,
+ const char *chip_id, bool strict,
+ bool by_name)
+{
+ struct gpiod_chip_info *chip_info;
+ struct gpiod_line_info *line_info;
+ struct line_resolver *resolver;
+ int num_chips, i, offset;
+ struct gpiod_chip *chip;
+ bool chip_used;
+ char **paths;
+
+ if (chip_id == NULL)
+ by_name = true;
+
+ num_chips = chip_paths(chip_id, &paths);
+ if (chip_id && (num_chips == 0))
+ die("cannot find GPIO chip character device '%s'", chip_id);
+
+ resolver = resolver_init(num_lines, lines, num_chips, strict, by_name);
+
+ for (i = 0; (i < num_chips) && !resolve_done(resolver); i++) {
+ chip_used = false;
+ chip = gpiod_chip_open(paths[i]);
+ if (!chip) {
+ if ((errno == EACCES) && (chip_id == NULL)) {
+ free(paths[i]);
+ continue;
+ }
+
+ die_perror("unable to open chip '%s'", paths[i]);
+ }
+
+ chip_info = gpiod_chip_get_info(chip);
+ if (!chip_info)
+ die_perror("unable to get info for '%s'", paths[i]);
+
+ num_lines = gpiod_chip_info_get_num_lines(chip_info);
+
+ if (i == 0 && chip_id && !by_name)
+ chip_used = resolve_lines_by_offset(resolver, num_lines);
+
+ for (offset = 0;
+ (offset < num_lines) && !resolve_done(resolver);
+ offset++) {
+ line_info = gpiod_chip_get_line_info(chip, offset);
+ if (!line_info)
+ die_perror("unable to read the info for line %d from %s",
+ offset,
+ gpiod_chip_info_get_name(chip_info));
+
+ if (resolve_line(resolver, line_info, i))
+ chip_used = true;
+ else
+ gpiod_line_info_free(line_info);
+
+ }
+
+ gpiod_chip_close(chip);
+
+ if (chip_used) {
+ resolver->chips[resolver->num_chips].info = chip_info;
+ resolver->chips[resolver->num_chips].path = paths[i];
+ resolver->num_chips++;
+ } else {
+ gpiod_chip_info_free(chip_info);
+ free(paths[i]);
+ }
+ }
+ free(paths);
+
+ return resolver;
+}
+
+void validate_resolution(struct line_resolver *resolver, const char *chip_id)
+{
+ struct resolved_line *line, *prev;
+ bool valid = true;
+ int i, j;
+
+ for (i = 0; i < resolver->num_lines; i++) {
+ line = &resolver->lines[i];
+ if (line->resolved) {
+ for (j = 0; j < i; j++) {
+ prev = &resolver->lines[j];
+ if (prev->resolved &&
+ (prev->chip_num == line->chip_num) &&
+ (prev->offset == line->offset)) {
+ print_error("lines '%s' and '%s' are the same line",
+ prev->id, line->id);
+ valid = false;
+ break;
+ }
+ }
+ continue;
+ }
+ valid = false;
+ if (chip_id && line->id_as_offset != -1)
+ print_error("offset %s is out of range on chip '%s'",
+ line->id, chip_id);
+ else
+ print_error("cannot find line '%s'", line->id);
+ }
+ if (!valid)
+ exit(EXIT_FAILURE);
+}
+
+void free_line_resolver(struct line_resolver *resolver)
+{
+ int i;
+
+ if (!resolver)
+ return;
+
+ for (i = 0; i < resolver->num_lines; i++)
+ gpiod_line_info_free(resolver->lines[i].info);
+
+ for (i = 0; i < resolver->num_chips; i++) {
+ gpiod_chip_info_free(resolver->chips[i].info);
+ free(resolver->chips[i].path);
+ }
+
+ free(resolver->chips);
+ free(resolver);
+}
+
+int get_line_offsets_and_values(struct line_resolver *resolver, int chip_num,
+ unsigned int *offsets,
+ enum gpiod_line_value *values)
+{
+ struct resolved_line *line;
+ int i, num_lines = 0;
+
+ for (i = 0; i < resolver->num_lines; i++) {
+ line = &resolver->lines[i];
+ if (line->chip_num == chip_num) {
+ offsets[num_lines] = line->offset;
+ if (values)
+ values[num_lines] = line->value;
+
+ num_lines++;
+ }
+ }
+
+ return num_lines;
+}
+
+const char *get_chip_name(struct line_resolver *resolver, int chip_num)
+{
+ return gpiod_chip_info_get_name(resolver->chips[chip_num].info);
+}
+
+const char *get_line_name(struct line_resolver *resolver, int chip_num,
+ unsigned int offset)
+{
+ struct resolved_line *line;
+ int i;
+
+ for (i = 0; i < resolver->num_lines; i++) {
+ line = &resolver->lines[i];
+ if (line->info && (line->offset == offset) &&
+ (line->chip_num == chip_num))
+ return gpiod_line_info_get_name(
+ resolver->lines[i].info);
+ }
+
+ return 0;
+}
+
+void set_line_values(struct line_resolver *resolver, int chip_num,
+ enum gpiod_line_value *values)
+{
+ int i, j;
+
+ for (i = 0, j = 0; i < resolver->num_lines; i++) {
+ if (resolver->lines[i].chip_num == chip_num) {
+ resolver->lines[i].value = values[j];
+ j++;
+ }
+ }
+}
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com> */
+/* SPDX-FileCopyrightText: 2022 Kent Gibson <warthog618@gmail.com> */
+
+#ifndef __GPIOD_TOOLS_COMMON_H__
+#define __GPIOD_TOOLS_COMMON_H__
+
+#include <gpiod.h>
+
+/*
+ * Various helpers for the GPIO tools.
+ *
+ * NOTE: This is not a stable interface - it's only to avoid duplicating
+ * common code.
+ */
+
+#define NORETURN __attribute__((noreturn))
+#define PRINTF(fmt, arg) __attribute__((format(printf, fmt, arg)))
+
+#define GETOPT_NULL_LONGOPT NULL, 0, NULL, 0
+
+struct resolved_line {
+ /* from the command line */
+ const char *id;
+
+ /*
+ * id parsed as int, if that is an option, or -1 if line must be
+ * resolved by name
+ */
+ int id_as_offset;
+
+ /* line has been located on a chip */
+ bool resolved;
+
+ /* remaining fields only valid once resolved... */
+
+ /* info for the line */
+ struct gpiod_line_info *info;
+
+ /* num of relevant chip in line_resolver */
+ int chip_num;
+
+ /* offset of line on chip */
+ unsigned int offset;
+
+ /* line value for gpioget/set */
+ int value;
+};
+
+struct resolved_chip {
+ /* info of the relevant chips */
+ struct gpiod_chip_info *info;
+
+ /* path to the chip */
+ char *path;
+};
+
+/* a resolver from requested line names/offsets to lines on the system */
+struct line_resolver {
+ /*
+ * number of chips the lines span, and number of entries in chips
+ */
+ int num_chips;
+
+ /* number of lines in lines */
+ int num_lines;
+
+ /* number of lines found */
+ int num_found;
+
+ /* perform exhaustive search to check line names are unique */
+ bool strict;
+
+ /* details of the relevant chips */
+ struct resolved_chip *chips;
+
+ /* descriptors for the requested lines */
+ struct resolved_line lines[];
+};
+
+void set_prog_name(const char *name);
+const char *get_prog_name(void);
+const char *get_prog_short_name(void);
+void print_error(const char *fmt, ...) PRINTF(1, 2);
+void print_perror(const char *fmt, ...) PRINTF(1, 2);
+void die(const char *fmt, ...) NORETURN PRINTF(1, 2);
+void die_perror(const char *fmt, ...) NORETURN PRINTF(1, 2);
+void print_version(void);
+int parse_bias_or_die(const char *option);
+int parse_period(const char *option);
+unsigned int parse_period_or_die(const char *option);
+int parse_uint(const char *option);
+unsigned int parse_uint_or_die(const char *option);
+void print_bias_help(void);
+void print_chip_help(void);
+void print_period_help(void);
+void print_event_time(uint64_t evtime, int format);
+void print_line_attributes(struct gpiod_line_info *info, bool unquoted_strings);
+void print_line_id(struct line_resolver *resolver, int chip_num,
+ unsigned int offset, const char *chip_id, bool unquoted);
+bool chip_path_lookup(const char *id, char **path_ptr);
+int chip_paths(const char *id, char ***paths_ptr);
+int all_chip_paths(char ***paths_ptr);
+struct line_resolver *resolve_lines(int num_lines, char **lines,
+ const char *chip_id, bool strict,
+ bool by_name);
+struct line_resolver *resolver_init(int num_lines, char **lines, int num_chips,
+ bool strict, bool by_name);
+bool resolve_lines_by_offset(struct line_resolver *resolver,
+ unsigned int num_lines);
+bool resolve_done(struct line_resolver *resolver);
+void validate_resolution(struct line_resolver *resolver, const char *chip_id);
+void free_line_resolver(struct line_resolver *resolver);
+int get_line_offsets_and_values(struct line_resolver *resolver, int chip_num,
+ unsigned int *offsets,
+ enum gpiod_line_value *values);
+const char *get_chip_name(struct line_resolver *resolver, int chip_num);
+const char *get_line_name(struct line_resolver *resolver, int chip_num,
+ unsigned int offset);
+void set_line_values(struct line_resolver *resolver, int chip_num,
+ enum gpiod_line_value *values);
+
+#endif /* __GPIOD_TOOLS_COMMON_H__ */