Project import generated by Copybara.
GitOrigin-RevId: 736879d3e4e7f92b1490308628540ec547cda7d0
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..dc96031
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,75 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE:= libgpiod
+LOCAL_SRC_FILES := \
+ lib/core.c \
+ lib/ctxless.c \
+ lib/helpers.c \
+ lib/iter.c \
+ lib/misc.c
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
+LOCAL_MODULE_TAGS := optional
+LOCAL_CFLAGS := -Wall -Wno-missing-field-initializers
+include $(BUILD_SHARED_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE:= gpiodetect
+LOCAL_SRC_FILES := \
+ tools/$(LOCAL_MODULE).c \
+ tools/tools-common.c
+LOCAL_SHARED_LIBRARIES := libgpiod
+LOCAL_MODULE_TAGS := optional
+LOCAL_CFLAGS := -Wall -Wno-missing-field-initializers
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE:= gpiofind
+LOCAL_SRC_FILES := \
+ tools/$(LOCAL_MODULE).c \
+ tools/tools-common.c
+LOCAL_SHARED_LIBRARIES := libgpiod
+LOCAL_MODULE_TAGS := optional
+LOCAL_CFLAGS := -Wall -Wno-missing-field-initializers
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE:= gpioget
+LOCAL_SRC_FILES := \
+ tools/$(LOCAL_MODULE).c \
+ tools/tools-common.c
+LOCAL_SHARED_LIBRARIES := libgpiod
+LOCAL_MODULE_TAGS := optional
+LOCAL_CFLAGS := -Wall -Wno-missing-field-initializers
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE:= gpioset
+LOCAL_SRC_FILES := \
+ tools/$(LOCAL_MODULE).c \
+ tools/tools-common.c
+LOCAL_SHARED_LIBRARIES := libgpiod
+LOCAL_MODULE_TAGS := optional
+LOCAL_CFLAGS := -Wall -Wno-missing-field-initializers
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE:= gpioinfo
+LOCAL_SRC_FILES := \
+ tools/$(LOCAL_MODULE).c \
+ tools/tools-common.c
+LOCAL_SHARED_LIBRARIES := libgpiod
+LOCAL_MODULE_TAGS := optional
+LOCAL_CFLAGS := -Wall -Wno-missing-field-initializers
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE:= gpiomon
+LOCAL_SRC_FILES := \
+ tools/$(LOCAL_MODULE).c \
+ tools/tools-common.c
+LOCAL_SHARED_LIBRARIES := libgpiod
+LOCAL_MODULE_TAGS := optional
+LOCAL_CFLAGS := -Wall -Wno-missing-field-initializers
+include $(BUILD_EXECUTABLE)
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..5522aa5
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,508 @@
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin St, 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 St, 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!
diff --git a/Doxyfile b/Doxyfile
new file mode 100644
index 0000000..b194095
--- /dev/null
+++ b/Doxyfile
@@ -0,0 +1,109 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+#
+# This file is part of libgpiod.
+#
+# Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+#
+
+# libgpiod doxygen configuration
+
+# General configuration
+PROJECT_NAME = libgpiod
+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 =
+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 = include/gpiod.h
+SOURCE_BROWSER = YES
+INLINE_SOURCES = NO
+REFERENCED_BY_RELATION = YES
+REFERENCES_RELATION = YES
+ALPHABETICAL_INDEX = NO
+COLS_IN_ALPHA_INDEX = 5
+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 = a4wide
+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
+PERL_PATH =
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..adf0d9c
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,41 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+#
+# This file is part of libgpiod.
+#
+# Copyright (C) 2017-2019 Bartosz Golaszewski <bartekgola@gmail.com>
+#
+
+ACLOCAL_AMFLAGS = -I m4
+AUTOMAKE_OPTIONS = foreign
+SUBDIRS = include lib bindings
+
+if WITH_TOOLS
+
+SUBDIRS += tools man
+
+endif
+
+if WITH_TESTS
+
+SUBDIRS += tests
+
+endif
+
+if HAS_DOXYGEN
+
+doc:
+ @(cat Doxyfile; \
+ echo PROJECT_NUMBER = $(VERSION_STR); \
+ echo INPUT += bindings/cxx/gpiod.hpp) | doxygen -
+.PHONY: doc
+
+clean-local:
+ rm -rf doc
+
+EXTRA_DIST = Doxyfile
+
+endif
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = libgpiod.pc
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..20640ef
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,195 @@
+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
diff --git a/NOTICE b/NOTICE
new file mode 120000
index 0000000..d24842f
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1 @@
+COPYING
\ No newline at end of file
diff --git a/README b/README
new file mode 100644
index 0000000..6794d4f
--- /dev/null
+++ b/README
@@ -0,0 +1,170 @@
+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. It does not depend on any
+libraries other than the standard C library with GNU extensions.
+
+The autoconf version needed to compile the project is 2.61.
+
+Recent (as in >= v4.8) kernel headers are also required for the GPIO user
+API definitions.
+
+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.
+
+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 all lines of specified gpiochips, their names, consumers,
+ direction, active state and additional flags
+
+* gpioget - read values of specified GPIO lines
+
+* gpioset - set values of specified GPIO lines, potentially keep the lines
+ exported and wait until timeout, user input or signal
+
+* gpiofind - find the gpiochip name and line offset given the line name
+
+* gpiomon - wait for events on GPIO lines, specify which events to watch,
+ how many events to process before exiting or if the events
+ should be reported to the console
+
+Examples:
+
+ # Read the value of a single GPIO line.
+ $ gpioget gpiochip1 23
+ 0
+
+ # Read two values at the same time. Set the active state of the lines
+ # to low.
+ $ gpioget --active-low gpiochip1 23 24
+ 1 1
+
+ # Set values of two lines, then daemonize and wait for a signal (SIGINT or
+ # SIGTERM) before releasing them.
+ $ gpioset --mode=signal --background gpiochip1 23=1 24=0
+
+ # Set the value of a single line, then exit immediately. This is useful
+ # for floating pins.
+ $ gpioset gpiochip1 23=1
+
+ # Find a GPIO line by name.
+ $ gpiofind "USR-LED-2"
+ gpiochip1 23
+
+ # Toggle a GPIO by name, then wait for the user to press ENTER.
+ $ gpioset --mode=wait `gpiofind "USR-LED-2"`=1
+
+ # Wait for three rising edge events on a single GPIO line, then exit.
+ $ gpiomon --num-events=3 --rising-edge gpiochip2 3
+ event: RISING EDGE offset: 3 timestamp: [ 1151.814356387]
+ event: RISING EDGE offset: 3 timestamp: [ 1151.815449803]
+ event: RISING EDGE offset: 3 timestamp: [ 1152.091556803]
+
+ # Wait for a single falling edge event. Specify a custom output format.
+ $ gpiomon --format="%e %o %s %n" --falling-edge gpiochip1 4
+ 0 4 1156 615459801
+
+ # Pause execution until a single event of any type occurs. Don't print
+ # anything. Find the line by name.
+ $ gpiomon --num-events=1 --silent `gpiofind "USR-IN"`
+
+ # Monitor multiple lines, exit after the first event.
+ $ gpiomon --silent --num-events=1 gpiochip0 2 3 5
+
+TESTING
+-------
+
+A comprehensive testing framework is included with the library and can be
+used to test both the library code and the linux kernel user-space interface.
+
+The minimum kernel version required to run the tests is 4.16. The tests work
+together with the gpio-mockup kernel module which must be enabled. NOTE: the
+module must not be built-in.
+
+To build the testing executable run:
+
+ ./configure --enable-tests
+ make check
+
+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:
+
+ sudo ./tests/gpiod-test
+
+BINDINGS
+--------
+
+High-level, object-oriented bindings for C++ and python3 are provided. They
+can be enabled by passing --enable-bindings-cxx and --enable-bindings-python
+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.
+
+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.
+
+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 patches and bug reports to
+linux-gpio@vger.kernel.org (add the [libgpiod] prefix to the e-mail subject
+line) and stick to the linux kernel coding style when submitting new code.
diff --git a/autogen.sh b/autogen.sh
new file mode 100755
index 0000000..1da6874
--- /dev/null
+++ b/autogen.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+#
+# This file is part of libgpiod.
+#
+# Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+# 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
diff --git a/bindings/Makefile.am b/bindings/Makefile.am
new file mode 100644
index 0000000..24e2c98
--- /dev/null
+++ b/bindings/Makefile.am
@@ -0,0 +1,21 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+#
+# This file is part of libgpiod.
+#
+# Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+#
+
+SUBDIRS = .
+
+if WITH_BINDINGS_CXX
+
+SUBDIRS += cxx
+
+endif
+
+if WITH_BINDINGS_PYTHON
+
+SUBDIRS += python
+
+endif
diff --git a/bindings/cxx/Makefile.am b/bindings/cxx/Makefile.am
new file mode 100644
index 0000000..d901836
--- /dev/null
+++ b/bindings/cxx/Makefile.am
@@ -0,0 +1,21 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+#
+# This file is part of libgpiod.
+#
+# Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+#
+
+lib_LTLIBRARIES = libgpiodcxx.la
+libgpiodcxx_la_SOURCES = chip.cpp iter.cpp line.cpp line_bulk.cpp
+libgpiodcxx_la_CPPFLAGS = -Wall -Wextra -g -std=gnu++11
+libgpiodcxx_la_CPPFLAGS += -fvisibility=hidden -I$(top_srcdir)/include/
+libgpiodcxx_la_LDFLAGS = -version-info $(subst .,:,$(ABI_CXX_VERSION))
+libgpiodcxx_la_LDFLAGS += -lgpiod -L$(top_builddir)/lib
+
+include_HEADERS = gpiod.hpp
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = libgpiodcxx.pc
+
+SUBDIRS = . examples
diff --git a/bindings/cxx/chip.cpp b/bindings/cxx/chip.cpp
new file mode 100644
index 0000000..9b825c7
--- /dev/null
+++ b/bindings/cxx/chip.cpp
@@ -0,0 +1,201 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+#include <functional>
+#include <gpiod.hpp>
+#include <map>
+#include <system_error>
+#include <utility>
+
+namespace gpiod {
+
+namespace {
+
+::gpiod_chip* open_lookup(const ::std::string& device)
+{
+ return ::gpiod_chip_open_lookup(device.c_str());
+}
+
+::gpiod_chip* open_by_path(const ::std::string& device)
+{
+ return ::gpiod_chip_open(device.c_str());
+}
+
+::gpiod_chip* open_by_name(const ::std::string& device)
+{
+ return ::gpiod_chip_open_by_name(device.c_str());
+}
+
+::gpiod_chip* open_by_label(const ::std::string& device)
+{
+ return ::gpiod_chip_open_by_label(device.c_str());
+}
+
+::gpiod_chip* open_by_number(const ::std::string& device)
+{
+ return ::gpiod_chip_open_by_number(::std::stoul(device));
+}
+
+using open_func = ::std::function<::gpiod_chip* (const ::std::string&)>;
+
+const ::std::map<int, open_func> open_funcs = {
+ { chip::OPEN_LOOKUP, open_lookup, },
+ { chip::OPEN_BY_PATH, open_by_path, },
+ { chip::OPEN_BY_NAME, open_by_name, },
+ { chip::OPEN_BY_LABEL, open_by_label, },
+ { chip::OPEN_BY_NUMBER, open_by_number, },
+};
+
+void chip_deleter(::gpiod_chip* chip)
+{
+ ::gpiod_chip_close(chip);
+}
+
+} /* namespace */
+
+chip::chip(const ::std::string& device, int how)
+ : _m_chip()
+{
+ this->open(device, how);
+}
+
+chip::chip(::gpiod_chip* chip)
+ : _m_chip(chip, chip_deleter)
+{
+
+}
+
+void chip::open(const ::std::string& device, int how)
+{
+ auto func = open_funcs.at(how);
+
+ ::gpiod_chip *chip = func(device);
+ if (!chip)
+ throw ::std::system_error(errno, ::std::system_category(),
+ "cannot open GPIO device " + device);
+
+ this->_m_chip.reset(chip, chip_deleter);
+}
+
+void chip::reset(void) noexcept
+{
+ this->_m_chip.reset();
+}
+
+::std::string chip::name(void) const
+{
+ this->throw_if_noref();
+
+ return ::std::move(::std::string(::gpiod_chip_name(this->_m_chip.get())));
+}
+
+::std::string chip::label(void) const
+{
+ this->throw_if_noref();
+
+ return ::std::move(::std::string(::gpiod_chip_label(this->_m_chip.get())));
+}
+
+unsigned int chip::num_lines(void) const
+{
+ this->throw_if_noref();
+
+ return ::gpiod_chip_num_lines(this->_m_chip.get());
+}
+
+line chip::get_line(unsigned int offset) const
+{
+ this->throw_if_noref();
+
+ if (offset >= this->num_lines())
+ throw ::std::out_of_range("line offset greater than the number of lines");
+
+ ::gpiod_line* line_handle = ::gpiod_chip_get_line(this->_m_chip.get(), offset);
+ if (!line_handle)
+ throw ::std::system_error(errno, ::std::system_category(),
+ "error getting GPIO line from chip");
+
+ return ::std::move(line(line_handle, *this));
+}
+
+line chip::find_line(const ::std::string& name) const
+{
+ this->throw_if_noref();
+
+ ::gpiod_line* handle = ::gpiod_chip_find_line(this->_m_chip.get(), name.c_str());
+ if (!handle && errno != ENOENT)
+ throw ::std::system_error(errno, ::std::system_category(),
+ "error looking up GPIO line by name");
+
+ return ::std::move(handle ? line(handle, *this) : line());
+}
+
+line_bulk chip::get_lines(const ::std::vector<unsigned int>& offsets) const
+{
+ line_bulk lines;
+
+ for (auto& it: offsets)
+ lines.append(this->get_line(it));
+
+ return ::std::move(lines);
+}
+
+line_bulk chip::get_all_lines(void) const
+{
+ line_bulk lines;
+
+ for (unsigned int i = 0; i < this->num_lines(); i++)
+ lines.append(this->get_line(i));
+
+ return ::std::move(lines);
+}
+
+line_bulk chip::find_lines(const ::std::vector<::std::string>& names) const
+{
+ line_bulk lines;
+ line line;
+
+ for (auto& it: names) {
+ line = this->find_line(it);
+ if (!line) {
+ lines.clear();
+ return ::std::move(lines);
+ }
+
+ lines.append(line);
+ }
+
+ return ::std::move(lines);
+}
+
+bool chip::operator==(const chip& rhs) const noexcept
+{
+ return this->_m_chip.get() == rhs._m_chip.get();
+}
+
+bool chip::operator!=(const chip& rhs) const noexcept
+{
+ return this->_m_chip.get() != rhs._m_chip.get();
+}
+
+chip::operator bool(void) const noexcept
+{
+ return this->_m_chip.get() != nullptr;
+}
+
+bool chip::operator!(void) const noexcept
+{
+ return this->_m_chip.get() == nullptr;
+}
+
+void chip::throw_if_noref(void) const
+{
+ if (!this->_m_chip.get())
+ throw ::std::logic_error("object not associated with an open GPIO chip");
+}
+
+} /* namespace gpiod */
diff --git a/bindings/cxx/examples/Makefile.am b/bindings/cxx/examples/Makefile.am
new file mode 100644
index 0000000..201e1db
--- /dev/null
+++ b/bindings/cxx/examples/Makefile.am
@@ -0,0 +1,33 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+#
+# This file is part of libgpiod.
+#
+# Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+#
+
+AM_CPPFLAGS = -I$(top_srcdir)/bindings/cxx/ -I$(top_srcdir)/include
+AM_CPPFLAGS += -Wall -Wextra -g -std=gnu++11
+AM_LDFLAGS = -lgpiodcxx -L$(top_builddir)/bindings/cxx/
+
+check_PROGRAMS = gpiod_cxx_tests \
+ gpiodetectcxx \
+ gpiofindcxx \
+ gpiogetcxx \
+ gpioinfocxx \
+ gpiomoncxx \
+ gpiosetcxx
+
+gpiod_cxx_tests_SOURCES = gpiod_cxx_tests.cpp
+
+gpiodetectcxx_SOURCES = gpiodetectcxx.cpp
+
+gpiofindcxx_SOURCES = gpiofindcxx.cpp
+
+gpiogetcxx_SOURCES = gpiogetcxx.cpp
+
+gpioinfocxx_SOURCES = gpioinfocxx.cpp
+
+gpiomoncxx_SOURCES = gpiomoncxx.cpp
+
+gpiosetcxx_SOURCES = gpiosetcxx.cpp
diff --git a/bindings/cxx/examples/gpiod_cxx_tests.cpp b/bindings/cxx/examples/gpiod_cxx_tests.cpp
new file mode 100644
index 0000000..767fe98
--- /dev/null
+++ b/bindings/cxx/examples/gpiod_cxx_tests.cpp
@@ -0,0 +1,487 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+/*
+ * Misc tests/examples of the C++ API.
+ *
+ * These tests assume that at least one dummy gpiochip is present in the
+ * system and that it's detected as gpiochip0.
+ */
+
+#include <gpiod.hpp>
+
+#include <stdexcept>
+#include <cstdlib>
+#include <iostream>
+#include <fstream>
+#include <map>
+#include <cstring>
+#include <cerrno>
+#include <functional>
+
+#include <poll.h>
+
+namespace {
+
+using test_func = ::std::function<void (void)>;
+
+::std::vector<::std::pair<::std::string, test_func>> test_funcs;
+
+struct test_case
+{
+ test_case(const ::std::string& name, test_func& func)
+ {
+ test_funcs.push_back(::std::make_pair(name, func));
+ }
+};
+
+#define TEST_CASE(_func) \
+ test_func _test_func_##_func(_func); \
+ test_case _test_case_##_func(#_func, _test_func_##_func)
+
+std::string boolstr(bool b)
+{
+ return b ? "true" : "false";
+}
+
+void fire_line_event(const ::std::string& chip, unsigned int offset, bool rising)
+{
+ ::std::string path;
+
+ path = "/sys/kernel/debug/gpio-mockup-event/" + chip + "/" + ::std::to_string(offset);
+
+ ::std::ofstream out(path);
+ if (!out)
+ throw ::std::runtime_error("unable to open " + path);
+
+ out << ::std::to_string(rising ? 1 : 0) << ::std::endl;
+}
+
+void print_event(const ::gpiod::line_event& event)
+{
+ ::std::cerr << "type: "
+ << (event.event_type == ::gpiod::line_event::RISING_EDGE ? "rising" : "falling")
+ << "\n"
+ << "timestamp: "
+ << ::std::chrono::duration_cast<::std::chrono::seconds>(event.timestamp).count()
+ << "."
+ << event.timestamp.count() % 1000000000
+ << "\n"
+ << "source line offset: "
+ << event.source.offset()
+ << ::std::endl;
+}
+
+void chip_open_default_lookup(void)
+{
+ ::gpiod::chip by_name("gpiochip0");
+ ::gpiod::chip by_path("/dev/gpiochip0");
+ ::gpiod::chip by_label("gpio-mockup-A");
+ ::gpiod::chip by_number("0");
+
+ ::std::cerr << "all good" << ::std::endl;
+}
+TEST_CASE(chip_open_default_lookup);
+
+void chip_open_different_modes(void)
+{
+ ::gpiod::chip by_name("gpiochip0", ::gpiod::chip::OPEN_BY_NAME);
+ ::gpiod::chip by_path("/dev/gpiochip0", ::gpiod::chip::OPEN_BY_PATH);
+ ::gpiod::chip by_label("gpio-mockup-A", ::gpiod::chip::OPEN_BY_LABEL);
+ ::gpiod::chip by_number("0", ::gpiod::chip::OPEN_BY_NUMBER);
+
+ ::std::cerr << "all good" << ::std::endl;
+}
+TEST_CASE(chip_open_different_modes);
+
+void chip_open_nonexistent(void)
+{
+ try {
+ ::gpiod::chip chip("/nonexistent_gpiochip");
+ } catch (const ::std::system_error& exc) {
+ ::std::cerr << "system_error thrown as expected: " << exc.what() << ::std::endl;
+ return;
+ }
+
+ throw ::std::runtime_error("system_error should have been thrown");
+}
+TEST_CASE(chip_open_nonexistent);
+
+void chip_open_method(void)
+{
+ ::gpiod::chip chip;
+
+ if (chip)
+ throw ::std::runtime_error("chip should be empty");
+
+ chip.open("gpiochip0");
+
+ ::std::cerr << "all good" << ::std::endl;
+}
+TEST_CASE(chip_open_method);
+
+void chip_reset(void)
+{
+ ::gpiod::chip chip("gpiochip0");
+
+ if (!chip)
+ throw ::std::runtime_error("chip object is not valid");
+
+ chip.reset();
+
+ if (chip)
+ throw ::std::runtime_error("chip should be invalid");
+
+ ::std::cerr << "all good" << ::std::endl;
+}
+TEST_CASE(chip_reset);
+
+void chip_info(void)
+{
+ ::gpiod::chip chip("gpiochip0");
+
+ ::std::cerr << "chip name: " << chip.name() << ::std::endl;
+ ::std::cerr << "chip label: " << chip.label() << ::std::endl;
+ ::std::cerr << "number of lines " << chip.num_lines() << ::std::endl;
+}
+TEST_CASE(chip_info);
+
+void chip_operators(void)
+{
+ ::gpiod::chip chip1("gpiochip0");
+ auto chip2 = chip1;
+
+ if ((chip1 != chip2) || !(chip1 == chip2))
+ throw ::std::runtime_error("chip objects should be equal");
+
+ ::std::cerr << "all good" << ::std::endl;
+}
+TEST_CASE(chip_operators);
+
+void chip_line_ops(void)
+{
+ ::gpiod::chip chip("gpiochip0");
+
+ auto line = chip.get_line(3);
+ ::std::cerr << "Got line by offset: " << line.offset() << ::std::endl;
+
+ line = chip.find_line("gpio-mockup-A-4");
+ ::std::cerr << "Got line by name: " << line.name() << ::std::endl;
+
+ auto lines = chip.get_lines({ 1, 2, 3, 6, 6 });
+ ::std::cerr << "Got multiple lines by offset: ";
+ for (auto& it: lines)
+ ::std::cerr << it.offset() << " ";
+ ::std::cerr << ::std::endl;
+
+ lines = chip.find_lines({ "gpio-mockup-A-1", "gpio-mockup-A-4", "gpio-mockup-A-7" });
+ ::std::cerr << "Got multiple lines by name: ";
+ for (auto& it: lines)
+ ::std::cerr << it.name() << " ";
+ ::std::cerr << ::std::endl;
+}
+TEST_CASE(chip_line_ops);
+
+void chip_find_lines_nonexistent(void)
+{
+ ::gpiod::chip chip("gpiochip0");
+
+ auto lines = chip.find_lines({ "gpio-mockup-A-1", "nonexistent", "gpio-mockup-A-4" });
+ if (lines)
+ throw ::std::logic_error("line_bulk object should be invalid");
+
+ ::std::cerr << "line_bulk invalid as expected" << ::std::endl;
+}
+TEST_CASE(chip_find_lines_nonexistent);
+
+void line_info(void)
+{
+ ::gpiod::chip chip("gpiochip0");
+ ::gpiod::line line = chip.get_line(2);
+
+ ::std::cerr << "line offset: " << line.offset() << ::std::endl;
+ ::std::cerr << "line name: " << line.name() << ::std::endl;
+ ::std::cerr << "line direction: "
+ << (line.direction() == ::gpiod::line::DIRECTION_INPUT ?
+ "input" : "output") << ::std::endl;
+ ::std::cerr << "line active state: "
+ << (line.active_state() == ::gpiod::line::ACTIVE_LOW ?
+ "active low" : "active high") << :: std::endl;
+}
+TEST_CASE(line_info);
+
+void empty_objects(void)
+{
+ ::std::cerr << "Are initialized line & chip objects 'false'?" << ::std::endl;
+
+ ::gpiod::line line;
+ if (line)
+ throw ::std::logic_error("line built with a default constructor should be 'false'");
+
+ ::gpiod::chip chip;
+ if (chip)
+ throw ::std::logic_error("chip built with a default constructor should be 'false'");
+
+ ::std::cerr << "YES" << ::std::endl;
+}
+TEST_CASE(empty_objects);
+
+void line_bulk_iterator(void)
+{
+ ::std::cerr << "Checking line_bulk iterators" << ::std::endl;
+
+ ::gpiod::chip chip("gpiochip0");
+ ::gpiod::line_bulk bulk = chip.get_lines({ 0, 1, 2, 3, 4 });
+
+ for (auto& it: bulk)
+ ::std::cerr << it.name() << ::std::endl;
+
+ ::std::cerr << "DONE" << ::std::endl;
+}
+TEST_CASE(line_bulk_iterator);
+
+void single_line_test(void)
+{
+ const ::std::string line_name("gpio-mockup-A-4");
+
+ ::std::cerr << "Looking up a GPIO line by name (" << line_name << ")" << ::std::endl;
+
+ auto line = ::gpiod::find_line(line_name);
+ if (!line)
+ throw ::std::runtime_error(line_name + " line not found");
+
+ ::std::cerr << "Requesting a single line" << ::std::endl;
+
+ ::gpiod::line_request conf;
+ conf.consumer = "gpiod_cxx_tests";
+ conf.request_type = ::gpiod::line_request::DIRECTION_OUTPUT;
+
+ line.request(conf, 1);
+ ::std::cerr << "Reading value" << ::std::endl;
+ ::std::cerr << line.get_value() << ::std::endl;
+ ::std::cerr << "Changing value to 0" << ::std::endl;
+ line.set_value(0);
+ ::std::cerr << "Reading value again" << ::std::endl;
+ ::std::cerr << line.get_value() << ::std::endl;
+}
+TEST_CASE(single_line_test);
+
+void line_flags(void)
+{
+ ::gpiod::chip chip("gpiochip0");
+
+ auto line = chip.get_line(0);
+
+ ::std::cerr << "line is used: " << boolstr(line.is_used()) << ::std::endl;
+ ::std::cerr << "line is requested: " << boolstr(line.is_requested()) << ::std::endl;
+
+ ::std::cerr << "requesting line" << ::std::endl;
+
+ ::gpiod::line_request config;
+ config.consumer = "gpiod_cxx_tests";
+ config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT;
+ config.flags = ::gpiod::line_request::FLAG_OPEN_DRAIN;
+ line.request(config);
+
+ ::std::cerr << "line is used: " << boolstr(line.is_used()) << ::std::endl;
+ ::std::cerr << "line is open drain: " << boolstr(line.is_open_drain()) << ::std::endl;
+ ::std::cerr << "line is open source: " << boolstr(line.is_open_source()) << ::std::endl;
+ ::std::cerr << "line is requested: " << boolstr(line.is_requested()) << ::std::endl;
+
+ if (!line.is_open_drain())
+ throw ::std::logic_error("line should be open-drain");
+}
+TEST_CASE(line_flags);
+
+void multiple_lines_test(void)
+{
+ ::gpiod::chip chip("gpiochip0");
+
+ ::std::cerr << "Getting multiple lines by offsets" << ::std::endl;
+
+ auto lines = chip.get_lines({ 0, 2, 3, 4, 6 });
+
+ ::std::cerr << "Requesting them for output" << ::std::endl;
+
+ ::gpiod::line_request config;
+ config.consumer = "gpiod_cxx_tests";
+ config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT;
+
+ lines.request(config);
+
+ ::std::cerr << "Setting values" << ::std::endl;
+
+ lines.set_values({ 0, 1, 1, 0, 1});
+
+ ::std::cerr << "Requesting the lines for input" << ::std::endl;
+
+ config.request_type = ::gpiod::line_request::DIRECTION_INPUT;
+ lines.release();
+ lines.request(config);
+
+ ::std::cerr << "Reading the values" << ::std::endl;
+
+ auto vals = lines.get_values();
+
+ for (auto& it: vals)
+ ::std::cerr << it << " ";
+ ::std::cerr << ::std::endl;
+}
+TEST_CASE(multiple_lines_test);
+
+void chip_get_all_lines(void)
+{
+ ::gpiod::chip chip("gpiochip0");
+
+ ::std::cerr << "Getting all lines from a chip" << ::std::endl;
+
+ auto lines = chip.get_all_lines();
+
+ for (auto& it: lines)
+ ::std::cerr << "Offset: " << it.offset() << ::std::endl;
+}
+TEST_CASE(chip_get_all_lines);
+
+void line_event_single_line(void)
+{
+ ::gpiod::chip chip("gpiochip0");
+
+ auto line = chip.get_line(1);
+
+ ::gpiod::line_request config;
+ config.consumer = "gpiod_cxx_tests";
+ config.request_type = ::gpiod::line_request::EVENT_BOTH_EDGES;
+
+ ::std::cerr << "requesting line for events" << ::std::endl;
+ line.request(config);
+
+ ::std::cerr << "generating a line event" << ::std::endl;
+ fire_line_event("gpiochip0", 1, true);
+
+ if (!line.event_wait(::std::chrono::nanoseconds(1000000000)))
+ throw ::std::runtime_error("waiting for event timed out");
+
+ auto event = line.event_read();
+
+ ::std::cerr << "event received:" << ::std::endl;
+ print_event(event);
+}
+TEST_CASE(line_event_single_line);
+
+void line_event_multiple_lines(void)
+{
+ ::gpiod::chip chip("gpiochip0");
+
+ auto lines = chip.get_lines({ 1, 2, 3, 4, 5 });
+
+ ::gpiod::line_request config;
+ config.consumer = "gpiod_cxx_tests";
+ config.request_type = ::gpiod::line_request::EVENT_BOTH_EDGES;
+
+ ::std::cerr << "requesting lines for events" << ::std::endl;
+ lines.request(config);
+
+ ::std::cerr << "generating two line events" << ::std::endl;
+ fire_line_event("gpiochip0", 1, true);
+ fire_line_event("gpiochip0", 2, true);
+
+ auto event_lines = lines.event_wait(::std::chrono::nanoseconds(1000000000));
+ if (!event_lines || event_lines.size() != 2)
+ throw ::std::runtime_error("expected two line events");
+
+ ::std::vector<::gpiod::line_event> events;
+ for (auto& line: event_lines) {
+ auto event = line.event_read();
+ events.push_back(event);
+ }
+
+ ::std::cerr << "events received:" << ::std::endl;
+ for (auto& event: events)
+ print_event(event);
+}
+TEST_CASE(line_event_multiple_lines);
+
+void line_event_poll_fd(void)
+{
+ ::std::map<int, ::gpiod::line> fd_line_map;
+
+ ::gpiod::chip chip("gpiochip0");
+ auto lines = chip.get_lines({ 1, 2, 3, 4, 5, 6 });
+
+ ::gpiod::line_request config;
+ config.consumer = "gpiod_cxx_tests";
+ config.request_type = ::gpiod::line_request::EVENT_BOTH_EDGES;
+
+ ::std::cerr << "requesting lines for events" << ::std::endl;
+ lines.request(config);
+
+ ::std::cerr << "generating three line events" << ::std::endl;
+ fire_line_event("gpiochip0", 2, true);
+ fire_line_event("gpiochip0", 3, true);
+ fire_line_event("gpiochip0", 5, false);
+
+ fd_line_map[lines[1].event_get_fd()] = lines[1];
+ fd_line_map[lines[2].event_get_fd()] = lines[2];
+ fd_line_map[lines[4].event_get_fd()] = lines[4];
+
+ pollfd fds[3];
+ fds[0].fd = lines[1].event_get_fd();
+ fds[1].fd = lines[2].event_get_fd();
+ fds[2].fd = lines[4].event_get_fd();
+
+ fds[0].events = fds[1].events = fds[2].events = POLLIN | POLLPRI;
+
+ int rv = poll(fds, 3, 1000);
+ if (rv < 0)
+ throw ::std::runtime_error("error polling for events: "
+ + ::std::string(::strerror(errno)));
+ else if (rv == 0)
+ throw ::std::runtime_error("poll() timed out while waiting for events");
+
+ if (rv != 3)
+ throw ::std::runtime_error("expected three line events");
+
+ ::std::cerr << "events received:" << ::std::endl;
+ for (unsigned int i = 0; i < 3; i++) {
+ if (fds[i].revents)
+ print_event(fd_line_map[fds[i].fd].event_read());
+ }
+}
+TEST_CASE(line_event_poll_fd);
+
+void chip_iterator(void)
+{
+ ::std::cerr << "iterating over all GPIO chips in the system:" << ::std::endl;
+
+ for (auto& it: ::gpiod::make_chip_iter())
+ ::std::cerr << it.name() << ::std::endl;
+}
+TEST_CASE(chip_iterator);
+
+void line_iterator(void)
+{
+ ::std::cerr << "iterating over all lines exposed by a GPIO chip:" << ::std::endl;
+
+ ::gpiod::chip chip("gpiochip0");
+
+ for (auto& it: ::gpiod::line_iter(chip))
+ ::std::cerr << it.offset() << ": " << it.name() << ::std::endl;
+}
+TEST_CASE(line_iterator);
+
+} /* namespace */
+
+int main(int, char **)
+{
+ for (auto& it: test_funcs) {
+ ::std::cerr << "=================================================" << ::std::endl;
+ ::std::cerr << it.first << ":\n" << ::std::endl;
+ it.second();
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/bindings/cxx/examples/gpiodetectcxx.cpp b/bindings/cxx/examples/gpiodetectcxx.cpp
new file mode 100644
index 0000000..6da5573
--- /dev/null
+++ b/bindings/cxx/examples/gpiodetectcxx.cpp
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+/* C++ reimplementation of the gpiodetect tool. */
+
+#include <gpiod.hpp>
+
+#include <cstdlib>
+#include <iostream>
+
+int main(int argc, char **argv)
+{
+ if (argc != 1) {
+ ::std::cerr << "usage: " << argv[0] << ::std::endl;
+ return EXIT_FAILURE;
+ }
+
+ for (auto& it: ::gpiod::make_chip_iter()) {
+ ::std::cout << it.name() << " ["
+ << it.label() << "] ("
+ << it.num_lines() << " lines)" << ::std::endl;
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/bindings/cxx/examples/gpiofindcxx.cpp b/bindings/cxx/examples/gpiofindcxx.cpp
new file mode 100644
index 0000000..08fb62c
--- /dev/null
+++ b/bindings/cxx/examples/gpiofindcxx.cpp
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+/* C++ reimplementation of the gpiofind tool. */
+
+#include <gpiod.hpp>
+
+#include <cstdlib>
+#include <iostream>
+
+int main(int argc, char **argv)
+{
+ if (argc != 2) {
+ ::std::cerr << "usage: " << argv[0] << " <line name>" << ::std::endl;
+ return EXIT_FAILURE;
+ }
+
+ ::gpiod::line line = ::gpiod::find_line(argv[1]);
+ if (!line)
+ return EXIT_FAILURE;
+
+ ::std::cout << line.get_chip().name() << " " << line.offset() << ::std::endl;
+
+ return EXIT_SUCCESS;
+}
diff --git a/bindings/cxx/examples/gpiogetcxx.cpp b/bindings/cxx/examples/gpiogetcxx.cpp
new file mode 100644
index 0000000..d565a43
--- /dev/null
+++ b/bindings/cxx/examples/gpiogetcxx.cpp
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+/* Simplified C++ reimplementation of the gpioget tool. */
+
+#include <gpiod.hpp>
+
+#include <cstdlib>
+#include <iostream>
+
+int main(int argc, char **argv)
+{
+ if (argc < 3) {
+ ::std::cerr << "usage: " << argv[0] << " <chip> <line_offset0> ..." << ::std::endl;
+ return EXIT_FAILURE;
+ }
+
+ ::std::vector<unsigned int> offsets;
+
+ for (int i = 2; i < argc; i++)
+ offsets.push_back(::std::stoul(argv[i]));
+
+ ::gpiod::chip chip(argv[1]);
+ auto lines = chip.get_lines(offsets);
+
+ lines.request({
+ argv[0],
+ ::gpiod::line_request::DIRECTION_INPUT,
+ 0
+ });
+
+ auto vals = lines.get_values();
+
+ for (auto& it: vals)
+ ::std::cout << it << ' ';
+ ::std::cout << ::std::endl;
+
+ return EXIT_SUCCESS;
+}
diff --git a/bindings/cxx/examples/gpioinfocxx.cpp b/bindings/cxx/examples/gpioinfocxx.cpp
new file mode 100644
index 0000000..02d69b6
--- /dev/null
+++ b/bindings/cxx/examples/gpioinfocxx.cpp
@@ -0,0 +1,51 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+/* Simplified C++ reimplementation of the gpioinfo tool. */
+
+#include <gpiod.hpp>
+
+#include <cstdlib>
+#include <iostream>
+
+int main(int argc, char **argv)
+{
+ if (argc != 1) {
+ ::std::cerr << "usage: " << argv[0] << ::std::endl;
+ return EXIT_FAILURE;
+ }
+
+ for (auto& cit: ::gpiod::make_chip_iter()) {
+ ::std::cout << cit.name() << " - " << cit.num_lines() << " lines:" << ::std::endl;
+
+ for (auto& lit: ::gpiod::line_iter(cit)) {
+ ::std::cout << "\tline ";
+ ::std::cout.width(3);
+ ::std::cout << lit.offset() << ": ";
+
+ ::std::cout.width(12);
+ ::std::cout << (lit.name().empty() ? "unnamed" : lit.name());
+ ::std::cout << " ";
+
+ ::std::cout.width(12);
+ ::std::cout << (lit.consumer().empty() ? "unused" : lit.consumer());
+ ::std::cout << " ";
+
+ ::std::cout.width(8);
+ ::std::cout << (lit.direction() == ::gpiod::line::DIRECTION_INPUT ? "input" : "output");
+ ::std::cout << " ";
+
+ ::std::cout.width(10);
+ ::std::cout << (lit.active_state() == ::gpiod::line::ACTIVE_LOW
+ ? "active-low" : "active-high");
+
+ ::std::cout << ::std::endl;
+ }
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/bindings/cxx/examples/gpiomoncxx.cpp b/bindings/cxx/examples/gpiomoncxx.cpp
new file mode 100644
index 0000000..c603d5d
--- /dev/null
+++ b/bindings/cxx/examples/gpiomoncxx.cpp
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+/* Simplified C++ reimplementation of the gpiomon tool. */
+
+#include <gpiod.hpp>
+
+#include <cstdlib>
+#include <iostream>
+
+namespace {
+
+void print_event(const ::gpiod::line_event& event)
+{
+ if (event.event_type == ::gpiod::line_event::RISING_EDGE)
+ ::std::cout << " RISING EDGE";
+ else if (event.event_type == ::gpiod::line_event::FALLING_EDGE)
+ ::std::cout << "FALLING EDGE";
+ else
+ throw ::std::logic_error("invalid event type");
+
+ ::std::cout << " ";
+
+ ::std::cout << ::std::chrono::duration_cast<::std::chrono::seconds>(event.timestamp).count();
+ ::std::cout << ".";
+ ::std::cout << event.timestamp.count() % 1000000000;
+
+ ::std::cout << " line: " << event.source.offset();
+
+ ::std::cout << ::std::endl;
+}
+
+} /* namespace */
+
+int main(int argc, char **argv)
+{
+ if (argc < 3) {
+ ::std::cout << "usage: " << argv[0] << " <chip> <offset0> ..." << ::std::endl;
+ return EXIT_FAILURE;
+ }
+
+ ::std::vector<unsigned int> offsets;
+ offsets.reserve(argc);
+ for (int i = 2; i < argc; i++)
+ offsets.push_back(::std::stoul(argv[i]));
+
+ ::gpiod::chip chip(argv[1]);
+ auto lines = chip.get_lines(offsets);
+
+ lines.request({
+ argv[0],
+ ::gpiod::line_request::EVENT_BOTH_EDGES,
+ 0,
+ });
+
+ for (;;) {
+ auto events = lines.event_wait(::std::chrono::nanoseconds(1000000000));
+ if (events) {
+ for (auto& it: events)
+ print_event(it.event_read());
+ }
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/bindings/cxx/examples/gpiosetcxx.cpp b/bindings/cxx/examples/gpiosetcxx.cpp
new file mode 100644
index 0000000..5c0b8c9
--- /dev/null
+++ b/bindings/cxx/examples/gpiosetcxx.cpp
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+/* Simplified C++ reimplementation of the gpioset tool. */
+
+#include <gpiod.hpp>
+
+#include <cstdlib>
+#include <iostream>
+
+int main(int argc, char **argv)
+{
+ if (argc < 3) {
+ ::std::cerr << "usage: " << argv[0] << " <chip> <line_offset0>=<value0> ..." << ::std::endl;
+ return EXIT_FAILURE;
+ }
+
+ ::std::vector<unsigned int> offsets;
+ ::std::vector<int> values;
+
+ for (int i = 2; i < argc; i++) {
+ ::std::string arg(argv[i]);
+
+ size_t pos = arg.find('=');
+
+ ::std::string offset(arg.substr(0, pos));
+ ::std::string value(arg.substr(pos + 1, ::std::string::npos));
+
+ if (offset.empty() || value.empty())
+ throw ::std::invalid_argument("invalid argument: " + ::std::string(argv[i]));
+
+ offsets.push_back(::std::stoul(offset));
+ values.push_back(::std::stoul(value));
+ }
+
+ ::gpiod::chip chip(argv[1]);
+ auto lines = chip.get_lines(offsets);
+
+ lines.request({
+ argv[0],
+ ::gpiod::line_request::DIRECTION_OUTPUT,
+ 0
+ }, values);
+
+ ::std::cin.get();
+
+ return EXIT_SUCCESS;
+}
diff --git a/bindings/cxx/gpiod.hpp b/bindings/cxx/gpiod.hpp
new file mode 100644
index 0000000..b92597f
--- /dev/null
+++ b/bindings/cxx/gpiod.hpp
@@ -0,0 +1,970 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+#ifndef __LIBGPIOD_GPIOD_CXX_HPP__
+#define __LIBGPIOD_GPIOD_CXX_HPP__
+
+#include <bitset>
+#include <chrono>
+#include <gpiod.h>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace gpiod {
+
+class line;
+class line_bulk;
+class line_event;
+class line_iter;
+class chip_iter;
+
+/**
+ * @defgroup __gpiod_cxx__ C++ bindings
+ * @{
+ */
+
+/**
+ * @brief Represents a GPIO chip.
+ *
+ * Internally this class holds a smart pointer to an open GPIO chip descriptor.
+ * Multiple objects of this class can reference the same chip. The chip is
+ * closed and all resources freed when the last reference is dropped.
+ */
+class chip
+{
+public:
+
+ /**
+ * @brief Default constructor. Creates an empty GPIO chip object.
+ */
+ GPIOD_API chip(void) = default;
+
+ /**
+ * @brief Constructor. Opens the chip using chip::open.
+ * @param device String describing the GPIO chip.
+ * @param how Indicates how the chip should be opened.
+ */
+ GPIOD_API chip(const ::std::string& device, int how = OPEN_LOOKUP);
+
+ /**
+ * @brief Copy constructor. References the object held by other.
+ * @param other Other chip object.
+ */
+ GPIOD_API chip(const chip& other) = default;
+
+ /**
+ * @brief Move constructor. References the object held by other.
+ * @param other Other chip object.
+ */
+ GPIOD_API chip(chip&& other) = default;
+
+ /**
+ * @brief Assignment operator. References the object held by other.
+ * @param other Other chip object.
+ * @return Reference to this object.
+ */
+ GPIOD_API chip& operator=(const chip& other) = default;
+
+ /**
+ * @brief Move assignment operator. References the object held by other.
+ * @param other Other chip object.
+ * @return Reference to this object.
+ */
+ GPIOD_API chip& operator=(chip&& other) = default;
+
+ /**
+ * @brief Destructor. Unreferences the internal chip object.
+ */
+ GPIOD_API ~chip(void) = default;
+
+ /**
+ * @brief Open a GPIO chip.
+ * @param device String describing the GPIO chip.
+ * @param how Indicates how the chip should be opened.
+ *
+ * If the object already holds a reference to an open chip, it will be
+ * closed and the reference reset.
+ */
+ GPIOD_API void open(const ::std::string &device, int how = OPEN_LOOKUP);
+
+ /**
+ * @brief Reset the internal smart pointer owned by this object.
+ */
+ GPIOD_API void reset(void) noexcept;
+
+ /**
+ * @brief Return the name of the chip held by this object.
+ * @return Name of the GPIO chip.
+ */
+ GPIOD_API ::std::string name(void) const;
+
+ /**
+ * @brief Return the label of the chip held by this object.
+ * @return Label of the GPIO chip.
+ */
+ GPIOD_API ::std::string label(void) const;
+
+ /**
+ * @brief Return the number of lines exposed by this chip.
+ * @return Number of lines.
+ */
+ GPIOD_API unsigned int num_lines(void) const;
+
+ /**
+ * @brief Get the line exposed by this chip at given offset.
+ * @param offset Offset of the line.
+ * @return Line object.
+ */
+ GPIOD_API line get_line(unsigned int offset) const;
+
+ /**
+ * @brief Get the line exposed by this chip by name.
+ * @param name Line name.
+ * @return Line object.
+ */
+ GPIOD_API line find_line(const ::std::string& name) const;
+
+ /**
+ * @brief Get a set of lines exposed by this chip at given offsets.
+ * @param offsets Vector of line offsets.
+ * @return Set of lines held by a line_bulk object.
+ */
+ GPIOD_API line_bulk get_lines(const ::std::vector<unsigned int>& offsets) const;
+
+ /**
+ * @brief Get all lines exposed by this chip.
+ * @return All lines exposed by this chip held by a line_bulk object.
+ */
+ GPIOD_API line_bulk get_all_lines(void) const;
+
+ /**
+ * @brief Get a set of lines exposed by this chip by their names.
+ * @param names Vector of line names.
+ * @return Set of lines held by a line_bulk object.
+ */
+ GPIOD_API line_bulk find_lines(const ::std::vector<::std::string>& names) const;
+
+ /**
+ * @brief Equality operator.
+ * @param rhs Right-hand side of the equation.
+ * @return True if rhs references the same chip. False otherwise.
+ */
+ GPIOD_API bool operator==(const chip& rhs) const noexcept;
+
+ /**
+ * @brief Inequality operator.
+ * @param rhs Right-hand side of the equation.
+ * @return False if rhs references the same chip. True otherwise.
+ */
+ GPIOD_API bool operator!=(const chip& rhs) const noexcept;
+
+ /**
+ * @brief Check if this object holds a reference to a GPIO chip.
+ * @return True if this object references a GPIO chip, false otherwise.
+ */
+ GPIOD_API operator bool(void) const noexcept;
+
+ /**
+ * @brief Check if this object doesn't hold a reference to a GPIO chip.
+ * @return False if this object references a GPIO chip, true otherwise.
+ */
+ GPIOD_API bool operator!(void) const noexcept;
+
+ /**
+ * @brief Affect the way in which chip::chip and chip::open will try
+ * to open a GPIO chip character device.
+ */
+ enum : int {
+ OPEN_LOOKUP = 1,
+ /**< Open based on the best guess what the supplied string is. */
+ OPEN_BY_PATH,
+ /**< Assume the string is a path to the GPIO chardev. */
+ OPEN_BY_NAME,
+ /**< Assume the string is the name of the chip */
+ OPEN_BY_LABEL,
+ /**< Assume the string is the label of the GPIO chip. */
+ OPEN_BY_NUMBER,
+ /**< Assume the string is the number of the GPIO chip. */
+ };
+
+private:
+
+ chip(::gpiod_chip* chip);
+
+ void throw_if_noref(void) const;
+
+ ::std::shared_ptr<::gpiod_chip> _m_chip;
+
+ friend chip_iter;
+ friend line_iter;
+};
+
+/**
+ * @brief Stores the configuration for line requests.
+ */
+struct line_request
+{
+ /**
+ * @brief Request types.
+ */
+ enum : int {
+ DIRECTION_AS_IS = 1,
+ /**< Request for values, don't change the direction. */
+ DIRECTION_INPUT,
+ /**< Request for reading line values. */
+ DIRECTION_OUTPUT,
+ /**< Request for driving the GPIO lines. */
+ EVENT_FALLING_EDGE,
+ /**< Listen for falling edge events. */
+ EVENT_RISING_EDGE,
+ /**< Listen for rising edge events. */
+ EVENT_BOTH_EDGES,
+ /**< Listen for all types of events. */
+ };
+
+ GPIOD_API static const ::std::bitset<32> FLAG_ACTIVE_LOW;
+ /**< Set the active state to 'low' (high is the default). */
+ GPIOD_API static const ::std::bitset<32> FLAG_OPEN_SOURCE;
+ /**< The line is an open-source port. */
+ GPIOD_API static const ::std::bitset<32> FLAG_OPEN_DRAIN;
+ /**< The line is an open-drain port. */
+
+ ::std::string consumer;
+ /**< Consumer name to pass to the request. */
+ int request_type;
+ /**< Type of the request. */
+ ::std::bitset<32> flags;
+ /**< Additional request flags. */
+};
+
+/**
+ * @brief Represents a single GPIO line.
+ *
+ * Internally this class holds a raw pointer to a GPIO line descriptor and a
+ * reference to the parent chip. All line resources are freed when the last
+ * reference to the parent chip is dropped.
+ */
+class line
+{
+public:
+
+ /**
+ * @brief Default constructor. Creates an empty line object.
+ */
+ GPIOD_API line(void);
+
+ /**
+ * @brief Copy constructor.
+ * @param other Other line object.
+ */
+ GPIOD_API line(const line& other) = default;
+
+ /**
+ * @brief Move constructor.
+ * @param other Other line object.
+ */
+ GPIOD_API line(line&& other) = default;
+
+ /**
+ * @brief Assignment operator.
+ * @param other Other line object.
+ * @return Reference to this object.
+ */
+ GPIOD_API line& operator=(const line& other) = default;
+
+ /**
+ * @brief Move assignment operator.
+ * @param other Other line object.
+ * @return Reference to this object.
+ */
+ GPIOD_API line& operator=(line&& other) = default;
+
+ /**
+ * @brief Destructor.
+ */
+ GPIOD_API ~line(void) = default;
+
+ /**
+ * @brief Get the offset of this line.
+ * @return Offet of this line.
+ */
+ GPIOD_API unsigned int offset(void) const;
+
+ /**
+ * @brief Get the name of this line (if any).
+ * @return Name of this line or an empty string if it is unnamed.
+ */
+ GPIOD_API ::std::string name(void) const;
+
+ /**
+ * @brief Get the consumer of this line (if any).
+ * @return Name of the consumer of this line or an empty string if it
+ * is unused.
+ */
+ GPIOD_API ::std::string consumer(void) const;
+
+ /**
+ * @brief Get current direction of this line.
+ * @return Current direction setting.
+ */
+ GPIOD_API int direction(void) const noexcept;
+
+ /**
+ * @brief Get current active state of this line.
+ * @return Current active state setting.
+ */
+ GPIOD_API int active_state(void) const noexcept;
+
+ /**
+ * @brief Check if this line is used by the kernel or other user space
+ * process.
+ * @return True if this line is in use, false otherwise.
+ */
+ GPIOD_API bool is_used(void) const;
+
+ /**
+ * @brief Check if this line represents an open-drain GPIO.
+ * @return True if the line is an open-drain GPIO, false otherwise.
+ */
+ GPIOD_API bool is_open_drain(void) const;
+
+ /**
+ * @brief Check if this line represents an open-source GPIO.
+ * @return True if the line is an open-source GPIO, false otherwise.
+ */
+ GPIOD_API bool is_open_source(void) const;
+
+ /**
+ * @brief Request this line.
+ * @param config Request config (see gpiod::line_request).
+ * @param default_val Default value - only matters for OUTPUT direction.
+ */
+ GPIOD_API void request(const line_request& config, int default_val = 0) const;
+
+ /**
+ * @brief Release the line if it was previously requested.
+ */
+ GPIOD_API void release(void) const;
+
+ /**
+ * @brief Check if this user has ownership of this line.
+ * @return True if the user has ownership of this line, false otherwise.
+ */
+ GPIOD_API bool is_requested(void) const;
+
+ /**
+ * @brief Read the line value.
+ * @return Current value (0 or 1).
+ */
+ GPIOD_API int get_value(void) const;
+
+ /**
+ * @brief Set the value of this line.
+ * @param val New value (0 or 1).
+ */
+ GPIOD_API void set_value(int val) const;
+
+ /**
+ * @brief Wait for an event on this line.
+ * @param timeout Time to wait before returning if no event occurred.
+ * @return True if an event occurred and can be read, false if the wait
+ * timed out.
+ */
+ GPIOD_API bool event_wait(const ::std::chrono::nanoseconds& timeout) const;
+
+ /**
+ * @brief Read a line event.
+ * @return Line event object.
+ */
+ GPIOD_API line_event event_read(void) const;
+
+ /**
+ * @brief Get the event file descriptor associated with this line.
+ * @return File descriptor number.
+ */
+ GPIOD_API int event_get_fd(void) const;
+
+ /**
+ * @brief Get the reference to the parent chip.
+ * @return Reference to the parent chip object.
+ */
+ GPIOD_API const chip& get_chip(void) const;
+
+ /**
+ * @brief Reset the state of this object.
+ *
+ * This is useful when the user needs to e.g. keep the line_event object
+ * but wants to drop the reference to the GPIO chip indirectly held by
+ * the line being the source of the event.
+ */
+ GPIOD_API void reset(void);
+
+ /**
+ * @brief Check if two line objects reference the same GPIO line.
+ * @param rhs Right-hand side of the equation.
+ * @return True if both objects reference the same line, fale otherwise.
+ */
+ GPIOD_API bool operator==(const line& rhs) const noexcept;
+
+ /**
+ * @brief Check if two line objects reference different GPIO lines.
+ * @param rhs Right-hand side of the equation.
+ * @return False if both objects reference the same line, true otherwise.
+ */
+ GPIOD_API bool operator!=(const line& rhs) const noexcept;
+
+ /**
+ * @brief Check if this object holds a reference to any GPIO line.
+ * @return True if this object references a GPIO line, false otherwise.
+ */
+ GPIOD_API operator bool(void) const noexcept;
+
+ /**
+ * @brief Check if this object doesn't reference any GPIO line.
+ * @return True if this object doesn't reference any GPIO line, true
+ * otherwise.
+ */
+ GPIOD_API bool operator!(void) const noexcept;
+
+ /**
+ * @brief Possible direction settings.
+ */
+ enum : int {
+ DIRECTION_INPUT = 1,
+ /**< Line's direction setting is input. */
+ DIRECTION_OUTPUT,
+ /**< Line's direction setting is output. */
+ };
+
+ /**
+ * @brief Possible active state settings.
+ */
+ enum : int {
+ ACTIVE_LOW = 1,
+ /**< Line's active state is low. */
+ ACTIVE_HIGH,
+ /**< Line's active state is high. */
+ };
+
+private:
+
+ line(::gpiod_line* line, const chip& owner);
+
+ void throw_if_null(void) const;
+
+ ::gpiod_line* _m_line;
+ chip _m_chip;
+
+ friend chip;
+ friend line_bulk;
+ friend line_iter;
+};
+
+/**
+ * @brief Find a GPIO line by name. Search all GPIO chips present on the system.
+ * @param name Name of the line.
+ * @return Returns a line object - empty if the line was not found.
+ */
+GPIOD_API line find_line(const ::std::string& name);
+
+/**
+ * @brief Describes a single GPIO line event.
+ */
+struct line_event
+{
+
+ /**
+ * @brief Possible event types.
+ */
+ enum : int {
+ RISING_EDGE = 1,
+ /**< Rising edge event. */
+ FALLING_EDGE,
+ /**< Falling edge event. */
+ };
+
+ ::std::chrono::nanoseconds timestamp;
+ /**< Best estimate of time of event occurrence in nanoseconds. */
+ int event_type;
+ /**< Type of the event that occurred. */
+ line source;
+ /**< Line object referencing the GPIO line on which the event occurred. */
+};
+
+/**
+ * @brief Represents a set of GPIO lines.
+ *
+ * Internally an object of this class stores an array of line objects
+ * owned by a single chip.
+ */
+class line_bulk
+{
+public:
+
+ /**
+ * @brief Default constructor. Creates an empty line_bulk object.
+ */
+ GPIOD_API line_bulk(void) = default;
+
+ /**
+ * @brief Construct a line_bulk from a vector of lines.
+ * @param lines Vector of gpiod::line objects.
+ * @note All lines must be owned by the same GPIO chip.
+ */
+ GPIOD_API line_bulk(const ::std::vector<line>& lines);
+
+ /**
+ * @brief Copy constructor.
+ * @param other Other line_bulk object.
+ */
+ GPIOD_API line_bulk(const line_bulk& other) = default;
+
+ /**
+ * @brief Move constructor.
+ * @param other Other line_bulk object.
+ */
+ GPIOD_API line_bulk(line_bulk&& other) = default;
+
+ /**
+ * @brief Assignment operator.
+ * @param other Other line_bulk object.
+ * @return Reference to this object.
+ */
+ GPIOD_API line_bulk& operator=(const line_bulk& other) = default;
+
+ /**
+ * @brief Move assignment operator.
+ * @param other Other line_bulk object.
+ * @return Reference to this object.
+ */
+ GPIOD_API line_bulk& operator=(line_bulk&& other) = default;
+
+ /**
+ * @brief Destructor.
+ */
+ GPIOD_API ~line_bulk(void) = default;
+
+ /**
+ * @brief Add a line to this line_bulk object.
+ * @param new_line Line to add.
+ * @note The new line must be owned by the same chip as all the other
+ * lines already held by this line_bulk object.
+ */
+ GPIOD_API void append(const line& new_line);
+
+ /**
+ * @brief Get the line at given offset.
+ * @param offset Offset of the line to get.
+ * @return Reference to the line object.
+ */
+ GPIOD_API line& get(unsigned int offset);
+
+ /**
+ * @brief Get the line at given offset without bounds checking.
+ * @param offset Offset of the line to get.
+ * @return Reference to the line object.
+ * @note No bounds checking is performed.
+ */
+ GPIOD_API line& operator[](unsigned int offset);
+
+ /**
+ * @brief Get the number of lines currently held by this object.
+ * @return Number of elements in this line_bulk.
+ */
+ GPIOD_API unsigned int size(void) const noexcept;
+
+ /**
+ * @brief Check if this line_bulk doesn't hold any lines.
+ * @return True if this object is empty, false otherwise.
+ */
+ GPIOD_API bool empty(void) const noexcept;
+
+ /**
+ * @brief Remove all lines from this object.
+ */
+ GPIOD_API void clear(void);
+
+ /**
+ * @brief Request all lines held by this object.
+ * @param config Request config (see gpiod::line_request).
+ * @param default_vals Vector of default values. Only relevant for
+ * output direction requests.
+ */
+ GPIOD_API void request(const line_request& config,
+ const std::vector<int> default_vals = std::vector<int>()) const;
+
+ /**
+ * @brief Release all lines held by this object.
+ */
+ GPIOD_API void release(void) const;
+
+ /**
+ * @brief Read values from all lines held by this object.
+ * @return Vector containing line values the order of which corresponds
+ * with the order of lines in the internal array.
+ */
+ GPIOD_API ::std::vector<int> get_values(void) const;
+
+ /**
+ * @brief Set values of all lines held by this object.
+ * @param values Vector of values to set. Must be the same size as the
+ * number of lines held by this line_bulk.
+ */
+ GPIOD_API void set_values(const ::std::vector<int>& values) const;
+
+ /**
+ * @brief Poll the set of lines for line events.
+ * @param timeout Number of nanoseconds to wait before returning an
+ * empty line_bulk.
+ * @return Returns a line_bulk object containing lines on which events
+ * occurred.
+ */
+ GPIOD_API line_bulk event_wait(const ::std::chrono::nanoseconds& timeout) const;
+
+ /**
+ * @brief Check if this object holds any lines.
+ * @return True if this line_bulk holds at least one line, false otherwise.
+ */
+ GPIOD_API operator bool(void) const noexcept;
+
+ /**
+ * @brief Check if this object doesn't hold any lines.
+ * @return True if this line_bulk is empty, false otherwise.
+ */
+ GPIOD_API bool operator!(void) const noexcept;
+
+ /**
+ * @brief Max number of lines that this object can hold.
+ */
+ GPIOD_API static const unsigned int MAX_LINES;
+
+ /**
+ * @brief Iterator for iterating over lines held by line_bulk.
+ */
+ class iterator
+ {
+ public:
+
+ /**
+ * @brief Default constructor. Builds an empty iterator object.
+ */
+ GPIOD_API iterator(void) = default;
+
+ /**
+ * @brief Copy constructor.
+ * @param other Other line_bulk iterator.
+ */
+ GPIOD_API iterator(const iterator& other) = default;
+
+ /**
+ * @brief Move constructor.
+ * @param other Other line_bulk iterator.
+ */
+ GPIOD_API iterator(iterator&& other) = default;
+
+ /**
+ * @brief Assignment operator.
+ * @param other Other line_bulk iterator.
+ * @return Reference to this iterator.
+ */
+ GPIOD_API iterator& operator=(const iterator& other) = default;
+
+ /**
+ * @brief Move assignment operator.
+ * @param other Other line_bulk iterator.
+ * @return Reference to this iterator.
+ */
+ GPIOD_API iterator& operator=(iterator&& other) = default;
+
+ /**
+ * @brief Destructor.
+ */
+ GPIOD_API ~iterator(void) = default;
+
+ /**
+ * @brief Advance the iterator by one element.
+ * @return Reference to this iterator.
+ */
+ GPIOD_API iterator& operator++(void);
+
+ /**
+ * @brief Dereference current element.
+ * @return Current GPIO line by reference.
+ */
+ GPIOD_API const line& operator*(void) const;
+
+ /**
+ * @brief Member access operator.
+ * @return Current GPIO line by pointer.
+ */
+ GPIOD_API const line* operator->(void) const;
+
+ /**
+ * @brief Check if this operator points to the same element.
+ * @param rhs Right-hand side of the equation.
+ * @return True if this iterator points to the same GPIO line,
+ * false otherwise.
+ */
+ GPIOD_API bool operator==(const iterator& rhs) const noexcept;
+
+ /**
+ * @brief Check if this operator doesn't point to the same element.
+ * @param rhs Right-hand side of the equation.
+ * @return True if this iterator doesn't point to the same GPIO
+ * line, false otherwise.
+ */
+ GPIOD_API bool operator!=(const iterator& rhs) const noexcept;
+
+ private:
+
+ iterator(const ::std::vector<line>::iterator& it);
+
+ ::std::vector<line>::iterator _m_iter;
+
+ friend line_bulk;
+ };
+
+ /**
+ * @brief Returns an iterator to the first line.
+ * @return A line_bulk iterator.
+ */
+ GPIOD_API iterator begin(void) noexcept;
+
+ /**
+ * @brief Returns an iterator to the element following the last line.
+ * @return A line_bulk iterator.
+ */
+ GPIOD_API iterator end(void) noexcept;
+
+private:
+
+ void throw_if_empty(void) const;
+ void to_line_bulk(::gpiod_line_bulk* bulk) const;
+
+ ::std::vector<line> _m_bulk;
+};
+
+/**
+ * @brief Create a new chip_iter.
+ * @return New chip iterator object pointing to the first GPIO chip on the system.
+ * @note This function is needed as we already use the default constructor of
+ * gpiod::chip_iter as the return value of gpiod::end.
+ */
+GPIOD_API chip_iter make_chip_iter(void);
+
+/**
+ * @brief Support for range-based loops for chip iterators.
+ * @param iter A chip iterator.
+ * @return Iterator unchanged.
+ */
+GPIOD_API chip_iter begin(chip_iter iter) noexcept;
+
+/**
+ * @brief Support for range-based loops for chip iterators.
+ * @param iter A chip iterator.
+ * @return New end iterator.
+ */
+GPIOD_API chip_iter end(const chip_iter& iter) noexcept;
+
+/**
+ * @brief Allows to iterate over all GPIO chips present on the system.
+ */
+class chip_iter
+{
+public:
+
+ /**
+ * @brief Default constructor. Creates the end iterator.
+ */
+ GPIOD_API chip_iter(void) = default;
+
+ /**
+ * @brief Copy constructor.
+ * @param other Other chip_iter.
+ */
+ GPIOD_API chip_iter(const chip_iter& other) = default;
+
+ /**
+ * @brief Move constructor.
+ * @param other Other chip_iter.
+ */
+ GPIOD_API chip_iter(chip_iter&& other) = default;
+
+ /**
+ * @brief Assignment operator.
+ * @param other Other chip_iter.
+ * @return Reference to this iterator.
+ */
+ GPIOD_API chip_iter& operator=(const chip_iter& other) = default;
+
+ /**
+ * @brief Move assignment operator.
+ * @param other Other chip_iter.
+ * @return Reference to this iterator.
+ */
+ GPIOD_API chip_iter& operator=(chip_iter&& other) = default;
+
+ /**
+ * @brief Destructor.
+ */
+ GPIOD_API ~chip_iter(void) = default;
+
+ /**
+ * @brief Advance the iterator by one element.
+ * @return Reference to this iterator.
+ */
+ GPIOD_API chip_iter& operator++(void);
+
+ /**
+ * @brief Dereference current element.
+ * @return Current GPIO chip by reference.
+ */
+ GPIOD_API const chip& operator*(void) const;
+
+ /**
+ * @brief Member access operator.
+ * @return Current GPIO chip by pointer.
+ */
+ GPIOD_API const chip* operator->(void) const;
+
+ /**
+ * @brief Check if this operator points to the same element.
+ * @param rhs Right-hand side of the equation.
+ * @return True if this iterator points to the same chip_iter,
+ * false otherwise.
+ */
+ GPIOD_API bool operator==(const chip_iter& rhs) const noexcept;
+
+ /**
+ * @brief Check if this operator doesn't point to the same element.
+ * @param rhs Right-hand side of the equation.
+ * @return True if this iterator doesn't point to the same chip_iter,
+ * false otherwise.
+ */
+ GPIOD_API bool operator!=(const chip_iter& rhs) const noexcept;
+
+private:
+
+ chip_iter(::gpiod_chip_iter* iter);
+
+ ::std::shared_ptr<::gpiod_chip_iter> _m_iter;
+ chip _m_current;
+
+ friend chip_iter make_chip_iter(void);
+};
+
+/**
+ * @brief Support for range-based loops for line iterators.
+ * @param iter A line iterator.
+ * @return Iterator unchanged.
+ */
+GPIOD_API line_iter begin(line_iter iter) noexcept;
+
+/**
+ * @brief Support for range-based loops for line iterators.
+ * @param iter A line iterator.
+ * @return New end iterator.
+ */
+GPIOD_API line_iter end(const line_iter& iter) noexcept;
+
+/**
+ * @brief Allows to iterate over all lines owned by a GPIO chip.
+ */
+class line_iter
+{
+public:
+
+ /**
+ * @brief Default constructor. Creates the end iterator.
+ */
+ GPIOD_API line_iter(void) = default;
+
+ /**
+ * @brief Constructor. Creates the begin iterator.
+ * @param owner Chip owning the GPIO lines over which we want to iterate.
+ */
+ GPIOD_API line_iter(const chip& owner);
+
+ /**
+ * @brief Copy constructor.
+ * @param other Other line iterator.
+ */
+ GPIOD_API line_iter(const line_iter& other) = default;
+
+ /**
+ * @brief Move constructor.
+ * @param other Other line iterator.
+ */
+ GPIOD_API line_iter(line_iter&& other) = default;
+
+ /**
+ * @brief Assignment operator.
+ * @param other Other line iterator.
+ * @return Reference to this line_iter.
+ */
+ GPIOD_API line_iter& operator=(const line_iter& other) = default;
+
+ /**
+ * @brief Move assignment operator.
+ * @param other Other line iterator.
+ * @return Reference to this line_iter.
+ */
+ GPIOD_API line_iter& operator=(line_iter&& other) = default;
+
+ /**
+ * @brief Destructor.
+ */
+ GPIOD_API ~line_iter(void) = default;
+
+ /**
+ * @brief Advance the iterator by one element.
+ * @return Reference to this iterator.
+ */
+ GPIOD_API line_iter& operator++(void);
+
+ /**
+ * @brief Dereference current element.
+ * @return Current GPIO line by reference.
+ */
+ GPIOD_API const line& operator*(void) const;
+
+ /**
+ * @brief Member access operator.
+ * @return Current GPIO line by pointer.
+ */
+ GPIOD_API const line* operator->(void) const;
+
+ /**
+ * @brief Check if this operator points to the same element.
+ * @param rhs Right-hand side of the equation.
+ * @return True if this iterator points to the same line_iter,
+ * false otherwise.
+ */
+ GPIOD_API bool operator==(const line_iter& rhs) const noexcept;
+
+ /**
+ * @brief Check if this operator doesn't point to the same element.
+ * @param rhs Right-hand side of the equation.
+ * @return True if this iterator doesn't point to the same line_iter,
+ * false otherwise.
+ */
+ GPIOD_API bool operator!=(const line_iter& rhs) const noexcept;
+
+private:
+
+ ::std::shared_ptr<::gpiod_line_iter> _m_iter;
+ line _m_current;
+};
+
+/**
+ * @}
+ */
+
+} /* namespace gpiod */
+
+#endif /* __LIBGPIOD_GPIOD_CXX_HPP__ */
diff --git a/bindings/cxx/iter.cpp b/bindings/cxx/iter.cpp
new file mode 100644
index 0000000..8c0b9ba
--- /dev/null
+++ b/bindings/cxx/iter.cpp
@@ -0,0 +1,143 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+#include <gpiod.hpp>
+#include <system_error>
+
+namespace gpiod {
+
+namespace {
+
+void chip_iter_deleter(::gpiod_chip_iter* iter)
+{
+ ::gpiod_chip_iter_free_noclose(iter);
+}
+
+void line_iter_deleter(::gpiod_line_iter* iter)
+{
+ ::gpiod_line_iter_free(iter);
+}
+
+::gpiod_line_iter* make_line_iter(::gpiod_chip* chip)
+{
+ ::gpiod_line_iter* iter;
+
+ iter = ::gpiod_line_iter_new(chip);
+ if (!iter)
+ throw ::std::system_error(errno, ::std::system_category(),
+ "error creating GPIO line iterator");
+
+ return iter;
+}
+
+} /* namespace */
+
+chip_iter make_chip_iter(void)
+{
+ ::gpiod_chip_iter* iter = ::gpiod_chip_iter_new();
+ if (!iter)
+ throw ::std::system_error(errno, ::std::system_category(),
+ "error creating GPIO chip iterator");
+
+ return ::std::move(chip_iter(iter));
+}
+
+bool chip_iter::operator==(const chip_iter& rhs) const noexcept
+{
+ return this->_m_current == rhs._m_current;
+}
+
+bool chip_iter::operator!=(const chip_iter& rhs) const noexcept
+{
+ return this->_m_current != rhs._m_current;
+}
+
+chip_iter::chip_iter(::gpiod_chip_iter *iter)
+ : _m_iter(iter, chip_iter_deleter)
+{
+ ::gpiod_chip* first = ::gpiod_chip_iter_next_noclose(this->_m_iter.get());
+
+ if (first != nullptr)
+ this->_m_current = ::std::move(chip(first));
+}
+
+chip_iter& chip_iter::operator++(void)
+{
+ ::gpiod_chip* next = ::gpiod_chip_iter_next_noclose(this->_m_iter.get());
+
+ this->_m_current = next ? chip(next) : chip();
+
+ return *this;
+}
+
+const chip& chip_iter::operator*(void) const
+{
+ return this->_m_current;
+}
+
+const chip* chip_iter::operator->(void) const
+{
+ return ::std::addressof(this->_m_current);
+}
+
+chip_iter begin(chip_iter iter) noexcept
+{
+ return iter;
+}
+
+chip_iter end(const chip_iter&) noexcept
+{
+ return ::std::move(chip_iter());
+}
+
+line_iter begin(line_iter iter) noexcept
+{
+ return iter;
+}
+
+line_iter end(const line_iter&) noexcept
+{
+ return ::std::move(line_iter());
+}
+
+line_iter::line_iter(const chip& owner)
+ : _m_iter(make_line_iter(owner._m_chip.get()), line_iter_deleter),
+ _m_current(line(::gpiod_line_iter_next(this->_m_iter.get()), owner))
+{
+
+}
+
+line_iter& line_iter::operator++(void)
+{
+ ::gpiod_line* next = ::gpiod_line_iter_next(this->_m_iter.get());
+
+ this->_m_current = next ? line(next, this->_m_current._m_chip) : line();
+
+ return *this;
+}
+
+const line& line_iter::operator*(void) const
+{
+ return this->_m_current;
+}
+
+const line* line_iter::operator->(void) const
+{
+ return ::std::addressof(this->_m_current);
+}
+
+bool line_iter::operator==(const line_iter& rhs) const noexcept
+{
+ return this->_m_current._m_line == rhs._m_current._m_line;
+}
+
+bool line_iter::operator!=(const line_iter& rhs) const noexcept
+{
+ return this->_m_current._m_line != rhs._m_current._m_line;
+}
+
+} /* namespace gpiod */
diff --git a/bindings/cxx/libgpiodcxx.pc.in b/bindings/cxx/libgpiodcxx.pc.in
new file mode 100644
index 0000000..9d227c9
--- /dev/null
+++ b/bindings/cxx/libgpiodcxx.pc.in
@@ -0,0 +1,11 @@
+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}
diff --git a/bindings/cxx/line.cpp b/bindings/cxx/line.cpp
new file mode 100644
index 0000000..f8d0e62
--- /dev/null
+++ b/bindings/cxx/line.cpp
@@ -0,0 +1,242 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+#include <gpiod.hpp>
+#include <system_error>
+
+namespace gpiod {
+
+line::line(void)
+ : _m_line(nullptr),
+ _m_chip()
+{
+
+}
+
+line::line(::gpiod_line* line, const chip& owner)
+ : _m_line(line),
+ _m_chip(owner)
+{
+
+}
+
+unsigned int line::offset(void) const
+{
+ this->throw_if_null();
+
+ return ::gpiod_line_offset(this->_m_line);
+}
+
+::std::string line::name(void) const
+{
+ this->throw_if_null();
+
+ const char* name = ::gpiod_line_name(this->_m_line);
+
+ return ::std::move(name ? ::std::string(name) : ::std::string());
+}
+
+::std::string line::consumer(void) const
+{
+ this->throw_if_null();
+
+ const char* consumer = ::gpiod_line_consumer(this->_m_line);
+
+ return ::std::move(consumer ? ::std::string(consumer) : ::std::string());
+}
+
+int line::direction(void) const noexcept
+{
+ this->throw_if_null();
+
+ int dir = ::gpiod_line_direction(this->_m_line);
+
+ return dir == GPIOD_LINE_DIRECTION_INPUT ? DIRECTION_INPUT : DIRECTION_OUTPUT;
+}
+
+int line::active_state(void) const noexcept
+{
+ this->throw_if_null();
+
+ int active = ::gpiod_line_active_state(this->_m_line);
+
+ return active == GPIOD_LINE_ACTIVE_STATE_HIGH ? ACTIVE_HIGH : ACTIVE_LOW;
+}
+
+bool line::is_used(void) const
+{
+ this->throw_if_null();
+
+ return ::gpiod_line_is_used(this->_m_line);
+}
+
+bool line::is_open_drain(void) const
+{
+ this->throw_if_null();
+
+ return ::gpiod_line_is_open_drain(this->_m_line);
+}
+
+bool line::is_open_source(void) const
+{
+ this->throw_if_null();
+
+ return ::gpiod_line_is_open_source(this->_m_line);
+}
+
+void line::request(const line_request& config, int default_val) const
+{
+ this->throw_if_null();
+
+ line_bulk bulk({ *this });
+
+ bulk.request(config, { default_val });
+}
+
+void line::release(void) const
+{
+ this->throw_if_null();
+
+ line_bulk bulk({ *this });
+
+ bulk.release();
+}
+
+bool line::is_requested(void) const
+{
+ this->throw_if_null();
+
+ return ::gpiod_line_is_requested(this->_m_line);
+}
+
+/*
+ * REVISIT: Check the performance of get/set_value & event_wait compared to
+ * the C API. Creating a line_bulk object involves a memory allocation every
+ * time this method if called. If the performance is significantly lower,
+ * switch to calling the C functions for setting/getting line values and
+ * polling for events on single lines directly.
+ */
+
+int line::get_value(void) const
+{
+ this->throw_if_null();
+
+ line_bulk bulk({ *this });
+
+ return bulk.get_values()[0];
+}
+
+void line::set_value(int val) const
+{
+ this->throw_if_null();
+
+ line_bulk bulk({ *this });
+
+ bulk.set_values({ val });
+}
+
+bool line::event_wait(const ::std::chrono::nanoseconds& timeout) const
+{
+ this->throw_if_null();
+
+ line_bulk bulk({ *this });
+
+ line_bulk event_bulk = bulk.event_wait(timeout);
+
+ return ::std::move(event_bulk);
+}
+
+line_event line::event_read(void) const
+{
+ this->throw_if_null();
+
+ ::gpiod_line_event event_buf;
+ line_event event;
+ int rv;
+
+ rv = ::gpiod_line_event_read(this->_m_line, ::std::addressof(event_buf));
+ if (rv < 0)
+ throw ::std::system_error(errno, ::std::system_category(),
+ "error reading line event");
+
+ if (event_buf.event_type == GPIOD_LINE_EVENT_RISING_EDGE)
+ event.event_type = line_event::RISING_EDGE;
+ else if (event_buf.event_type == GPIOD_LINE_EVENT_FALLING_EDGE)
+ event.event_type = line_event::FALLING_EDGE;
+
+ event.timestamp = ::std::chrono::nanoseconds(
+ event_buf.ts.tv_nsec + (event_buf.ts.tv_sec * 1000000000));
+
+ event.source = *this;
+
+ return ::std::move(event);
+}
+
+int line::event_get_fd(void) const
+{
+ this->throw_if_null();
+
+ int ret = ::gpiod_line_event_get_fd(this->_m_line);
+
+ if (ret < 0)
+ ::std::system_error(errno, ::std::system_category(),
+ "unable to get the line event file descriptor");
+
+ return ret;
+}
+
+const chip& line::get_chip(void) const
+{
+ return this->_m_chip;
+}
+
+void line::reset(void)
+{
+ this->_m_line = nullptr;
+ this->_m_chip.reset();
+}
+
+bool line::operator==(const line& rhs) const noexcept
+{
+ return this->_m_line == rhs._m_line;
+}
+
+bool line::operator!=(const line& rhs) const noexcept
+{
+ return this->_m_line != rhs._m_line;
+}
+
+line::operator bool(void) const noexcept
+{
+ return this->_m_line != nullptr;
+}
+
+bool line::operator!(void) const noexcept
+{
+ return this->_m_line == nullptr;
+}
+
+void line::throw_if_null(void) const
+{
+ if (!this->_m_line)
+ throw ::std::logic_error("object not holding a GPIO line handle");
+}
+
+line find_line(const ::std::string& name)
+{
+ line ret;
+
+ for (auto& it: make_chip_iter()) {
+ ret = it.find_line(name);
+ if (ret)
+ break;
+ }
+
+ return ::std::move(ret);
+}
+
+} /* namespace gpiod */
diff --git a/bindings/cxx/line_bulk.cpp b/bindings/cxx/line_bulk.cpp
new file mode 100644
index 0000000..3006b65
--- /dev/null
+++ b/bindings/cxx/line_bulk.cpp
@@ -0,0 +1,269 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+#include <gpiod.hpp>
+#include <map>
+#include <system_error>
+
+namespace gpiod {
+
+const ::std::bitset<32> line_request::FLAG_ACTIVE_LOW("001");
+const ::std::bitset<32> line_request::FLAG_OPEN_SOURCE("010");
+const ::std::bitset<32> line_request::FLAG_OPEN_DRAIN("100");
+
+namespace {
+
+const ::std::map<int, int> reqtype_mapping = {
+ { line_request::DIRECTION_AS_IS, GPIOD_LINE_REQUEST_DIRECTION_AS_IS, },
+ { line_request::DIRECTION_INPUT, GPIOD_LINE_REQUEST_DIRECTION_INPUT, },
+ { line_request::DIRECTION_OUTPUT, GPIOD_LINE_REQUEST_DIRECTION_OUTPUT, },
+ { line_request::EVENT_FALLING_EDGE, GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE, },
+ { line_request::EVENT_RISING_EDGE, GPIOD_LINE_REQUEST_EVENT_RISING_EDGE, },
+ { line_request::EVENT_BOTH_EDGES, GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES, },
+};
+
+struct bitset_cmp
+{
+ bool operator()(const ::std::bitset<32>& lhs, const ::std::bitset<32>& rhs) const
+ {
+ return lhs.to_ulong() < rhs.to_ulong();
+ }
+};
+
+const ::std::map<::std::bitset<32>, int, bitset_cmp> reqflag_mapping = {
+ { line_request::FLAG_ACTIVE_LOW, GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW, },
+ { line_request::FLAG_OPEN_DRAIN, GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN, },
+ { line_request::FLAG_OPEN_SOURCE, GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE, },
+};
+
+} /* namespace */
+
+const unsigned int line_bulk::MAX_LINES = GPIOD_LINE_BULK_MAX_LINES;
+
+line_bulk::line_bulk(const ::std::vector<line>& lines)
+ : _m_bulk()
+{
+ this->_m_bulk.reserve(lines.size());
+
+ for (auto& it: lines)
+ this->append(it);
+}
+
+void line_bulk::append(const line& new_line)
+{
+ if (!new_line)
+ throw ::std::logic_error("line_bulk cannot hold empty line objects");
+
+ if (this->_m_bulk.size() >= MAX_LINES)
+ throw ::std::logic_error("maximum number of lines reached");
+
+ if (this->_m_bulk.size() >= 1 && this->_m_bulk.begin()->get_chip() != new_line.get_chip())
+ throw std::logic_error("line_bulk cannot hold GPIO lines from different chips");
+
+ this->_m_bulk.push_back(new_line);
+}
+
+line& line_bulk::get(unsigned int offset)
+{
+ return this->_m_bulk.at(offset);
+}
+
+line& line_bulk::operator[](unsigned int offset)
+{
+ return this->_m_bulk[offset];
+}
+
+unsigned int line_bulk::size(void) const noexcept
+{
+ return this->_m_bulk.size();
+}
+
+bool line_bulk::empty(void) const noexcept
+{
+ return this->_m_bulk.empty();
+}
+
+void line_bulk::clear(void)
+{
+ this->_m_bulk.clear();
+}
+
+void line_bulk::request(const line_request& config, const std::vector<int> default_vals) const
+{
+ this->throw_if_empty();
+
+ if (!default_vals.empty() && this->size() != default_vals.size())
+ throw ::std::invalid_argument("the number of default values must correspond with the number of lines");
+
+ ::gpiod_line_request_config conf;
+ ::gpiod_line_bulk bulk;
+ int rv;
+
+ this->to_line_bulk(::std::addressof(bulk));
+
+ conf.consumer = config.consumer.c_str();
+ conf.request_type = reqtype_mapping.at(config.request_type);
+ conf.flags = 0;
+
+ for (auto& it: reqflag_mapping) {
+ if ((it.first & config.flags).to_ulong())
+ conf.flags |= it.second;
+ }
+
+ rv = ::gpiod_line_request_bulk(::std::addressof(bulk),
+ ::std::addressof(conf),
+ default_vals.empty() ? NULL : default_vals.data());
+ if (rv)
+ throw ::std::system_error(errno, ::std::system_category(),
+ "error requesting GPIO lines");
+}
+
+void line_bulk::release(void) const
+{
+ this->throw_if_empty();
+
+ ::gpiod_line_bulk bulk;
+
+ this->to_line_bulk(::std::addressof(bulk));
+
+ ::gpiod_line_release_bulk(::std::addressof(bulk));
+}
+
+::std::vector<int> line_bulk::get_values(void) const
+{
+ this->throw_if_empty();
+
+ ::std::vector<int> values;
+ ::gpiod_line_bulk bulk;
+ int rv;
+
+ this->to_line_bulk(::std::addressof(bulk));
+ values.resize(this->_m_bulk.size());
+
+ rv = ::gpiod_line_get_value_bulk(::std::addressof(bulk), values.data());
+ if (rv)
+ throw ::std::system_error(errno, ::std::system_category(),
+ "error reading GPIO line values");
+
+ return ::std::move(values);
+}
+
+void line_bulk::set_values(const ::std::vector<int>& values) const
+{
+ this->throw_if_empty();
+
+ if (values.size() != this->_m_bulk.size())
+ throw ::std::invalid_argument("the size of values array must correspond with the number of lines");
+
+ ::gpiod_line_bulk bulk;
+ int rv;
+
+ this->to_line_bulk(::std::addressof(bulk));
+
+ rv = ::gpiod_line_set_value_bulk(::std::addressof(bulk), values.data());
+ if (rv)
+ throw ::std::system_error(errno, ::std::system_category(),
+ "error setting GPIO line values");
+}
+
+line_bulk line_bulk::event_wait(const ::std::chrono::nanoseconds& timeout) const
+{
+ this->throw_if_empty();
+
+ ::gpiod_line_bulk bulk, event_bulk;
+ ::timespec ts;
+ line_bulk ret;
+ int rv;
+
+ this->to_line_bulk(::std::addressof(bulk));
+
+ ::gpiod_line_bulk_init(::std::addressof(event_bulk));
+
+ ts.tv_sec = timeout.count() / 1000000000ULL;
+ ts.tv_nsec = timeout.count() % 1000000000ULL;
+
+ rv = ::gpiod_line_event_wait_bulk(::std::addressof(bulk),
+ ::std::addressof(ts),
+ ::std::addressof(event_bulk));
+ if (rv < 0) {
+ throw ::std::system_error(errno, ::std::system_category(),
+ "error polling for events");
+ } else if (rv > 0) {
+ for (unsigned int i = 0; i < event_bulk.num_lines; i++)
+ ret.append(line(event_bulk.lines[i], this->_m_bulk[i].get_chip()));
+ }
+
+ return ::std::move(ret);
+}
+
+line_bulk::operator bool(void) const noexcept
+{
+ return !this->_m_bulk.empty();
+}
+
+bool line_bulk::operator!(void) const noexcept
+{
+ return this->_m_bulk.empty();
+}
+
+line_bulk::iterator::iterator(const ::std::vector<line>::iterator& it)
+ : _m_iter(it)
+{
+
+}
+
+line_bulk::iterator& line_bulk::iterator::operator++(void)
+{
+ this->_m_iter++;
+
+ return *this;
+}
+
+const line& line_bulk::iterator::operator*(void) const
+{
+ return *this->_m_iter;
+}
+
+const line* line_bulk::iterator::operator->(void) const
+{
+ return this->_m_iter.operator->();
+}
+
+bool line_bulk::iterator::operator==(const iterator& rhs) const noexcept
+{
+ return this->_m_iter == rhs._m_iter;
+}
+
+bool line_bulk::iterator::operator!=(const iterator& rhs) const noexcept
+{
+ return this->_m_iter != rhs._m_iter;
+}
+
+line_bulk::iterator line_bulk::begin(void) noexcept
+{
+ return ::std::move(line_bulk::iterator(this->_m_bulk.begin()));
+}
+
+line_bulk::iterator line_bulk::end(void) noexcept
+{
+ return ::std::move(line_bulk::iterator(this->_m_bulk.end()));
+}
+
+void line_bulk::throw_if_empty(void) const
+{
+ if (this->_m_bulk.empty())
+ throw std::logic_error("line_bulk not holding any GPIO lines");
+}
+
+void line_bulk::to_line_bulk(::gpiod_line_bulk *bulk) const
+{
+ ::gpiod_line_bulk_init(bulk);
+ for (auto& it: this->_m_bulk)
+ ::gpiod_line_bulk_add(bulk, it._m_line);
+}
+
+} /* namespace gpiod */
diff --git a/bindings/python/Makefile.am b/bindings/python/Makefile.am
new file mode 100644
index 0000000..2c00c6e
--- /dev/null
+++ b/bindings/python/Makefile.am
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+#
+# This file is part of libgpiod.
+#
+# Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+#
+
+SUBDIRS = . examples
+
+pyexec_LTLIBRARIES = gpiod.la
+
+gpiod_la_SOURCES = gpiodmodule.c
+
+gpiod_la_CFLAGS = -I$(top_srcdir)/include/
+gpiod_la_CFLAGS += -Wall -Wextra -g $(PYTHON_CPPFLAGS)
+gpiod_la_LDFLAGS = -module -avoid-version
+gpiod_la_LIBADD = $(top_builddir)/lib/libgpiod.la $(PYTHON_LIBS)
diff --git a/bindings/python/examples/Makefile.am b/bindings/python/examples/Makefile.am
new file mode 100644
index 0000000..2745718
--- /dev/null
+++ b/bindings/python/examples/Makefile.am
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+#
+# This file is part of libgpiod.
+#
+# Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+#
+
+EXTRA_DIST = gpiod_tests.py \
+ gpiodetect.py \
+ gpiofind.py \
+ gpioget.py \
+ gpioinfo.py \
+ gpiomon.py \
+ gpioset.py
diff --git a/bindings/python/examples/gpiod_tests.py b/bindings/python/examples/gpiod_tests.py
new file mode 100755
index 0000000..910613a
--- /dev/null
+++ b/bindings/python/examples/gpiod_tests.py
@@ -0,0 +1,437 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+#
+# This file is part of libgpiod.
+#
+# Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+#
+
+'''Misc tests of libgpiod python bindings.
+
+These tests assume that at least one dummy gpiochip is present in the
+system and that it's detected as gpiochip0.
+'''
+
+import gpiod
+import select
+
+test_cases = []
+
+def add_test(name, func):
+ global test_cases
+
+ test_cases.append((name, func))
+
+def fire_line_event(chip, offset, rising):
+ path = '/sys/kernel/debug/gpio-mockup-event/{}/{}'.format(chip, offset)
+ with open(path, 'w') as fd:
+ fd.write('{}'.format(1 if rising else 0))
+
+def print_event(event):
+ print('type: {}'.format('rising' if event.type == gpiod.LineEvent.RISING_EDGE else 'falling'))
+ print('timestamp: {}.{}'.format(event.sec, event.nsec))
+ print('source line offset: {}'.format(event.source.offset()))
+
+def chip_open_default_lookup():
+ by_name = gpiod.Chip('gpiochip0')
+ by_path = gpiod.Chip('/dev/gpiochip0')
+ by_label = gpiod.Chip('gpio-mockup-A')
+ by_number = gpiod.Chip('0')
+ print('All good')
+ by_name.close()
+ by_path.close()
+ by_label.close()
+ by_number.close()
+
+add_test('Open a GPIO chip using different lookup modes', chip_open_default_lookup)
+
+def chip_open_different_modes():
+ chip = gpiod.Chip('/dev/gpiochip0', gpiod.Chip.OPEN_BY_PATH)
+ chip.close()
+ chip = gpiod.Chip('gpiochip0', gpiod.Chip.OPEN_BY_NAME)
+ chip.close()
+ chip = gpiod.Chip('gpio-mockup-A', gpiod.Chip.OPEN_BY_LABEL)
+ chip.close()
+ chip = gpiod.Chip('0', gpiod.Chip.OPEN_BY_NUMBER)
+ chip.close()
+ print('All good')
+
+add_test('Open a GPIO chip using different modes', chip_open_different_modes)
+
+def chip_open_nonexistent():
+ try:
+ chip = gpiod.Chip('/nonexistent_gpiochip')
+ except OSError as ex:
+ print('Exception raised as expected: {}'.format(ex))
+ return
+
+ assert False, 'OSError expected'
+
+add_test('Try to open a nonexistent GPIO chip', chip_open_nonexistent)
+
+def chip_open_no_args():
+ try:
+ chip = gpiod.Chip()
+ except TypeError:
+ print('Error as expected')
+ return
+
+ assert False, 'TypeError expected'
+
+add_test('Open a GPIO chip without arguments', chip_open_no_args)
+
+def chip_use_after_close():
+ chip = gpiod.Chip('gpiochip0')
+ line = chip.get_line(2)
+ chip.close()
+
+ try:
+ chip.name()
+ except ValueError as ex:
+ print('Error as expected: {}'.format(ex))
+
+ try:
+ line = chip.get_line(3)
+ except ValueError as ex:
+ print('Error as expected: {}'.format(ex))
+ return
+
+ assert False, 'ValueError expected'
+
+add_test('Use a GPIO chip after closing it', chip_use_after_close)
+
+def chip_with_statement():
+ with gpiod.Chip('gpiochip0') as chip:
+ print('Chip name in controlled execution: {}'.format(chip.name()))
+ line = chip.get_line(3)
+ print('Got line from chip in controlled execution: {}'.format(line.name()))
+
+add_test('Use a GPIO chip in controlled execution', chip_with_statement)
+
+def chip_info():
+ chip = gpiod.Chip('gpiochip0')
+ print('name: {}'.format(chip.name()))
+ print('label: {}'.format(chip.label()))
+ print('lines: {}'.format(chip.num_lines()))
+ chip.close()
+
+add_test('Print chip info', chip_info)
+
+def print_chip():
+ chip = gpiod.Chip('/dev/gpiochip0')
+ print(chip)
+ chip.close()
+
+add_test('Print chip object', print_chip)
+
+def create_chip_without_arguments():
+ try:
+ chip = gpiod.Chip()
+ except TypeError as ex:
+ print('Exception raised as expected: {}'.format(ex))
+ return
+
+ assert False, 'TypeError expected'
+
+add_test('Create chip object without arguments', create_chip_without_arguments)
+
+def create_line_object():
+ try:
+ line = gpiod.Line()
+ except NotImplementedError:
+ print('Error as expected')
+ return
+
+ assert False, 'NotImplementedError expected'
+
+add_test('Create a line object - should fail', create_line_object)
+
+def print_line():
+ chip = gpiod.Chip('gpio-mockup-A')
+ line = chip.get_line(3)
+ print(line)
+ chip.close()
+
+add_test('Print line object', print_line)
+
+def find_line():
+ line = gpiod.find_line('gpio-mockup-A-4')
+ print('found line - offset: {}'.format(line.offset()))
+ line.owner().close()
+
+add_test('Find line globally', find_line)
+
+def create_empty_line_bulk():
+ try:
+ lines = gpiod.LineBulk()
+ except TypeError:
+ print('Error as expected')
+ return
+
+ assert False, 'TypeError expected'
+
+add_test('Create a line bulk object - should fail', create_empty_line_bulk)
+
+def get_lines():
+ chip = gpiod.Chip('gpio-mockup-A')
+
+ print('getting four lines from chip')
+ lines = chip.get_lines([2, 4, 5, 7])
+
+ print('Retrieved lines:')
+ for line in lines:
+ print(line)
+
+ chip.close()
+
+add_test('Get lines from chip', get_lines)
+
+def get_all_lines():
+ chip = gpiod.Chip('gpio-mockup-A')
+
+ print('Retrieving all lines from chip')
+ lines = chip.get_all_lines()
+
+ print('Retrieved lines:')
+ for line in lines:
+ print(line)
+
+ chip.close()
+
+add_test('Get all lines from chip', get_all_lines)
+
+def find_lines():
+ chip = gpiod.Chip('gpiochip0')
+
+ print('looking up lines by names')
+ lines = chip.find_lines(['gpio-mockup-A-3', 'gpio-mockup-A-4', 'gpio-mockup-A-7'])
+
+ print('Retrieved lines:')
+ for line in lines:
+ print(line)
+
+ chip.close()
+
+add_test('Find multiple lines by name', find_lines)
+
+def find_lines_one_bad():
+ chip = gpiod.Chip('gpiochip0')
+
+ print('looking up lines by names')
+ try:
+ lines = chip.find_lines(['gpio-mockup-A-3', 'nonexistent', 'gpio-mockup-A-7'])
+ except TypeError as ex:
+ print('Error as expected')
+ return
+
+ assert False, 'TypeError expected'
+
+add_test('Find multiple lines but one line name is non-existent', find_lines_one_bad)
+
+def create_line_bulk_from_lines():
+ chip = gpiod.Chip('gpio-mockup-A')
+ line1 = chip.get_line(2)
+ line2 = chip.get_line(4)
+ line3 = chip.get_line(6)
+ lines = gpiod.LineBulk([line1, line2, line3])
+ print('Created LineBulk:')
+ print(lines)
+ chip.close()
+
+add_test('Create a LineBulk from a list of lines', create_line_bulk_from_lines)
+
+def line_bulk_to_list():
+ chip = gpiod.Chip('gpio-mockup-A')
+ lines = chip.get_lines((1, 2, 3))
+ print(lines.to_list())
+ chip.close()
+
+add_test('Convert a LineBulk to a list', line_bulk_to_list)
+
+def line_flags():
+ chip = gpiod.Chip('gpiochip0')
+ line = chip.get_line(3)
+
+ print('line is used: {}'.format(line.is_used()))
+ print('line is requested: {}'.format(line.is_requested()))
+
+ print('requesting line')
+ line.request(consumer="gpiod_test.py", type=gpiod.LINE_REQ_DIR_OUT,
+ flags=(gpiod.LINE_REQ_FLAG_OPEN_DRAIN | gpiod.LINE_REQ_FLAG_ACTIVE_LOW))
+
+ print('line is used: {}'.format(line.is_used()))
+ print('line is open drain: {}'.format(line.is_open_drain()))
+ print('line is open source: {}'.format(line.is_open_source()))
+ print('line is requested: {}'.format(line.is_requested()))
+ print('line is active-low: {}'.format(
+ "True" if line.active_state() == gpiod.Line.ACTIVE_LOW else "False"))
+
+ chip.close()
+
+add_test('Check various line flags', line_flags)
+
+def get_value_single_line():
+ chip = gpiod.Chip('gpio-mockup-A')
+ line = chip.get_line(2)
+ line.request(consumer="gpiod_test.py", type=gpiod.LINE_REQ_DIR_IN)
+ print('line value: {}'.format(line.get_value()))
+ chip.close()
+
+add_test('Get value - single line', get_value_single_line)
+
+def set_value_single_line():
+ chip = gpiod.Chip('gpiochip0')
+ line = chip.get_line(3)
+ line.request(consumer="gpiod_test.py", type=gpiod.LINE_REQ_DIR_IN)
+
+ print('line value before: {}'.format(line.get_value()))
+ line.release()
+ line.request(consumer="gpiod_test.py", type=gpiod.LINE_REQ_DIR_OUT)
+ line.set_value(1)
+ line.release()
+ line.request(consumer="gpiod_test.py", type=gpiod.LINE_REQ_DIR_IN)
+ print('line value after: {}'.format(line.get_value()))
+
+ chip.close()
+
+add_test('Set value - single line', set_value_single_line)
+
+def request_line_with_default_values():
+ chip = gpiod.Chip('gpiochip0')
+ line = chip.get_line(3)
+
+ print('requesting a single line with a default value')
+ line.request(consumer='gpiod_test.py', type=gpiod.LINE_REQ_DIR_OUT, default_vals=[ 1 ])
+
+ print('line value after request: {}'.format(line.get_value()))
+
+ chip.close()
+
+add_test('Request line with default value', request_line_with_default_values)
+
+def request_multiple_lines_with_default_values():
+ chip = gpiod.Chip('gpiochip0')
+ lines = chip.get_lines(( 1, 2, 3, 4, 5 ))
+
+ print('requesting lines with default values')
+ lines.request(consumer='gpiod_test.py', type=gpiod.LINE_REQ_DIR_OUT, default_vals=( 1, 0, 1, 0, 1 ))
+
+ print('line values after request: {}'.format(lines.get_values()))
+
+ chip.close()
+
+add_test('Request multiple lines with default values', request_multiple_lines_with_default_values)
+
+def request_line_incorrect_number_of_def_vals():
+ with gpiod.Chip('gpiochip0') as chip:
+ lines = chip.get_lines(( 1, 2, 3, 4, 5 ))
+
+ print('requesting lines with incorrect number of default values')
+ try:
+ lines.request(consumer='gpiod_test.py',
+ type=gpiod.LINE_REQ_DIR_OUT,
+ default_vals=( 1, 0, 1, 0 ))
+ except TypeError:
+ print('TypeError raised as expected')
+ return
+
+ assert False, 'TypeError expected'
+
+add_test('Request with incorrect number of default values', request_line_incorrect_number_of_def_vals)
+
+def line_event_single_line():
+ chip = gpiod.Chip('gpiochip0')
+ line = chip.get_line(1)
+
+ print('requesting line for events')
+ line.request(consumer="gpiod_test.py", type=gpiod.LINE_REQ_EV_BOTH_EDGES)
+
+ print('generating a line event')
+ fire_line_event('gpiochip0', 1, True)
+ assert line.event_wait(sec=1), 'Expected a line event to occur'
+
+ print('event received')
+ event = line.event_read()
+ print_event(event)
+
+ chip.close()
+
+add_test('Monitor a single line for events', line_event_single_line)
+
+def line_event_multiple_lines():
+ chip = gpiod.Chip('gpiochip0')
+ lines = chip.get_lines((1, 2, 3, 4, 5))
+
+ print('requesting lines for events')
+ lines.request(consumer="gpiod_test.py", type=gpiod.LINE_REQ_EV_BOTH_EDGES)
+
+ print('generating two line events')
+ fire_line_event('gpiochip0', 1, True)
+ fire_line_event('gpiochip0', 2, True)
+
+ events = lines.event_wait(sec=1)
+ assert events is not None and len(events) == 2, 'Expected to receive two line events'
+
+ print('events received:')
+ for line in events:
+ event = line.event_read()
+ print_event(event)
+
+ chip.close()
+
+add_test('Monitor multiple lines for events', line_event_multiple_lines)
+
+def line_event_poll_fd():
+ chip = gpiod.Chip('gpiochip0')
+ lines = chip.get_lines((1, 2, 3, 4, 5, 6))
+ print('requesting lines for events')
+ lines.request(consumer="gpiod_test.py", type=gpiod.LINE_REQ_EV_BOTH_EDGES)
+
+ print('generating three line events')
+ fire_line_event('gpiochip0', 2, True)
+ fire_line_event('gpiochip0', 3, False)
+ fire_line_event('gpiochip0', 5, True)
+
+ print('retrieving the file descriptors')
+ inputs = []
+ fd_mapping = {}
+ for line in lines:
+ inputs.append(line.event_get_fd())
+ fd_mapping[line.event_get_fd()] = line
+
+ readable, writable, exceptional = select.select(inputs, [], inputs, 1.0)
+ assert len(readable) == 3, 'Expected to receive three line events'
+
+ print('events received:')
+ for fd in readable:
+ line = fd_mapping[fd]
+ event = line.event_read()
+ print_event(event)
+
+ chip.close()
+
+add_test('Monitor multiple lines using their file descriptors', line_event_poll_fd)
+
+def line_event_repr():
+ with gpiod.Chip('gpiochip0') as chip:
+ line = chip.get_line(1)
+
+ print('requesting line for events')
+ line.request(consumer="gpiod_test.py", type=gpiod.LINE_REQ_EV_BOTH_EDGES)
+
+ print('generating a line event')
+ fire_line_event('gpiochip0', 1, True)
+ assert line.event_wait(sec=1), 'Expected a line event to occur'
+
+ print('event received: {}'.format(line.event_read()))
+
+add_test('Line event string repr', line_event_repr)
+
+print('API version is {}'.format(gpiod.version_string()))
+
+for name, func in test_cases:
+ print('==============================================')
+ print('{}:'.format(name))
+ print()
+ func()
diff --git a/bindings/python/examples/gpiodetect.py b/bindings/python/examples/gpiodetect.py
new file mode 100755
index 0000000..f539f04
--- /dev/null
+++ b/bindings/python/examples/gpiodetect.py
@@ -0,0 +1,18 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+#
+# This file is part of libgpiod.
+#
+# Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+#
+
+'''Reimplementation of the gpiodetect tool in Python.'''
+
+import gpiod
+
+for chip in gpiod.ChipIter():
+ print('{} [{}] ({} lines)'.format(chip.name(),
+ chip.label(),
+ chip.num_lines()))
+ chip.close()
diff --git a/bindings/python/examples/gpiofind.py b/bindings/python/examples/gpiofind.py
new file mode 100755
index 0000000..30b8e2b
--- /dev/null
+++ b/bindings/python/examples/gpiofind.py
@@ -0,0 +1,20 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+#
+# This file is part of libgpiod.
+#
+# Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+#
+
+'''Reimplementation of the gpiofind tool in Python.'''
+
+import gpiod
+import sys
+
+line = gpiod.find_line(sys.argv[1])
+if line is None:
+ sys.exit(1)
+
+print('{} {}'.format(line.owner().name(), line.offset()))
+line.owner().close()
diff --git a/bindings/python/examples/gpioget.py b/bindings/python/examples/gpioget.py
new file mode 100755
index 0000000..bc7645b
--- /dev/null
+++ b/bindings/python/examples/gpioget.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+#
+# This file is part of libgpiod.
+#
+# Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+#
+
+'''Simplified reimplementation of the gpioget tool in Python.'''
+
+import gpiod
+import sys
+
+if len(sys.argv) < 3:
+ raise TypeError('usage: gpioget.py <gpiochip> <offset1> <offset2> ...')
+
+with gpiod.Chip(sys.argv[1]) as chip:
+ offsets = []
+ for off in sys.argv[2:]:
+ offsets.append(int(off))
+
+ lines = chip.get_lines(offsets)
+ lines.request(consumer=sys.argv[0], type=gpiod.LINE_REQ_DIR_IN)
+ vals = lines.get_values()
+
+ for val in vals:
+ print(val, end=' ')
+ print()
diff --git a/bindings/python/examples/gpioinfo.py b/bindings/python/examples/gpioinfo.py
new file mode 100755
index 0000000..f9d91bf
--- /dev/null
+++ b/bindings/python/examples/gpioinfo.py
@@ -0,0 +1,31 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+#
+# This file is part of libgpiod.
+#
+# Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+#
+
+'''Simplified reimplementation of the gpioinfo tool in Python.'''
+
+import gpiod
+
+for chip in gpiod.ChipIter():
+ print('{} - {} lines:'.format(chip.name(), chip.num_lines()))
+
+ for line in gpiod.LineIter(chip):
+ offset = line.offset()
+ name = line.name()
+ consumer = line.consumer()
+ direction = line.direction()
+ active_state = line.active_state()
+
+ print('\tline {:>3}: {:>18} {:>12} {:>8} {:>10}'.format(
+ offset,
+ 'unnamed' if name is None else name,
+ 'unused' if consumer is None else consumer,
+ 'input' if direction == gpiod.Line.DIRECTION_INPUT else 'output',
+ 'active-low' if active_state == gpiod.Line.ACTIVE_LOW else 'active-high'))
+
+ chip.close()
diff --git a/bindings/python/examples/gpiomon.py b/bindings/python/examples/gpiomon.py
new file mode 100755
index 0000000..9cb71da
--- /dev/null
+++ b/bindings/python/examples/gpiomon.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+#
+# This file is part of libgpiod.
+#
+# Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+#
+
+'''Simplified reimplementation of the gpiomon tool in Python.'''
+
+import gpiod
+import sys
+
+def print_event(event):
+ if event.type == gpiod.LineEvent.RISING_EDGE:
+ print(' RISING EDGE', end='')
+ elif event.type == gpiod.LineEvent.FALLING_EDGE:
+ print('FALLING EDGE', end='')
+ else:
+ raise TypeError('Invalid event type')
+
+ print(' {}.{} line: {}'.format(event.sec, event.nsec, event.source.offset()))
+
+if len(sys.argv) < 3:
+ raise TypeError('usage: gpiomon.py <gpiochip> <offset1> <offset2> ...')
+
+with gpiod.Chip(sys.argv[1]) as chip:
+ offsets = []
+ for off in sys.argv[2:]:
+ offsets.append(int(off))
+
+ lines = chip.get_lines(offsets)
+ lines.request(consumer=sys.argv[0], type=gpiod.LINE_REQ_EV_BOTH_EDGES)
+
+ try:
+ while True:
+ ev_lines = lines.event_wait(sec=1)
+ if ev_lines:
+ for line in ev_lines:
+ event = line.event_read()
+ print_event(event)
+ except KeyboardInterrupt:
+ sys.exit(130)
diff --git a/bindings/python/examples/gpioset.py b/bindings/python/examples/gpioset.py
new file mode 100755
index 0000000..70fbcc9
--- /dev/null
+++ b/bindings/python/examples/gpioset.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+#
+# This file is part of libgpiod.
+#
+# Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+#
+
+'''Simplified reimplementation of the gpioset tool in Python.'''
+
+import gpiod
+import sys
+
+if len(sys.argv) < 3:
+ raise TypeError('usage: gpioset.py <gpiochip> <offset1>=<value1> ...')
+
+with gpiod.Chip(sys.argv[1]) as chip:
+ offsets = []
+ values = []
+ for arg in sys.argv[2:]:
+ arg = arg.split('=')
+ offsets.append(int(arg[0]))
+ values.append(int(arg[1]))
+
+ lines = chip.get_lines(offsets)
+ lines.request(consumer=sys.argv[0], type=gpiod.LINE_REQ_DIR_OUT)
+ vals = lines.set_values(values)
diff --git a/bindings/python/gpiodmodule.c b/bindings/python/gpiodmodule.c
new file mode 100644
index 0000000..ec28553
--- /dev/null
+++ b/bindings/python/gpiodmodule.c
@@ -0,0 +1,2391 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+#include <Python.h>
+#include <gpiod.h>
+
+typedef struct {
+ PyObject_HEAD
+ struct gpiod_chip *chip;
+} gpiod_ChipObject;
+
+typedef struct {
+ PyObject_HEAD
+ struct gpiod_line *line;
+ gpiod_ChipObject *owner;
+} gpiod_LineObject;
+
+typedef struct {
+ PyObject_HEAD
+ struct gpiod_line_event event;
+ gpiod_LineObject *source;
+} gpiod_LineEventObject;
+
+typedef struct {
+ PyObject_HEAD
+ PyObject **lines;
+ Py_ssize_t num_lines;
+ Py_ssize_t iter_idx;
+} gpiod_LineBulkObject;
+
+typedef struct {
+ PyObject_HEAD
+ struct gpiod_chip_iter *iter;
+} gpiod_ChipIterObject;
+
+typedef struct {
+ PyObject_HEAD
+ struct gpiod_line_iter *iter;
+ gpiod_ChipObject *owner;
+} gpiod_LineIterObject;
+
+static gpiod_LineBulkObject *gpiod_LineToLineBulk(gpiod_LineObject *line);
+static gpiod_LineObject *gpiod_MakeLineObject(gpiod_ChipObject *owner,
+ struct gpiod_line *line);
+
+enum {
+ gpiod_LINE_REQ_DIR_AS_IS = 1,
+ gpiod_LINE_REQ_DIR_IN,
+ gpiod_LINE_REQ_DIR_OUT,
+ gpiod_LINE_REQ_EV_FALLING_EDGE,
+ gpiod_LINE_REQ_EV_RISING_EDGE,
+ gpiod_LINE_REQ_EV_BOTH_EDGES,
+};
+
+enum {
+ gpiod_LINE_REQ_FLAG_OPEN_DRAIN = GPIOD_BIT(0),
+ gpiod_LINE_REQ_FLAG_OPEN_SOURCE = GPIOD_BIT(1),
+ gpiod_LINE_REQ_FLAG_ACTIVE_LOW = GPIOD_BIT(2),
+};
+
+enum {
+ gpiod_DIRECTION_INPUT = 1,
+ gpiod_DIRECTION_OUTPUT,
+};
+
+enum {
+ gpiod_ACTIVE_HIGH = 1,
+ gpiod_ACTIVE_LOW,
+};
+
+enum {
+ gpiod_RISING_EDGE = 1,
+ gpiod_FALLING_EDGE,
+};
+
+static bool gpiod_ChipIsClosed(gpiod_ChipObject *chip)
+{
+ if (!chip->chip) {
+ PyErr_SetString(PyExc_ValueError,
+ "I/O operation on closed file");
+ return true;
+ }
+
+ return false;
+}
+
+static PyObject *gpiod_CallMethodPyArgs(PyObject *obj, const char *method,
+ PyObject *args, PyObject *kwds)
+{
+ PyObject *callable, *ret;
+
+ callable = PyObject_GetAttrString((PyObject *)obj, method);
+ if (!callable)
+ return NULL;
+
+ ret = PyObject_Call(callable, args, kwds);
+ Py_DECREF(callable);
+
+ return ret;
+}
+
+static int gpiod_LineEvent_init(void)
+{
+ PyErr_SetString(PyExc_NotImplementedError,
+ "Only gpiod.Line can create new LineEvent objects.");
+ return -1;
+}
+
+static void gpiod_LineEvent_dealloc(gpiod_LineEventObject *self)
+{
+ if (self->source)
+ Py_DECREF(self->source);
+
+ PyObject_Del(self);
+}
+
+PyDoc_STRVAR(gpiod_LineEvent_get_type_doc,
+"Event type of this line event (integer).");
+
+PyObject *gpiod_LineEvent_get_type(gpiod_LineEventObject *self)
+{
+ int rv;
+
+ if (self->event.event_type == GPIOD_LINE_EVENT_RISING_EDGE)
+ rv = gpiod_RISING_EDGE;
+ else
+ rv = gpiod_FALLING_EDGE;
+
+ return Py_BuildValue("I", rv);
+}
+
+PyDoc_STRVAR(gpiod_LineEvent_get_sec_doc,
+"Seconds value of the line event timestamp (integer).");
+
+PyObject *gpiod_LineEvent_get_sec(gpiod_LineEventObject *self)
+{
+ return Py_BuildValue("I", self->event.ts.tv_sec);
+}
+
+PyDoc_STRVAR(gpiod_LineEvent_get_nsec_doc,
+"Nanoseconds value of the line event timestamp (integer).");
+
+PyObject *gpiod_LineEvent_get_nsec(gpiod_LineEventObject *self)
+{
+ return Py_BuildValue("I", self->event.ts.tv_nsec);
+}
+
+PyDoc_STRVAR(gpiod_LineEvent_get_source_doc,
+"Line object representing the GPIO line on which this event\n"
+"occurred (gpiod.Line object).");
+
+gpiod_LineObject *gpiod_LineEvent_get_source(gpiod_LineEventObject *self)
+{
+ Py_INCREF(self->source);
+ return self->source;
+}
+
+static PyGetSetDef gpiod_LineEvent_getset[] = {
+ {
+ .name = "type",
+ .get = (getter)gpiod_LineEvent_get_type,
+ .doc = gpiod_LineEvent_get_type_doc,
+ },
+ {
+ .name = "sec",
+ .get = (getter)gpiod_LineEvent_get_sec,
+ .doc = gpiod_LineEvent_get_sec_doc,
+ },
+ {
+ .name = "nsec",
+ .get = (getter)gpiod_LineEvent_get_nsec,
+ .doc = gpiod_LineEvent_get_nsec_doc,
+ },
+ {
+ .name = "source",
+ .get = (getter)gpiod_LineEvent_get_source,
+ .doc = gpiod_LineEvent_get_source_doc,
+ },
+ { }
+};
+
+static PyObject *gpiod_LineEvent_repr(gpiod_LineEventObject *self)
+{
+ PyObject *line_repr, *ret;
+ const char *edge;
+
+ if (self->event.event_type == GPIOD_LINE_EVENT_RISING_EDGE)
+ edge = "RISING EDGE";
+ else
+ edge = "FALLING EDGE";
+
+ line_repr = PyObject_CallMethod((PyObject *)self->source,
+ "__repr__", "");
+
+ ret = PyUnicode_FromFormat("'%s (%ld.%ld) source(%S)'",
+ edge, self->event.ts.tv_sec,
+ self->event.ts.tv_nsec, line_repr);
+ Py_DECREF(line_repr);
+
+ return ret;
+}
+
+PyDoc_STRVAR(gpiod_LineEventType_doc,
+"Represents a single GPIO line event. This object is immutable and can only\n"
+"be created by an instance of gpiod.Line.");
+
+static PyTypeObject gpiod_LineEventType = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "gpiod.LineEvent",
+ .tp_basicsize = sizeof(gpiod_LineEventObject),
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_doc = gpiod_LineEventType_doc,
+ .tp_new = PyType_GenericNew,
+ .tp_init = (initproc)gpiod_LineEvent_init,
+ .tp_dealloc = (destructor)gpiod_LineEvent_dealloc,
+ .tp_getset = gpiod_LineEvent_getset,
+ .tp_repr = (reprfunc)gpiod_LineEvent_repr,
+};
+
+static int gpiod_Line_init(void)
+{
+ PyErr_SetString(PyExc_NotImplementedError,
+ "Only gpiod.Chip can create new Line objects.");
+ return -1;
+}
+
+static void gpiod_Line_dealloc(gpiod_LineObject *self)
+{
+ if (self->owner)
+ Py_DECREF(self->owner);
+
+ PyObject_Del(self);
+}
+
+PyDoc_STRVAR(gpiod_Line_owner_doc,
+"owner() -> Chip object owning the line\n"
+"\n"
+"Get the GPIO chip owning this line.");
+
+static PyObject *gpiod_Line_owner(gpiod_LineObject *self)
+{
+ Py_INCREF(self->owner);
+ return (PyObject *)self->owner;
+}
+
+PyDoc_STRVAR(gpiod_Line_offset_doc,
+"offset() -> integer\n"
+"\n"
+"Get the offset of the GPIO line.");
+
+static PyObject *gpiod_Line_offset(gpiod_LineObject *self)
+{
+ if (gpiod_ChipIsClosed(self->owner))
+ return NULL;
+
+ return Py_BuildValue("I", gpiod_line_offset(self->line));
+}
+
+PyDoc_STRVAR(gpiod_Line_name_doc,
+"name() -> string\n"
+"\n"
+"Get the name of the GPIO line.");
+
+static PyObject *gpiod_Line_name(gpiod_LineObject *self)
+{
+ const char *name;
+
+ if (gpiod_ChipIsClosed(self->owner))
+ return NULL;
+
+ name = gpiod_line_name(self->line);
+ if (name)
+ return PyUnicode_FromFormat("%s", name);
+
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(gpiod_Line_consumer_doc,
+"consumer() -> string\n"
+"\n"
+"Get the consumer string of the GPIO line.");
+
+static PyObject *gpiod_Line_consumer(gpiod_LineObject *self)
+{
+ const char *consumer;
+
+ if (gpiod_ChipIsClosed(self->owner))
+ return NULL;
+
+ consumer = gpiod_line_consumer(self->line);
+ if (consumer)
+ return PyUnicode_FromFormat("%s", consumer);
+
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(gpiod_Line_direction_doc,
+"direction() -> integer\n"
+"\n"
+"Get the direction setting of this GPIO line.");
+
+static PyObject *gpiod_Line_direction(gpiod_LineObject *self)
+{
+ PyObject *ret;
+ int dir;
+
+ if (gpiod_ChipIsClosed(self->owner))
+ return NULL;
+
+ dir = gpiod_line_direction(self->line);
+
+ if (dir == GPIOD_LINE_DIRECTION_INPUT)
+ ret = Py_BuildValue("I", gpiod_DIRECTION_INPUT);
+ else
+ ret = Py_BuildValue("I", gpiod_DIRECTION_OUTPUT);
+
+ return ret;
+}
+
+PyDoc_STRVAR(gpiod_Line_active_state_doc,
+"active_state() -> integer\n"
+"\n"
+"Get the active state setting of this GPIO line.");
+
+static PyObject *gpiod_Line_active_state(gpiod_LineObject *self)
+{
+ PyObject *ret;
+ int active;
+
+ if (gpiod_ChipIsClosed(self->owner))
+ return NULL;
+
+ active = gpiod_line_active_state(self->line);
+
+ if (active == GPIOD_LINE_ACTIVE_STATE_HIGH)
+ ret = Py_BuildValue("I", gpiod_ACTIVE_HIGH);
+ else
+ ret = Py_BuildValue("I", gpiod_ACTIVE_LOW);
+
+ return ret;
+}
+
+PyDoc_STRVAR(gpiod_Line_is_used_doc,
+"is_used() -> boolean\n"
+"\n"
+"Check if this line is used by the kernel or other user space process.");
+
+static PyObject *gpiod_Line_is_used(gpiod_LineObject *self)
+{
+ if (gpiod_ChipIsClosed(self->owner))
+ return NULL;
+
+ if (gpiod_line_is_used(self->line))
+ Py_RETURN_TRUE;
+
+ Py_RETURN_FALSE;
+}
+
+PyDoc_STRVAR(gpiod_Line_is_open_drain_doc,
+"is_open_drain() -> boolean\n"
+"\n"
+"Check if this line represents an open-drain GPIO.");
+
+static PyObject *gpiod_Line_is_open_drain(gpiod_LineObject *self)
+{
+ if (gpiod_ChipIsClosed(self->owner))
+ return NULL;
+
+ if (gpiod_line_is_open_drain(self->line))
+ Py_RETURN_TRUE;
+
+ Py_RETURN_FALSE;
+}
+
+PyDoc_STRVAR(gpiod_Line_is_open_source_doc,
+"is_open_source() -> boolean\n"
+"\n"
+"Check if this line represents an open-source GPIO.");
+
+static PyObject *gpiod_Line_is_open_source(gpiod_LineObject *self)
+{
+ if (gpiod_ChipIsClosed(self->owner))
+ return NULL;
+
+ if (gpiod_line_is_open_source(self->line))
+ Py_RETURN_TRUE;
+
+ Py_RETURN_FALSE;
+}
+
+PyDoc_STRVAR(gpiod_Line_request_doc,
+"request(consumer[, type[, flags[, default_vals]]]) -> None\n"
+"\n"
+"Request this GPIO line.\n"
+"\n"
+" consumer\n"
+" Name of the consumer.\n"
+" type\n"
+" Type of the request.\n"
+" flags\n"
+" Other configuration flags.\n"
+" default_val\n"
+" Default value of this line."
+"\n"
+"Note: default_vals argument (sequence of default values passed down to\n"
+"LineBulk.request()) is still supported for backward compatibility but is\n"
+"now deprecated when requesting single lines.");
+
+static PyObject *gpiod_Line_request(gpiod_LineObject *self,
+ PyObject *args, PyObject *kwds)
+{
+ PyObject *ret, *def_val, *def_vals;
+ gpiod_LineBulkObject *bulk_obj;
+ int rv;
+
+ def_val = PyDict_GetItemString(kwds, "default_val");
+ def_vals = PyDict_GetItemString(kwds, "default_vals");
+
+ if (def_val && def_vals) {
+ PyErr_SetString(PyExc_TypeError,
+ "Cannot pass both default_val and default_vals arguments at the same time");
+ return NULL;
+ }
+
+ if (def_val) {
+ /*
+ * If default_val was passed as a single value, we wrap it
+ * in a tuple and add it to the kwds dictionary to be passed
+ * down to LineBulk.request(). We also remove the 'default_val'
+ * entry from kwds.
+ *
+ * I'm not sure if it's allowed to modify the kwds dictionary
+ * but it doesn't seem to cause any problems. If it does then
+ * we can simply copy the dictionary before calling
+ * LineBulk.request().
+ */
+ rv = PyDict_DelItemString(kwds, "default_val");
+ if (rv)
+ return NULL;
+
+ def_vals = Py_BuildValue("(O)", def_val);
+ if (!def_vals)
+ return NULL;
+
+ rv = PyDict_SetItemString(kwds, "default_vals", def_vals);
+ if (rv) {
+ Py_DECREF(def_vals);
+ return NULL;
+ }
+ }
+
+ bulk_obj = gpiod_LineToLineBulk(self);
+ if (!bulk_obj)
+ return NULL;
+
+ ret = gpiod_CallMethodPyArgs((PyObject *)bulk_obj,
+ "request", args, kwds);
+ Py_DECREF(bulk_obj);
+
+ return ret;
+}
+
+PyDoc_STRVAR(gpiod_Line_is_requested_doc,
+"is_requested() -> boolean\n"
+"\n"
+"Check if this user has ownership of this line.");
+
+static PyObject *gpiod_Line_is_requested(gpiod_LineObject *self)
+{
+ if (gpiod_ChipIsClosed(self->owner))
+ return NULL;
+
+ if (gpiod_line_is_requested(self->line))
+ Py_RETURN_TRUE;
+
+ Py_RETURN_FALSE;
+}
+
+PyDoc_STRVAR(gpiod_Line_get_value_doc,
+"get_value() -> integer\n"
+"\n"
+"Read the current value of this GPIO line.");
+
+static PyObject *gpiod_Line_get_value(gpiod_LineObject *self)
+{
+ gpiod_LineBulkObject *bulk_obj;
+ PyObject *vals, *ret;
+
+ bulk_obj = gpiod_LineToLineBulk(self);
+ if (!bulk_obj)
+ return NULL;
+
+ vals = PyObject_CallMethod((PyObject *)bulk_obj, "get_values", "");
+ Py_DECREF(bulk_obj);
+ if (!vals)
+ return NULL;
+
+ ret = PyList_GetItem(vals, 0);
+ Py_INCREF(ret);
+ Py_DECREF(vals);
+
+ return ret;
+}
+
+PyDoc_STRVAR(gpiod_Line_set_value_doc,
+"set_value(value) -> None\n"
+"\n"
+"Set the value of this GPIO line.\n"
+"\n"
+" value\n"
+" New value (integer)");
+
+static PyObject *gpiod_Line_set_value(gpiod_LineObject *self, PyObject *args)
+{
+ gpiod_LineBulkObject *bulk_obj;
+ PyObject *val, *vals, *ret;
+ int rv;
+
+ rv = PyArg_ParseTuple(args, "O", &val);
+ if (!rv)
+ return NULL;
+
+ bulk_obj = gpiod_LineToLineBulk(self);
+ if (!bulk_obj)
+ return NULL;
+
+ vals = Py_BuildValue("((O))", val);
+ if (!vals) {
+ Py_DECREF(bulk_obj);
+ return NULL;
+ }
+
+ ret = PyObject_CallMethod((PyObject *)bulk_obj,
+ "set_values", "O", vals);
+ Py_DECREF(bulk_obj);
+ Py_DECREF(vals);
+
+ return ret;
+}
+
+PyDoc_STRVAR(gpiod_Line_release_doc,
+"release() -> None\n"
+"\n"
+"Release this GPIO line.");
+
+static PyObject *gpiod_Line_release(gpiod_LineObject *self)
+{
+ gpiod_LineBulkObject *bulk_obj;
+ PyObject *ret;
+
+ bulk_obj = gpiod_LineToLineBulk(self);
+ if (!bulk_obj)
+ return NULL;
+
+ ret = PyObject_CallMethod((PyObject *)bulk_obj, "release", "");
+ Py_DECREF(bulk_obj);
+
+ return ret;
+}
+
+PyDoc_STRVAR(gpiod_Line_event_wait_doc,
+"event_wait([sec[ ,nsec]]) -> boolean\n"
+"\n"
+"Wait for a line event to occur on this GPIO line.\n"
+"\n"
+" sec\n"
+" Number of seconds to wait before timeout.\n"
+" nsec\n"
+" Number of nanoseconds to wait before timeout.\n"
+"\n"
+"Returns True if an event occurred on this line before timeout. False\n"
+"otherwise.");
+
+static PyObject *gpiod_Line_event_wait(gpiod_LineObject *self,
+ PyObject *args, PyObject *kwds)
+{
+ gpiod_LineBulkObject *bulk_obj;
+ PyObject *events;
+
+ bulk_obj = gpiod_LineToLineBulk(self);
+ if (!bulk_obj)
+ return NULL;
+
+ events = gpiod_CallMethodPyArgs((PyObject *)bulk_obj,
+ "event_wait", args, kwds);
+ Py_DECREF(bulk_obj);
+ if (!events)
+ return NULL;
+
+ if (events == Py_None) {
+ Py_DECREF(Py_None);
+ Py_RETURN_FALSE;
+ }
+
+ Py_DECREF(events);
+ Py_RETURN_TRUE;
+}
+
+PyDoc_STRVAR(gpiod_Line_event_read_doc,
+"event_read() -> gpiod.LineEvent object\n"
+"\n"
+"Read a single line event from this GPIO line object.");
+
+static gpiod_LineEventObject *gpiod_Line_event_read(gpiod_LineObject *self)
+{
+ gpiod_LineEventObject *ret;
+ int rv;
+
+ if (gpiod_ChipIsClosed(self->owner))
+ return NULL;
+
+ ret = PyObject_New(gpiod_LineEventObject, &gpiod_LineEventType);
+ if (!ret)
+ return NULL;
+
+ ret->source = NULL;
+
+ Py_BEGIN_ALLOW_THREADS;
+ rv = gpiod_line_event_read(self->line, &ret->event);
+ Py_END_ALLOW_THREADS;
+ if (rv) {
+ PyErr_SetFromErrno(PyExc_OSError);
+ Py_DECREF(ret);
+ return NULL;
+ }
+
+ Py_INCREF(self);
+ ret->source = self;
+
+ return ret;
+}
+
+PyDoc_STRVAR(gpiod_Line_event_get_fd_doc,
+"event_get_fd() -> integer\n"
+"\n"
+"Get the event file descriptor number associated with this line.");
+
+static PyObject *gpiod_Line_event_get_fd(gpiod_LineObject *self)
+{
+ int fd;
+
+ if (gpiod_ChipIsClosed(self->owner))
+ return NULL;
+
+ fd = gpiod_line_event_get_fd(self->line);
+ if (fd < 0) {
+ PyErr_SetFromErrno(PyExc_OSError);
+ return NULL;
+ }
+
+ return PyLong_FromLong(fd);
+}
+
+static PyObject *gpiod_Line_repr(gpiod_LineObject *self)
+{
+ PyObject *chip_name, *ret;
+ const char *line_name;
+
+ if (gpiod_ChipIsClosed(self->owner))
+ return NULL;
+
+ chip_name = PyObject_CallMethod((PyObject *)self->owner, "name", "");
+ if (!chip_name)
+ return NULL;
+
+ line_name = gpiod_line_name(self->line);
+
+ ret = PyUnicode_FromFormat("'%S:%u /%s/'", chip_name,
+ gpiod_line_offset(self->line),
+ line_name ?: "unnamed");
+ Py_DECREF(chip_name);
+ return ret;
+}
+
+static PyMethodDef gpiod_Line_methods[] = {
+ {
+ .ml_name = "owner",
+ .ml_meth = (PyCFunction)gpiod_Line_owner,
+ .ml_flags = METH_NOARGS,
+ .ml_doc = gpiod_Line_owner_doc,
+ },
+ {
+ .ml_name = "offset",
+ .ml_meth = (PyCFunction)gpiod_Line_offset,
+ .ml_flags = METH_NOARGS,
+ .ml_doc = gpiod_Line_offset_doc,
+ },
+ {
+ .ml_name = "name",
+ .ml_meth = (PyCFunction)gpiod_Line_name,
+ .ml_flags = METH_NOARGS,
+ .ml_doc = gpiod_Line_name_doc,
+ },
+ {
+ .ml_name = "consumer",
+ .ml_meth = (PyCFunction)gpiod_Line_consumer,
+ .ml_flags = METH_NOARGS,
+ .ml_doc = gpiod_Line_consumer_doc,
+ },
+ {
+ .ml_name = "direction",
+ .ml_meth = (PyCFunction)gpiod_Line_direction,
+ .ml_flags = METH_NOARGS,
+ .ml_doc = gpiod_Line_direction_doc,
+ },
+ {
+ .ml_name = "active_state",
+ .ml_meth = (PyCFunction)gpiod_Line_active_state,
+ .ml_flags = METH_NOARGS,
+ .ml_doc = gpiod_Line_active_state_doc,
+ },
+ {
+ .ml_name = "is_used",
+ .ml_meth = (PyCFunction)gpiod_Line_is_used,
+ .ml_flags = METH_NOARGS,
+ .ml_doc = gpiod_Line_is_used_doc,
+ },
+ {
+ .ml_name = "is_open_drain",
+ .ml_meth = (PyCFunction)gpiod_Line_is_open_drain,
+ .ml_flags = METH_NOARGS,
+ .ml_doc = gpiod_Line_is_open_drain_doc,
+ },
+ {
+ .ml_name = "is_open_source",
+ .ml_meth = (PyCFunction)gpiod_Line_is_open_source,
+ .ml_flags = METH_NOARGS,
+ .ml_doc = gpiod_Line_is_open_source_doc,
+ },
+ {
+ .ml_name = "request",
+ .ml_meth = (PyCFunction)gpiod_Line_request,
+ .ml_flags = METH_VARARGS | METH_KEYWORDS,
+ .ml_doc = gpiod_Line_request_doc,
+ },
+ {
+ .ml_name = "is_requested",
+ .ml_meth = (PyCFunction)gpiod_Line_is_requested,
+ .ml_flags = METH_NOARGS,
+ .ml_doc = gpiod_Line_is_requested_doc,
+ },
+ {
+ .ml_name = "get_value",
+ .ml_meth = (PyCFunction)gpiod_Line_get_value,
+ .ml_flags = METH_NOARGS,
+ .ml_doc = gpiod_Line_get_value_doc,
+ },
+ {
+ .ml_name = "set_value",
+ .ml_meth = (PyCFunction)gpiod_Line_set_value,
+ .ml_flags = METH_VARARGS,
+ .ml_doc = gpiod_Line_set_value_doc,
+ },
+ {
+ .ml_name = "release",
+ .ml_meth = (PyCFunction)gpiod_Line_release,
+ .ml_flags = METH_NOARGS,
+ .ml_doc = gpiod_Line_release_doc,
+ },
+ {
+ .ml_name = "event_wait",
+ .ml_meth = (PyCFunction)gpiod_Line_event_wait,
+ .ml_flags = METH_VARARGS | METH_KEYWORDS,
+ .ml_doc = gpiod_Line_event_wait_doc,
+ },
+ {
+ .ml_name = "event_read",
+ .ml_meth = (PyCFunction)gpiod_Line_event_read,
+ .ml_flags = METH_NOARGS,
+ .ml_doc = gpiod_Line_event_read_doc,
+ },
+ {
+ .ml_name = "event_get_fd",
+ .ml_meth = (PyCFunction)gpiod_Line_event_get_fd,
+ .ml_flags = METH_NOARGS,
+ .ml_doc = gpiod_Line_event_get_fd_doc,
+ },
+ { }
+};
+
+PyDoc_STRVAR(gpiod_LineType_doc,
+"Represents a GPIO line.\n"
+"\n"
+"The lifetime of this object is managed by the chip that owns it. Once\n"
+"the corresponding gpiod.Chip is closed, a gpiod.Line object must not be\n"
+"used.\n"
+"\n"
+"Line objects can only be created by the owning chip.");
+
+static PyTypeObject gpiod_LineType = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "gpiod.Line",
+ .tp_basicsize = sizeof(gpiod_LineObject),
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_doc = gpiod_LineType_doc,
+ .tp_new = PyType_GenericNew,
+ .tp_init = (initproc)gpiod_Line_init,
+ .tp_dealloc = (destructor)gpiod_Line_dealloc,
+ .tp_repr = (reprfunc)gpiod_Line_repr,
+ .tp_methods = gpiod_Line_methods,
+};
+
+static bool gpiod_LineBulkOwnerIsClosed(gpiod_LineBulkObject *self)
+{
+ gpiod_LineObject *line = (gpiod_LineObject *)self->lines[0];
+
+ return gpiod_ChipIsClosed(line->owner);
+}
+
+static int gpiod_LineBulk_init(gpiod_LineBulkObject *self, PyObject *args)
+{
+ PyObject *lines, *iter, *next;
+ Py_ssize_t i;
+ int rv;
+
+ rv = PyArg_ParseTuple(args, "O", &lines);
+ if (!rv)
+ return -1;
+
+ self->num_lines = PyObject_Size(lines);
+ if (self->num_lines < 1) {
+ PyErr_SetString(PyExc_TypeError,
+ "Argument must be a non-empty sequence");
+ return -1;
+ }
+ if (self->num_lines > GPIOD_LINE_BULK_MAX_LINES) {
+ PyErr_SetString(PyExc_TypeError,
+ "Too many objects in the sequence");
+ return -1;
+ }
+
+ self->lines = PyMem_Calloc(self->num_lines, sizeof(PyObject *));
+ if (!self->lines) {
+ PyErr_SetString(PyExc_MemoryError, "Out of memory");
+ return -1;
+ }
+
+ iter = PyObject_GetIter(lines);
+ if (!iter) {
+ PyMem_Free(self->lines);
+ return -1;
+ }
+
+ for (i = 0;;) {
+ next = PyIter_Next(iter);
+ if (!next) {
+ Py_DECREF(iter);
+ break;
+ }
+
+ if (next->ob_type != &gpiod_LineType) {
+ PyErr_SetString(PyExc_TypeError,
+ "Argument must be a sequence of GPIO lines");
+ Py_DECREF(next);
+ Py_DECREF(iter);
+ goto errout;
+ }
+
+ self->lines[i++] = next;
+ }
+
+ self->iter_idx = -1;
+
+ return 0;
+
+errout:
+
+ if (i > 0) {
+ for (--i; i >= 0; i--)
+ Py_DECREF(self->lines[i]);
+ }
+ PyMem_Free(self->lines);
+ self->lines = NULL;
+
+ return -1;
+}
+
+static void gpiod_LineBulk_dealloc(gpiod_LineBulkObject *self)
+{
+ Py_ssize_t i;
+
+ if (!self->lines)
+ return;
+
+ for (i = 0; i < self->num_lines; i++)
+ Py_DECREF(self->lines[i]);
+
+ PyMem_Free(self->lines);
+ PyObject_Del(self);
+}
+
+static PyObject *gpiod_LineBulk_iternext(gpiod_LineBulkObject *self)
+{
+ if (self->iter_idx < 0) {
+ self->iter_idx = 0; /* First element */
+ } else if (self->iter_idx >= self->num_lines) {
+ self->iter_idx = -1;
+ return NULL; /* Last element */
+ }
+
+ Py_INCREF(self->lines[self->iter_idx]);
+ return self->lines[self->iter_idx++];
+}
+
+PyDoc_STRVAR(gpiod_LineBulk_to_list_doc,
+"to_list() -> list of gpiod.Line objects\n"
+"\n"
+"Convert this LineBulk to a list");
+
+static PyObject *gpiod_LineBulk_to_list(gpiod_LineBulkObject *self)
+{
+ PyObject *list;
+ Py_ssize_t i;
+ int rv;
+
+ list = PyList_New(self->num_lines);
+ if (!list)
+ return NULL;
+
+ for (i = 0; i < self->num_lines; i++) {
+ Py_INCREF(self->lines[i]);
+ rv = PyList_SetItem(list, i, self->lines[i]);
+ if (rv < 0) {
+ Py_DECREF(list);
+ return NULL;
+ }
+ }
+
+ return list;
+}
+
+static void gpiod_LineBulkObjToCLineBulk(gpiod_LineBulkObject *bulk_obj,
+ struct gpiod_line_bulk *bulk)
+{
+ gpiod_LineObject *line_obj;
+ Py_ssize_t i;
+
+ gpiod_line_bulk_init(bulk);
+
+ for (i = 0; i < bulk_obj->num_lines; i++) {
+ line_obj = (gpiod_LineObject *)bulk_obj->lines[i];
+ gpiod_line_bulk_add(bulk, line_obj->line);
+ }
+}
+
+static void gpiod_MakeRequestConfig(struct gpiod_line_request_config *conf,
+ const char *consumer,
+ int request_type, int flags)
+{
+ memset(conf, 0, sizeof(*conf));
+
+ conf->consumer = consumer;
+
+ switch (request_type) {
+ case gpiod_LINE_REQ_DIR_IN:
+ conf->request_type = GPIOD_LINE_REQUEST_DIRECTION_INPUT;
+ break;
+ case gpiod_LINE_REQ_DIR_OUT:
+ conf->request_type = GPIOD_LINE_REQUEST_DIRECTION_OUTPUT;
+ break;
+ case gpiod_LINE_REQ_EV_FALLING_EDGE:
+ conf->request_type = GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE;
+ break;
+ case gpiod_LINE_REQ_EV_RISING_EDGE:
+ conf->request_type = GPIOD_LINE_REQUEST_EVENT_RISING_EDGE;
+ break;
+ case gpiod_LINE_REQ_EV_BOTH_EDGES:
+ conf->request_type = GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES;
+ break;
+ case gpiod_LINE_REQ_DIR_AS_IS:
+ default:
+ conf->request_type = GPIOD_LINE_REQUEST_DIRECTION_AS_IS;
+ break;
+ }
+
+ if (flags & gpiod_LINE_REQ_FLAG_OPEN_DRAIN)
+ conf->flags |= GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN;
+ if (flags & gpiod_LINE_REQ_FLAG_OPEN_SOURCE)
+ conf->flags |= GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE;
+ if (flags & gpiod_LINE_REQ_FLAG_ACTIVE_LOW)
+ conf->flags |= GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW;
+}
+
+PyDoc_STRVAR(gpiod_LineBulk_request_doc,
+"request(consumer[, type[, flags[, default_vals]]]) -> None\n"
+"\n"
+"Request all lines held by this LineBulk object.\n"
+"\n"
+" consumer\n"
+" Name of the consumer.\n"
+" type\n"
+" Type of the request.\n"
+" flags\n"
+" Other configuration flags.\n"
+" default_vals\n"
+" List of default values.\n");
+
+static PyObject *gpiod_LineBulk_request(gpiod_LineBulkObject *self,
+ PyObject *args, PyObject *kwds)
+{
+ static char *kwlist[] = { "consumer",
+ "type",
+ "flags",
+ "default_vals",
+ NULL };
+
+ int rv, type = gpiod_LINE_REQ_DIR_AS_IS, flags = 0,
+ default_vals[GPIOD_LINE_BULK_MAX_LINES], val;
+ PyObject *def_vals_obj = NULL, *iter, *next;
+ struct gpiod_line_request_config conf;
+ struct gpiod_line_bulk bulk;
+ Py_ssize_t num_def_vals;
+ char *consumer = NULL;
+ Py_ssize_t i;
+
+ if (gpiod_LineBulkOwnerIsClosed(self))
+ return NULL;
+
+ rv = PyArg_ParseTupleAndKeywords(args, kwds, "s|iiO", kwlist,
+ &consumer, &type,
+ &flags, &def_vals_obj);
+ if (!rv)
+ return NULL;
+
+ gpiod_LineBulkObjToCLineBulk(self, &bulk);
+ gpiod_MakeRequestConfig(&conf, consumer, type, flags);
+
+ if (def_vals_obj) {
+ memset(default_vals, 0, sizeof(default_vals));
+
+ num_def_vals = PyObject_Size(def_vals_obj);
+ if (num_def_vals != self->num_lines) {
+ PyErr_SetString(PyExc_TypeError,
+ "Number of default values is not the same as the number of lines");
+ return NULL;
+ }
+
+ iter = PyObject_GetIter(def_vals_obj);
+ if (!iter)
+ return NULL;
+
+ for (i = 0;; i++) {
+ next = PyIter_Next(iter);
+ if (!next) {
+ Py_DECREF(iter);
+ break;
+ }
+
+ val = PyLong_AsUnsignedLong(next);
+ Py_DECREF(next);
+ if (PyErr_Occurred()) {
+ Py_DECREF(iter);
+ return NULL;
+ }
+
+ default_vals[i] = !!val;
+ }
+ }
+
+ Py_BEGIN_ALLOW_THREADS;
+ rv = gpiod_line_request_bulk(&bulk, &conf, default_vals);
+ Py_END_ALLOW_THREADS;
+ if (rv) {
+ PyErr_SetFromErrno(PyExc_OSError);
+ return NULL;
+ }
+
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(gpiod_LineBulk_get_values_doc,
+"get_values() -> list of integers\n"
+"\n"
+"Read the values of all the lines held by this LineBulk object. The index\n"
+"of each value in the returned list corresponds with the index of the line\n"
+"in this gpiod.LineBulk object.");
+
+static PyObject *gpiod_LineBulk_get_values(gpiod_LineBulkObject *self)
+{
+ int rv, vals[GPIOD_LINE_BULK_MAX_LINES];
+ struct gpiod_line_bulk bulk;
+ PyObject *val_list, *val;
+ Py_ssize_t i;
+
+ if (gpiod_LineBulkOwnerIsClosed(self))
+ return NULL;
+
+ gpiod_LineBulkObjToCLineBulk(self, &bulk);
+
+ memset(vals, 0, sizeof(vals));
+ Py_BEGIN_ALLOW_THREADS;
+ rv = gpiod_line_get_value_bulk(&bulk, vals);
+ Py_END_ALLOW_THREADS;
+ if (rv) {
+ PyErr_SetFromErrno(PyExc_OSError);
+ return NULL;
+ }
+
+ val_list = PyList_New(self->num_lines);
+ if (!val_list)
+ return NULL;
+
+ for (i = 0; i < self->num_lines; i++) {
+ val = Py_BuildValue("i", vals[i]);
+ if (!val) {
+ Py_DECREF(val_list);
+ return NULL;
+ }
+
+ rv = PyList_SetItem(val_list, i, val);
+ if (rv < 0) {
+ Py_DECREF(val);
+ Py_DECREF(val_list);
+ return NULL;
+ }
+ }
+
+ return val_list;
+}
+
+PyDoc_STRVAR(gpiod_LineBulk_set_values_doc,
+"set_values(values) -> None\n"
+"\n"
+"Set the values of all the lines held by this LineBulk object.\n"
+"\n"
+" values\n"
+" List of values (integers) to set.\n"
+"\n"
+"The number of values in the list passed as argument must be the same as\n"
+"the number of lines held by this gpiod.LineBulk object. The index of each\n"
+"value corresponds with the index of each line in the object.\n");
+
+static PyObject *gpiod_LineBulk_set_values(gpiod_LineBulkObject *self,
+ PyObject *args)
+{
+ int rv, vals[GPIOD_LINE_BULK_MAX_LINES], val;
+ PyObject *val_list, *iter, *next;
+ struct gpiod_line_bulk bulk;
+ Py_ssize_t num_vals, i;
+
+ if (gpiod_LineBulkOwnerIsClosed(self))
+ return NULL;
+
+ gpiod_LineBulkObjToCLineBulk(self, &bulk);
+ memset(vals, 0, sizeof(vals));
+
+ rv = PyArg_ParseTuple(args, "O", &val_list);
+ if (!rv)
+ return NULL;
+
+ num_vals = PyObject_Size(val_list);
+ if (self->num_lines != num_vals) {
+ PyErr_SetString(PyExc_TypeError,
+ "Number of values must correspond with the number of lines");
+ return NULL;
+ }
+
+ iter = PyObject_GetIter(val_list);
+ if (!iter)
+ return NULL;
+
+ for (i = 0;; i++) {
+ next = PyIter_Next(iter);
+ if (!next) {
+ Py_DECREF(iter);
+ break;
+ }
+
+ val = PyLong_AsLong(next);
+ Py_DECREF(next);
+ if (PyErr_Occurred()) {
+ Py_DECREF(iter);
+ return NULL;
+ }
+
+ vals[i] = (int)val;
+ }
+
+ Py_BEGIN_ALLOW_THREADS;
+ rv = gpiod_line_set_value_bulk(&bulk, vals);
+ Py_END_ALLOW_THREADS;
+ if (rv) {
+ PyErr_SetFromErrno(PyExc_OSError);
+ return NULL;
+ }
+
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(gpiod_LineBulk_release_doc,
+"release() -> None\n"
+"\n"
+"Release all lines held by this LineBulk object.");
+
+static PyObject *gpiod_LineBulk_release(gpiod_LineBulkObject *self)
+{
+ struct gpiod_line_bulk bulk;
+
+ if (gpiod_LineBulkOwnerIsClosed(self))
+ return NULL;
+
+ gpiod_LineBulkObjToCLineBulk(self, &bulk);
+ gpiod_line_release_bulk(&bulk);
+
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(gpiod_LineBulk_event_wait_doc,
+"event_wait([sec[ ,nsec]]) -> gpiod.LineBulk object or None\n"
+"\n"
+"Poll the lines held by this LineBulk Object for line events.\n"
+"\n"
+" sec\n"
+" Number of seconds to wait before timeout.\n"
+" nsec\n"
+" Number of nanoseconds to wait before timeout.\n"
+"\n"
+"Returns a gpiod.LineBulk object containing references to lines on which\n"
+"events occurred or None if we reached the timeout without any event\n"
+"occurring.");
+
+static PyObject *gpiod_LineBulk_event_wait(gpiod_LineBulkObject *self,
+ PyObject *args, PyObject *kwds)
+{
+ static char *kwlist[] = { "sec", "nsec", NULL };
+
+ struct gpiod_line_bulk bulk, ev_bulk;
+ struct gpiod_line *line, **line_ptr;
+ gpiod_LineObject *line_obj;
+ gpiod_ChipObject *owner;
+ long sec = 0, nsec = 0;
+ struct timespec ts;
+ PyObject *ret;
+ Py_ssize_t i;
+ int rv;
+
+ if (gpiod_LineBulkOwnerIsClosed(self))
+ return NULL;
+
+ rv = PyArg_ParseTupleAndKeywords(args, kwds,
+ "|ll", kwlist, &sec, &nsec);
+ if (!rv)
+ return NULL;
+
+ ts.tv_sec = sec;
+ ts.tv_nsec = nsec;
+
+ gpiod_LineBulkObjToCLineBulk(self, &bulk);
+
+ Py_BEGIN_ALLOW_THREADS;
+ rv = gpiod_line_event_wait_bulk(&bulk, &ts, &ev_bulk);
+ Py_END_ALLOW_THREADS;
+ if (rv < 0) {
+ PyErr_SetFromErrno(PyExc_OSError);
+ return NULL;
+ } else if (rv == 0) {
+ Py_RETURN_NONE;
+ }
+
+ ret = PyList_New(gpiod_line_bulk_num_lines(&ev_bulk));
+ if (!ret)
+ return NULL;
+
+ owner = ((gpiod_LineObject *)(self->lines[0]))->owner;
+
+ i = 0;
+ gpiod_line_bulk_foreach_line(&ev_bulk, line, line_ptr) {
+ line_obj = gpiod_MakeLineObject(owner, line);
+ if (!line_obj) {
+ Py_DECREF(ret);
+ return NULL;
+ }
+
+ rv = PyList_SetItem(ret, i++, (PyObject *)line_obj);
+ if (rv < 0) {
+ Py_DECREF(ret);
+ return NULL;
+ }
+ }
+
+ return ret;
+}
+
+static PyObject *gpiod_LineBulk_repr(gpiod_LineBulkObject *self)
+{
+ PyObject *list, *list_repr, *chip_name, *ret;
+ gpiod_LineObject *line;
+
+ if (gpiod_LineBulkOwnerIsClosed(self))
+ return NULL;
+
+ list = gpiod_LineBulk_to_list(self);
+ if (!list)
+ return NULL;
+
+ list_repr = PyObject_Repr(list);
+ Py_DECREF(list);
+ if (!list_repr)
+ return NULL;
+
+ line = (gpiod_LineObject *)self->lines[0];
+ chip_name = PyObject_CallMethod((PyObject *)line->owner, "name", "");
+ if (!chip_name) {
+ Py_DECREF(list_repr);
+ return NULL;
+ }
+
+ ret = PyUnicode_FromFormat("%U%U", chip_name, list_repr);
+ Py_DECREF(chip_name);
+ Py_DECREF(list_repr);
+ return ret;
+}
+
+static PyMethodDef gpiod_LineBulk_methods[] = {
+ {
+ .ml_name = "to_list",
+ .ml_meth = (PyCFunction)gpiod_LineBulk_to_list,
+ .ml_doc = gpiod_LineBulk_to_list_doc,
+ .ml_flags = METH_NOARGS,
+ },
+ {
+ .ml_name = "request",
+ .ml_meth = (PyCFunction)gpiod_LineBulk_request,
+ .ml_doc = gpiod_LineBulk_request_doc,
+ .ml_flags = METH_VARARGS | METH_KEYWORDS,
+ },
+ {
+ .ml_name = "get_values",
+ .ml_meth = (PyCFunction)gpiod_LineBulk_get_values,
+ .ml_doc = gpiod_LineBulk_get_values_doc,
+ .ml_flags = METH_NOARGS,
+ },
+ {
+ .ml_name = "set_values",
+ .ml_meth = (PyCFunction)gpiod_LineBulk_set_values,
+ .ml_doc = gpiod_LineBulk_set_values_doc,
+ .ml_flags = METH_VARARGS,
+ },
+ {
+ .ml_name = "release",
+ .ml_meth = (PyCFunction)gpiod_LineBulk_release,
+ .ml_doc = gpiod_LineBulk_release_doc,
+ .ml_flags = METH_NOARGS,
+ },
+ {
+ .ml_name = "event_wait",
+ .ml_meth = (PyCFunction)gpiod_LineBulk_event_wait,
+ .ml_doc = gpiod_LineBulk_event_wait_doc,
+ .ml_flags = METH_VARARGS | METH_KEYWORDS,
+ },
+ { }
+};
+
+PyDoc_STRVAR(gpiod_LineBulkType_doc,
+"Represents a set of GPIO lines.\n"
+"\n"
+"Objects of this type are immutable. The constructor takes as argument\n"
+"a sequence of gpiod.Line objects. It doesn't accept objects of any other\n"
+"type.");
+
+static PyTypeObject gpiod_LineBulkType = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "gpiod.LineBulk",
+ .tp_basicsize = sizeof(gpiod_LineBulkType),
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_doc = gpiod_LineBulkType_doc,
+ .tp_new = PyType_GenericNew,
+ .tp_init = (initproc)gpiod_LineBulk_init,
+ .tp_dealloc = (destructor)gpiod_LineBulk_dealloc,
+ .tp_iter = PyObject_SelfIter,
+ .tp_iternext = (iternextfunc)gpiod_LineBulk_iternext,
+ .tp_repr = (reprfunc)gpiod_LineBulk_repr,
+ .tp_methods = gpiod_LineBulk_methods,
+};
+
+static gpiod_LineBulkObject *gpiod_LineToLineBulk(gpiod_LineObject *line)
+{
+ gpiod_LineBulkObject *ret;
+ PyObject *args;
+
+ args = Py_BuildValue("((O))", line);
+ if (!args)
+ return NULL;
+
+ ret = (gpiod_LineBulkObject *)PyObject_CallObject(
+ (PyObject *)&gpiod_LineBulkType,
+ args);
+ Py_DECREF(args);
+
+ return ret;
+}
+
+enum {
+ gpiod_OPEN_LOOKUP = 1,
+ gpiod_OPEN_BY_NAME,
+ gpiod_OPEN_BY_PATH,
+ gpiod_OPEN_BY_LABEL,
+ gpiod_OPEN_BY_NUMBER,
+};
+
+static int gpiod_Chip_init(gpiod_ChipObject *self, PyObject *args)
+{
+ int rv, how = gpiod_OPEN_LOOKUP;
+ PyThreadState *thread;
+ char *descr;
+
+ rv = PyArg_ParseTuple(args, "s|i", &descr, &how);
+ if (!rv)
+ return -1;
+
+ thread = PyEval_SaveThread();
+ switch (how) {
+ case gpiod_OPEN_LOOKUP:
+ self->chip = gpiod_chip_open_lookup(descr);
+ break;
+ case gpiod_OPEN_BY_NAME:
+ self->chip = gpiod_chip_open_by_name(descr);
+ break;
+ case gpiod_OPEN_BY_PATH:
+ self->chip = gpiod_chip_open(descr);
+ break;
+ case gpiod_OPEN_BY_LABEL:
+ self->chip = gpiod_chip_open_by_label(descr);
+ break;
+ case gpiod_OPEN_BY_NUMBER:
+ self->chip = gpiod_chip_open_by_number(atoi(descr));
+ break;
+ default:
+ PyEval_RestoreThread(thread);
+ PyErr_BadArgument();
+ return -1;
+ }
+ PyEval_RestoreThread(thread);
+ if (!self->chip) {
+ PyErr_SetFromErrno(PyExc_OSError);
+ return -1;
+ }
+
+ return 0;
+}
+
+static void gpiod_Chip_dealloc(gpiod_ChipObject *self)
+{
+ if (self->chip)
+ gpiod_chip_close(self->chip);
+
+ PyObject_Del(self);
+}
+
+static PyObject *gpiod_Chip_repr(gpiod_ChipObject *self)
+{
+ if (gpiod_ChipIsClosed(self))
+ return NULL;
+
+ return PyUnicode_FromFormat("'%s /%s/ %u lines'",
+ gpiod_chip_name(self->chip),
+ gpiod_chip_label(self->chip),
+ gpiod_chip_num_lines(self->chip));
+}
+
+PyDoc_STRVAR(gpiod_Chip_close_doc,
+"close() -> None\n"
+"\n"
+"Close the associated gpiochip descriptor. The chip object must no longer\n"
+"be used after this method is called.\n");
+
+static PyObject *gpiod_Chip_close(gpiod_ChipObject *self)
+{
+ if (gpiod_ChipIsClosed(self))
+ return NULL;
+
+ gpiod_chip_close(self->chip);
+ self->chip = NULL;
+
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(gpiod_Chip_enter_doc,
+"Controlled execution enter callback.");
+
+static PyObject *gpiod_Chip_enter(gpiod_ChipObject *chip)
+{
+ Py_INCREF(chip);
+ return (PyObject *)chip;
+}
+
+PyDoc_STRVAR(gpiod_Chip_exit_doc,
+"Controlled execution exit callback.");
+
+static PyObject *gpiod_Chip_exit(gpiod_ChipObject *chip)
+{
+ return PyObject_CallMethod((PyObject *)chip, "close", "");
+}
+
+PyDoc_STRVAR(gpiod_Chip_name_doc,
+"name() -> string\n"
+"\n"
+"Get the name of the GPIO chip");
+
+static PyObject *gpiod_Chip_name(gpiod_ChipObject *self)
+{
+ if (gpiod_ChipIsClosed(self))
+ return NULL;
+
+ return PyUnicode_FromFormat("%s", gpiod_chip_name(self->chip));
+}
+
+PyDoc_STRVAR(gpiod_Chip_label_doc,
+"label() -> string\n"
+"\n"
+"Get the label of the GPIO chip");
+
+static PyObject *gpiod_Chip_label(gpiod_ChipObject *self)
+{
+ if (gpiod_ChipIsClosed(self))
+ return NULL;
+
+ return PyUnicode_FromFormat("%s", gpiod_chip_label(self->chip));
+}
+
+PyDoc_STRVAR(gpiod_Chip_num_lines_doc,
+"num_lines() -> integer\n"
+"\n"
+"Get the number of lines exposed by this GPIO chip.");
+
+static PyObject *gpiod_Chip_num_lines(gpiod_ChipObject *self)
+{
+ if (gpiod_ChipIsClosed(self))
+ return NULL;
+
+ return Py_BuildValue("I", gpiod_chip_num_lines(self->chip));
+}
+
+static gpiod_LineObject *
+gpiod_MakeLineObject(gpiod_ChipObject *owner, struct gpiod_line *line)
+{
+ gpiod_LineObject *obj;
+
+ obj = PyObject_New(gpiod_LineObject, &gpiod_LineType);
+ if (!obj)
+ return NULL;
+
+ obj->line = line;
+ Py_INCREF(owner);
+ obj->owner = owner;
+
+ return obj;
+}
+
+PyDoc_STRVAR(gpiod_Chip_get_line_doc,
+"get_line(offset) -> gpiod.Line object\n"
+"\n"
+"Get the GPIO line at given offset.\n"
+"\n"
+" offset\n"
+" Line offset (integer)");
+
+static gpiod_LineObject *
+gpiod_Chip_get_line(gpiod_ChipObject *self, PyObject *args)
+{
+ struct gpiod_line *line;
+ unsigned int offset;
+ int rv;
+
+ if (gpiod_ChipIsClosed(self))
+ return NULL;
+
+ rv = PyArg_ParseTuple(args, "I", &offset);
+ if (!rv)
+ return NULL;
+
+ Py_BEGIN_ALLOW_THREADS;
+ line = gpiod_chip_get_line(self->chip, offset);
+ Py_END_ALLOW_THREADS;
+ if (!line) {
+ PyErr_SetFromErrno(PyExc_OSError);
+ return NULL;
+ }
+
+ return gpiod_MakeLineObject(self, line);
+}
+
+PyDoc_STRVAR(gpiod_Chip_find_line_doc,
+"find_line(name) -> gpiod.Line object or None\n"
+"\n"
+"Get the GPIO line by name.\n"
+"\n"
+" name\n"
+" Line name (string)\n"
+"\n"
+"Returns a gpiod.Line object or None if line with given name is not\n"
+"associated with this chip.");
+
+static gpiod_LineObject *
+gpiod_Chip_find_line(gpiod_ChipObject *self, PyObject *args)
+{
+ struct gpiod_line *line;
+ const char *name;
+ int rv;
+
+ if (gpiod_ChipIsClosed(self))
+ return NULL;
+
+ rv = PyArg_ParseTuple(args, "s", &name);
+ if (!rv)
+ return NULL;
+
+ Py_BEGIN_ALLOW_THREADS;
+ line = gpiod_chip_find_line(self->chip, name);
+ Py_END_ALLOW_THREADS;
+ if (!line) {
+ if (errno == ENOENT) {
+ Py_INCREF(Py_None);
+ return (gpiod_LineObject *)Py_None;
+ }
+
+ PyErr_SetFromErrno(PyExc_OSError);
+ return NULL;
+ }
+
+ return gpiod_MakeLineObject(self, line);
+}
+
+static gpiod_LineBulkObject *gpiod_ListToLineBulk(PyObject *lines)
+{
+ gpiod_LineBulkObject *bulk;
+ PyObject *arg;
+
+ arg = PyTuple_Pack(1, lines);
+ if (!arg)
+ return NULL;
+
+ bulk = (gpiod_LineBulkObject *)PyObject_CallObject(
+ (PyObject *)&gpiod_LineBulkType,
+ arg);
+ Py_DECREF(arg);
+
+ return bulk;
+}
+
+PyDoc_STRVAR(gpiod_Chip_get_lines_doc,
+"get_lines(offsets) -> gpiod.LineBulk object\n"
+"\n"
+"Get a set of GPIO lines by their offsets.\n"
+"\n"
+" offsets\n"
+" List of lines offsets.");
+
+static gpiod_LineBulkObject *
+gpiod_Chip_get_lines(gpiod_ChipObject *self, PyObject *args)
+{
+ PyObject *offsets, *iter, *next, *lines, *arg;
+ gpiod_LineBulkObject *bulk;
+ Py_ssize_t num_offsets, i;
+ gpiod_LineObject *line;
+ int rv;
+
+ rv = PyArg_ParseTuple(args, "O", &offsets);
+ if (!rv)
+ return NULL;
+
+ num_offsets = PyObject_Size(offsets);
+ if (num_offsets < 1) {
+ PyErr_SetString(PyExc_TypeError,
+ "Argument must be a non-empty sequence of offsets");
+ return NULL;
+ }
+
+ lines = PyList_New(num_offsets);
+ if (!lines)
+ return NULL;
+
+ iter = PyObject_GetIter(offsets);
+ if (!iter) {
+ Py_DECREF(lines);
+ return NULL;
+ }
+
+ for (i = 0;;) {
+ next = PyIter_Next(iter);
+ if (!next) {
+ Py_DECREF(iter);
+ break;
+ }
+
+ arg = PyTuple_Pack(1, next);
+ Py_DECREF(next);
+ if (!arg) {
+ Py_DECREF(iter);
+ Py_DECREF(lines);
+ return NULL;
+ }
+
+ line = gpiod_Chip_get_line(self, arg);
+ Py_DECREF(arg);
+ if (!line) {
+ Py_DECREF(iter);
+ Py_DECREF(lines);
+ return NULL;
+ }
+
+ rv = PyList_SetItem(lines, i++, (PyObject *)line);
+ if (rv < 0) {
+ Py_DECREF(line);
+ Py_DECREF(iter);
+ Py_DECREF(lines);
+ return NULL;
+ }
+ }
+
+ bulk = gpiod_ListToLineBulk(lines);
+ Py_DECREF(lines);
+ if (!bulk)
+ return NULL;
+
+ return bulk;
+}
+
+PyDoc_STRVAR(gpiod_Chip_get_all_lines_doc,
+"get_all_lines() -> gpiod.LineBulk object\n"
+"\n"
+"Get all lines exposed by this Chip.");
+
+static gpiod_LineBulkObject *
+gpiod_Chip_get_all_lines(gpiod_ChipObject *self)
+{
+ gpiod_LineBulkObject *bulk_obj;
+ struct gpiod_line_bulk bulk;
+ gpiod_LineObject *line_obj;
+ struct gpiod_line *line;
+ unsigned int offset;
+ PyObject *list;
+ int rv;
+
+ if (gpiod_ChipIsClosed(self))
+ return NULL;
+
+ rv = gpiod_chip_get_all_lines(self->chip, &bulk);
+ if (rv) {
+ PyErr_SetFromErrno(PyExc_OSError);
+ return NULL;
+ }
+
+ list = PyList_New(gpiod_line_bulk_num_lines(&bulk));
+ if (!list)
+ return NULL;
+
+ gpiod_line_bulk_foreach_line_off(&bulk, line, offset) {
+ line_obj = gpiod_MakeLineObject(self, line);
+ if (!line_obj) {
+ Py_DECREF(list);
+ return NULL;
+ }
+
+ rv = PyList_SetItem(list, offset, (PyObject *)line_obj);
+ if (rv < 0) {
+ Py_DECREF(line_obj);
+ Py_DECREF(list);
+ return NULL;
+ }
+ }
+
+ bulk_obj = gpiod_ListToLineBulk(list);
+ Py_DECREF(list);
+ if (!bulk_obj)
+ return NULL;
+
+ return bulk_obj;
+}
+
+PyDoc_STRVAR(gpiod_Chip_find_lines_doc,
+"find_lines(names) -> gpiod.LineBulk object\n"
+"\n"
+"Look up a set of lines by their names.\n"
+"\n"
+" names\n"
+" Sequence of line names.\n"
+"\n"
+"Unlike find_line(), this method raises an exception if at least one line\n"
+"from the list doesn't exist.");
+
+static gpiod_LineBulkObject *
+gpiod_Chip_find_lines(gpiod_ChipObject *self, PyObject *args)
+{
+ PyObject *names, *lines, *iter, *next, *arg;
+ gpiod_LineBulkObject *bulk;
+ Py_ssize_t num_names, i;
+ gpiod_LineObject *line;
+ int rv;
+
+ rv = PyArg_ParseTuple(args, "O", &names);
+ if (!rv)
+ return NULL;
+
+ num_names = PyObject_Size(names);
+ if (num_names < 1) {
+ PyErr_SetString(PyExc_TypeError,
+ "Argument must be a non-empty sequence of names");
+ return NULL;
+ }
+
+ lines = PyList_New(num_names);
+ if (!lines)
+ return NULL;
+
+ iter = PyObject_GetIter(names);
+ if (!iter) {
+ Py_DECREF(lines);
+ return NULL;
+ }
+
+ for (i = 0;;) {
+ next = PyIter_Next(iter);
+ if (!next) {
+ Py_DECREF(iter);
+ break;
+ }
+
+ arg = PyTuple_Pack(1, next);
+ if (!arg) {
+ Py_DECREF(iter);
+ Py_DECREF(lines);
+ return NULL;
+ }
+
+ line = gpiod_Chip_find_line(self, arg);
+ Py_DECREF(arg);
+ if (!line || (PyObject *)line == Py_None) {
+ Py_DECREF(iter);
+ Py_DECREF(lines);
+ if ((PyObject *)line == Py_None)
+ PyErr_SetString(PyExc_TypeError,
+ "Unable to find all lines from the list");
+ return NULL;
+ }
+
+ rv = PyList_SetItem(lines, i++, (PyObject *)line);
+ if (rv < 0) {
+ Py_DECREF(line);
+ Py_DECREF(iter);
+ Py_DECREF(lines);
+ return NULL;
+ }
+ }
+
+ bulk = gpiod_ListToLineBulk(lines);
+ Py_DECREF(lines);
+ return bulk;
+}
+
+static PyMethodDef gpiod_Chip_methods[] = {
+ {
+ .ml_name = "close",
+ .ml_meth = (PyCFunction)gpiod_Chip_close,
+ .ml_flags = METH_NOARGS,
+ .ml_doc = gpiod_Chip_close_doc,
+ },
+ {
+ .ml_name = "__enter__",
+ .ml_meth = (PyCFunction)gpiod_Chip_enter,
+ .ml_flags = METH_NOARGS,
+ .ml_doc = gpiod_Chip_enter_doc,
+ },
+ {
+ .ml_name = "__exit__",
+ .ml_meth = (PyCFunction)gpiod_Chip_exit,
+ .ml_flags = METH_VARARGS,
+ .ml_doc = gpiod_Chip_exit_doc,
+ },
+ {
+ .ml_name = "name",
+ .ml_meth = (PyCFunction)gpiod_Chip_name,
+ .ml_flags = METH_NOARGS,
+ .ml_doc = gpiod_Chip_name_doc,
+ },
+ {
+ .ml_name = "label",
+ .ml_meth = (PyCFunction)gpiod_Chip_label,
+ .ml_flags = METH_NOARGS,
+ .ml_doc = gpiod_Chip_label_doc,
+ },
+ {
+ .ml_name = "num_lines",
+ .ml_meth = (PyCFunction)gpiod_Chip_num_lines,
+ .ml_flags = METH_NOARGS,
+ .ml_doc = gpiod_Chip_num_lines_doc,
+ },
+ {
+ .ml_name = "get_line",
+ .ml_meth = (PyCFunction)gpiod_Chip_get_line,
+ .ml_flags = METH_VARARGS,
+ .ml_doc = gpiod_Chip_get_line_doc,
+ },
+ {
+ .ml_name = "find_line",
+ .ml_meth = (PyCFunction)gpiod_Chip_find_line,
+ .ml_flags = METH_VARARGS,
+ .ml_doc = gpiod_Chip_find_line_doc,
+ },
+ {
+ .ml_name = "get_lines",
+ .ml_meth = (PyCFunction)gpiod_Chip_get_lines,
+ .ml_flags = METH_VARARGS,
+ .ml_doc = gpiod_Chip_get_lines_doc,
+ },
+ {
+ .ml_name = "get_all_lines",
+ .ml_meth = (PyCFunction)gpiod_Chip_get_all_lines,
+ .ml_flags = METH_NOARGS,
+ .ml_doc = gpiod_Chip_get_all_lines_doc,
+ },
+ {
+ .ml_name = "find_lines",
+ .ml_meth = (PyCFunction)gpiod_Chip_find_lines,
+ .ml_flags = METH_VARARGS,
+ .ml_doc = gpiod_Chip_find_lines_doc,
+ },
+ { }
+};
+
+PyDoc_STRVAR(gpiod_ChipType_doc,
+"Represents a GPIO chip.\n"
+"\n"
+"Chip object manages all resources associated with the GPIO chip\n"
+"it represents.\n"
+"\n"
+"The gpiochip device file is opened during the object's construction.\n"
+"The Chip object's constructor takes a description string as argument the\n"
+"meaning of which depends on the second, optional parameter which defines\n"
+"the way the description string should be interpreted. The available\n"
+"options are: OPEN_BY_LABEL, OPEN_BY_NAME, OPEN_BY_NUMBER, OPEN_BY_PATH,\n"
+"and OPEN_LOOKUP. The last option means that libgpiod should open the chip\n"
+"based on the best guess what the path is. This is also the default if the\n"
+"second argument is missing.\n"
+"\n"
+"Callers must close the chip by calling the close() method when it's no\n"
+"longer used.\n"
+"\n"
+"Example:\n"
+"\n"
+" chip = gpiod.Chip('gpiochip0', gpiod.Chip.OPEN_BY_NAME)\n"
+" do_something(chip)\n"
+" chip.close()\n"
+"\n"
+"The gpiod.Chip class also supports controlled execution ('with' statement).\n"
+"\n"
+"Example:\n"
+"\n"
+" with gpiod.Chip('0', gpiod.Chip.OPEN_BY_NUMBER) as chip:\n"
+" do_something(chip)");
+
+static PyTypeObject gpiod_ChipType = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "gpiod.Chip",
+ .tp_basicsize = sizeof(gpiod_ChipObject),
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_doc = gpiod_ChipType_doc,
+ .tp_new = PyType_GenericNew,
+ .tp_init = (initproc)gpiod_Chip_init,
+ .tp_dealloc = (destructor)gpiod_Chip_dealloc,
+ .tp_repr = (reprfunc)gpiod_Chip_repr,
+ .tp_methods = gpiod_Chip_methods,
+};
+
+static int gpiod_ChipIter_init(gpiod_ChipIterObject *self)
+{
+ self->iter = gpiod_chip_iter_new();
+ if (!self->iter) {
+ PyErr_SetFromErrno(PyExc_OSError);
+ return -1;
+ }
+
+ return 0;
+}
+
+static void gpiod_ChipIter_dealloc(gpiod_ChipIterObject *self)
+{
+ if (self->iter)
+ gpiod_chip_iter_free_noclose(self->iter);
+
+ PyObject_Del(self);
+}
+
+static gpiod_ChipObject *gpiod_ChipIter_next(gpiod_ChipIterObject *self)
+{
+ gpiod_ChipObject *chip_obj;
+ struct gpiod_chip *chip;
+
+ Py_BEGIN_ALLOW_THREADS;
+ chip = gpiod_chip_iter_next_noclose(self->iter);
+ Py_END_ALLOW_THREADS;
+ if (!chip)
+ return NULL; /* Last element. */
+
+ chip_obj = PyObject_New(gpiod_ChipObject, &gpiod_ChipType);
+ if (!chip_obj) {
+ gpiod_chip_close(chip);
+ return NULL;
+ }
+
+ chip_obj->chip = chip;
+
+ return chip_obj;
+}
+
+PyDoc_STRVAR(gpiod_ChipIterType_doc,
+"Allows to iterate over all GPIO chips in the system.\n"
+"\n"
+"The ChipIter's constructor takes no arguments.\n"
+"\n"
+"Each iteration yields the next open GPIO chip handle. The caller is\n"
+"responsible for closing each chip\n"
+"\n"
+"Example:\n"
+"\n"
+" for chip in gpiod.ChipIter():\n"
+" do_something_with_chip(chip)\n"
+" chip.close()");
+
+static PyTypeObject gpiod_ChipIterType = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "gpiod.ChipIter",
+ .tp_basicsize = sizeof(gpiod_ChipIterObject),
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_doc = gpiod_ChipIterType_doc,
+ .tp_new = PyType_GenericNew,
+ .tp_init = (initproc)gpiod_ChipIter_init,
+ .tp_dealloc = (destructor)gpiod_ChipIter_dealloc,
+ .tp_iter = PyObject_SelfIter,
+ .tp_iternext = (iternextfunc)gpiod_ChipIter_next,
+};
+
+static int gpiod_LineIter_init(gpiod_LineIterObject *self, PyObject *args)
+{
+ gpiod_ChipObject *chip_obj;
+ int rv;
+
+ rv = PyArg_ParseTuple(args, "O!", &gpiod_ChipType,
+ (PyObject *)&chip_obj);
+ if (!rv)
+ return -1;
+
+ if (gpiod_ChipIsClosed(chip_obj))
+ return -1;
+
+ Py_BEGIN_ALLOW_THREADS;
+ self->iter = gpiod_line_iter_new(chip_obj->chip);
+ Py_END_ALLOW_THREADS;
+ if (!self->iter) {
+ PyErr_SetFromErrno(PyExc_OSError);
+ return -1;
+ }
+
+ self->owner = chip_obj;
+ Py_INCREF(chip_obj);
+
+ return 0;
+}
+
+static void gpiod_LineIter_dealloc(gpiod_LineIterObject *self)
+{
+ if (self->iter)
+ gpiod_line_iter_free(self->iter);
+
+ PyObject_Del(self);
+}
+
+static gpiod_LineObject *gpiod_LineIter_next(gpiod_LineIterObject *self)
+{
+ struct gpiod_line *line;
+
+ line = gpiod_line_iter_next(self->iter);
+ if (!line)
+ return NULL; /* Last element. */
+
+ return gpiod_MakeLineObject(self->owner, line);
+}
+
+PyDoc_STRVAR(gpiod_LineIterType_doc,
+"Allows to iterate over all lines exposed by a GPIO chip.\n"
+"\n"
+"New line iterator is created by passing a reference to an open gpiod.Chip\n"
+"object to the constructor of gpiod.LineIter.\n"
+"\n"
+"Caller doesn't need to handle the resource management for lines as their\n"
+"lifetime is managed by the owning chip.\n"
+"\n"
+"Example:\n"
+"\n"
+" chip = gpiod.Chip('gpiochip0')\n"
+" for line in gpiod.LineIter(chip):\n"
+" do_stuff_with_line(line)");
+
+static PyTypeObject gpiod_LineIterType = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "gpiod.LineIter",
+ .tp_basicsize = sizeof(gpiod_LineIterObject),
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_doc = gpiod_LineIterType_doc,
+ .tp_new = PyType_GenericNew,
+ .tp_init = (initproc)gpiod_LineIter_init,
+ .tp_dealloc = (destructor)gpiod_LineIter_dealloc,
+ .tp_iter = PyObject_SelfIter,
+ .tp_iternext = (iternextfunc)gpiod_LineIter_next,
+};
+
+PyDoc_STRVAR(gpiod_Module_find_line_doc,
+"find_line(name) -> gpiod.Line object or None\n"
+"\n"
+"Lookup a GPIO line by name. Search all gpiochips. Returns a gpiod.Line\n"
+"or None if a line with given name doesn't exist in the system.\n"
+"\n"
+"NOTE: the gpiod.Chip object owning the returned line must be closed\n"
+"by the caller.\n"
+"\n"
+" name\n"
+" Name of the line to find (string).");
+
+static gpiod_LineObject *gpiod_Module_find_line(PyObject *self GPIOD_UNUSED,
+ PyObject *args)
+{
+ gpiod_LineObject *line_obj;
+ gpiod_ChipObject *chip_obj;
+ struct gpiod_chip *chip;
+ struct gpiod_line *line;
+ const char *name;
+ int rv;
+
+ rv = PyArg_ParseTuple(args, "s", &name);
+ if (!rv)
+ return NULL;
+
+ Py_BEGIN_ALLOW_THREADS;
+ line = gpiod_line_find(name);
+ Py_END_ALLOW_THREADS;
+ if (!line) {
+ if (errno == ENOENT) {
+ Py_INCREF(Py_None);
+ return (gpiod_LineObject *)Py_None;
+ }
+
+ PyErr_SetFromErrno(PyExc_OSError);
+ return NULL;
+ }
+
+ chip = gpiod_line_get_chip(line);
+
+ chip_obj = PyObject_New(gpiod_ChipObject, &gpiod_ChipType);
+ if (!chip_obj) {
+ gpiod_chip_close(chip);
+ return NULL;
+ }
+
+ chip_obj->chip = chip;
+
+ line_obj = gpiod_MakeLineObject(chip_obj, line);
+ if (!line_obj)
+ return NULL;
+
+ /*
+ * PyObject_New() set the reference count for the chip object at 1 and
+ * the call to gpiod_MakeLineObject() increased it to 2. However when
+ * we return the object to the line object to the python interpreter,
+ * there'll be only a single reference holder to the chip - the line
+ * object itself. Decrease the chip reference here manually.
+ */
+ Py_DECREF(line_obj->owner);
+
+ return line_obj;
+}
+
+PyDoc_STRVAR(gpiod_Module_version_string_doc,
+"version_string() -> string\n"
+"\n"
+"Get the API version of the library as a human-readable string.");
+
+static PyObject *gpiod_Module_version_string(void)
+{
+ return PyUnicode_FromFormat("%s", gpiod_version_string());
+}
+
+static PyMethodDef gpiod_module_methods[] = {
+ {
+ .ml_name = "find_line",
+ .ml_meth = (PyCFunction)gpiod_Module_find_line,
+ .ml_flags = METH_VARARGS,
+ .ml_doc = gpiod_Module_find_line_doc,
+ },
+ {
+ .ml_name = "version_string",
+ .ml_meth = (PyCFunction)gpiod_Module_version_string,
+ .ml_flags = METH_NOARGS,
+ .ml_doc = gpiod_Module_version_string_doc,
+ },
+ { }
+};
+
+typedef struct {
+ const char *name;
+ PyTypeObject *typeobj;
+} gpiod_PyType;
+
+static gpiod_PyType gpiod_PyType_list[] = {
+ { .name = "Chip", .typeobj = &gpiod_ChipType, },
+ { .name = "Line", .typeobj = &gpiod_LineType, },
+ { .name = "LineEvent", .typeobj = &gpiod_LineEventType, },
+ { .name = "LineBulk", .typeobj = &gpiod_LineBulkType, },
+ { .name = "LineIter", .typeobj = &gpiod_LineIterType, },
+ { .name = "ChipIter", .typeobj = &gpiod_ChipIterType },
+ { }
+};
+
+typedef struct {
+ PyTypeObject *typeobj;
+ const char *name;
+ long int val;
+} gpiod_ConstDescr;
+
+static gpiod_ConstDescr gpiod_ConstList[] = {
+ {
+ .typeobj = &gpiod_ChipType,
+ .name = "OPEN_LOOKUP",
+ .val = gpiod_OPEN_LOOKUP,
+ },
+ {
+ .typeobj = &gpiod_ChipType,
+ .name = "OPEN_BY_PATH",
+ .val = gpiod_OPEN_BY_PATH,
+ },
+ {
+ .typeobj = &gpiod_ChipType,
+ .name = "OPEN_BY_NAME",
+ .val = gpiod_OPEN_BY_NAME,
+ },
+ {
+ .typeobj = &gpiod_ChipType,
+ .name = "OPEN_BY_LABEL",
+ .val = gpiod_OPEN_BY_LABEL,
+ },
+ {
+ .typeobj = &gpiod_ChipType,
+ .name = "OPEN_BY_NUMBER",
+ .val = gpiod_OPEN_BY_NUMBER,
+ },
+ {
+ .typeobj = &gpiod_LineType,
+ .name = "DIRECTION_INPUT",
+ .val = gpiod_DIRECTION_INPUT,
+ },
+ {
+ .typeobj = &gpiod_LineType,
+ .name = "DIRECTION_OUTPUT",
+ .val = gpiod_DIRECTION_OUTPUT,
+ },
+ {
+ .typeobj = &gpiod_LineType,
+ .name = "ACTIVE_HIGH",
+ .val = gpiod_ACTIVE_HIGH,
+ },
+ {
+ .typeobj = &gpiod_LineType,
+ .name = "ACTIVE_LOW",
+ .val = gpiod_ACTIVE_LOW,
+ },
+ {
+ .typeobj = &gpiod_LineEventType,
+ .name = "RISING_EDGE",
+ .val = gpiod_RISING_EDGE,
+ },
+ {
+ .typeobj = &gpiod_LineEventType,
+ .name = "FALLING_EDGE",
+ .val = gpiod_FALLING_EDGE,
+ },
+ { }
+};
+
+PyDoc_STRVAR(gpiod_Module_doc,
+"Python bindings for libgpiod.\n\
+\n\
+This module wraps the native C API of libgpiod in a set of python classes.");
+
+static PyModuleDef gpiod_Module = {
+ PyModuleDef_HEAD_INIT,
+ .m_name = "gpiod",
+ .m_doc = gpiod_Module_doc,
+ .m_size = -1,
+ .m_methods = gpiod_module_methods,
+};
+
+typedef struct {
+ const char *name;
+ long int value;
+} gpiod_ModuleConst;
+
+static gpiod_ModuleConst gpiod_ModuleConsts[] = {
+ {
+ .name = "LINE_REQ_DIR_AS_IS",
+ .value = gpiod_LINE_REQ_DIR_AS_IS,
+ },
+ {
+ .name = "LINE_REQ_DIR_IN",
+ .value = gpiod_LINE_REQ_DIR_IN,
+ },
+ {
+ .name = "LINE_REQ_DIR_OUT",
+ .value = gpiod_LINE_REQ_DIR_OUT,
+ },
+ {
+ .name = "LINE_REQ_EV_FALLING_EDGE",
+ .value = gpiod_LINE_REQ_EV_FALLING_EDGE,
+ },
+ {
+ .name = "LINE_REQ_EV_RISING_EDGE",
+ .value = gpiod_LINE_REQ_EV_RISING_EDGE,
+ },
+ {
+ .name = "LINE_REQ_EV_BOTH_EDGES",
+ .value = gpiod_LINE_REQ_EV_BOTH_EDGES,
+ },
+ {
+ .name = "LINE_REQ_FLAG_OPEN_DRAIN",
+ .value = gpiod_LINE_REQ_FLAG_OPEN_DRAIN,
+ },
+ {
+ .name = "LINE_REQ_FLAG_OPEN_SOURCE",
+ .value = gpiod_LINE_REQ_FLAG_OPEN_SOURCE,
+ },
+ {
+ .name = "LINE_REQ_FLAG_ACTIVE_LOW",
+ .value = gpiod_LINE_REQ_FLAG_ACTIVE_LOW,
+ },
+ { }
+};
+
+PyMODINIT_FUNC PyInit_gpiod(void)
+{
+ gpiod_ConstDescr *const_descr;
+ gpiod_ModuleConst *mod_const;
+ PyObject *module, *val;
+ gpiod_PyType *type;
+ unsigned int i;
+ int rv;
+
+ module = PyModule_Create(&gpiod_Module);
+ if (!module)
+ return NULL;
+
+ for (i = 0; gpiod_PyType_list[i].typeobj; i++) {
+ type = &gpiod_PyType_list[i];
+
+ rv = PyType_Ready(type->typeobj);
+ if (rv)
+ return NULL;
+
+ Py_INCREF(type->typeobj);
+ rv = PyModule_AddObject(module, type->name,
+ (PyObject *)type->typeobj);
+ if (rv < 0)
+ return NULL;
+ }
+
+ for (i = 0; gpiod_ConstList[i].name; i++) {
+ const_descr = &gpiod_ConstList[i];
+
+ val = PyLong_FromLong(const_descr->val);
+ if (!val)
+ return NULL;
+
+ rv = PyDict_SetItemString(const_descr->typeobj->tp_dict,
+ const_descr->name, val);
+ Py_DECREF(val);
+ if (rv)
+ return NULL;
+ }
+
+ for (i = 0; gpiod_ModuleConsts[i].name; i++) {
+ mod_const = &gpiod_ModuleConsts[i];
+
+ rv = PyModule_AddIntConstant(module,
+ mod_const->name, mod_const->value);
+ if (rv < 0)
+ return NULL;
+ }
+
+ return module;
+}
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..7a2870c
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,198 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+#
+# This file is part of libgpiod.
+#
+# Copyright (C) 2017-2019 Bartosz Golaszewski <bartekgola@gmail.com>
+#
+
+AC_PREREQ(2.61)
+
+AC_INIT([libgpiod], 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, [3.1.1])
+# Have a separate ABI version for C++ bindings:
+AC_SUBST(ABI_CXX_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_ARG_VAR([PYTHON_CPPFLAGS],
+ [Compiler flags to find Python headers [default: auto-detect]])
+AC_ARG_VAR([PYTHON_LIBS],
+ [Libraries to link into Python extensions [default: auto-detect]])
+
+AC_CONFIG_SRCDIR([lib])
+AC_CONFIG_HEADER([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_LIBTOOL
+AC_PROG_INSTALL
+
+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])])
+
+# This is always checked (library needs this)
+AC_HEADER_STDC
+AC_FUNC_MALLOC
+AC_CHECK_FUNC([ioctl], [], [FUNC_NOT_FOUND_LIB([ioctl])])
+AC_CHECK_FUNC([asprintf], [], [FUNC_NOT_FOUND_LIB([asprintf])])
+AC_CHECK_FUNC([scandir], [], [FUNC_NOT_FOUND_LIB([scandir])])
+AC_CHECK_FUNC([alphasort], [], [FUNC_NOT_FOUND_LIB([alphasort])])
+AC_CHECK_FUNC([ppoll], [], [FUNC_NOT_FOUND_LIB([ppoll])])
+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([sys/poll.h], [], [HEADER_NOT_FOUND_LIB([sys/poll.h])])
+AC_CHECK_HEADERS([sys/sysmacros.h], [], [HEADER_NOT_FOUND_LIB([sys/sysmacros.h])])
+AC_CHECK_HEADERS([linux/gpio.h], [], [HEADER_NOT_FOUND_LIB([linux/gpio.h])])
+
+AC_ARG_ENABLE([tools],
+ [AC_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_DEFUN([HEADER_NOT_FOUND_TOOLS],
+ [ERR_NOT_FOUND([$1 header], [tools])])
+
+if test "x$with_tools" = xtrue
+then
+ # These are only needed to build tools
+ AC_CHECK_FUNC([basename], [], [FUNC_NOT_FOUND_TOOLS([basename])])
+ AC_CHECK_FUNC([daemon], [], [FUNC_NOT_FOUND_TOOLS([daemon])])
+ AC_CHECK_FUNC([signalfd], [], [FUNC_NOT_FOUND_TOOLS([signalfd])])
+ AC_CHECK_FUNC([setlinebuf], [], [FUNC_NOT_FOUND_TOOLS([setlinebuf])])
+ AC_CHECK_HEADERS([sys/signalfd.h], [], [HEADER_NOT_FOUND_TOOLS([sys/signalfd.h])])
+fi
+
+AC_ARG_ENABLE([install-tests],
+ [AC_HELP_STRING([--enable-install-tests],
+ [enable install tests [default=no]])],
+ [if test "x$enableval" = xyes; then with_install_tests=true; with_tests=true; fi],
+ [with_install_tests=false])
+AM_CONDITIONAL([WITH_INSTALL_TESTS], [test "x$with_install_tests" = xtrue])
+
+AC_ARG_ENABLE([tests],
+ [AC_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_DEFUN([FUNC_NOT_FOUND_TESTS],
+ [ERR_NOT_FOUND([$1()], [tests])])
+
+if test "x$with_tests" = xtrue
+then
+ AC_CHECK_FUNC([qsort], [], [FUNC_NOT_FOUND_TESTS([qsort])])
+ AC_CHECK_FUNC([regexec], [], [FUNC_NOT_FOUND_TESTS([regexec])])
+
+ PKG_CHECK_MODULES([KMOD], [libkmod >= 18])
+ PKG_CHECK_MODULES([UDEV], [libudev >= 215])
+fi
+
+AC_ARG_ENABLE([bindings-cxx],
+ [AC_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
+ AC_LIBTOOL_CXX
+ # This needs autoconf-archive
+ AX_CXX_COMPILE_STDCXX_11([ext], [mandatory])
+fi
+
+AC_ARG_ENABLE([bindings-python],
+ [AC_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.0], [],
+ [AC_MSG_ERROR([python3 not found - needed for python bindings])])
+ AS_IF([test -z "$PYTHON_CPPFLAGS"],
+ [AC_SUBST(PYTHON_CPPFLAGS, [`$PYTHON-config --includes`])])
+ AS_IF([test -z "$PYTHON_LIBS"],
+ [AC_SUBST(PYTHON_LIBS, [`$PYTHON-config --libs`])])
+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
+
+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([libgpiod.pc
+ Makefile
+ include/Makefile
+ lib/Makefile
+ tools/Makefile
+ tests/Makefile
+ bindings/cxx/libgpiodcxx.pc
+ bindings/Makefile
+ bindings/cxx/Makefile
+ bindings/cxx/examples/Makefile
+ bindings/python/Makefile
+ bindings/python/examples/Makefile
+ man/Makefile])
+
+AC_OUTPUT
diff --git a/include/Makefile.am b/include/Makefile.am
new file mode 100644
index 0000000..dc1afa9
--- /dev/null
+++ b/include/Makefile.am
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+#
+# This file is part of libgpiod.
+#
+# Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+#
+
+include_HEADERS = gpiod.h
diff --git a/include/config.h b/include/config.h
new file mode 100644
index 0000000..cad5a54
--- /dev/null
+++ b/include/config.h
@@ -0,0 +1,96 @@
+/* config.h. Generated from config.h.in by configure. */
+/* config.h.in. Generated from configure.ac by autoheader. */
+
+/* Full library version string. */
+#define GPIOD_VERSION_STR "1.3"
+
+/* define if the compiler supports basic C++11 syntax */
+/* #undef HAVE_CXX11 */
+
+/* Define to 1 if you have the <dirent.h> header file. */
+#define HAVE_DIRENT_H 1
+
+/* Define to 1 if you have the <dlfcn.h> header file. */
+#define HAVE_DLFCN_H 1
+
+/* Define to 1 if you have the <getopt.h> header file. */
+#define HAVE_GETOPT_H 1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the <linux/gpio.h> header file. */
+#define HAVE_LINUX_GPIO_H 1
+
+/* Define to 1 if your system has a GNU libc compatible `malloc' function, and
+ to 0 otherwise. */
+#define HAVE_MALLOC 1
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the <sys/poll.h> header file. */
+#define HAVE_SYS_POLL_H 1
+
+/* Define to 1 if you have the <sys/signalfd.h> header file. */
+#define HAVE_SYS_SIGNALFD_H 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/sysmacros.h> header file. */
+#define HAVE_SYS_SYSMACROS_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define to the sub-directory where libtool stores uninstalled libraries. */
+#define LT_OBJDIR ".libs/"
+
+/* Name of package */
+#define PACKAGE "libgpiod"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT ""
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "libgpiod"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "libgpiod 1.3"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "libgpiod"
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL ""
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "1.3"
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Version number of package */
+#define VERSION "1.3"
+
+/* We want GNU extensions */
+#define _GNU_SOURCE /**/
+
+/* Define to rpl_malloc if the replacement function should be used. */
+/* #undef malloc */
diff --git a/include/gpiod.h b/include/gpiod.h
new file mode 100644
index 0000000..2679478
--- /dev/null
+++ b/include/gpiod.h
@@ -0,0 +1,1404 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+#ifndef __LIBGPIOD_GPIOD_H__
+#define __LIBGPIOD_GPIOD_H__
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <time.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.
+ *
+ * <p>The public header is logically split into two high-level parts: the
+ * simple API and the low-level API. The former allows users to easily
+ * interact with the GPIOs in the system without dealing with the low-level
+ * data structures and resource control. The latter gives the user much more
+ * fine-grained control over the GPIO interface.
+ *
+ * <p>The low-level API is further logically split into several parts such
+ * as: GPIO chip & line operators, iterators, GPIO events handling etc.
+ *
+ * <p>General note on error handling: all routines exported by libgpiod 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 bails out on error condition by returning
+ * a NULL pointer.
+ */
+
+struct gpiod_chip;
+struct gpiod_line;
+struct gpiod_chip_iter;
+struct gpiod_line_iter;
+struct gpiod_line_bulk;
+
+/**
+ * @defgroup __common__ Common helper macros
+ * @{
+ *
+ * Commonly used utility macros.
+ */
+
+/**
+ * @brief Makes symbol visible.
+ */
+#define GPIOD_API __attribute__((visibility("default")))
+
+/**
+ * @brief Marks a function argument or variable as potentially unused.
+ */
+#define GPIOD_UNUSED __attribute__((unused))
+
+/**
+ * @brief Shift 1 by given offset.
+ * @param nr Bit position.
+ * @return 1 shifted by nr.
+ */
+#define GPIOD_BIT(nr) (1UL << (nr))
+
+/**
+ * @brief Marks a public function as deprecated.
+ */
+#define GPIOD_DEPRECATED __attribute__((deprecated))
+
+/**
+ * @}
+ *
+ * @defgroup __high_level__ High-level API
+ * @{
+ *
+ * Simple high-level routines for straightforward GPIO manipulation without
+ * the need to use the gpiod_* structures or to keep track of resources.
+ */
+
+/**
+ * @brief Read current value from a single GPIO line.
+ * @param device Name, path, number or label of the gpiochip.
+ * @param offset Offset of the GPIO line.
+ * @param active_low The active state of this line - true if low.
+ * @param consumer Name of the consumer.
+ * @return 0 or 1 (GPIO value) if the operation succeeds, -1 on error.
+ */
+int gpiod_ctxless_get_value(const char *device, unsigned int offset,
+ bool active_low, const char *consumer) GPIOD_API;
+
+/**
+ * @brief Read current values from a set of GPIO lines.
+ * @param device Name, path, number or label of the gpiochip.
+ * @param offsets Array of offsets of lines whose values should be read.
+ * @param values Buffer in which the values will be stored.
+ * @param num_lines Number of lines, must be > 0.
+ * @param active_low The active state of the lines - true if low.
+ * @param consumer Name of the consumer.
+ * @return 0 if the operation succeeds, -1 on error.
+ */
+int gpiod_ctxless_get_value_multiple(const char *device,
+ const unsigned int *offsets, int *values,
+ unsigned int num_lines, bool active_low,
+ const char *consumer) GPIOD_API;
+
+/**
+ * @brief Simple set value callback signature.
+ */
+typedef void (*gpiod_ctxless_set_value_cb)(void *);
+
+/**
+ * @brief Set value of a single GPIO line.
+ * @param device Name, path, number or label of the gpiochip.
+ * @param offset The offset of the GPIO line.
+ * @param value New value (0 or 1).
+ * @param active_low The active state of this line - true if low.
+ * @param consumer Name of the consumer.
+ * @param cb Optional callback function that will be called right after setting
+ * the value. Users can use this, for example, to pause the execution
+ * after toggling a GPIO.
+ * @param data Optional user data that will be passed to the callback function.
+ * @return 0 if the operation succeeds, -1 on error.
+ */
+int gpiod_ctxless_set_value(const char *device, unsigned int offset, int value,
+ bool active_low, const char *consumer,
+ gpiod_ctxless_set_value_cb cb,
+ void *data) GPIOD_API;
+
+/**
+ * @brief Set values of multiple GPIO lines.
+ * @param device Name, path, number or label of the gpiochip.
+ * @param offsets Array of offsets of lines the values of which should be set.
+ * @param values Array of integers containing new values.
+ * @param num_lines Number of lines, must be > 0.
+ * @param active_low The active state of the lines - true if low.
+ * @param consumer Name of the consumer.
+ * @param cb Optional callback function that will be called right after setting
+ * all values. Works the same as in ::gpiod_ctxless_set_value.
+ * @param data Optional user data that will be passed to the callback function.
+ * @return 0 if the operation succeeds, -1 on error.
+ */
+int gpiod_ctxless_set_value_multiple(const char *device,
+ const unsigned int *offsets,
+ const int *values, unsigned int num_lines,
+ bool active_low, const char *consumer,
+ gpiod_ctxless_set_value_cb cb,
+ void *data) GPIOD_API;
+
+/**
+ * @brief Event types that the ctxless event monitor can wait for.
+ */
+enum {
+ /**< Wait for rising edge events only. */
+ GPIOD_CTXLESS_EVENT_RISING_EDGE = 1,
+ /**< Wait for falling edge events only. */
+ GPIOD_CTXLESS_EVENT_FALLING_EDGE,
+ /**< Wait for both types of events. */
+ GPIOD_CTXLESS_EVENT_BOTH_EDGES,
+};
+
+/**
+ * @brief Event types that can be passed to the ctxless event callback.
+ */
+enum {
+ GPIOD_CTXLESS_EVENT_CB_TIMEOUT = 1,
+ /**< Waiting for events timed out. */
+ GPIOD_CTXLESS_EVENT_CB_RISING_EDGE,
+ /**< Rising edge event occured. */
+ GPIOD_CTXLESS_EVENT_CB_FALLING_EDGE,
+ /**< Falling edge event occured. */
+};
+
+/**
+ * @brief Return status values that the ctxless event callback can return.
+ */
+enum {
+ GPIOD_CTXLESS_EVENT_CB_RET_ERR = -1,
+ /**< Stop processing events and indicate an error. */
+ GPIOD_CTXLESS_EVENT_CB_RET_OK = 0,
+ /**< Continue processing events. */
+ GPIOD_CTXLESS_EVENT_CB_RET_STOP = 1,
+ /**< Stop processing events. */
+};
+
+/**
+ * @brief Simple event callback signature.
+ *
+ * The callback function takes the following arguments: event type (int),
+ * GPIO line offset (unsigned int), event timestamp (const struct timespec *)
+ * and a pointer to user data (void *).
+ *
+ * This callback is called by the ctxless event loop functions for each GPIO
+ * event. If the callback returns ::GPIOD_CTXLESS_EVENT_CB_RET_ERR, it should
+ * also set errno.
+ */
+typedef int (*gpiod_ctxless_event_handle_cb)(int, unsigned int,
+ const struct timespec *, void *);
+
+/**
+ * @brief Return status values that the ctxless event poll callback can return.
+ *
+ * Positive value returned from the polling callback indicates the number of
+ * events that occurred on the set of monitored lines.
+ */
+enum {
+ GPIOD_CTXLESS_EVENT_POLL_RET_STOP = -2,
+ /**< The event loop should stop processing events. */
+ GPIOD_CTXLESS_EVENT_POLL_RET_ERR = -1,
+ /**< Polling error occurred (the polling function should set errno). */
+ GPIOD_CTXLESS_EVENT_POLL_RET_TIMEOUT = 0,
+ /**< Poll timed out. */
+};
+
+/**
+ * @brief Helper structure for the ctxless event loop poll callback.
+ */
+struct gpiod_ctxless_event_poll_fd {
+ int fd;
+ /**< File descriptor number. */
+ bool event;
+ /**< Indicates whether an event occurred on this file descriptor. */
+};
+
+/**
+ * @brief Simple event poll callback signature.
+ *
+ * The poll callback function takes the following arguments: number of lines
+ * (unsigned int), an array of file descriptors on which input events should
+ * be monitored (struct gpiod_ctxless_event_poll_fd *), poll timeout
+ * (const struct timespec *) and a pointer to user data (void *).
+ *
+ * The callback should poll for input events on the set of descriptors and
+ * return an appropriate value that can be interpreted by the event loop
+ * routine.
+ */
+typedef int (*gpiod_ctxless_event_poll_cb)(unsigned int,
+ struct gpiod_ctxless_event_poll_fd *,
+ const struct timespec *, void *);
+
+/**
+ * @brief Wait for events on a single GPIO line.
+ * @param device Name, path, number or label of the gpiochip.
+ * @param offset GPIO line offset to monitor.
+ * @param active_low The active state of this line - true if low.
+ * @param consumer Name of the consumer.
+ * @param timeout Maximum wait time for each iteration.
+ * @param poll_cb Callback function to call when waiting for events.
+ * @param event_cb Callback function to call for each line event.
+ * @param data User data passed to the callback.
+ * @return 0 if no errors were encountered, -1 if an error occurred.
+ * @note The way the ctxless event loop works is described in detail in
+ * ::gpiod_ctxless_event_loop_multiple - this is just a wrapper aound
+ * this routine which calls it for a single GPIO line.
+ */
+int gpiod_ctxless_event_loop(const char *device, unsigned int offset,
+ bool active_low, const char *consumer,
+ const struct timespec *timeout,
+ gpiod_ctxless_event_poll_cb poll_cb,
+ gpiod_ctxless_event_handle_cb event_cb,
+ void *data) GPIOD_API GPIOD_DEPRECATED;
+
+/**
+ * @brief Wait for events on multiple GPIO lines.
+ * @param device Name, path, number or label of the gpiochip.
+ * @param offsets Array of GPIO line offsets to monitor.
+ * @param num_lines Number of lines to monitor.
+ * @param active_low The active state of this line - true if low.
+ * @param consumer Name of the consumer.
+ * @param timeout Maximum wait time for each iteration.
+ * @param poll_cb Callback function to call when waiting for events. Can
+ * be NULL.
+ * @param event_cb Callback function to call on event occurrence.
+ * @param data User data passed to the callback.
+ * @return 0 no errors were encountered, -1 if an error occurred.
+ * @note The poll callback can be NULL in which case the routine will fall
+ * back to a basic, ppoll() based callback.
+ *
+ * Internally this routine opens the GPIO chip, requests the set of lines for
+ * both-edges events and calls the polling callback in a loop. The role of the
+ * polling callback is to detect input events on a set of file descriptors and
+ * notify the caller about the fds ready for reading.
+ *
+ * The ctxless event loop then reads each queued event from marked descriptors
+ * and calls the event callback. Both callbacks can stop the loop at any
+ * point.
+ *
+ * The poll_cb argument can be NULL in which case the function falls back to
+ * a default, ppoll() based callback.
+ */
+int gpiod_ctxless_event_loop_multiple(const char *device,
+ const unsigned int *offsets,
+ unsigned int num_lines, bool active_low,
+ const char *consumer,
+ const struct timespec *timeout,
+ gpiod_ctxless_event_poll_cb poll_cb,
+ gpiod_ctxless_event_handle_cb event_cb,
+ void *data) GPIOD_API GPIOD_DEPRECATED;
+
+/**
+ * @brief Wait for events on a single GPIO line.
+ * @param device Name, path, number or label of the gpiochip.
+ * @param event_type Type of events to listen for.
+ * @param offset GPIO line offset to monitor.
+ * @param active_low The active state of this line - true if low.
+ * @param consumer Name of the consumer.
+ * @param timeout Maximum wait time for each iteration.
+ * @param poll_cb Callback function to call when waiting for events.
+ * @param event_cb Callback function to call for each line event.
+ * @param data User data passed to the callback.
+ * @return 0 if no errors were encountered, -1 if an error occurred.
+ * @note The way the ctxless event loop works is described in detail in
+ * ::gpiod_ctxless_event_monitor_multiple - this is just a wrapper aound
+ * this routine which calls it for a single GPIO line.
+ */
+int gpiod_ctxless_event_monitor(const char *device, int event_type,
+ unsigned int offset, bool active_low,
+ const char *consumer,
+ const struct timespec *timeout,
+ gpiod_ctxless_event_poll_cb poll_cb,
+ gpiod_ctxless_event_handle_cb event_cb,
+ void *data) GPIOD_API;
+
+/**
+ * @brief Wait for events on multiple GPIO lines.
+ * @param device Name, path, number or label of the gpiochip.
+ * @param event_type Type of events to listen for.
+ * @param offsets Array of GPIO line offsets to monitor.
+ * @param num_lines Number of lines to monitor.
+ * @param active_low The active state of this line - true if low.
+ * @param consumer Name of the consumer.
+ * @param timeout Maximum wait time for each iteration.
+ * @param poll_cb Callback function to call when waiting for events. Can
+ * be NULL.
+ * @param event_cb Callback function to call on event occurrence.
+ * @param data User data passed to the callback.
+ * @return 0 no errors were encountered, -1 if an error occurred.
+ * @note The poll callback can be NULL in which case the routine will fall
+ * back to a basic, ppoll() based callback.
+ *
+ * Internally this routine opens the GPIO chip, requests the set of lines for
+ * the type of events specified in the event_type paramter and calls the
+ * polling callback in a loop. The role of the polling callback is to detect
+ * input events on a set of file descriptors and notify the caller about the
+ * fds ready for reading.
+ *
+ * The ctxless event loop then reads each queued event from marked descriptors
+ * and calls the event callback. Both callbacks can stop the loop at any
+ * point.
+ *
+ * The poll_cb argument can be NULL in which case the function falls back to
+ * a default, ppoll() based callback.
+ */
+int gpiod_ctxless_event_monitor_multiple(
+ const char *device, int event_type,
+ const unsigned int *offsets,
+ unsigned int num_lines, bool active_low,
+ const char *consumer, const struct timespec *timeout,
+ gpiod_ctxless_event_poll_cb poll_cb,
+ gpiod_ctxless_event_handle_cb event_cb,
+ void *data) GPIOD_API;
+
+/**
+ * @brief Determine the chip name and line offset of a line with given name.
+ * @param name The name of the GPIO line to lookup.
+ * @param chipname Buffer in which the name of the GPIO chip will be stored.
+ * @param chipname_size Size of the chip name buffer.
+ * @param offset Pointer to an integer in which the line offset will be stored.
+ * @return -1 on error, 0 if the line with given name doesn't exist and 1 if
+ * the line was found. In the first two cases the contents of chipname
+ * and offset remain unchanged.
+ * @note The chip name is truncated if the buffer can't hold its entire size.
+ */
+int gpiod_ctxless_find_line(const char *name, char *chipname,
+ size_t chipname_size,
+ unsigned int *offset) GPIOD_API;
+
+/**
+ * @}
+ *
+ * @defgroup __chips__ GPIO chip operations
+ * @{
+ *
+ * Functions and data structures dealing with GPIO chips.
+ */
+
+/**
+ * @brief Open a gpiochip by path.
+ * @param path Path to the gpiochip device file.
+ * @return GPIO chip handle or NULL if an error occurred.
+ */
+struct gpiod_chip *gpiod_chip_open(const char *path) GPIOD_API;
+
+/**
+ * @brief Open a gpiochip by name.
+ * @param name Name of the gpiochip to open.
+ * @return GPIO chip handle or NULL if an error occurred.
+ *
+ * This routine appends name to '/dev/' to create the path.
+ */
+struct gpiod_chip *gpiod_chip_open_by_name(const char *name) GPIOD_API;
+
+/**
+ * @brief Open a gpiochip by number.
+ * @param num Number of the gpiochip.
+ * @return GPIO chip handle or NULL if an error occurred.
+ *
+ * This routine appends num to '/dev/gpiochip' to create the path.
+ */
+struct gpiod_chip *gpiod_chip_open_by_number(unsigned int num) GPIOD_API;
+
+/**
+ * @brief Open a gpiochip by label.
+ * @param label Label of the gpiochip to open.
+ * @return GPIO chip handle or NULL if the chip with given label was not found
+ * or an error occured.
+ * @note If the chip cannot be found but no other error occurred, errno is set
+ * to ENOENT.
+ */
+struct gpiod_chip *gpiod_chip_open_by_label(const char *label) GPIOD_API;
+
+/**
+ * @brief Open a gpiochip based on the best guess what the path is.
+ * @param descr String describing the gpiochip.
+ * @return GPIO chip handle or NULL if an error occurred.
+ *
+ * This routine tries to figure out whether the user passed it the path to the
+ * GPIO chip, its name, label or number as a string. Then it tries to open it
+ * using one of the gpiod_chip_open** variants.
+ */
+struct gpiod_chip *gpiod_chip_open_lookup(const char *descr) GPIOD_API;
+
+/**
+ * @brief Close a GPIO chip handle and release all allocated resources.
+ * @param chip The GPIO chip object.
+ */
+void gpiod_chip_close(struct gpiod_chip *chip) GPIOD_API;
+
+/**
+ * @brief Get the GPIO chip name as represented in the kernel.
+ * @param chip The GPIO chip object.
+ * @return Pointer to a human-readable string containing the chip name.
+ */
+const char *gpiod_chip_name(struct gpiod_chip *chip) GPIOD_API;
+
+/**
+ * @brief Get the GPIO chip label as represented in the kernel.
+ * @param chip The GPIO chip object.
+ * @return Pointer to a human-readable string containing the chip label.
+ */
+const char *gpiod_chip_label(struct gpiod_chip *chip) GPIOD_API;
+
+/**
+ * @brief Get the number of GPIO lines exposed by this chip.
+ * @param chip The GPIO chip object.
+ * @return Number of GPIO lines.
+ */
+unsigned int gpiod_chip_num_lines(struct gpiod_chip *chip) GPIOD_API;
+
+/**
+ * @brief Get the handle to the GPIO line at given offset.
+ * @param chip The GPIO chip object.
+ * @param offset The offset of the GPIO line.
+ * @return Pointer to the GPIO line handle or NULL if an error occured.
+ */
+struct gpiod_line *
+gpiod_chip_get_line(struct gpiod_chip *chip, unsigned int offset) GPIOD_API;
+
+/**
+ * @brief Retrieve a set of lines and store them in a line bulk object.
+ * @param chip The GPIO chip object.
+ * @param offsets Array of offsets of lines to retrieve.
+ * @param num_offsets Number of lines to retrieve.
+ * @param bulk Line bulk object in which to store the line handles.
+ * @return 0 on success, -1 on error.
+ */
+int gpiod_chip_get_lines(struct gpiod_chip *chip,
+ unsigned int *offsets, unsigned int num_offsets,
+ struct gpiod_line_bulk *bulk) GPIOD_API;
+
+/**
+ * @brief Retrieve all lines exposed by a chip and store them in a bulk object.
+ * @param chip The GPIO chip object.
+ * @param bulk Line bulk object in which to store the line handles.
+ * @return 0 on success, -1 on error.
+ */
+int gpiod_chip_get_all_lines(struct gpiod_chip *chip,
+ struct gpiod_line_bulk *bulk) GPIOD_API;
+
+/**
+ * @brief Find a GPIO line by name among lines associated with given GPIO chip.
+ * @param chip The GPIO chip object.
+ * @param name The name of the GPIO line.
+ * @return Pointer to the GPIO line handle or NULL if the line could not be
+ * found or an error occurred.
+ * @note In case a line with given name is not associated with given chip, the
+ * function sets errno to ENOENT.
+ */
+struct gpiod_line *
+gpiod_chip_find_line(struct gpiod_chip *chip, const char *name) GPIOD_API;
+
+/**
+ * @brief Find a set of GPIO lines by names among lines exposed by this chip.
+ * @param chip The GPIO chip object.
+ * @param names Array of pointers to C-strings containing the names of the
+ * lines to lookup. Must end with a NULL-pointer.
+ * @param bulk Line bulk object in which the located lines will be stored.
+ * @return 0 if all lines were located, -1 on error.
+ * @note If at least one line from the list could not be found among the lines
+ * exposed by this chip, the function sets errno to ENOENT.
+ */
+int gpiod_chip_find_lines(struct gpiod_chip *chip, const char **names,
+ struct gpiod_line_bulk *bulk) GPIOD_API;
+
+/**
+ * @}
+ *
+ * @defgroup __lines__ GPIO line operations
+ * @{
+ *
+ * Functions and data structures dealing with GPIO lines.
+ *
+ * @defgroup __line_bulk__ Operating on multiple lines
+ * @{
+ */
+
+/**
+ * @brief Maximum number of GPIO lines that can be requested at once.
+ */
+#define GPIOD_LINE_BULK_MAX_LINES 64
+
+/**
+ * @brief Helper structure for storing a set of GPIO line objects.
+ *
+ * This structure is used in all operations involving sets of GPIO lines. If
+ * a bulk object is being passed to a function while containing zero lines,
+ * the result is undefined.
+ */
+struct gpiod_line_bulk {
+ struct gpiod_line *lines[GPIOD_LINE_BULK_MAX_LINES];
+ /**< Buffer for line pointers. */
+ unsigned int num_lines;
+ /**< Number of lines currently held in this structure. */
+};
+
+/**
+ * @brief Static initializer for GPIO bulk objects.
+ *
+ * This macro simply sets the internally held number of lines to 0.
+ */
+#define GPIOD_LINE_BULK_INITIALIZER { { NULL }, 0 }
+
+/**
+ * @brief Initialize a GPIO bulk object.
+ * @param bulk Line bulk object.
+ *
+ * This routine simply sets the internally held number of lines to 0.
+ */
+static inline void gpiod_line_bulk_init(struct gpiod_line_bulk *bulk)
+{
+ bulk->num_lines = 0;
+}
+
+/**
+ * @brief Add a single line to a GPIO bulk object.
+ * @param bulk Line bulk object.
+ * @param line Line to add.
+ */
+static inline void gpiod_line_bulk_add(struct gpiod_line_bulk *bulk,
+ struct gpiod_line *line)
+{
+ bulk->lines[bulk->num_lines++] = line;
+}
+
+/**
+ * @brief Retrieve the line handle from a line bulk object at given offset.
+ * @param bulk Line bulk object.
+ * @param offset Line offset.
+ * @return Line handle at given offset.
+ */
+static inline struct gpiod_line *
+gpiod_line_bulk_get_line(struct gpiod_line_bulk *bulk, unsigned int offset)
+{
+ return bulk->lines[offset];
+}
+
+/**
+ * @brief Retrieve the number of GPIO lines held by this line bulk object.
+ * @param bulk Line bulk object.
+ * @return Number of lines held by this line bulk.
+ */
+static inline unsigned int
+gpiod_line_bulk_num_lines(struct gpiod_line_bulk *bulk)
+{
+ return bulk->num_lines;
+}
+
+/**
+ * @brief Iterate over all line handles held by a line bulk object.
+ * @param bulk Line bulk object.
+ * @param line GPIO line handle. On each iteration, the subsequent line handle
+ * is assigned to this pointer.
+ * @param lineptr Pointer to a GPIO line handle used to store the loop state.
+ */
+#define gpiod_line_bulk_foreach_line(bulk, line, lineptr) \
+ for ((lineptr) = (bulk)->lines, (line) = *(lineptr); \
+ (lineptr) <= (bulk)->lines + ((bulk)->num_lines - 1); \
+ (lineptr)++, (line) = *(lineptr))
+
+/**
+ * @brief Iterate over all line handles held by a line bulk object (integer
+ * counter variant).
+ * @param bulk Line bulk object.
+ * @param line GPIO line handle. On each iteration, the subsequent line handle
+ * is assigned to this pointer.
+ * @param offset An integer variable used to store the loop state.
+ *
+ * This is a variant of ::gpiod_line_bulk_foreach_line which uses an integer
+ * variable (either signed or unsigned) to store the loop state. This offset
+ * variable is guaranteed to correspond with the offset of the current line in
+ * the bulk->lines array.
+ */
+#define gpiod_line_bulk_foreach_line_off(bulk, line, offset) \
+ for ((offset) = 0, (line) = (bulk)->lines[0]; \
+ (offset) < (bulk)->num_lines; \
+ (offset)++, (line) = (bulk)->lines[(offset)])
+
+/**
+ * @}
+ *
+ * @defgroup __line_info__ Line info
+ * @{
+ */
+
+/**
+ * @brief Possible direction settings.
+ */
+enum {
+ GPIOD_LINE_DIRECTION_INPUT = 1,
+ /**< Direction is input - we're reading the state of a GPIO line. */
+ GPIOD_LINE_DIRECTION_OUTPUT,
+ /**< Direction is output - we're driving the GPIO line. */
+};
+
+/**
+ * @brief Possible active state settings.
+ */
+enum {
+ GPIOD_LINE_ACTIVE_STATE_HIGH = 1,
+ /**< The active state of a GPIO is active-high. */
+ GPIOD_LINE_ACTIVE_STATE_LOW,
+ /**< The active state of a GPIO is active-low. */
+};
+
+/**
+ * @brief Read the GPIO line offset.
+ * @param line GPIO line object.
+ * @return Line offset.
+ */
+unsigned int gpiod_line_offset(struct gpiod_line *line) GPIOD_API;
+
+/**
+ * @brief Read the GPIO line name.
+ * @param line GPIO line object.
+ * @return Name of the GPIO line as it is represented in the kernel. This
+ * routine returns a pointer to a null-terminated string or NULL if
+ * the line is unnamed.
+ */
+const char *gpiod_line_name(struct gpiod_line *line) GPIOD_API;
+
+/**
+ * @brief Read the GPIO line consumer name.
+ * @param line GPIO line object.
+ * @return Name of the GPIO consumer name as it is represented in the
+ * kernel. This routine returns a pointer to a null-terminated string
+ * or NULL if the line is not used.
+ */
+const char *gpiod_line_consumer(struct gpiod_line *line) GPIOD_API;
+
+/**
+ * @brief Read the GPIO line direction setting.
+ * @param line GPIO line object.
+ * @return Returns GPIOD_LINE_DIRECTION_INPUT or GPIOD_LINE_DIRECTION_OUTPUT.
+ */
+int gpiod_line_direction(struct gpiod_line *line) GPIOD_API;
+
+/**
+ * @brief Read the GPIO line active state setting.
+ * @param line GPIO line object.
+ * @return Returns GPIOD_LINE_ACTIVE_STATE_HIGH or GPIOD_LINE_ACTIVE_STATE_LOW.
+ */
+int gpiod_line_active_state(struct gpiod_line *line) GPIOD_API;
+
+/**
+ * @brief Check if the line is currently in use.
+ * @param line GPIO line object.
+ * @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 gpiod_line_is_used(struct gpiod_line *line) GPIOD_API;
+
+/**
+ * @brief Check if the line is an open-drain GPIO.
+ * @param line GPIO line object.
+ * @return True if the line is an open-drain GPIO, false otherwise.
+ */
+bool gpiod_line_is_open_drain(struct gpiod_line *line) GPIOD_API;
+
+/**
+ * @brief Check if the line is an open-source GPIO.
+ * @param line GPIO line object.
+ * @return True if the line is an open-source GPIO, false otherwise.
+ */
+bool gpiod_line_is_open_source(struct gpiod_line *line) GPIOD_API;
+
+/**
+ * @brief Re-read the line info.
+ * @param line GPIO line object.
+ * @return 0 is the operation succeeds. In case of an error this routine
+ * returns -1 and sets the last error number.
+ *
+ * The line info is initially retrieved from the kernel by
+ * gpiod_chip_get_line(). Users can use this line to manually re-read the line
+ * info.
+ */
+int gpiod_line_update(struct gpiod_line *line) GPIOD_API;
+
+/**
+ * @brief Check if the line info needs to be updated.
+ * @param line GPIO line object.
+ * @return Returns false if the line is up-to-date. True otherwise.
+ *
+ * The line is updated by calling gpiod_line_update() from within
+ * gpiod_chip_get_line() and on every line request/release. However: an error
+ * returned from gpiod_line_update() only breaks the execution of the former.
+ * The request/release routines only set the internal up-to-date flag to false
+ * and continue their execution. This routine allows to check if a line info
+ * update failed at some point and we should call gpiod_line_update()
+ * explicitly.
+ */
+bool gpiod_line_needs_update(struct gpiod_line *line) GPIOD_API;
+
+/**
+ * @}
+ *
+ * @defgroup __line_request__ Line requests
+ * @{
+ */
+
+/**
+ * @brief Available types of requests.
+ */
+enum {
+ GPIOD_LINE_REQUEST_DIRECTION_AS_IS = 1,
+ /**< Request the line(s), but don't change current direction. */
+ GPIOD_LINE_REQUEST_DIRECTION_INPUT,
+ /**< Request the line(s) for reading the GPIO line state. */
+ GPIOD_LINE_REQUEST_DIRECTION_OUTPUT,
+ /**< Request the line(s) for setting the GPIO line state. */
+ GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE,
+ /**< Monitor both types of events. */
+ GPIOD_LINE_REQUEST_EVENT_RISING_EDGE,
+ /**< Only watch rising edge events. */
+ GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES,
+ /**< Only watch falling edge events. */
+};
+
+/**
+ * @brief Miscellaneous GPIO request flags.
+ */
+enum {
+ GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN = GPIOD_BIT(0),
+ /**< The line is an open-drain port. */
+ GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE = GPIOD_BIT(1),
+ /**< The line is an open-source port. */
+ GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW = GPIOD_BIT(2),
+ /**< The active state of the line is low (high is the default). */
+};
+
+/**
+ * @brief Structure holding configuration of a line request.
+ */
+struct gpiod_line_request_config {
+ const char *consumer;
+ /**< Name of the consumer. */
+ int request_type;
+ /**< Request type. */
+ int flags;
+ /**< Other configuration flags. */
+};
+
+/**
+ * @brief Reserve a single line.
+ * @param line GPIO line object.
+ * @param config Request options.
+ * @param default_val Initial line value - only relevant if we're setting
+ * the direction to output.
+ * @return 0 if the line was properly reserved. In case of an error this
+ * routine returns -1 and sets the last error number.
+ *
+ * If this routine succeeds, the caller takes ownership of the GPIO line until
+ * it's released.
+ */
+int gpiod_line_request(struct gpiod_line *line,
+ const struct gpiod_line_request_config *config,
+ int default_val) GPIOD_API;
+
+/**
+ * @brief Reserve a single line, set the direction to input.
+ * @param line GPIO line object.
+ * @param consumer Name of the consumer.
+ * @return 0 if the line was properly reserved, -1 on failure.
+ */
+int gpiod_line_request_input(struct gpiod_line *line,
+ const char *consumer) GPIOD_API;
+
+/**
+ * @brief Reserve a single line, set the direction to output.
+ * @param line GPIO line object.
+ * @param consumer Name of the consumer.
+ * @param default_val Initial line value.
+ * @return 0 if the line was properly reserved, -1 on failure.
+ */
+int gpiod_line_request_output(struct gpiod_line *line,
+ const char *consumer, int default_val) GPIOD_API;
+
+/**
+ * @brief Request rising edge event notifications on a single line.
+ * @param line GPIO line object.
+ * @param consumer Name of the consumer.
+ * @return 0 if the operation succeeds, -1 on failure.
+ */
+int gpiod_line_request_rising_edge_events(struct gpiod_line *line,
+ const char *consumer) GPIOD_API;
+
+/**
+ * @brief Request falling edge event notifications on a single line.
+ * @param line GPIO line object.
+ * @param consumer Name of the consumer.
+ * @return 0 if the operation succeeds, -1 on failure.
+ */
+int gpiod_line_request_falling_edge_events(struct gpiod_line *line,
+ const char *consumer) GPIOD_API;
+
+/**
+ * @brief Request all event type notifications on a single line.
+ * @param line GPIO line object.
+ * @param consumer Name of the consumer.
+ * @return 0 if the operation succeeds, -1 on failure.
+ */
+int gpiod_line_request_both_edges_events(struct gpiod_line *line,
+ const char *consumer) GPIOD_API;
+
+/**
+ * @brief Reserve a single line, set the direction to input.
+ * @param line GPIO line object.
+ * @param consumer Name of the consumer.
+ * @param flags Additional request flags.
+ * @return 0 if the line was properly reserved, -1 on failure.
+ */
+int gpiod_line_request_input_flags(struct gpiod_line *line,
+ const char *consumer, int flags) GPIOD_API;
+
+/**
+ * @brief Reserve a single line, set the direction to output.
+ * @param line GPIO line object.
+ * @param consumer Name of the consumer.
+ * @param flags Additional request flags.
+ * @param default_val Initial line value.
+ * @return 0 if the line was properly reserved, -1 on failure.
+ */
+int gpiod_line_request_output_flags(struct gpiod_line *line,
+ const char *consumer, int flags,
+ int default_val) GPIOD_API;
+
+/**
+ * @brief Request rising edge event notifications on a single line.
+ * @param line GPIO line object.
+ * @param consumer Name of the consumer.
+ * @param flags Additional request flags.
+ * @return 0 if the operation succeeds, -1 on failure.
+ */
+int gpiod_line_request_rising_edge_events_flags(struct gpiod_line *line,
+ const char *consumer,
+ int flags) GPIOD_API;
+
+/**
+ * @brief Request falling edge event notifications on a single line.
+ * @param line GPIO line object.
+ * @param consumer Name of the consumer.
+ * @param flags Additional request flags.
+ * @return 0 if the operation succeeds, -1 on failure.
+ */
+int gpiod_line_request_falling_edge_events_flags(struct gpiod_line *line,
+ const char *consumer,
+ int flags) GPIOD_API;
+
+/**
+ * @brief Request all event type notifications on a single line.
+ * @param line GPIO line object.
+ * @param consumer Name of the consumer.
+ * @param flags Additional request flags.
+ * @return 0 if the operation succeeds, -1 on failure.
+ */
+int gpiod_line_request_both_edges_events_flags(struct gpiod_line *line,
+ const char *consumer,
+ int flags) GPIOD_API;
+
+/**
+ * @brief Reserve a set of GPIO lines.
+ * @param bulk Set of GPIO lines to reserve.
+ * @param config Request options.
+ * @param default_vals Initial line values - only relevant if we're setting
+ * the direction to output.
+ * @return 0 if the all lines were properly requested. In case of an error
+ * this routine returns -1 and sets the last error number.
+ *
+ * If this routine succeeds, the caller takes ownership of the GPIO lines
+ * until they're released. All the requested lines must be prodivided by the
+ * same gpiochip.
+ */
+int gpiod_line_request_bulk(struct gpiod_line_bulk *bulk,
+ const struct gpiod_line_request_config *config,
+ const int *default_vals) GPIOD_API;
+
+/**
+ * @brief Reserve a set of GPIO lines, set the direction to input.
+ * @param bulk Set of GPIO lines to reserve.
+ * @param consumer Name of the consumer.
+ * @return 0 if the lines were properly reserved, -1 on failure.
+ */
+int gpiod_line_request_bulk_input(struct gpiod_line_bulk *bulk,
+ const char *consumer) GPIOD_API;
+
+/**
+ * @brief Reserve a set of GPIO lines, set the direction to output.
+ * @param bulk Set of GPIO lines to reserve.
+ * @param consumer Name of the consumer.
+ * @param default_vals Initial line values.
+ * @return 0 if the lines were properly reserved, -1 on failure.
+ */
+int gpiod_line_request_bulk_output(struct gpiod_line_bulk *bulk,
+ const char *consumer,
+ const int *default_vals) GPIOD_API;
+
+/**
+ * @brief Request rising edge event notifications on a set of lines.
+ * @param bulk Set of GPIO lines to request.
+ * @param consumer Name of the consumer.
+ * @return 0 if the operation succeeds, -1 on failure.
+ */
+int gpiod_line_request_bulk_rising_edge_events(struct gpiod_line_bulk *bulk,
+ const char *consumer) GPIOD_API;
+
+/**
+ * @brief Request falling edge event notifications on a set of lines.
+ * @param bulk Set of GPIO lines to request.
+ * @param consumer Name of the consumer.
+ * @return 0 if the operation succeeds, -1 on failure.
+ */
+int gpiod_line_request_bulk_falling_edge_events(struct gpiod_line_bulk *bulk,
+ const char *consumer) GPIOD_API;
+
+/**
+ * @brief Request all event type notifications on a set of lines.
+ * @param bulk Set of GPIO lines to request.
+ * @param consumer Name of the consumer.
+ * @return 0 if the operation succeeds, -1 on failure.
+ */
+int gpiod_line_request_bulk_both_edges_events(struct gpiod_line_bulk *bulk,
+ const char *consumer) GPIOD_API;
+
+/**
+ * @brief Reserve a set of GPIO lines, set the direction to input.
+ * @param bulk Set of GPIO lines to reserve.
+ * @param consumer Name of the consumer.
+ * @param flags Additional request flags.
+ * @return 0 if the lines were properly reserved, -1 on failure.
+ */
+int gpiod_line_request_bulk_input_flags(struct gpiod_line_bulk *bulk,
+ const char *consumer,
+ int flags) GPIOD_API;
+
+/**
+ * @brief Reserve a set of GPIO lines, set the direction to output.
+ * @param bulk Set of GPIO lines to reserve.
+ * @param consumer Name of the consumer.
+ * @param flags Additional request flags.
+ * @param default_vals Initial line values.
+ * @return 0 if the lines were properly reserved, -1 on failure.
+ */
+int gpiod_line_request_bulk_output_flags(struct gpiod_line_bulk *bulk,
+ const char *consumer, int flags,
+ const int *default_vals) GPIOD_API;
+
+/**
+ * @brief Request rising edge event notifications on a set of lines.
+ * @param bulk Set of GPIO lines to request.
+ * @param consumer Name of the consumer.
+ * @param flags Additional request flags.
+ * @return 0 if the operation succeeds, -1 on failure.
+ */
+int gpiod_line_request_bulk_rising_edge_events_flags(
+ struct gpiod_line_bulk *bulk,
+ const char *consumer,
+ int flags) GPIOD_API;
+
+/**
+ * @brief Request falling edge event notifications on a set of lines.
+ * @param bulk Set of GPIO lines to request.
+ * @param consumer Name of the consumer.
+ * @param flags Additional request flags.
+ * @return 0 if the operation succeeds, -1 on failure.
+ */
+int gpiod_line_request_bulk_falling_edge_events_flags(
+ struct gpiod_line_bulk *bulk,
+ const char *consumer,
+ int flags) GPIOD_API;
+
+/**
+ * @brief Request all event type notifications on a set of lines.
+ * @param bulk Set of GPIO lines to request.
+ * @param consumer Name of the consumer.
+ * @param flags Additional request flags.
+ * @return 0 if the operation succeeds, -1 on failure.
+ */
+int gpiod_line_request_bulk_both_edges_events_flags(
+ struct gpiod_line_bulk *bulk,
+ const char *consumer,
+ int flags) GPIOD_API;
+
+/**
+ * @brief Release a previously reserved line.
+ * @param line GPIO line object.
+ */
+void gpiod_line_release(struct gpiod_line *line) GPIOD_API;
+
+/**
+ * @brief Release a set of previously reserved lines.
+ * @param bulk Set of GPIO lines to release.
+ *
+ * If the lines were not previously requested together, the behavior is
+ * undefined.
+ */
+void gpiod_line_release_bulk(struct gpiod_line_bulk *bulk) GPIOD_API;
+
+/**
+ * @brief Check if the calling user has ownership of this line.
+ * @param line GPIO line object.
+ * @return True if given line was requested, false otherwise.
+ */
+bool gpiod_line_is_requested(struct gpiod_line *line) GPIOD_API;
+
+/**
+ * @brief Check if the calling user has neither requested ownership of this
+ * line nor configured any event notifications.
+ * @param line GPIO line object.
+ * @return True if given line is free, false otherwise.
+ */
+bool gpiod_line_is_free(struct gpiod_line *line) GPIOD_API;
+
+/**
+ * @}
+ *
+ * @defgroup __line_value__ Reading & setting line values
+ * @{
+ */
+
+/**
+ * @brief Read current value of a single GPIO line.
+ * @param line GPIO line object.
+ * @return 0 or 1 if the operation succeeds. On error this routine returns -1
+ * and sets the last error number.
+ */
+int gpiod_line_get_value(struct gpiod_line *line) GPIOD_API;
+
+/**
+ * @brief Read current values of a set of GPIO lines.
+ * @param bulk Set of GPIO lines to reserve.
+ * @param values An array big enough to hold line_bulk->num_lines values.
+ * @return 0 is the operation succeeds. In case of an error this routine
+ * returns -1 and sets the last error number.
+ *
+ * If succeeds, this routine fills the values array with a set of values in
+ * the same order, the lines are added to line_bulk. If the lines were not
+ * previously requested together, the behavior is undefined.
+ */
+int gpiod_line_get_value_bulk(struct gpiod_line_bulk *bulk,
+ int *values) GPIOD_API;
+
+/**
+ * @brief Set the value of a single GPIO line.
+ * @param line GPIO line object.
+ * @param value New value.
+ * @return 0 is the operation succeeds. In case of an error this routine
+ * returns -1 and sets the last error number.
+ */
+int gpiod_line_set_value(struct gpiod_line *line, int value) GPIOD_API;
+
+/**
+ * @brief Set the values of a set of GPIO lines.
+ * @param bulk Set of GPIO lines to reserve.
+ * @param values An array holding line_bulk->num_lines new values for lines.
+ * @return 0 is the operation succeeds. In case of an error this routine
+ * returns -1 and sets the last error number.
+ *
+ * If the lines were not previously requested together, the behavior is
+ * undefined.
+ */
+int gpiod_line_set_value_bulk(struct gpiod_line_bulk *bulk,
+ const int *values) GPIOD_API;
+
+/**
+ * @}
+ *
+ * @defgroup __line_event__ Line events handling
+ * @{
+ */
+
+/**
+ * @brief Event types.
+ */
+enum {
+ GPIOD_LINE_EVENT_RISING_EDGE = 1,
+ /**< Rising edge event. */
+ GPIOD_LINE_EVENT_FALLING_EDGE,
+ /**< Falling edge event. */
+};
+
+/**
+ * @brief Structure holding event info.
+ */
+struct gpiod_line_event {
+ struct timespec ts;
+ /**< Best estimate of time of event occurrence. */
+ int event_type;
+ /**< Type of the event that occurred. */
+};
+
+/**
+ * @brief Wait for an event on a single line.
+ * @param line GPIO line object.
+ * @param timeout Wait time limit.
+ * @return 0 if wait timed out, -1 if an error occurred, 1 if an event
+ * occurred.
+ */
+int gpiod_line_event_wait(struct gpiod_line *line,
+ const struct timespec *timeout) GPIOD_API;
+
+/**
+ * @brief Wait for events on a set of lines.
+ * @param bulk Set of GPIO lines to monitor.
+ * @param timeout Wait time limit.
+ * @param event_bulk Bulk object in which to store the line handles on which
+ * events occurred. Can be NULL.
+ * @return 0 if wait timed out, -1 if an error occurred, 1 if at least one
+ * event occurred.
+ */
+int gpiod_line_event_wait_bulk(struct gpiod_line_bulk *bulk,
+ const struct timespec *timeout,
+ struct gpiod_line_bulk *event_bulk) GPIOD_API;
+
+/**
+ * @brief Read the last event from the GPIO line.
+ * @param line GPIO line object.
+ * @param event Buffer to which the event data will be copied.
+ * @return 0 if the event was read correctly, -1 on error.
+ * @note This function will block if no event was queued for this line.
+ */
+int gpiod_line_event_read(struct gpiod_line *line,
+ struct gpiod_line_event *event) GPIOD_API;
+
+/**
+ * @brief Get the event file descriptor.
+ * @param line GPIO line object.
+ * @return Number of the event file descriptor or -1 if the user tries to
+ * retrieve the descriptor from a line that wasn't configured for
+ * event monitoring.
+ *
+ * Users may want to poll the event file descriptor on their own. This routine
+ * allows to access it.
+ */
+int gpiod_line_event_get_fd(struct gpiod_line *line) GPIOD_API;
+
+/**
+ * @brief Read the last GPIO event directly from a file descriptor.
+ * @param fd File descriptor.
+ * @param event Buffer in which the event data will be stored.
+ * @return 0 if the event was read correctly, -1 on error.
+ *
+ * Users who directly poll the file descriptor for incoming events can also
+ * directly read the event data from it using this routine. This function
+ * translates the kernel representation of the event to the libgpiod format.
+ */
+int gpiod_line_event_read_fd(int fd, struct gpiod_line_event *event) GPIOD_API;
+
+/**
+ * @}
+ *
+ * @defgroup __line_misc__ Misc line functions
+ * @{
+ */
+
+/**
+ * @brief Get a GPIO line handle by GPIO chip description and offset.
+ * @param device String describing the gpiochip.
+ * @param offset The offset of the GPIO line.
+ * @return GPIO line handle or NULL if an error occurred.
+ *
+ * This routine provides a shorter alternative to calling
+ * ::gpiod_chip_open_lookup and ::gpiod_chip_get_line.
+ *
+ * If this function succeeds, the caller is responsible for closing the
+ * associated GPIO chip.
+ */
+struct gpiod_line *
+gpiod_line_get(const char *device, unsigned int offset) GPIOD_API;
+
+/**
+ * @brief Find a GPIO line by its name.
+ * @param name Name of the GPIO line.
+ * @return Returns the GPIO line handle if the line exists in the system or
+ * NULL if it couldn't be located or an error occurred.
+ *
+ * If this routine succeeds, the user must manually close the GPIO chip owning
+ * this line to avoid memory leaks. If the line could not be found, this
+ * functions sets errno to ENOENT.
+ */
+struct gpiod_line *gpiod_line_find(const char *name) GPIOD_API;
+
+/**
+ * @brief Close a GPIO chip owning this line and release all resources.
+ * @param line GPIO line object
+ *
+ * After this function returns, the line must no longer be used.
+ */
+void gpiod_line_close_chip(struct gpiod_line *line) GPIOD_API;
+
+/**
+ * @brief Get the handle to the GPIO chip controlling this line.
+ * @param line The GPIO line object.
+ * @return Pointer to the GPIO chip handle controlling this line.
+ */
+struct gpiod_chip *gpiod_line_get_chip(struct gpiod_line *line) GPIOD_API;
+
+/**
+ * @}
+ *
+ * @}
+ *
+ * @defgroup __iterators__ Iterators for GPIO chips and lines
+ * @{
+ *
+ * These functions and data structures allow easy iterating over GPIO
+ * chips and lines.
+ */
+
+/**
+ * @brief Create a new gpiochip iterator.
+ * @return Pointer to a new chip iterator object or NULL if an error occurred.
+ *
+ * Internally this routine scans the /dev/ directory for GPIO chip device
+ * files, opens them and stores their the handles until ::gpiod_chip_iter_free
+ * or ::gpiod_chip_iter_free_noclose is called.
+ */
+struct gpiod_chip_iter *gpiod_chip_iter_new(void) GPIOD_API;
+
+/**
+ * @brief Release all resources allocated for the gpiochip iterator and close
+ * the most recently opened gpiochip (if any).
+ * @param iter The gpiochip iterator object.
+ */
+void gpiod_chip_iter_free(struct gpiod_chip_iter *iter) GPIOD_API;
+
+/**
+ * @brief Release all resources allocated for the gpiochip iterator but
+ * don't close the most recently opened gpiochip (if any).
+ * @param iter The gpiochip iterator object.
+ *
+ * Users may want to break the loop when iterating over gpiochips and keep
+ * the most recently opened chip active while freeing the iterator data.
+ * This routine enables that.
+ */
+void gpiod_chip_iter_free_noclose(struct gpiod_chip_iter *iter) GPIOD_API;
+
+/**
+ * @brief Get the next gpiochip handle.
+ * @param iter The gpiochip iterator object.
+ * @return Pointer to the next open gpiochip handle or NULL if no more chips
+ * are present in the system.
+ * @note The previous chip handle will be closed using ::gpiod_chip_iter_free.
+ */
+struct gpiod_chip *
+gpiod_chip_iter_next(struct gpiod_chip_iter *iter) GPIOD_API;
+
+/**
+ * @brief Get the next gpiochip handle without closing the previous one.
+ * @param iter The gpiochip iterator object.
+ * @return Pointer to the next open gpiochip handle or NULL if no more chips
+ * are present in the system.
+ * @note This function works just like ::gpiod_chip_iter_next but doesn't
+ * close the most recently opened chip handle.
+ */
+struct gpiod_chip *
+gpiod_chip_iter_next_noclose(struct gpiod_chip_iter *iter) GPIOD_API;
+
+/**
+ * @brief Iterate over all GPIO chips present in the system.
+ * @param iter An initialized GPIO chip iterator.
+ * @param chip Pointer to a GPIO chip handle. On each iteration the newly
+ * opened chip handle is assigned to this argument.
+ *
+ * The user must not close the GPIO chip manually - instead the previous chip
+ * handle is closed automatically on the next iteration. The last chip to be
+ * opened is closed internally by ::gpiod_chip_iter_free.
+ */
+#define gpiod_foreach_chip(iter, chip) \
+ for ((chip) = gpiod_chip_iter_next(iter); \
+ (chip); \
+ (chip) = gpiod_chip_iter_next(iter))
+
+/**
+ * @brief Iterate over all chips present in the system without closing them.
+ * @param iter An initialized GPIO chip iterator.
+ * @param chip Pointer to a GPIO chip handle. On each iteration the newly
+ * opened chip handle is assigned to this argument.
+ *
+ * The user must close all the GPIO chips manually after use, until then, the
+ * chips remain open. Free the iterator by calling
+ * ::gpiod_chip_iter_free_noclose to avoid closing the last chip automatically.
+ */
+#define gpiod_foreach_chip_noclose(iter, chip) \
+ for ((chip) = gpiod_chip_iter_next_noclose(iter); \
+ (chip); \
+ (chip) = gpiod_chip_iter_next_noclose(iter))
+
+/**
+ * @brief Create a new line iterator.
+ * @param chip Active gpiochip handle over the lines of which we want
+ * to iterate.
+ * @return New line iterator or NULL if an error occurred.
+ */
+struct gpiod_line_iter *
+gpiod_line_iter_new(struct gpiod_chip *chip) GPIOD_API;
+
+/**
+ * @brief Free all resources associated with a GPIO line iterator.
+ * @param iter Line iterator object.
+ */
+void gpiod_line_iter_free(struct gpiod_line_iter *iter) GPIOD_API;
+
+/**
+ * @brief Get the next GPIO line handle.
+ * @param iter The GPIO line iterator object.
+ * @return Pointer to the next GPIO line handle or NULL if there are no more
+ * lines left.
+ */
+struct gpiod_line *
+gpiod_line_iter_next(struct gpiod_line_iter *iter) GPIOD_API;
+
+/**
+ * @brief Iterate over all GPIO lines of a single chip.
+ * @param iter An initialized GPIO line iterator.
+ * @param line Pointer to a GPIO line handle - on each iteration, the
+ * next GPIO line will be assigned to this argument.
+ */
+#define gpiod_foreach_line(iter, line) \
+ for ((line) = gpiod_line_iter_next(iter); \
+ (line); \
+ (line) = gpiod_line_iter_next(iter))
+
+/**
+ * @}
+ *
+ * @defgroup __misc__ Stuff that didn't fit anywhere else
+ * @{
+ *
+ * Various libgpiod-related functions.
+ */
+
+/**
+ * @brief Get the API version of the library as a human-readable string.
+ * @return Human-readable string containing the library version.
+ */
+const char *gpiod_version_string(void) GPIOD_API;
+
+/**
+ * @}
+ */
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* __LIBGPIOD_GPIOD_H__ */
diff --git a/lib/Makefile.am b/lib/Makefile.am
new file mode 100644
index 0000000..3f797c4
--- /dev/null
+++ b/lib/Makefile.am
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+#
+# This file is part of libgpiod.
+#
+# Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+#
+
+lib_LTLIBRARIES = libgpiod.la
+libgpiod_la_SOURCES = core.c ctxless.c helpers.c iter.c misc.c
+libgpiod_la_CFLAGS = -Wall -Wextra -g
+libgpiod_la_CFLAGS += -fvisibility=hidden -I$(top_srcdir)/include/
+libgpiod_la_CFLAGS += -include $(top_builddir)/config.h
+libgpiod_la_LDFLAGS = -version-info $(subst .,:,$(ABI_VERSION))
diff --git a/lib/core.c b/lib/core.c
new file mode 100644
index 0000000..05e5a46
--- /dev/null
+++ b/lib/core.c
@@ -0,0 +1,861 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+/* Low-level, core library code. */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <gpiod.h>
+#include <linux/gpio.h>
+#include <poll.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+enum {
+ LINE_FREE = 0,
+ LINE_REQUESTED_VALUES,
+ LINE_REQUESTED_EVENTS,
+};
+
+struct line_fd_handle {
+ int fd;
+ int refcount;
+};
+
+struct gpiod_line {
+ unsigned int offset;
+ int direction;
+ int active_state;
+ bool used;
+ bool open_source;
+ bool open_drain;
+
+ int state;
+ bool up_to_date;
+
+ struct gpiod_chip *chip;
+ struct line_fd_handle *fd_handle;
+
+ char name[32];
+ char consumer[32];
+};
+
+struct gpiod_chip {
+ struct gpiod_line **lines;
+ unsigned int num_lines;
+
+ int fd;
+
+ char name[32];
+ char label[32];
+};
+
+static bool is_gpiochip_cdev(const char *path)
+{
+ char *name, *pathcpy, *sysfsp, sysfsdev[16], devstr[16];
+ struct stat statbuf;
+ bool ret = false;
+ int rv, fd;
+ ssize_t rd;
+
+ rv = lstat(path, &statbuf);
+ if (rv)
+ goto out;
+
+ /* Is it a character device? */
+ if (!S_ISCHR(statbuf.st_mode)) {
+ /*
+ * Passing a file descriptor not associated with a character
+ * device to ioctl() makes it set errno to ENOTTY. Let's do
+ * the same in order to stay compatible with the versions of
+ * libgpiod from before the introduction of this routine.
+ */
+ errno = ENOTTY;
+ goto out;
+ }
+
+ /* Get the basename. */
+ pathcpy = strdup(path);
+ if (!pathcpy)
+ goto out;
+
+ name = basename(pathcpy);
+
+ /* Do we have a corresponding sysfs attribute? */
+ rv = asprintf(&sysfsp, "/sys/bus/gpio/devices/%s/dev", name);
+ if (rv < 0)
+ goto out_free_pathcpy;
+
+ if (access(sysfsp, R_OK) != 0) {
+ /*
+ * This is a character device but not the one we're after.
+ * Before the introduction of this function, we'd fail with
+ * ENOTTY on the first GPIO ioctl() call for this file
+ * descriptor. Let's stay compatible here and keep returning
+ * the same error code.
+ */
+ errno = ENOTTY;
+ goto out_free_sysfsp;
+ }
+
+ /*
+ * Make sure the major and minor numbers of the character device
+ * correspond with the ones in the dev attribute in sysfs.
+ */
+ snprintf(devstr, sizeof(devstr), "%u:%u",
+ major(statbuf.st_rdev), minor(statbuf.st_rdev));
+
+ fd = open(sysfsp, O_RDONLY);
+ if (fd < 0)
+ goto out_free_sysfsp;
+
+ memset(sysfsdev, 0, sizeof(sysfsdev));
+ rd = read(fd, sysfsdev, strlen(devstr));
+ close(fd);
+ if (rd < 0)
+ goto out_free_sysfsp;
+
+ if (strcmp(sysfsdev, devstr) != 0) {
+ errno = ENODEV;
+ goto out_free_sysfsp;
+ }
+
+ ret = true;
+
+out_free_sysfsp:
+ free(sysfsp);
+out_free_pathcpy:
+ free(pathcpy);
+out:
+ return ret;
+}
+
+struct gpiod_chip *gpiod_chip_open(const char *path)
+{
+ struct gpiochip_info info;
+ struct gpiod_chip *chip;
+ int rv, fd;
+
+ fd = open(path, O_RDWR | O_CLOEXEC);
+ if (fd < 0)
+ return NULL;
+
+ /*
+ * We were able to open the file but is it really a gpiochip character
+ * device?
+ */
+ if (!is_gpiochip_cdev(path)) {
+ close(fd);
+ return NULL;
+ }
+
+ chip = malloc(sizeof(*chip));
+ if (!chip)
+ goto err_close_fd;
+
+ memset(chip, 0, sizeof(*chip));
+ memset(&info, 0, sizeof(info));
+
+ rv = ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &info);
+ if (rv < 0)
+ goto err_free_chip;
+
+ chip->fd = fd;
+ chip->num_lines = 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(chip->name, info.name, sizeof(chip->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 (info.label[0] == '\0')
+ strncpy(chip->label, "unknown", sizeof(chip->label));
+ else
+ strncpy(chip->label, info.label, sizeof(chip->label));
+
+ return chip;
+
+err_free_chip:
+ free(chip);
+err_close_fd:
+ close(fd);
+
+ return NULL;
+}
+
+void gpiod_chip_close(struct gpiod_chip *chip)
+{
+ struct gpiod_line *line;
+ unsigned int i;
+
+ if (chip->lines) {
+ for (i = 0; i < chip->num_lines; i++) {
+ line = chip->lines[i];
+ if (line) {
+ gpiod_line_release(line);
+ free(line);
+ }
+ }
+
+ free(chip->lines);
+ }
+
+ close(chip->fd);
+ free(chip);
+}
+
+const char *gpiod_chip_name(struct gpiod_chip *chip)
+{
+ return chip->name;
+}
+
+const char *gpiod_chip_label(struct gpiod_chip *chip)
+{
+ return chip->label;
+}
+
+unsigned int gpiod_chip_num_lines(struct gpiod_chip *chip)
+{
+ return chip->num_lines;
+}
+
+struct gpiod_line *
+gpiod_chip_get_line(struct gpiod_chip *chip, unsigned int offset)
+{
+ struct gpiod_line *line;
+ int rv;
+
+ if (offset >= chip->num_lines) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ if (!chip->lines) {
+ chip->lines = calloc(chip->num_lines,
+ sizeof(struct gpiod_line *));
+ if (!chip->lines)
+ return NULL;
+ }
+
+ if (!chip->lines[offset]) {
+ line = malloc(sizeof(*line));
+ if (!line)
+ return NULL;
+
+ memset(line, 0, sizeof(*line));
+
+ line->offset = offset;
+ line->chip = chip;
+
+ chip->lines[offset] = line;
+ } else {
+ line = chip->lines[offset];
+ }
+
+ rv = gpiod_line_update(line);
+ if (rv < 0)
+ return NULL;
+
+ return line;
+}
+
+static struct line_fd_handle *line_make_fd_handle(int fd)
+{
+ struct line_fd_handle *handle;
+
+ handle = malloc(sizeof(*handle));
+ if (!handle)
+ return NULL;
+
+ handle->fd = fd;
+ handle->refcount = 0;
+
+ return handle;
+}
+
+static void line_fd_incref(struct gpiod_line *line)
+{
+ line->fd_handle->refcount++;
+}
+
+static void line_fd_decref(struct gpiod_line *line)
+{
+ struct line_fd_handle *handle = line->fd_handle;
+
+ handle->refcount--;
+
+ if (handle->refcount == 0) {
+ close(handle->fd);
+ free(handle);
+ line->fd_handle = NULL;
+ }
+}
+
+static void line_set_fd(struct gpiod_line *line, struct line_fd_handle *handle)
+{
+ line->fd_handle = handle;
+ line_fd_incref(line);
+}
+
+static int line_get_fd(struct gpiod_line *line)
+{
+ return line->fd_handle->fd;
+}
+
+static void line_maybe_update(struct gpiod_line *line)
+{
+ int rv;
+
+ rv = gpiod_line_update(line);
+ if (rv < 0)
+ line->up_to_date = false;
+}
+
+struct gpiod_chip *gpiod_line_get_chip(struct gpiod_line *line)
+{
+ return line->chip;
+}
+
+unsigned int gpiod_line_offset(struct gpiod_line *line)
+{
+ return line->offset;
+}
+
+const char *gpiod_line_name(struct gpiod_line *line)
+{
+ return line->name[0] == '\0' ? NULL : line->name;
+}
+
+const char *gpiod_line_consumer(struct gpiod_line *line)
+{
+ return line->consumer[0] == '\0' ? NULL : line->consumer;
+}
+
+int gpiod_line_direction(struct gpiod_line *line)
+{
+ return line->direction;
+}
+
+int gpiod_line_active_state(struct gpiod_line *line)
+{
+ return line->active_state;
+}
+
+bool gpiod_line_is_used(struct gpiod_line *line)
+{
+ return line->used;
+}
+
+bool gpiod_line_is_open_drain(struct gpiod_line *line)
+{
+ return line->open_drain;
+}
+
+bool gpiod_line_is_open_source(struct gpiod_line *line)
+{
+ return line->open_source;
+}
+
+bool gpiod_line_needs_update(struct gpiod_line *line)
+{
+ return !line->up_to_date;
+}
+
+int gpiod_line_update(struct gpiod_line *line)
+{
+ struct gpioline_info info;
+ int rv;
+
+ memset(&info, 0, sizeof(info));
+ info.line_offset = line->offset;
+
+ rv = ioctl(line->chip->fd, GPIO_GET_LINEINFO_IOCTL, &info);
+ if (rv < 0)
+ return -1;
+
+ line->direction = info.flags & GPIOLINE_FLAG_IS_OUT
+ ? GPIOD_LINE_DIRECTION_OUTPUT
+ : GPIOD_LINE_DIRECTION_INPUT;
+ line->active_state = info.flags & GPIOLINE_FLAG_ACTIVE_LOW
+ ? GPIOD_LINE_ACTIVE_STATE_LOW
+ : GPIOD_LINE_ACTIVE_STATE_HIGH;
+
+ line->used = info.flags & GPIOLINE_FLAG_KERNEL;
+ line->open_drain = info.flags & GPIOLINE_FLAG_OPEN_DRAIN;
+ line->open_source = info.flags & GPIOLINE_FLAG_OPEN_SOURCE;
+
+ strncpy(line->name, info.name, sizeof(line->name));
+ strncpy(line->consumer, info.consumer, sizeof(line->consumer));
+
+ line->up_to_date = true;
+
+ return 0;
+}
+
+static bool line_bulk_same_chip(struct gpiod_line_bulk *bulk)
+{
+ struct gpiod_line *first_line, *line;
+ struct gpiod_chip *first_chip, *chip;
+ unsigned int i;
+
+ if (bulk->num_lines == 1)
+ return true;
+
+ first_line = gpiod_line_bulk_get_line(bulk, 0);
+ first_chip = gpiod_line_get_chip(first_line);
+
+ for (i = 1; i < bulk->num_lines; i++) {
+ line = bulk->lines[i];
+ chip = gpiod_line_get_chip(line);
+
+ if (first_chip != chip) {
+ errno = EINVAL;
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool line_bulk_all_requested(struct gpiod_line_bulk *bulk)
+{
+ struct gpiod_line *line, **lineptr;
+
+ gpiod_line_bulk_foreach_line(bulk, line, lineptr) {
+ if (!gpiod_line_is_requested(line)) {
+ errno = EPERM;
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool line_bulk_all_free(struct gpiod_line_bulk *bulk)
+{
+ struct gpiod_line *line, **lineptr;
+
+ gpiod_line_bulk_foreach_line(bulk, line, lineptr) {
+ if (!gpiod_line_is_free(line)) {
+ errno = EBUSY;
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static int line_request_values(struct gpiod_line_bulk *bulk,
+ const struct gpiod_line_request_config *config,
+ const int *default_vals)
+{
+ struct gpiod_line *line, **lineptr;
+ struct line_fd_handle *line_fd;
+ struct gpiohandle_request req;
+ unsigned int i;
+ int rv, fd;
+
+ if ((config->request_type != GPIOD_LINE_REQUEST_DIRECTION_OUTPUT) &&
+ (config->flags & (GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN |
+ GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE))) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if ((config->flags & GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN) &&
+ (config->flags & GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ memset(&req, 0, sizeof(req));
+
+ if (config->flags & GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN)
+ req.flags |= GPIOHANDLE_REQUEST_OPEN_DRAIN;
+ if (config->flags & GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE)
+ req.flags |= GPIOHANDLE_REQUEST_OPEN_SOURCE;
+ if (config->flags & GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW)
+ req.flags |= GPIOHANDLE_REQUEST_ACTIVE_LOW;
+
+ if (config->request_type == GPIOD_LINE_REQUEST_DIRECTION_INPUT)
+ req.flags |= GPIOHANDLE_REQUEST_INPUT;
+ else if (config->request_type == GPIOD_LINE_REQUEST_DIRECTION_OUTPUT)
+ req.flags |= GPIOHANDLE_REQUEST_OUTPUT;
+
+ req.lines = gpiod_line_bulk_num_lines(bulk);
+
+ gpiod_line_bulk_foreach_line_off(bulk, line, i) {
+ req.lineoffsets[i] = gpiod_line_offset(line);
+ if (config->request_type ==
+ GPIOD_LINE_REQUEST_DIRECTION_OUTPUT &&
+ default_vals)
+ req.default_values[i] = !!default_vals[i];
+ }
+
+ if (config->consumer)
+ strncpy(req.consumer_label, config->consumer,
+ sizeof(req.consumer_label) - 1);
+
+ line = gpiod_line_bulk_get_line(bulk, 0);
+ fd = line->chip->fd;
+
+ rv = ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &req);
+ if (rv < 0)
+ return -1;
+
+ line_fd = line_make_fd_handle(req.fd);
+ if (!line_fd)
+ return -1;
+
+ gpiod_line_bulk_foreach_line(bulk, line, lineptr) {
+ line->state = LINE_REQUESTED_VALUES;
+ line_set_fd(line, line_fd);
+ line_maybe_update(line);
+ }
+
+ return 0;
+}
+
+static int line_request_event_single(struct gpiod_line *line,
+ const struct gpiod_line_request_config *config)
+{
+ struct line_fd_handle *line_fd;
+ struct gpioevent_request req;
+ int rv;
+
+ memset(&req, 0, sizeof(req));
+
+ if (config->consumer)
+ strncpy(req.consumer_label, config->consumer,
+ sizeof(req.consumer_label) - 1);
+
+ req.lineoffset = gpiod_line_offset(line);
+ req.handleflags |= GPIOHANDLE_REQUEST_INPUT;
+
+ if (config->flags & GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN)
+ req.handleflags |= GPIOHANDLE_REQUEST_OPEN_DRAIN;
+ if (config->flags & GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE)
+ req.handleflags |= GPIOHANDLE_REQUEST_OPEN_SOURCE;
+ if (config->flags & GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW)
+ req.handleflags |= GPIOHANDLE_REQUEST_ACTIVE_LOW;
+
+ if (config->request_type == GPIOD_LINE_REQUEST_EVENT_RISING_EDGE)
+ req.eventflags |= GPIOEVENT_REQUEST_RISING_EDGE;
+ else if (config->request_type == GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE)
+ req.eventflags |= GPIOEVENT_REQUEST_FALLING_EDGE;
+ else if (config->request_type == GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES)
+ req.eventflags |= GPIOEVENT_REQUEST_BOTH_EDGES;
+
+ rv = ioctl(line->chip->fd, GPIO_GET_LINEEVENT_IOCTL, &req);
+ if (rv < 0)
+ return -1;
+
+ line_fd = line_make_fd_handle(req.fd);
+ if (!line_fd)
+ return -1;
+
+ line->state = LINE_REQUESTED_EVENTS;
+ line_set_fd(line, line_fd);
+ line_maybe_update(line);
+
+ return 0;
+}
+
+static int line_request_events(struct gpiod_line_bulk *bulk,
+ const struct gpiod_line_request_config *config)
+{
+ struct gpiod_line *line;
+ unsigned int off;
+ int rv, rev;
+
+ gpiod_line_bulk_foreach_line_off(bulk, line, off) {
+ rv = line_request_event_single(line, config);
+ if (rv) {
+ for (rev = off - 1; rev >= 0; rev--) {
+ line = gpiod_line_bulk_get_line(bulk, rev);
+ gpiod_line_release(line);
+ }
+
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int gpiod_line_request(struct gpiod_line *line,
+ const struct gpiod_line_request_config *config,
+ int default_val)
+{
+ struct gpiod_line_bulk bulk;
+
+ gpiod_line_bulk_init(&bulk);
+ gpiod_line_bulk_add(&bulk, line);
+
+ return gpiod_line_request_bulk(&bulk, config, &default_val);
+}
+
+static bool line_request_is_direction(int request)
+{
+ return request == GPIOD_LINE_REQUEST_DIRECTION_AS_IS ||
+ request == GPIOD_LINE_REQUEST_DIRECTION_INPUT ||
+ request == GPIOD_LINE_REQUEST_DIRECTION_OUTPUT;
+}
+
+static bool line_request_is_events(int request)
+{
+ return request == GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE ||
+ request == GPIOD_LINE_REQUEST_EVENT_RISING_EDGE ||
+ request == GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES;
+}
+
+int gpiod_line_request_bulk(struct gpiod_line_bulk *bulk,
+ const struct gpiod_line_request_config *config,
+ const int *default_vals)
+{
+ if (!line_bulk_same_chip(bulk) || !line_bulk_all_free(bulk))
+ return -1;
+
+ if (line_request_is_direction(config->request_type))
+ return line_request_values(bulk, config, default_vals);
+ else if (line_request_is_events(config->request_type))
+ return line_request_events(bulk, config);
+
+ errno = EINVAL;
+ return -1;
+}
+
+void gpiod_line_release(struct gpiod_line *line)
+{
+ struct gpiod_line_bulk bulk;
+
+ gpiod_line_bulk_init(&bulk);
+ gpiod_line_bulk_add(&bulk, line);
+
+ gpiod_line_release_bulk(&bulk);
+}
+
+void gpiod_line_release_bulk(struct gpiod_line_bulk *bulk)
+{
+ struct gpiod_line *line, **lineptr;
+
+ gpiod_line_bulk_foreach_line(bulk, line, lineptr) {
+ if (line->state != LINE_FREE) {
+ line_fd_decref(line);
+ line->state = LINE_FREE;
+ }
+ }
+}
+
+bool gpiod_line_is_requested(struct gpiod_line *line)
+{
+ return (line->state == LINE_REQUESTED_VALUES ||
+ line->state == LINE_REQUESTED_EVENTS);
+}
+
+bool gpiod_line_is_free(struct gpiod_line *line)
+{
+ return line->state == LINE_FREE;
+}
+
+int gpiod_line_get_value(struct gpiod_line *line)
+{
+ struct gpiod_line_bulk bulk;
+ int rv, value;
+
+ gpiod_line_bulk_init(&bulk);
+ gpiod_line_bulk_add(&bulk, line);
+
+ rv = gpiod_line_get_value_bulk(&bulk, &value);
+ if (rv < 0)
+ return -1;
+
+ return value;
+}
+
+int gpiod_line_get_value_bulk(struct gpiod_line_bulk *bulk, int *values)
+{
+ struct gpiohandle_data data;
+ struct gpiod_line *first;
+ unsigned int i;
+ int rv, fd;
+
+ if (!line_bulk_same_chip(bulk) || !line_bulk_all_requested(bulk))
+ return -1;
+
+ first = gpiod_line_bulk_get_line(bulk, 0);
+
+ memset(&data, 0, sizeof(data));
+
+ fd = line_get_fd(first);
+
+ rv = ioctl(fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data);
+ if (rv < 0)
+ return -1;
+
+ for (i = 0; i < gpiod_line_bulk_num_lines(bulk); i++)
+ values[i] = data.values[i];
+
+ return 0;
+}
+
+int gpiod_line_set_value(struct gpiod_line *line, int value)
+{
+ struct gpiod_line_bulk bulk;
+
+ gpiod_line_bulk_init(&bulk);
+ gpiod_line_bulk_add(&bulk, line);
+
+ return gpiod_line_set_value_bulk(&bulk, &value);
+}
+
+int gpiod_line_set_value_bulk(struct gpiod_line_bulk *bulk, const int *values)
+{
+ struct gpiohandle_data data;
+ struct gpiod_line *line;
+ unsigned int i;
+ int rv, fd;
+
+ if (!line_bulk_same_chip(bulk) || !line_bulk_all_requested(bulk))
+ return -1;
+
+ memset(&data, 0, sizeof(data));
+
+ for (i = 0; i < gpiod_line_bulk_num_lines(bulk); i++)
+ data.values[i] = (uint8_t)!!values[i];
+
+ line = gpiod_line_bulk_get_line(bulk, 0);
+ fd = line_get_fd(line);
+
+ rv = ioctl(fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data);
+ if (rv < 0)
+ return -1;
+
+ return 0;
+}
+
+int gpiod_line_event_wait(struct gpiod_line *line,
+ const struct timespec *timeout)
+{
+ struct gpiod_line_bulk bulk;
+
+ gpiod_line_bulk_init(&bulk);
+ gpiod_line_bulk_add(&bulk, line);
+
+ return gpiod_line_event_wait_bulk(&bulk, timeout, NULL);
+}
+
+int gpiod_line_event_wait_bulk(struct gpiod_line_bulk *bulk,
+ const struct timespec *timeout,
+ struct gpiod_line_bulk *event_bulk)
+{
+ struct pollfd fds[GPIOD_LINE_BULK_MAX_LINES];
+ unsigned int off, num_lines;
+ struct gpiod_line *line;
+ int rv;
+
+ if (!line_bulk_same_chip(bulk) || !line_bulk_all_requested(bulk))
+ return -1;
+
+ memset(fds, 0, sizeof(fds));
+ num_lines = gpiod_line_bulk_num_lines(bulk);
+
+ gpiod_line_bulk_foreach_line_off(bulk, line, off) {
+ fds[off].fd = line_get_fd(line);
+ fds[off].events = POLLIN | POLLPRI;
+ }
+
+ rv = ppoll(fds, num_lines, timeout, NULL);
+ if (rv < 0)
+ return -1;
+ else if (rv == 0)
+ return 0;
+
+ if (event_bulk)
+ gpiod_line_bulk_init(event_bulk);
+
+ for (off = 0; off < num_lines; off++) {
+ if (fds[off].revents) {
+ if (fds[off].revents & POLLNVAL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (event_bulk) {
+ line = gpiod_line_bulk_get_line(bulk, off);
+ gpiod_line_bulk_add(event_bulk, line);
+ }
+
+ if (!--rv)
+ break;
+ }
+ }
+
+ return 1;
+}
+
+int gpiod_line_event_read(struct gpiod_line *line,
+ struct gpiod_line_event *event)
+{
+ int fd;
+
+ if (line->state != LINE_REQUESTED_EVENTS) {
+ errno = EPERM;
+ return -1;
+ }
+
+ fd = line_get_fd(line);
+
+ return gpiod_line_event_read_fd(fd, event);
+}
+
+int gpiod_line_event_get_fd(struct gpiod_line *line)
+{
+ if (line->state != LINE_REQUESTED_EVENTS) {
+ errno = EPERM;
+ return -1;
+ }
+
+ return line_get_fd(line);
+}
+
+int gpiod_line_event_read_fd(int fd, struct gpiod_line_event *event)
+{
+ struct gpioevent_data evdata;
+ ssize_t rd;
+
+ memset(&evdata, 0, sizeof(evdata));
+
+ rd = read(fd, &evdata, sizeof(evdata));
+ if (rd < 0) {
+ return -1;
+ } else if (rd != sizeof(evdata)) {
+ errno = EIO;
+ return -1;
+ }
+
+ event->event_type = evdata.id == GPIOEVENT_EVENT_RISING_EDGE
+ ? GPIOD_LINE_EVENT_RISING_EDGE
+ : GPIOD_LINE_EVENT_FALLING_EDGE;
+
+ event->ts.tv_sec = evdata.timestamp / 1000000000ULL;
+ event->ts.tv_nsec = evdata.timestamp % 1000000000ULL;
+
+ return 0;
+}
diff --git a/lib/ctxless.c b/lib/ctxless.c
new file mode 100644
index 0000000..ba85018
--- /dev/null
+++ b/lib/ctxless.c
@@ -0,0 +1,369 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+/* Implementation of the high-level API. */
+
+
+#include <errno.h>
+#include <gpiod.h>
+#include <poll.h>
+#include <stdio.h>
+#include <string.h>
+
+int gpiod_ctxless_get_value(const char *device, unsigned int offset,
+ bool active_low, const char *consumer)
+{
+ int value, rv;
+
+ rv = gpiod_ctxless_get_value_multiple(device, &offset, &value,
+ 1, active_low, consumer);
+ if (rv < 0)
+ return rv;
+
+ return value;
+}
+
+int gpiod_ctxless_get_value_multiple(const char *device,
+ const unsigned int *offsets, int *values,
+ unsigned int num_lines, bool active_low,
+ const char *consumer)
+{
+ struct gpiod_line_bulk bulk;
+ struct gpiod_chip *chip;
+ struct gpiod_line *line;
+ unsigned int i;
+ int rv, flags;
+
+ if (!num_lines || num_lines > GPIOD_LINE_BULK_MAX_LINES) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ chip = gpiod_chip_open_lookup(device);
+ if (!chip)
+ return -1;
+
+ gpiod_line_bulk_init(&bulk);
+
+ for (i = 0; i < num_lines; i++) {
+ line = gpiod_chip_get_line(chip, offsets[i]);
+ if (!line) {
+ gpiod_chip_close(chip);
+ return -1;
+ }
+
+ gpiod_line_bulk_add(&bulk, line);
+ }
+
+ flags = active_low ? GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW : 0;
+
+ rv = gpiod_line_request_bulk_input_flags(&bulk, consumer, flags);
+ if (rv < 0) {
+ gpiod_chip_close(chip);
+ return -1;
+ }
+
+ memset(values, 0, sizeof(*values) * num_lines);
+ rv = gpiod_line_get_value_bulk(&bulk, values);
+
+ gpiod_chip_close(chip);
+
+ return rv;
+}
+
+int gpiod_ctxless_set_value(const char *device, unsigned int offset, int value,
+ bool active_low, const char *consumer,
+ gpiod_ctxless_set_value_cb cb, void *data)
+{
+ return gpiod_ctxless_set_value_multiple(device, &offset, &value, 1,
+ active_low, consumer, cb, data);
+}
+
+int gpiod_ctxless_set_value_multiple(const char *device,
+ const unsigned int *offsets,
+ const int *values, unsigned int num_lines,
+ bool active_low, const char *consumer,
+ gpiod_ctxless_set_value_cb cb, void *data)
+{
+ struct gpiod_line_bulk bulk;
+ struct gpiod_chip *chip;
+ struct gpiod_line *line;
+ unsigned int i;
+ int rv, flags;
+
+ if (!num_lines || num_lines > GPIOD_LINE_BULK_MAX_LINES) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ chip = gpiod_chip_open_lookup(device);
+ if (!chip)
+ return -1;
+
+ gpiod_line_bulk_init(&bulk);
+
+ for (i = 0; i < num_lines; i++) {
+ line = gpiod_chip_get_line(chip, offsets[i]);
+ if (!line) {
+ gpiod_chip_close(chip);
+ return -1;
+ }
+
+ gpiod_line_bulk_add(&bulk, line);
+ }
+
+ flags = active_low ? GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW : 0;
+
+ rv = gpiod_line_request_bulk_output_flags(&bulk, consumer,
+ flags, values);
+ if (rv < 0) {
+ gpiod_chip_close(chip);
+ return -1;
+ }
+
+ if (cb)
+ cb(data);
+
+ gpiod_chip_close(chip);
+
+ return 0;
+}
+
+static int basic_event_poll(unsigned int num_lines,
+ struct gpiod_ctxless_event_poll_fd *fds,
+ const struct timespec *timeout,
+ void *data GPIOD_UNUSED)
+{
+ struct pollfd poll_fds[GPIOD_LINE_BULK_MAX_LINES];
+ unsigned int i;
+ int rv, ret;
+
+ if (!num_lines || num_lines > GPIOD_LINE_BULK_MAX_LINES)
+ return GPIOD_CTXLESS_EVENT_POLL_RET_ERR;
+
+ memset(poll_fds, 0, sizeof(poll_fds));
+
+ for (i = 0; i < num_lines; i++) {
+ poll_fds[i].fd = fds[i].fd;
+ poll_fds[i].events = POLLIN | POLLPRI;
+ }
+
+ rv = ppoll(poll_fds, num_lines, timeout, NULL);
+ if (rv < 0) {
+ if (errno == EINTR)
+ return GPIOD_CTXLESS_EVENT_POLL_RET_TIMEOUT;
+ else
+ return GPIOD_CTXLESS_EVENT_POLL_RET_ERR;
+ } else if (rv == 0) {
+ return GPIOD_CTXLESS_EVENT_POLL_RET_TIMEOUT;
+ }
+
+ ret = rv;
+ for (i = 0; i < num_lines; i++) {
+ if (poll_fds[i].revents) {
+ fds[i].event = true;
+ if (!--rv)
+ break;
+ }
+ }
+
+ return ret;
+}
+
+int gpiod_ctxless_event_loop(const char *device, unsigned int offset,
+ bool active_low, const char *consumer,
+ const struct timespec *timeout,
+ gpiod_ctxless_event_poll_cb poll_cb,
+ gpiod_ctxless_event_handle_cb event_cb,
+ void *data)
+{
+ return gpiod_ctxless_event_monitor(device,
+ GPIOD_CTXLESS_EVENT_BOTH_EDGES,
+ offset, active_low, consumer,
+ timeout, poll_cb, event_cb, data);
+}
+
+int gpiod_ctxless_event_loop_multiple(const char *device,
+ const unsigned int *offsets,
+ unsigned int num_lines, bool active_low,
+ const char *consumer,
+ const struct timespec *timeout,
+ gpiod_ctxless_event_poll_cb poll_cb,
+ gpiod_ctxless_event_handle_cb event_cb,
+ void *data)
+{
+ return gpiod_ctxless_event_monitor_multiple(
+ device, GPIOD_CTXLESS_EVENT_BOTH_EDGES,
+ offsets, num_lines, active_low, consumer,
+ timeout, poll_cb, event_cb, data);
+}
+
+int gpiod_ctxless_event_monitor(const char *device, int event_type,
+ unsigned int offset, bool active_low,
+ const char *consumer,
+ const struct timespec *timeout,
+ gpiod_ctxless_event_poll_cb poll_cb,
+ gpiod_ctxless_event_handle_cb event_cb,
+ void *data)
+{
+ return gpiod_ctxless_event_monitor_multiple(device, event_type,
+ &offset, 1, active_low,
+ consumer, timeout,
+ poll_cb, event_cb, data);
+}
+
+int gpiod_ctxless_event_monitor_multiple(
+ const char *device, int event_type,
+ const unsigned int *offsets,
+ unsigned int num_lines, bool active_low,
+ const char *consumer,
+ const struct timespec *timeout,
+ gpiod_ctxless_event_poll_cb poll_cb,
+ gpiod_ctxless_event_handle_cb event_cb,
+ void *data)
+{
+ struct gpiod_ctxless_event_poll_fd fds[GPIOD_LINE_BULK_MAX_LINES];
+ struct gpiod_line_request_config conf;
+ struct gpiod_line_event event;
+ struct gpiod_line_bulk bulk;
+ int rv, ret, evtype, cnt;
+ struct gpiod_chip *chip;
+ struct gpiod_line *line;
+ unsigned int i;
+
+ if (!num_lines || num_lines > GPIOD_LINE_BULK_MAX_LINES) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (!poll_cb)
+ poll_cb = basic_event_poll;
+
+ chip = gpiod_chip_open_lookup(device);
+ if (!chip)
+ return -1;
+
+ gpiod_line_bulk_init(&bulk);
+
+ for (i = 0; i < num_lines; i++) {
+ line = gpiod_chip_get_line(chip, offsets[i]);
+ if (!line) {
+ gpiod_chip_close(chip);
+ return -1;
+ }
+
+ gpiod_line_bulk_add(&bulk, line);
+ }
+
+ conf.flags = active_low ? GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW : 0;
+ conf.consumer = consumer;
+
+ if (event_type == GPIOD_CTXLESS_EVENT_RISING_EDGE) {
+ conf.request_type = GPIOD_LINE_REQUEST_EVENT_RISING_EDGE;
+ } else if (event_type == GPIOD_CTXLESS_EVENT_FALLING_EDGE) {
+ conf.request_type = GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE;
+ } else if (event_type == GPIOD_CTXLESS_EVENT_BOTH_EDGES) {
+ conf.request_type = GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES;
+ } else {
+ errno = -EINVAL;
+ ret = -1;
+ goto out;
+ }
+
+ rv = gpiod_line_request_bulk(&bulk, &conf, NULL);
+ if (rv) {
+ ret = -1;
+ goto out;
+ }
+
+ memset(fds, 0, sizeof(fds));
+ for (i = 0; i < num_lines; i++) {
+ line = gpiod_line_bulk_get_line(&bulk, i);
+ fds[i].fd = gpiod_line_event_get_fd(line);
+ }
+
+ for (;;) {
+ for (i = 0; i < num_lines; i++)
+ fds[i].event = false;
+
+ cnt = poll_cb(num_lines, fds, timeout, data);
+ if (cnt == GPIOD_CTXLESS_EVENT_POLL_RET_ERR) {
+ ret = -1;
+ goto out;
+ } else if (cnt == GPIOD_CTXLESS_EVENT_POLL_RET_TIMEOUT) {
+ rv = event_cb(GPIOD_CTXLESS_EVENT_CB_TIMEOUT,
+ 0, &event.ts, data);
+ if (rv == GPIOD_CTXLESS_EVENT_CB_RET_ERR) {
+ ret = -1;
+ goto out;
+ } else if (rv == GPIOD_CTXLESS_EVENT_CB_RET_STOP) {
+ ret = 0;
+ goto out;
+ }
+ } else if (cnt == GPIOD_CTXLESS_EVENT_POLL_RET_STOP) {
+ ret = 0;
+ goto out;
+ }
+
+ for (i = 0; i < num_lines; i++) {
+ if (!fds[i].event)
+ continue;
+
+ line = gpiod_line_bulk_get_line(&bulk, i);
+ rv = gpiod_line_event_read(line, &event);
+ if (rv < 0) {
+ ret = rv;
+ goto out;
+ }
+
+ if (event.event_type == GPIOD_LINE_EVENT_RISING_EDGE)
+ evtype = GPIOD_CTXLESS_EVENT_CB_RISING_EDGE;
+ else
+ evtype = GPIOD_CTXLESS_EVENT_CB_FALLING_EDGE;
+
+ rv = event_cb(evtype, gpiod_line_offset(line),
+ &event.ts, data);
+ if (rv == GPIOD_CTXLESS_EVENT_CB_RET_ERR) {
+ ret = -1;
+ goto out;
+ } else if (rv == GPIOD_CTXLESS_EVENT_CB_RET_STOP) {
+ ret = 0;
+ goto out;
+ }
+
+ if (!--cnt)
+ break;
+ }
+ }
+
+out:
+ gpiod_chip_close(chip);
+
+ return ret;
+}
+
+int gpiod_ctxless_find_line(const char *name, char *chipname,
+ size_t chipname_size, unsigned int *offset)
+{
+ struct gpiod_chip *chip;
+ struct gpiod_line *line;
+
+ line = gpiod_line_find(name);
+ if (!line) {
+ if (errno == ENOENT)
+ return 0;
+ else
+ return -1;
+ }
+
+ chip = gpiod_line_get_chip(line);
+ snprintf(chipname, chipname_size, "%s", gpiod_chip_name(chip));
+ *offset = gpiod_line_offset(line);
+ gpiod_chip_close(chip);
+
+ return 1;
+}
diff --git a/lib/helpers.c b/lib/helpers.c
new file mode 100644
index 0000000..479f370
--- /dev/null
+++ b/lib/helpers.c
@@ -0,0 +1,443 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+/*
+ * More specific variants of the core API and misc functions that don't need
+ * access to neither the internal library data structures nor the kernel UAPI.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <gpiod.h>
+#include <stdio.h>
+#include <string.h>
+
+static bool isuint(const char *str)
+{
+ for (; *str && isdigit(*str); str++)
+ ;
+
+ return *str == '\0';
+}
+
+struct gpiod_chip *gpiod_chip_open_by_name(const char *name)
+{
+ struct gpiod_chip *chip;
+ char *path;
+ int rv;
+
+ rv = asprintf(&path, "/dev/%s", name);
+ if (rv < 0)
+ return NULL;
+
+ chip = gpiod_chip_open(path);
+ free(path);
+
+ return chip;
+}
+
+struct gpiod_chip *gpiod_chip_open_by_number(unsigned int num)
+{
+ struct gpiod_chip *chip;
+ char *path;
+ int rv;
+
+ rv = asprintf(&path, "/dev/gpiochip%u", num);
+ if (!rv)
+ return NULL;
+
+ chip = gpiod_chip_open(path);
+ free(path);
+
+ return chip;
+}
+
+struct gpiod_chip *gpiod_chip_open_by_label(const char *label)
+{
+ struct gpiod_chip_iter *iter;
+ struct gpiod_chip *chip;
+
+ iter = gpiod_chip_iter_new();
+ if (!iter)
+ return NULL;
+
+ gpiod_foreach_chip(iter, chip) {
+ if (strcmp(label, gpiod_chip_label(chip)) == 0) {
+ gpiod_chip_iter_free_noclose(iter);
+ return chip;
+ }
+ }
+
+ errno = ENOENT;
+ gpiod_chip_iter_free(iter);
+
+ return NULL;
+}
+
+struct gpiod_chip *gpiod_chip_open_lookup(const char *descr)
+{
+ struct gpiod_chip *chip;
+
+ if (isuint(descr)) {
+ chip = gpiod_chip_open_by_number(strtoul(descr, NULL, 10));
+ } else {
+ chip = gpiod_chip_open_by_label(descr);
+ if (!chip) {
+ if (strncmp(descr, "/dev/", 5))
+ chip = gpiod_chip_open_by_name(descr);
+ else
+ chip = gpiod_chip_open(descr);
+ }
+ }
+
+ return chip;
+}
+
+int gpiod_chip_get_lines(struct gpiod_chip *chip, unsigned int *offsets,
+ unsigned int num_offsets, struct gpiod_line_bulk *bulk)
+{
+ struct gpiod_line *line;
+ unsigned int i;
+
+ gpiod_line_bulk_init(bulk);
+
+ for (i = 0; i < num_offsets; i++) {
+ line = gpiod_chip_get_line(chip, offsets[i]);
+ if (!line)
+ return -1;
+
+ gpiod_line_bulk_add(bulk, line);
+ }
+
+ return 0;
+}
+
+int gpiod_chip_get_all_lines(struct gpiod_chip *chip,
+ struct gpiod_line_bulk *bulk)
+{
+ struct gpiod_line_iter *iter;
+ struct gpiod_line *line;
+
+ gpiod_line_bulk_init(bulk);
+
+ iter = gpiod_line_iter_new(chip);
+ if (!iter)
+ return -1;
+
+ gpiod_foreach_line(iter, line)
+ gpiod_line_bulk_add(bulk, line);
+
+ gpiod_line_iter_free(iter);
+
+ return 0;
+}
+
+struct gpiod_line *
+gpiod_chip_find_line(struct gpiod_chip *chip, const char *name)
+{
+ struct gpiod_line_iter *iter;
+ struct gpiod_line *line;
+ const char *tmp;
+
+ iter = gpiod_line_iter_new(chip);
+ if (!iter)
+ return NULL;
+
+ gpiod_foreach_line(iter, line) {
+ tmp = gpiod_line_name(line);
+ if (tmp && strcmp(tmp, name) == 0) {
+ gpiod_line_iter_free(iter);
+ return line;
+ }
+ }
+
+ errno = ENOENT;
+ gpiod_line_iter_free(iter);
+
+ return NULL;
+}
+
+int gpiod_chip_find_lines(struct gpiod_chip *chip,
+ const char **names, struct gpiod_line_bulk *bulk)
+{
+ struct gpiod_line *line;
+ int i;
+
+ gpiod_line_bulk_init(bulk);
+
+ for (i = 0; names[i]; i++) {
+ line = gpiod_chip_find_line(chip, names[i]);
+ if (!line)
+ return -1;
+
+ gpiod_line_bulk_add(bulk, line);
+ }
+
+ return 0;
+}
+
+int gpiod_line_request_input(struct gpiod_line *line, const char *consumer)
+{
+ struct gpiod_line_request_config config = {
+ .consumer = consumer,
+ .request_type = GPIOD_LINE_REQUEST_DIRECTION_INPUT,
+ };
+
+ return gpiod_line_request(line, &config, 0);
+}
+
+int gpiod_line_request_output(struct gpiod_line *line,
+ const char *consumer, int default_val)
+{
+ struct gpiod_line_request_config config = {
+ .consumer = consumer,
+ .request_type = GPIOD_LINE_REQUEST_DIRECTION_OUTPUT,
+ };
+
+ return gpiod_line_request(line, &config, default_val);
+}
+
+int gpiod_line_request_input_flags(struct gpiod_line *line,
+ const char *consumer, int flags)
+{
+ struct gpiod_line_request_config config = {
+ .consumer = consumer,
+ .request_type = GPIOD_LINE_REQUEST_DIRECTION_INPUT,
+ .flags = flags,
+ };
+
+ return gpiod_line_request(line, &config, 0);
+}
+
+int gpiod_line_request_output_flags(struct gpiod_line *line,
+ const char *consumer, int flags,
+ int default_val)
+{
+ struct gpiod_line_request_config config = {
+ .consumer = consumer,
+ .request_type = GPIOD_LINE_REQUEST_DIRECTION_OUTPUT,
+ .flags = flags,
+ };
+
+ return gpiod_line_request(line, &config, default_val);
+}
+
+static int line_event_request_type(struct gpiod_line *line,
+ const char *consumer, int flags, int type)
+{
+ struct gpiod_line_request_config config = {
+ .consumer = consumer,
+ .request_type = type,
+ .flags = flags,
+ };
+
+ return gpiod_line_request(line, &config, 0);
+}
+
+int gpiod_line_request_rising_edge_events(struct gpiod_line *line,
+ const char *consumer)
+{
+ return line_event_request_type(line, consumer, 0,
+ GPIOD_LINE_REQUEST_EVENT_RISING_EDGE);
+}
+
+int gpiod_line_request_falling_edge_events(struct gpiod_line *line,
+ const char *consumer)
+{
+ return line_event_request_type(line, consumer, 0,
+ GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE);
+}
+
+int gpiod_line_request_both_edges_events(struct gpiod_line *line,
+ const char *consumer)
+{
+ return line_event_request_type(line, consumer, 0,
+ GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES);
+}
+
+int gpiod_line_request_rising_edge_events_flags(struct gpiod_line *line,
+ const char *consumer,
+ int flags)
+{
+ return line_event_request_type(line, consumer, flags,
+ GPIOD_LINE_REQUEST_EVENT_RISING_EDGE);
+}
+
+int gpiod_line_request_falling_edge_events_flags(struct gpiod_line *line,
+ const char *consumer,
+ int flags)
+{
+ return line_event_request_type(line, consumer, flags,
+ GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE);
+}
+
+int gpiod_line_request_both_edges_events_flags(struct gpiod_line *line,
+ const char *consumer, int flags)
+{
+ return line_event_request_type(line, consumer, flags,
+ GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES);
+}
+
+int gpiod_line_request_bulk_input(struct gpiod_line_bulk *bulk,
+ const char *consumer)
+{
+ struct gpiod_line_request_config config = {
+ .consumer = consumer,
+ .request_type = GPIOD_LINE_REQUEST_DIRECTION_INPUT,
+ };
+
+ return gpiod_line_request_bulk(bulk, &config, 0);
+}
+
+int gpiod_line_request_bulk_output(struct gpiod_line_bulk *bulk,
+ const char *consumer,
+ const int *default_vals)
+{
+ struct gpiod_line_request_config config = {
+ .consumer = consumer,
+ .request_type = GPIOD_LINE_REQUEST_DIRECTION_OUTPUT,
+ };
+
+ return gpiod_line_request_bulk(bulk, &config, default_vals);
+}
+
+static int line_event_request_type_bulk(struct gpiod_line_bulk *bulk,
+ const char *consumer,
+ int flags, int type)
+{
+ struct gpiod_line_request_config config = {
+ .consumer = consumer,
+ .request_type = type,
+ .flags = flags,
+ };
+
+ return gpiod_line_request_bulk(bulk, &config, 0);
+}
+
+int gpiod_line_request_bulk_rising_edge_events(struct gpiod_line_bulk *bulk,
+ const char *consumer)
+{
+ return line_event_request_type_bulk(bulk, consumer, 0,
+ GPIOD_LINE_REQUEST_EVENT_RISING_EDGE);
+}
+
+int gpiod_line_request_bulk_falling_edge_events(struct gpiod_line_bulk *bulk,
+ const char *consumer)
+{
+ return line_event_request_type_bulk(bulk, consumer, 0,
+ GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE);
+}
+
+int gpiod_line_request_bulk_both_edges_events(struct gpiod_line_bulk *bulk,
+ const char *consumer)
+{
+ return line_event_request_type_bulk(bulk, consumer, 0,
+ GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES);
+}
+
+int gpiod_line_request_bulk_input_flags(struct gpiod_line_bulk *bulk,
+ const char *consumer, int flags)
+{
+ struct gpiod_line_request_config config = {
+ .consumer = consumer,
+ .request_type = GPIOD_LINE_REQUEST_DIRECTION_INPUT,
+ .flags = flags,
+ };
+
+ return gpiod_line_request_bulk(bulk, &config, 0);
+}
+
+int gpiod_line_request_bulk_output_flags(struct gpiod_line_bulk *bulk,
+ const char *consumer, int flags,
+ const int *default_vals)
+{
+ struct gpiod_line_request_config config = {
+ .consumer = consumer,
+ .request_type = GPIOD_LINE_REQUEST_DIRECTION_OUTPUT,
+ .flags = flags,
+ };
+
+ return gpiod_line_request_bulk(bulk, &config, default_vals);
+}
+
+int gpiod_line_request_bulk_rising_edge_events_flags(
+ struct gpiod_line_bulk *bulk,
+ const char *consumer, int flags)
+{
+ return line_event_request_type_bulk(bulk, consumer, flags,
+ GPIOD_LINE_REQUEST_EVENT_RISING_EDGE);
+}
+
+int gpiod_line_request_bulk_falling_edge_events_flags(
+ struct gpiod_line_bulk *bulk,
+ const char *consumer, int flags)
+{
+ return line_event_request_type_bulk(bulk, consumer, flags,
+ GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE);
+}
+
+int gpiod_line_request_bulk_both_edges_events_flags(
+ struct gpiod_line_bulk *bulk,
+ const char *consumer, int flags)
+{
+ return line_event_request_type_bulk(bulk, consumer, flags,
+ GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES);
+}
+
+struct gpiod_line *gpiod_line_get(const char *device, unsigned int offset)
+{
+ struct gpiod_chip *chip;
+ struct gpiod_line *line;
+
+ chip = gpiod_chip_open_lookup(device);
+ if (!chip)
+ return NULL;
+
+ line = gpiod_chip_get_line(chip, offset);
+ if (!line) {
+ gpiod_chip_close(chip);
+ return NULL;
+ }
+
+ return line;
+}
+
+struct gpiod_line *gpiod_line_find(const char *name)
+{
+ struct gpiod_chip_iter *iter;
+ struct gpiod_chip *chip;
+ struct gpiod_line *line;
+
+ iter = gpiod_chip_iter_new();
+ if (!iter)
+ return NULL;
+
+ gpiod_foreach_chip(iter, chip) {
+ line = gpiod_chip_find_line(chip, name);
+ if (line) {
+ gpiod_chip_iter_free_noclose(iter);
+ return line;
+ }
+
+ if (errno != ENOENT)
+ goto out;
+ }
+
+ errno = ENOENT;
+
+out:
+ gpiod_chip_iter_free(iter);
+
+ return NULL;
+}
+
+void gpiod_line_close_chip(struct gpiod_line *line)
+{
+ struct gpiod_chip *chip = gpiod_line_get_chip(line);
+
+ gpiod_chip_close(chip);
+}
diff --git a/lib/iter.c b/lib/iter.c
new file mode 100644
index 0000000..a4d883a
--- /dev/null
+++ b/lib/iter.c
@@ -0,0 +1,171 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+/* GPIO chip and line iterators. */
+
+#include <dirent.h>
+#include <gpiod.h>
+#include <string.h>
+
+struct gpiod_chip_iter {
+ struct gpiod_chip **chips;
+ unsigned int num_chips;
+ unsigned int offset;
+};
+
+struct gpiod_line_iter {
+ struct gpiod_line **lines;
+ unsigned int num_lines;
+ unsigned int offset;
+};
+
+static int dir_filter(const struct dirent *dir)
+{
+ return !strncmp(dir->d_name, "gpiochip", 8);
+}
+
+static void free_dirs(struct dirent ***dirs, unsigned int num_dirs)
+{
+ unsigned int i;
+
+ for (i = 0; i < num_dirs; i++)
+ free((*dirs)[i]);
+ free(*dirs);
+}
+
+struct gpiod_chip_iter *gpiod_chip_iter_new(void)
+{
+ struct gpiod_chip_iter *iter;
+ struct dirent **dirs;
+ int i, num_chips;
+
+ num_chips = scandir("/dev", &dirs, dir_filter, alphasort);
+ if (num_chips < 0)
+ return NULL;
+
+ iter = malloc(sizeof(*iter));
+ if (!iter)
+ goto err_free_dirs;
+
+ iter->num_chips = num_chips;
+ iter->offset = 0;
+
+ if (num_chips == 0) {
+ iter->chips = NULL;
+ return iter;
+ }
+
+ iter->chips = calloc(num_chips, sizeof(*iter->chips));
+ if (!iter->chips)
+ goto err_free_iter;
+
+ for (i = 0; i < num_chips; i++) {
+ iter->chips[i] = gpiod_chip_open_by_name(dirs[i]->d_name);
+ if (!iter->chips[i])
+ goto err_close_chips;
+ }
+
+ free_dirs(&dirs, num_chips);
+
+ return iter;
+
+err_close_chips:
+ for (i = 0; i < num_chips; i++) {
+ if (iter->chips[i])
+ gpiod_chip_close(iter->chips[i]);
+ }
+
+ free(iter->chips);
+
+err_free_iter:
+ free(iter);
+
+err_free_dirs:
+ free_dirs(&dirs, num_chips);
+
+ return NULL;
+}
+
+void gpiod_chip_iter_free(struct gpiod_chip_iter *iter)
+{
+ if (iter->offset > 0 && iter->offset < iter->num_chips)
+ gpiod_chip_close(iter->chips[iter->offset - 1]);
+ gpiod_chip_iter_free_noclose(iter);
+}
+
+void gpiod_chip_iter_free_noclose(struct gpiod_chip_iter *iter)
+{
+ unsigned int i;
+
+ for (i = iter->offset; i < iter->num_chips; i++) {
+ if (iter->chips[i])
+ gpiod_chip_close(iter->chips[i]);
+ }
+
+ if (iter->chips)
+ free(iter->chips);
+
+ free(iter);
+}
+
+struct gpiod_chip *gpiod_chip_iter_next(struct gpiod_chip_iter *iter)
+{
+ if (iter->offset > 0) {
+ gpiod_chip_close(iter->chips[iter->offset - 1]);
+ iter->chips[iter->offset - 1] = NULL;
+ }
+
+ return gpiod_chip_iter_next_noclose(iter);
+}
+
+struct gpiod_chip *gpiod_chip_iter_next_noclose(struct gpiod_chip_iter *iter)
+{
+ return iter->offset < (iter->num_chips)
+ ? iter->chips[iter->offset++] : NULL;
+}
+
+struct gpiod_line_iter *gpiod_line_iter_new(struct gpiod_chip *chip)
+{
+ struct gpiod_line_iter *iter;
+ unsigned int i;
+
+ iter = malloc(sizeof(*iter));
+ if (!iter)
+ return NULL;
+
+ iter->num_lines = gpiod_chip_num_lines(chip);
+ iter->offset = 0;
+
+ iter->lines = calloc(iter->num_lines, sizeof(*iter->lines));
+ if (!iter->lines) {
+ free(iter);
+ return NULL;
+ }
+
+ for (i = 0; i < iter->num_lines; i++) {
+ iter->lines[i] = gpiod_chip_get_line(chip, i);
+ if (!iter->lines[i]) {
+ free(iter->lines);
+ free(iter);
+ return NULL;
+ }
+ }
+
+ return iter;
+}
+
+void gpiod_line_iter_free(struct gpiod_line_iter *iter)
+{
+ free(iter->lines);
+ free(iter);
+}
+
+struct gpiod_line *gpiod_line_iter_next(struct gpiod_line_iter *iter)
+{
+ return iter->offset < (iter->num_lines)
+ ? iter->lines[iter->offset++] : NULL;
+}
diff --git a/lib/misc.c b/lib/misc.c
new file mode 100644
index 0000000..e3533bd
--- /dev/null
+++ b/lib/misc.c
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+/* Misc code that didn't fit anywhere else. */
+
+#include <gpiod.h>
+#include <config.h>
+
+const char *gpiod_version_string(void)
+{
+ return GPIOD_VERSION_STR;
+}
diff --git a/libgpiod.pc.in b/libgpiod.pc.in
new file mode 100644
index 0000000..48ff113
--- /dev/null
+++ b/libgpiod.pc.in
@@ -0,0 +1,11 @@
+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}
diff --git a/man/Makefile.am b/man/Makefile.am
new file mode 100644
index 0000000..8877858
--- /dev/null
+++ b/man/Makefile.am
@@ -0,0 +1,21 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+#
+# This file is part of libgpiod.
+#
+# Copyright (C) 2019 Bartosz Golaszewski <bartekgola@gmail.com>
+#
+
+if WITH_MANPAGES
+
+dist_man1_MANS = gpiodetect.man gpioinfo.man gpioget.man gpioset.man gpiofind.man gpiomon.man
+
+%.man: $(top_srcdir)/tools/$(*F)
+ help2man $(top_srcdir)/tools/$(*F) --include=./template --output=./$@ --no-info
+
+clean-local:
+ rm -f $(dist_man1_MANS)
+
+endif
+
+EXTRA_DIST = template
diff --git a/man/template b/man/template
new file mode 100644
index 0000000..f1040ff
--- /dev/null
+++ b/man/template
@@ -0,0 +1,7 @@
+[AUTHOR]
+Bartosz Golaszewski <bartekgola@gmail.com>
+
+[REPORTING BUGS]
+Report bugs to:
+ Bartosz Golaszewski <bartekgola@gmail.com>
+ linux-gpio <linux-gpio@vger.kernel.org>
diff --git a/tests/Makefile.am b/tests/Makefile.am
new file mode 100644
index 0000000..4d1e6d5
--- /dev/null
+++ b/tests/Makefile.am
@@ -0,0 +1,50 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+#
+# This file is part of libgpiod.
+#
+# Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+#
+
+AM_CFLAGS = -I$(top_srcdir)/include/ -include $(top_builddir)/config.h
+AM_CFLAGS += -Wall -Wextra -g $(KMOD_CFLAGS) $(UDEV_CFLAGS)
+AM_LDFLAGS = -pthread
+LDADD = $(top_builddir)/lib/libgpiod.la $(KMOD_LIBS) $(UDEV_LIBS)
+
+if WITH_INSTALL_TESTS
+bin_PROGRAMS = gpiod-test
+else
+check_PROGRAMS = gpiod-test
+endif
+
+gpiod_test_SOURCES = gpiod-test.c \
+ gpiod-test.h \
+ tests-chip.c \
+ tests-ctxless.c \
+ tests-event.c \
+ tests-iter.c \
+ tests-line.c \
+ tests-misc.c
+
+if WITH_TOOLS
+
+gpiod_test_SOURCES += tests-gpiodetect.c \
+ tests-gpiofind.c \
+ tests-gpioget.c \
+ tests-gpioinfo.c \
+ tests-gpiomon.c \
+ tests-gpioset.c
+
+endif
+
+check: check-am
+ @echo " ********************************************************"
+ @echo " * Tests have been built as tests/gpio-test. *"
+ @echo " * *"
+ @echo " * They require a recent linux kernel version and the *"
+ @echo " * gpio-mockup module (must not be built-in). *"
+ @echo " * *"
+ @echo " * Run the test executable with superuser privileges or *"
+ @echo " * make sure /dev/gpiochipX files are readable and *"
+ @echo " * writable by normal users. *"
+ @echo " ********************************************************"
diff --git a/tests/gpiod-test.c b/tests/gpiod-test.c
new file mode 100644
index 0000000..9cb9072
--- /dev/null
+++ b/tests/gpiod-test.c
@@ -0,0 +1,1226 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <libkmod.h>
+#include <libudev.h>
+#include <poll.h>
+#include <pthread.h>
+#include <regex.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/signalfd.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/utsname.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "gpiod-test.h"
+
+#define NORETURN __attribute__((noreturn))
+#define MALLOC __attribute__((malloc))
+
+static const unsigned int min_kern_major = 4;
+static const unsigned int min_kern_minor = 16;
+static const unsigned int min_kern_release = 0;
+
+struct mockup_chip {
+ char *path;
+ char *name;
+ unsigned int number;
+};
+
+struct event_thread {
+ pthread_t thread_id;
+ pthread_mutex_t lock;
+ pthread_cond_t cond;
+ bool running;
+ bool should_stop;
+ unsigned int chip_index;
+ unsigned int line_offset;
+ unsigned int freq;
+ int event_type;
+};
+
+struct gpiotool_proc {
+ pid_t pid;
+ bool running;
+ int stdin_fd;
+ int stdout_fd;
+ int stderr_fd;
+ char *stdout;
+ char *stderr;
+ int sig_fd;
+ int exit_status;
+};
+
+struct test_context {
+ struct mockup_chip **chips;
+ size_t num_chips;
+ bool test_failed;
+ char *failed_msg;
+ char *custom_str;
+ struct event_thread event;
+ struct gpiotool_proc tool_proc;
+ bool running;
+};
+
+static struct {
+ struct _test_case *test_list_head;
+ struct _test_case *test_list_tail;
+ unsigned int num_tests;
+ unsigned int tests_failed;
+ struct kmod_ctx *module_ctx;
+ struct kmod_module *module;
+ struct test_context test_ctx;
+ pid_t main_pid;
+ int pipesize;
+ char *pipebuf;
+ char *toolpath;
+} globals;
+
+enum {
+ CNORM = 0,
+ CGREEN,
+ CRED,
+ CREDBOLD,
+ CYELLOW,
+};
+
+static const char *const term_colors[] = {
+ "\033[0m",
+ "\033[32m",
+ "\033[31m",
+ "\033[1m\033[31m",
+ "\033[33m",
+};
+
+static void set_color(int color)
+{
+ fprintf(stderr, "%s", term_colors[color]);
+}
+
+static void reset_color(void)
+{
+ fprintf(stderr, "%s", term_colors[CNORM]);
+}
+
+static void pr_raw_v(const char *fmt, va_list va)
+{
+ vfprintf(stderr, fmt, va);
+}
+
+static TEST_PRINTF(1, 2) void pr_raw(const char *fmt, ...)
+{
+ va_list va;
+
+ va_start(va, fmt);
+ pr_raw_v(fmt, va);
+ va_end(va);
+}
+
+static void print_header(const char *hdr, int color)
+{
+ char buf[9];
+
+ snprintf(buf, sizeof(buf), "[%s]", hdr);
+
+ set_color(color);
+ pr_raw("%-8s", buf);
+ reset_color();
+}
+
+static void vmsgn(const char *hdr, int hdr_clr, const char *fmt, va_list va)
+{
+ print_header(hdr, hdr_clr);
+ pr_raw_v(fmt, va);
+}
+
+static void vmsg(const char *hdr, int hdr_clr, const char *fmt, va_list va)
+{
+ vmsgn(hdr, hdr_clr, fmt, va);
+ pr_raw("\n");
+}
+
+static TEST_PRINTF(1, 2) void msg(const char *fmt, ...)
+{
+ va_list va;
+
+ va_start(va, fmt);
+ vmsg("INFO", CGREEN, fmt, va);
+ va_end(va);
+}
+
+static TEST_PRINTF(1, 2) void err(const char *fmt, ...)
+{
+ va_list va;
+
+ va_start(va, fmt);
+ vmsg("ERROR", CRED, fmt, va);
+ va_end(va);
+}
+
+static void die_test_cleanup(void)
+{
+ struct gpiotool_proc *proc = &globals.test_ctx.tool_proc;
+ int rv;
+
+ if (proc->running) {
+ kill(proc->pid, SIGKILL);
+ waitpid(proc->pid, &rv, 0);
+ }
+
+ if (globals.test_ctx.running)
+ pr_raw("\n");
+}
+
+static TEST_PRINTF(1, 2) NORETURN void die(const char *fmt, ...)
+{
+ va_list va;
+
+ die_test_cleanup();
+
+ va_start(va, fmt);
+ vmsg("FATAL", CRED, fmt, va);
+ va_end(va);
+
+ exit(EXIT_FAILURE);
+}
+
+static TEST_PRINTF(1, 2) NORETURN void die_perr(const char *fmt, ...)
+{
+ va_list va;
+
+ die_test_cleanup();
+
+ va_start(va, fmt);
+ vmsgn("FATAL", CRED, fmt, va);
+ pr_raw(": %s\n", strerror(errno));
+ va_end(va);
+
+ exit(EXIT_FAILURE);
+}
+
+static MALLOC void *xmalloc(size_t size)
+{
+ void *ptr;
+
+ ptr = malloc(size);
+ if (!ptr)
+ die("out of memory");
+
+ return ptr;
+}
+
+static MALLOC void *xzalloc(size_t size)
+{
+ void *ptr;
+
+ ptr = xmalloc(size);
+ memset(ptr, 0, size);
+
+ return ptr;
+}
+
+static MALLOC void *xcalloc(size_t nmemb, size_t size)
+{
+ void *ptr;
+
+ ptr = calloc(nmemb, size);
+ if (!ptr)
+ die("out of memory");
+
+ return ptr;
+}
+
+static MALLOC char *xstrdup(const char *str)
+{
+ char *ret;
+
+ ret = strdup(str);
+ if (!ret)
+ die("out of memory");
+
+ return ret;
+}
+
+static TEST_PRINTF(2, 3) char *xappend(char *str, const char *fmt, ...)
+{
+ char *new, *ret;
+ va_list va;
+ int rv;
+
+ va_start(va, fmt);
+ rv = vasprintf(&new, fmt, va);
+ va_end(va);
+ if (rv < 0)
+ die_perr("vasprintf");
+
+ if (!str)
+ return new;
+
+ ret = realloc(str, strlen(str) + strlen(new) + 1);
+ if (!ret)
+ die("out of memory");
+
+ strcat(ret, new);
+ free(new);
+
+ return ret;
+}
+
+static int get_pipesize(void)
+{
+ int pipe_fds[2], rv;
+
+ rv = pipe(pipe_fds);
+ if (rv < 0)
+ die_perr("unable to create a pipe");
+
+ /*
+ * Since linux v2.6.11 the default pipe capacity is 16 system pages.
+ * We make an assumption here that gpio-tools won't output more than
+ * that, so we can read everything after the program terminated. If
+ * they did output more than the pipe capacity however, the write()
+ * system call would block and the process would be killed by the
+ * testing framework.
+ */
+ rv = fcntl(pipe_fds[0], F_GETPIPE_SZ);
+ if (rv < 0)
+ die_perr("unable to retrieve the pipe capacity");
+
+ close(pipe_fds[0]);
+ close(pipe_fds[1]);
+
+ return rv;
+}
+
+static void check_chip_index(unsigned int index)
+{
+ if (index >= globals.test_ctx.num_chips)
+ die("invalid chip number requested from test code");
+}
+
+static bool mockup_loaded(void)
+{
+ int state;
+
+ if (!globals.module_ctx || !globals.module)
+ return false;
+
+ state = kmod_module_get_initstate(globals.module);
+
+ return state == KMOD_MODULE_LIVE;
+}
+
+static void cleanup_func(void)
+{
+ /* Don't cleanup from child processes. */
+ if (globals.main_pid != getpid())
+ return;
+
+ msg("cleaning up");
+
+ free(globals.pipebuf);
+
+ if (mockup_loaded())
+ kmod_module_remove_module(globals.module, 0);
+
+ if (globals.module)
+ kmod_module_unref(globals.module);
+
+ if (globals.module_ctx)
+ kmod_unref(globals.module_ctx);
+}
+
+static void event_lock(void)
+{
+ pthread_mutex_lock(&globals.test_ctx.event.lock);
+}
+
+static void event_unlock(void)
+{
+ pthread_mutex_unlock(&globals.test_ctx.event.lock);
+}
+
+static void *event_worker(void *data TEST_UNUSED)
+{
+ struct event_thread *ev = &globals.test_ctx.event;
+ struct timeval tv_now, tv_add, tv_res;
+ struct timespec ts;
+ int rv, i, fd;
+ char *path;
+ ssize_t rd;
+ char buf;
+
+ for (i = 0;; i++) {
+ event_lock();
+ if (ev->should_stop) {
+ event_unlock();
+ break;
+ }
+
+ gettimeofday(&tv_now, NULL);
+ tv_add.tv_sec = 0;
+ tv_add.tv_usec = ev->freq * 1000;
+ timeradd(&tv_now, &tv_add, &tv_res);
+ ts.tv_sec = tv_res.tv_sec;
+ ts.tv_nsec = tv_res.tv_usec * 1000;
+
+ rv = pthread_cond_timedwait(&ev->cond, &ev->lock, &ts);
+ if (rv == ETIMEDOUT) {
+ path = xappend(NULL,
+ "/sys/kernel/debug/gpio-mockup-event/gpio-mockup-%c/%u",
+ 'A' + ev->chip_index, ev->line_offset);
+
+ fd = open(path, O_RDWR);
+ free(path);
+ if (fd < 0)
+ die_perr("error opening gpio event file");
+
+ if (ev->event_type == TEST_EVENT_RISING)
+ buf = '1';
+ else if (ev->event_type == TEST_EVENT_FALLING)
+ buf = '0';
+ else
+ buf = i % 2 == 0 ? '1' : '0';
+
+ rd = write(fd, &buf, 1);
+ close(fd);
+ if (rd < 0)
+ die_perr("error writing to gpio event file");
+ else if (rd != 1)
+ die("invalid write size to gpio event file");
+ } else if (rv != 0) {
+ die("error waiting for conditional variable: %s",
+ strerror(rv));
+ }
+
+ event_unlock();
+ }
+
+ return NULL;
+}
+
+static void gpiotool_proc_make_pipes(int *in_fds, int *out_fds, int *err_fds)
+{
+ int rv, i, *fds[3];
+
+ fds[0] = in_fds;
+ fds[1] = out_fds;
+ fds[2] = err_fds;
+
+ for (i = 0; i < 3; i++) {
+ rv = pipe(fds[i]);
+ if (rv < 0)
+ die_perr("unable to create a pipe");
+ }
+}
+
+static void gpiotool_proc_dup_fds(int in_fd, int out_fd, int err_fd)
+{
+ int old_fds[3], new_fds[3], i, rv;
+
+ old_fds[0] = in_fd;
+ old_fds[1] = out_fd;
+ old_fds[2] = err_fd;
+
+ new_fds[0] = STDIN_FILENO;
+ new_fds[1] = STDOUT_FILENO;
+ new_fds[2] = STDERR_FILENO;
+
+ for (i = 0; i < 3; i++) {
+ rv = dup2(old_fds[i], new_fds[i]);
+ if (rv < 0)
+ die_perr("unable to duplicate a file descriptor");
+
+ close(old_fds[i]);
+ }
+}
+
+static NORETURN void gpiotool_proc_exec(const char *path, va_list va)
+{
+ size_t num_args;
+ unsigned int i;
+ char **argv;
+ va_list va2;
+
+ va_copy(va2, va);
+ for (num_args = 2; va_arg(va2, char *) != NULL; num_args++)
+ ;
+ va_end(va2);
+
+ argv = xcalloc(num_args, sizeof(char *));
+
+ argv[0] = (char *)path;
+ for (i = 1; i < num_args; i++)
+ argv[i] = va_arg(va, char *);
+ va_end(va);
+
+ execv(path, argv);
+ die_perr("unable to exec '%s'", path);
+}
+
+static void gpiotool_proc_cleanup(void)
+{
+ struct gpiotool_proc *proc = &globals.test_ctx.tool_proc;
+
+ if (proc->stdout)
+ free(proc->stdout);
+
+ if (proc->stderr)
+ free(proc->stderr);
+
+ proc->stdout = proc->stderr = NULL;
+}
+
+void test_tool_signal(int signum)
+{
+ struct gpiotool_proc *proc = &globals.test_ctx.tool_proc;
+ int rv;
+
+ rv = kill(proc->pid, signum);
+ if (rv)
+ die_perr("unable to send signal to process %d", proc->pid);
+}
+
+void test_tool_stdin_write(const char *fmt, ...)
+{
+ struct gpiotool_proc *proc = &globals.test_ctx.tool_proc;
+ ssize_t written;
+ va_list va;
+ char *in;
+ int rv;
+
+ va_start(va, fmt);
+ rv = vasprintf(&in, fmt, va);
+ va_end(va);
+ if (rv < 0)
+ die_perr("error building string");
+
+ written = write(proc->stdin_fd, in, rv);
+ free(in);
+ if (written < 0)
+ die_perr("error writing to child process' stdin");
+ if (written != rv)
+ die("unable to write all data to child process' stdin");
+}
+
+void test_tool_run(char *tool, ...)
+{
+ int in_fds[2], out_fds[2], err_fds[2], rv;
+ struct gpiotool_proc *proc;
+ sigset_t sigmask;
+ char *path;
+ va_list va;
+
+ proc = &globals.test_ctx.tool_proc;
+ if (proc->running)
+ die("unable to start %s - another tool already running", tool);
+
+ if (proc->pid)
+ gpiotool_proc_cleanup();
+
+ event_lock();
+ if (globals.test_ctx.event.running)
+ die("refusing to fork when the event thread is running");
+ if (!globals.toolpath)
+ die("asked to run tests for gpio-tools, but the executables were not found");
+
+ gpiotool_proc_make_pipes(in_fds, out_fds, err_fds);
+
+ path = xappend(NULL, "%s/%s", globals.toolpath, tool);
+ rv = access(path, R_OK | X_OK);
+ if (rv)
+ die_perr("unable to execute '%s'", path);
+
+ sigemptyset(&sigmask);
+ sigaddset(&sigmask, SIGCHLD);
+
+ rv = sigprocmask(SIG_BLOCK, &sigmask, NULL);
+ if (rv)
+ die_perr("unable to block SIGCHLD");
+
+ proc->sig_fd = signalfd(-1, &sigmask, 0);
+ if (proc->sig_fd < 0)
+ die_perr("unable to create signalfd");
+
+ proc->pid = fork();
+ if (proc->pid < 0) {
+ die_perr("unable to fork");
+ } else if (proc->pid == 0) {
+ /* Child process. */
+ close(proc->sig_fd);
+ gpiotool_proc_dup_fds(in_fds[0], out_fds[1], err_fds[1]);
+ va_start(va, tool);
+ gpiotool_proc_exec(path, va);
+ } else {
+ /* Parent process. */
+ close(in_fds[0]);
+ proc->stdin_fd = in_fds[1];
+ close(out_fds[1]);
+ proc->stdout_fd = out_fds[0];
+ close(err_fds[1]);
+ proc->stderr_fd = err_fds[0];
+
+ proc->running = true;
+ }
+
+ event_unlock();
+ free(path);
+}
+
+static void gpiotool_readall(int fd, char **out)
+{
+ ssize_t rd;
+ int i;
+
+ memset(globals.pipebuf, 0, globals.pipesize);
+ rd = read(fd, globals.pipebuf, globals.pipesize);
+ if (rd < 0) {
+ die_perr("error reading output from subprocess");
+ } else if (rd == 0) {
+ *out = NULL;
+ } else {
+ for (i = 0; i < rd; i++) {
+ if (!isascii(globals.pipebuf[i]))
+ die("GPIO tool program printed a non-ASCII character");
+ }
+
+ *out = xzalloc(rd + 1);
+ memcpy(*out, globals.pipebuf, rd);
+ }
+}
+
+void test_tool_wait(void)
+{
+ struct signalfd_siginfo sinfo;
+ struct gpiotool_proc *proc;
+ struct pollfd pfd;
+ sigset_t sigmask;
+ ssize_t rd;
+ int rv;
+
+ proc = &globals.test_ctx.tool_proc;
+
+ if (!proc->running)
+ die("gpiotool process not running");
+
+ pfd.fd = proc->sig_fd;
+ pfd.events = POLLIN | POLLPRI;
+
+ rv = poll(&pfd, 1, 5000);
+ if (rv == 0)
+ die("tool program is taking too long to terminate");
+ else if (rv < 0)
+ die_perr("error when polling the signalfd");
+
+ rd = read(proc->sig_fd, &sinfo, sizeof(sinfo));
+ close(proc->sig_fd);
+ if (rd < 0)
+ die_perr("error reading signal info");
+ else if (rd != sizeof(sinfo))
+ die("invalid size of signal info");
+
+ sigemptyset(&sigmask);
+ sigaddset(&sigmask, SIGCHLD);
+
+ rv = sigprocmask(SIG_UNBLOCK, &sigmask, NULL);
+ if (rv)
+ die_perr("unable to unblock SIGCHLD");
+
+ gpiotool_readall(proc->stdout_fd, &proc->stdout);
+ gpiotool_readall(proc->stderr_fd, &proc->stderr);
+
+ waitpid(proc->pid, &proc->exit_status, 0);
+
+ close(proc->stdin_fd);
+ close(proc->stdout_fd);
+ close(proc->stderr_fd);
+
+ proc->running = false;
+}
+
+const char *test_tool_stdout(void)
+{
+ struct gpiotool_proc *proc = &globals.test_ctx.tool_proc;
+
+ return proc->stdout;
+}
+
+const char *test_tool_stderr(void)
+{
+ struct gpiotool_proc *proc = &globals.test_ctx.tool_proc;
+
+ return proc->stderr;
+}
+
+bool test_tool_exited(void)
+{
+ struct gpiotool_proc *proc = &globals.test_ctx.tool_proc;
+
+ return WIFEXITED(proc->exit_status);
+}
+
+int test_tool_exit_status(void)
+{
+ struct gpiotool_proc *proc = &globals.test_ctx.tool_proc;
+
+ return WEXITSTATUS(proc->exit_status);
+}
+
+static void check_kernel(void)
+{
+ unsigned int major, minor, release;
+ struct utsname un;
+ int rv;
+
+ msg("checking the linux kernel version");
+
+ rv = uname(&un);
+ if (rv)
+ die_perr("uname");
+
+ rv = sscanf(un.release, "%u.%u.%u", &major, &minor, &release);
+ if (rv != 3)
+ die("error reading kernel release version");
+
+ if (major < min_kern_major) {
+ goto bad_version;
+ } else if (major > min_kern_major) {
+ goto good_version;
+ } else {
+ if (minor < min_kern_minor) {
+ goto bad_version;
+ } else if (minor > min_kern_minor) {
+ goto good_version;
+ } else {
+ if (release < min_kern_release)
+ goto bad_version;
+ else
+ goto good_version;
+ }
+ }
+
+good_version:
+ msg("kernel release is v%u.%u.%u - ok to run tests",
+ major, minor, release);
+
+ return;
+
+bad_version:
+ die("linux kernel version must be at least v%u.%u.%u - got v%u.%u.%u",
+ min_kern_major, min_kern_minor, min_kern_release,
+ major, minor, release);
+}
+
+static void check_gpio_mockup(void)
+{
+ const char *modpath;
+ int rv;
+
+ msg("checking gpio-mockup availability");
+
+ globals.module_ctx = kmod_new(NULL, NULL);
+ if (!globals.module_ctx)
+ die_perr("error creating kernel module context");
+
+ rv = kmod_module_new_from_name(globals.module_ctx,
+ "gpio-mockup", &globals.module);
+ if (rv)
+ die_perr("error allocating module info");
+
+ /* First see if we can find the module. */
+ modpath = kmod_module_get_path(globals.module);
+ if (!modpath)
+ die("the gpio-mockup module does not exist in the system or is built into the kernel");
+
+ /* Then see if we can freely load and unload it. */
+ rv = kmod_module_probe_insert_module(globals.module, 0,
+ "gpio_mockup_ranges=-1,4",
+ NULL, NULL, NULL);
+ if (rv)
+ die_perr("unable to load gpio-mockup");
+
+ rv = kmod_module_remove_module(globals.module, 0);
+ if (rv)
+ die_perr("unable to remove gpio-mockup");
+
+ msg("gpio-mockup ok");
+}
+
+static void check_tool_path(void)
+{
+ /*
+ * Let's check gpiodetect only and assume all the other tools are in
+ * the same directory.
+ */
+ static const char *const tool = "gpiodetect";
+
+ char *progpath, *progdir, *toolpath, *pathenv, *tok;
+
+ /* First check if we're running the from the top source directory. */
+ progpath = xstrdup(program_invocation_name);
+ progdir = dirname(progpath);
+
+ toolpath = xappend(NULL, "%s/../../tools/%s", progdir, tool);
+ if (access(toolpath, R_OK | X_OK) == 0) {
+ free(progpath);
+ goto out;
+ }
+ free(toolpath);
+
+ /* Is the tool in the same directory maybe? */
+ toolpath = xappend(NULL, "%s/%s", progdir, tool);
+ free(progpath);
+ if (access(toolpath, R_OK | X_OK) == 0)
+ goto out;
+ free(toolpath);
+
+ /* Next iterate over directories in $PATH. */
+ pathenv = getenv("PATH");
+ if (!pathenv)
+ return;
+
+ progpath = xstrdup(pathenv);
+ tok = strtok(progpath, ":");
+ while (tok) {
+ toolpath = xappend(NULL, "%s/%s", tok, tool);
+ if (access(toolpath, R_OK) == 0) {
+ free(progpath);
+ goto out;
+ }
+
+ free(toolpath);
+ tok = strtok(NULL, ":");
+ }
+
+ free(progpath);
+ toolpath = NULL;
+
+out:
+ if (toolpath) {
+ toolpath = dirname(toolpath);
+ msg("using gpio-tools from '%s'", toolpath);
+ globals.toolpath = toolpath;
+ }
+}
+
+static void load_module(struct _test_chip_descr *descr)
+{
+ unsigned int i;
+ char *modarg;
+ int rv;
+
+ if (descr->num_chips == 0)
+ return;
+
+ modarg = xappend(NULL, "gpio_mockup_ranges=");
+ for (i = 0; i < descr->num_chips; i++)
+ modarg = xappend(modarg, "-1,%u,", descr->num_lines[i]);
+ modarg[strlen(modarg) - 1] = '\0'; /* Remove the last comma. */
+
+ if (descr->flags & TEST_FLAG_NAMED_LINES)
+ modarg = xappend(modarg, " gpio_mockup_named_lines");
+
+ rv = kmod_module_probe_insert_module(globals.module, 0,
+ modarg, NULL, NULL, NULL);
+ if (rv)
+ die_perr("unable to load gpio-mockup");
+
+ free(modarg);
+}
+
+static int chipcmp(const void *c1, const void *c2)
+{
+ const struct mockup_chip *chip1 = *(const struct mockup_chip **)c1;
+ const struct mockup_chip *chip2 = *(const struct mockup_chip **)c2;
+
+ return chip1->number > chip2->number;
+}
+
+static bool devpath_is_mockup(const char *devpath)
+{
+ static const char mockup_devpath[] = "/devices/platform/gpio-mockup";
+
+ return !strncmp(devpath, mockup_devpath, sizeof(mockup_devpath) - 1);
+}
+
+static void prepare_test(struct _test_chip_descr *descr)
+{
+ const char *devpath, *devnode, *sysname, *action;
+ struct udev_monitor *monitor;
+ unsigned int detected = 0;
+ struct test_context *ctx;
+ struct mockup_chip *chip;
+ struct udev_device *dev;
+ struct udev *udev_ctx;
+ struct pollfd pfd;
+ int rv;
+
+ ctx = &globals.test_ctx;
+ memset(ctx, 0, sizeof(*ctx));
+ ctx->num_chips = descr->num_chips;
+ ctx->chips = xcalloc(ctx->num_chips, sizeof(*ctx->chips));
+ pthread_mutex_init(&ctx->event.lock, NULL);
+ pthread_cond_init(&ctx->event.cond, NULL);
+
+ /*
+ * We'll setup the udev monitor, insert the module and wait for the
+ * mockup gpiochips to appear.
+ */
+
+ udev_ctx = udev_new();
+ if (!udev_ctx)
+ die_perr("error creating udev context");
+
+ monitor = udev_monitor_new_from_netlink(udev_ctx, "udev");
+ if (!monitor)
+ die_perr("error creating udev monitor");
+
+ rv = udev_monitor_filter_add_match_subsystem_devtype(monitor,
+ "gpio", NULL);
+ if (rv < 0)
+ die_perr("error adding udev filters");
+
+ rv = udev_monitor_enable_receiving(monitor);
+ if (rv < 0)
+ die_perr("error enabling udev event receiving");
+
+ load_module(descr);
+
+ pfd.fd = udev_monitor_get_fd(monitor);
+ pfd.events = POLLIN | POLLPRI;
+
+ while (ctx->num_chips > detected) {
+ rv = poll(&pfd, 1, 5000);
+ if (rv < 0)
+ die_perr("error polling for uevents");
+ else if (rv == 0)
+ die("timeout waiting for gpiochips");
+
+ dev = udev_monitor_receive_device(monitor);
+ if (!dev)
+ die_perr("error reading device info");
+
+ devpath = udev_device_get_devpath(dev);
+ devnode = udev_device_get_devnode(dev);
+ sysname = udev_device_get_sysname(dev);
+ action = udev_device_get_action(dev);
+
+ if (!devpath || !devnode || !sysname ||
+ !devpath_is_mockup(devpath) ||
+ strcmp(action, "add") != 0) {
+ udev_device_unref(dev);
+ continue;
+ }
+
+ chip = xzalloc(sizeof(*chip));
+ chip->name = xstrdup(sysname);
+ chip->path = xstrdup(devnode);
+ rv = sscanf(sysname, "gpiochip%u", &chip->number);
+ if (rv != 1)
+ die("unable to determine chip number");
+
+ ctx->chips[detected++] = chip;
+ udev_device_unref(dev);
+ }
+
+ udev_monitor_unref(monitor);
+ udev_unref(udev_ctx);
+
+ /*
+ * We can't assume that the order in which the mockup gpiochip
+ * devices are created will be deterministic, yet we want the
+ * index passed to the test_chip_*() functions to correspond with the
+ * order in which the chips were defined in the TEST_DEFINE()
+ * macro.
+ *
+ * Once all gpiochips are there, sort them by chip number.
+ */
+ qsort(ctx->chips, ctx->num_chips, sizeof(*ctx->chips), chipcmp);
+}
+
+static void run_test(struct _test_case *test)
+{
+ errno = 0;
+
+ print_header("TEST", CYELLOW);
+ pr_raw("'%s': ", test->name);
+
+ globals.test_ctx.running = true;
+ test->func();
+ globals.test_ctx.running = false;
+
+ if (globals.test_ctx.test_failed) {
+ globals.tests_failed++;
+ set_color(CREDBOLD);
+ pr_raw("FAILED:");
+ reset_color();
+ set_color(CRED);
+ pr_raw("\n\t\t'%s': %s\n",
+ test->name, globals.test_ctx.failed_msg);
+ reset_color();
+ free(globals.test_ctx.failed_msg);
+ } else {
+ set_color(CGREEN);
+ pr_raw("PASS\n");
+ reset_color();
+ }
+}
+
+static void teardown_test(void)
+{
+ struct gpiotool_proc *tool_proc;
+ struct mockup_chip *chip;
+ struct event_thread *ev;
+ unsigned int i;
+ int rv;
+
+ event_lock();
+ ev = &globals.test_ctx.event;
+
+ if (ev->running) {
+ ev->should_stop = true;
+ pthread_cond_broadcast(&ev->cond);
+ event_unlock();
+
+ rv = pthread_join(globals.test_ctx.event.thread_id, NULL);
+ if (rv != 0)
+ die("error joining event thread: %s",
+ strerror(rv));
+
+ pthread_mutex_destroy(&globals.test_ctx.event.lock);
+ pthread_cond_destroy(&globals.test_ctx.event.cond);
+ } else {
+ event_unlock();
+ }
+
+ tool_proc = &globals.test_ctx.tool_proc;
+ if (tool_proc->running) {
+ test_tool_signal(SIGKILL);
+ test_tool_wait();
+ }
+
+ if (tool_proc->pid)
+ gpiotool_proc_cleanup();
+
+ for (i = 0; i < globals.test_ctx.num_chips; i++) {
+ chip = globals.test_ctx.chips[i];
+
+ free(chip->path);
+ free(chip->name);
+ free(chip);
+ }
+
+ free(globals.test_ctx.chips);
+
+ if (globals.test_ctx.custom_str)
+ free(globals.test_ctx.custom_str);
+
+ if (mockup_loaded()) {
+ rv = kmod_module_remove_module(globals.module, 0);
+ if (rv)
+ die_perr("unable to remove gpio-mockup");
+ }
+}
+
+int main(int argc TEST_UNUSED, char **argv TEST_UNUSED)
+{
+ struct _test_case *test;
+
+ globals.main_pid = getpid();
+ globals.pipesize = get_pipesize();
+ globals.pipebuf = xmalloc(globals.pipesize);
+ atexit(cleanup_func);
+
+ msg("libgpiod test suite");
+ msg("%u tests registered", globals.num_tests);
+
+ check_kernel();
+ check_gpio_mockup();
+ check_tool_path();
+
+ msg("running tests");
+
+ for (test = globals.test_list_head; test; test = test->_next) {
+ prepare_test(&test->chip_descr);
+ run_test(test);
+ teardown_test();
+ }
+
+ if (!globals.tests_failed)
+ msg("all tests passed");
+ else
+ err("%u out of %u tests failed",
+ globals.tests_failed, globals.num_tests);
+
+ return globals.tests_failed ? EXIT_FAILURE : EXIT_SUCCESS;
+}
+
+void test_close_chip(struct gpiod_chip **chip)
+{
+ if (*chip)
+ gpiod_chip_close(*chip);
+}
+
+void test_line_close_chip(struct gpiod_line **line)
+{
+ if (*line)
+ gpiod_line_close_chip(*line);
+}
+
+void test_free_chip_iter(struct gpiod_chip_iter **iter)
+{
+ if (*iter)
+ gpiod_chip_iter_free(*iter);
+}
+
+void test_free_line_iter(struct gpiod_line_iter **iter)
+{
+ if (*iter)
+ gpiod_line_iter_free(*iter);
+}
+
+void test_free_chip_iter_noclose(struct gpiod_chip_iter **iter)
+{
+ if (*iter)
+ gpiod_chip_iter_free_noclose(*iter);
+}
+
+const char *test_chip_path(unsigned int index)
+{
+ check_chip_index(index);
+
+ return globals.test_ctx.chips[index]->path;
+}
+
+const char *test_chip_name(unsigned int index)
+{
+ check_chip_index(index);
+
+ return globals.test_ctx.chips[index]->name;
+}
+
+unsigned int test_chip_num(unsigned int index)
+{
+ check_chip_index(index);
+
+ return globals.test_ctx.chips[index]->number;
+}
+
+void _test_register(struct _test_case *test)
+{
+ struct _test_case *tmp;
+
+ if (!globals.test_list_head) {
+ globals.test_list_head = globals.test_list_tail = test;
+ test->_next = NULL;
+ } else {
+ tmp = globals.test_list_tail;
+ globals.test_list_tail = test;
+ test->_next = NULL;
+ tmp->_next = test;
+ }
+
+ globals.num_tests++;
+}
+
+void _test_print_failed(const char *fmt, ...)
+{
+ va_list va;
+ int rv;
+
+ va_start(va, fmt);
+ rv = vasprintf(&globals.test_ctx.failed_msg, fmt, va);
+ va_end(va);
+ if (rv < 0)
+ die_perr("vasprintf");
+
+ globals.test_ctx.test_failed = true;
+}
+
+void test_set_event(unsigned int chip_index, unsigned int line_offset,
+ int event_type, unsigned int freq)
+{
+ struct event_thread *ev = &globals.test_ctx.event;
+ int rv;
+
+ event_lock();
+
+ if (!ev->running) {
+ rv = pthread_create(&ev->thread_id, NULL, event_worker, NULL);
+ if (rv != 0)
+ die("error creating event thread: %s",
+ strerror(rv));
+
+ ev->running = true;
+ }
+
+ ev->chip_index = chip_index;
+ ev->line_offset = line_offset;
+ ev->event_type = event_type;
+ ev->freq = freq;
+
+ pthread_cond_broadcast(&ev->cond);
+
+ event_unlock();
+}
+
+bool test_regex_match(const char *str, const char *pattern)
+{
+ char errbuf[128];
+ regex_t regex;
+ bool ret;
+ int rv;
+
+ rv = regcomp(®ex, pattern, REG_EXTENDED | REG_NEWLINE);
+ if (rv) {
+ regerror(rv, ®ex, errbuf, sizeof(errbuf));
+ die("unable to compile regex '%s': %s", pattern, errbuf);
+ }
+
+ rv = regexec(®ex, str, 0, 0, 0);
+ if (rv == REG_NOERROR) {
+ ret = true;
+ } else if (rv == REG_NOMATCH) {
+ ret = false;
+ } else {
+ regerror(rv, ®ex, errbuf, sizeof(errbuf));
+ die("unable to run a regex match: %s", errbuf);
+ }
+
+ regfree(®ex);
+
+ return ret;
+}
+
+const char *test_build_str(const char *fmt, ...)
+{
+ va_list va;
+ char *str;
+ int rv;
+
+ if (globals.test_ctx.custom_str)
+ free(globals.test_ctx.custom_str);
+
+ va_start(va, fmt);
+ rv = vasprintf(&str, fmt, va);
+ va_end(va);
+ if (rv < 0)
+ die_perr("error creating custom string");
+
+ globals.test_ctx.custom_str = str;
+
+ return str;
+}
diff --git a/tests/gpiod-test.h b/tests/gpiod-test.h
new file mode 100644
index 0000000..7b02408
--- /dev/null
+++ b/tests/gpiod-test.h
@@ -0,0 +1,164 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+/* Testing framework - functions and definitions used by test cases. */
+
+#ifndef __GPIOD_TEST_H__
+#define __GPIOD_TEST_H__
+
+#include <gpiod.h>
+#include <string.h>
+
+#define TEST_CONSUMER "gpiod-test"
+
+#define TEST_INIT __attribute__((constructor))
+#define TEST_UNUSED __attribute__((unused))
+#define TEST_PRINTF(fmt, arg) __attribute__((format(printf, fmt, arg)))
+#define TEST_CLEANUP(func) __attribute__((cleanup(func)))
+#define TEST_BIT(nr) (1UL << (nr))
+
+#define TEST_ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x)))
+
+typedef void (*_test_func)(void);
+
+struct _test_chip_descr {
+ unsigned int num_chips;
+ unsigned int *num_lines;
+ int flags;
+};
+
+struct _test_case {
+ struct _test_case *_next;
+
+ const char *name;
+ _test_func func;
+
+ struct _test_chip_descr chip_descr;
+};
+
+void _test_register(struct _test_case *test);
+void _test_print_failed(const char *fmt, ...) TEST_PRINTF(1, 2);
+
+enum {
+ TEST_FLAG_NAMED_LINES = TEST_BIT(0),
+};
+
+/*
+ * This macro should be used for code brevity instead of manually declaring
+ * the _test_case structure.
+ *
+ * The macro accepts the following arguments:
+ * _a_func: name of the test function
+ * _a_name: name of the test case (will be shown to user)
+ * _a_flags: various switches for the test case
+ *
+ * The last argument must be an array of unsigned integers specifying the
+ * number of GPIO lines in each subsequent mockup chip. The size of this
+ * array will at the same time specify the number of gpiochips to create.
+ */
+#define TEST_DEFINE(_a_func, _a_name, _a_flags, ...) \
+ static unsigned int _##_a_func##_lines[] = __VA_ARGS__; \
+ static struct _test_case _##_a_func##_descr = { \
+ .name = _a_name, \
+ .func = _a_func, \
+ .chip_descr = { \
+ .num_chips = TEST_ARRAY_SIZE( \
+ _##_a_func##_lines), \
+ .num_lines = _##_a_func##_lines, \
+ .flags = _a_flags, \
+ }, \
+ }; \
+ static TEST_INIT void _test_register_##_a_func##_test(void) \
+ { \
+ _test_register(&_##_a_func##_descr); \
+ } \
+ static int _test_##_a_func##_sentinel TEST_UNUSED
+
+/*
+ * We want libgpiod tests to co-exist with gpiochips created by other GPIO
+ * drivers. For that reason we can't just use hardcoded device file paths or
+ * gpiochip names.
+ *
+ * The test suite detects the chips that were exported by the gpio-mockup
+ * module and stores them in the internal test context structure. Test cases
+ * should use the routines declared below to access the gpiochip path, name
+ * or number by index corresponding with the order in which the mockup chips
+ * were requested in the TEST_DEFINE() macro.
+ */
+const char *test_chip_path(unsigned int index);
+const char *test_chip_name(unsigned int index);
+unsigned int test_chip_num(unsigned int index);
+
+enum {
+ TEST_EVENT_FALLING,
+ TEST_EVENT_RISING,
+ TEST_EVENT_ALTERNATING,
+};
+
+void test_set_event(unsigned int chip_index, unsigned int line_offset,
+ int event_type, unsigned int freq);
+
+void test_tool_run(char *tool, ...);
+void test_tool_wait(void);
+const char *test_tool_stdout(void);
+const char *test_tool_stderr(void);
+bool test_tool_exited(void);
+int test_tool_exit_status(void);
+void test_tool_signal(int signum);
+void test_tool_stdin_write(const char *fmt, ...) TEST_PRINTF(1, 2);
+
+/*
+ * Every TEST_ASSERT_*() macro expansion can make a test function return, so
+ * it would be quite difficult to keep track of every resource allocation. At
+ * the same time we don't want any deliberate memory leaks as we also use this
+ * test suite to find actual memory leaks in the library with valgrind.
+ *
+ * For this reason, the tests should generally always use the TEST_CLEANUP()
+ * macro for dynamically allocated variables and objects that need closing.
+ *
+ * The functions below can be reused by different tests for common use cases.
+ */
+void test_close_chip(struct gpiod_chip **chip);
+void test_line_close_chip(struct gpiod_line **line);
+void test_free_chip_iter(struct gpiod_chip_iter **iter);
+void test_free_chip_iter_noclose(struct gpiod_chip_iter **iter);
+void test_free_line_iter(struct gpiod_line_iter **iter);
+
+#define TEST_CLEANUP_CHIP TEST_CLEANUP(test_close_chip)
+
+bool test_regex_match(const char *str, const char *pattern);
+
+/*
+ * Return a custom string built according to printf() formatting rules. The
+ * returned string is valid until the next call to this routine.
+ */
+const char *test_build_str(const char *fmt, ...) TEST_PRINTF(1, 2);
+
+#define TEST_ASSERT(statement) \
+ do { \
+ if (!(statement)) { \
+ _test_print_failed( \
+ "assertion failed (%s:%d): '%s'", \
+ __FILE__, __LINE__, #statement); \
+ return; \
+ } \
+ } while (0)
+
+#define TEST_ASSERT_FALSE(statement) TEST_ASSERT(!(statement))
+#define TEST_ASSERT_NOT_NULL(ptr) TEST_ASSERT((ptr) != NULL)
+#define TEST_ASSERT_RET_OK(status) TEST_ASSERT((status) == 0)
+#define TEST_ASSERT_NULL(ptr) TEST_ASSERT((ptr) == NULL)
+#define TEST_ASSERT_ERRNO_IS(errnum) TEST_ASSERT(errno == (errnum))
+#define TEST_ASSERT_EQ(a1, a2) TEST_ASSERT((a1) == (a2))
+#define TEST_ASSERT_NOTEQ(a1, a2) TEST_ASSERT((a1) != (a2))
+#define TEST_ASSERT_STR_EQ(s1, s2) TEST_ASSERT(strcmp(s1, s2) == 0)
+#define TEST_ASSERT_STR_CONTAINS(s, p) TEST_ASSERT(strstr(s, p) != NULL)
+#define TEST_ASSERT_STR_NOTCONT(s, p) TEST_ASSERT(strstr(s, p) == NULL)
+#define TEST_ASSERT_REGEX_MATCH(s, p) TEST_ASSERT(test_regex_match(s, p))
+#define TEST_ASSERT_REGEX_NOMATCH(s, p) TEST_ASSERT(!test_regex_match(s, p))
+
+#endif /* __GPIOD_TEST_H__ */
diff --git a/tests/tests-chip.c b/tests/tests-chip.c
new file mode 100644
index 0000000..0acb5c7
--- /dev/null
+++ b/tests/tests-chip.c
@@ -0,0 +1,336 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+/* Test cases for GPIO chip handling. */
+
+#include <errno.h>
+#include <stdio.h>
+
+#include "gpiod-test.h"
+
+static void chip_open_good(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip = NULL;
+
+ chip = gpiod_chip_open(test_chip_path(0));
+ TEST_ASSERT_NOT_NULL(chip);
+}
+TEST_DEFINE(chip_open_good,
+ "gpiod_chip_open() - good",
+ 0, { 8 });
+
+static void chip_open_nonexistent(void)
+{
+ struct gpiod_chip *chip;
+
+ chip = gpiod_chip_open("/dev/nonexistent_gpiochip");
+ TEST_ASSERT_NULL(chip);
+ TEST_ASSERT_ERRNO_IS(ENOENT);
+}
+TEST_DEFINE(chip_open_nonexistent,
+ "gpiod_chip_open() - nonexistent chip",
+ 0, { 8 });
+
+static void chip_open_notty(void)
+{
+ struct gpiod_chip *chip;
+
+ chip = gpiod_chip_open("/dev/null");
+ TEST_ASSERT_NULL(chip);
+ TEST_ASSERT_ERRNO_IS(ENOTTY);
+}
+TEST_DEFINE(chip_open_notty,
+ "gpiod_chip_open() - notty",
+ 0, { 8 });
+
+static void chip_open_by_name_good(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip = NULL;
+
+ chip = gpiod_chip_open_by_name(test_chip_name(0));
+ TEST_ASSERT_NOT_NULL(chip);
+}
+TEST_DEFINE(chip_open_by_name_good,
+ "gpiod_chip_open_by_name() - good",
+ 0, { 8 });
+
+static void chip_open_by_number_good(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip = NULL;
+
+ chip = gpiod_chip_open_by_number(test_chip_num(0));
+ TEST_ASSERT_NOT_NULL(chip);
+}
+TEST_DEFINE(chip_open_by_number_good,
+ "gpiod_chip_open_by_number() - good",
+ 0, { 8 });
+
+static void chip_open_lookup(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip_by_label = NULL;
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip_by_name = NULL;
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip_by_path = NULL;
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip_by_num = NULL;
+
+ chip_by_name = gpiod_chip_open_lookup(test_chip_name(1));
+ chip_by_path = gpiod_chip_open_lookup(test_chip_path(1));
+ chip_by_num = gpiod_chip_open_lookup(
+ test_build_str("%u", test_chip_num(1)));
+ chip_by_label = gpiod_chip_open_lookup("gpio-mockup-B");
+
+ TEST_ASSERT_NOT_NULL(chip_by_name);
+ TEST_ASSERT_NOT_NULL(chip_by_path);
+ TEST_ASSERT_NOT_NULL(chip_by_num);
+ TEST_ASSERT_NOT_NULL(chip_by_label);
+
+ TEST_ASSERT_STR_EQ(gpiod_chip_name(chip_by_name), test_chip_name(1));
+ TEST_ASSERT_STR_EQ(gpiod_chip_name(chip_by_path), test_chip_name(1));
+ TEST_ASSERT_STR_EQ(gpiod_chip_name(chip_by_num), test_chip_name(1));
+ TEST_ASSERT_STR_EQ(gpiod_chip_name(chip_by_label), test_chip_name(1));
+}
+TEST_DEFINE(chip_open_lookup,
+ "gpiod_chip_open_lookup() - good",
+ 0, { 8, 8, 8 });
+
+static void chip_open_by_label_good(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip = NULL;
+
+ chip = gpiod_chip_open_by_label("gpio-mockup-D");
+ TEST_ASSERT_NOT_NULL(chip);
+ TEST_ASSERT_STR_EQ(gpiod_chip_name(chip), test_chip_name(3));
+}
+TEST_DEFINE(chip_open_by_label_good,
+ "gpiod_chip_open_by_label() - good",
+ 0, { 4, 4, 4, 4, 4 });
+
+static void chip_open_by_label_bad(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip = NULL;
+
+ chip = gpiod_chip_open_by_label("nonexistent_gpio_chip");
+ TEST_ASSERT_NULL(chip);
+ TEST_ASSERT_ERRNO_IS(ENOENT);
+}
+TEST_DEFINE(chip_open_by_label_bad,
+ "gpiod_chip_open_by_label() - bad",
+ 0, { 4, 4, 4, 4, 4 });
+
+static void chip_name(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip0 = NULL;
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip1 = NULL;
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip2 = NULL;
+
+ chip0 = gpiod_chip_open(test_chip_path(0));
+ chip1 = gpiod_chip_open(test_chip_path(1));
+ chip2 = gpiod_chip_open(test_chip_path(2));
+ TEST_ASSERT_NOT_NULL(chip0);
+ TEST_ASSERT_NOT_NULL(chip1);
+ TEST_ASSERT_NOT_NULL(chip2);
+
+ TEST_ASSERT_STR_EQ(gpiod_chip_name(chip0), test_chip_name(0));
+ TEST_ASSERT_STR_EQ(gpiod_chip_name(chip1), test_chip_name(1));
+ TEST_ASSERT_STR_EQ(gpiod_chip_name(chip2), test_chip_name(2));
+}
+TEST_DEFINE(chip_name,
+ "gpiod_chip_name()",
+ 0, { 8, 8, 8 });
+
+static void chip_label(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip0 = NULL;
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip1 = NULL;
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip2 = NULL;
+
+ chip0 = gpiod_chip_open(test_chip_path(0));
+ chip1 = gpiod_chip_open(test_chip_path(1));
+ chip2 = gpiod_chip_open(test_chip_path(2));
+ TEST_ASSERT_NOT_NULL(chip0);
+ TEST_ASSERT_NOT_NULL(chip1);
+ TEST_ASSERT_NOT_NULL(chip2);
+
+ TEST_ASSERT_STR_EQ(gpiod_chip_label(chip0), "gpio-mockup-A");
+ TEST_ASSERT_STR_EQ(gpiod_chip_label(chip1), "gpio-mockup-B");
+ TEST_ASSERT_STR_EQ(gpiod_chip_label(chip2), "gpio-mockup-C");
+}
+TEST_DEFINE(chip_label,
+ "gpiod_chip_label()",
+ 0, { 8, 8, 8 });
+
+static void chip_num_lines(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip0 = NULL;
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip1 = NULL;
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip2 = NULL;
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip3 = NULL;
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip4 = NULL;
+
+ chip0 = gpiod_chip_open(test_chip_path(0));
+ chip1 = gpiod_chip_open(test_chip_path(1));
+ chip2 = gpiod_chip_open(test_chip_path(2));
+ chip3 = gpiod_chip_open(test_chip_path(3));
+ chip4 = gpiod_chip_open(test_chip_path(4));
+ TEST_ASSERT_NOT_NULL(chip0);
+ TEST_ASSERT_NOT_NULL(chip1);
+ TEST_ASSERT_NOT_NULL(chip2);
+ TEST_ASSERT_NOT_NULL(chip3);
+ TEST_ASSERT_NOT_NULL(chip4);
+
+ TEST_ASSERT_EQ(gpiod_chip_num_lines(chip0), 1);
+ TEST_ASSERT_EQ(gpiod_chip_num_lines(chip1), 4);
+ TEST_ASSERT_EQ(gpiod_chip_num_lines(chip2), 8);
+ TEST_ASSERT_EQ(gpiod_chip_num_lines(chip3), 16);
+ TEST_ASSERT_EQ(gpiod_chip_num_lines(chip4), 32);
+}
+TEST_DEFINE(chip_num_lines,
+ "gpiod_chip_num_lines()",
+ 0, { 1, 4, 8, 16, 32 });
+
+static void chip_get_lines(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip = NULL;
+ struct gpiod_line_bulk bulk;
+ unsigned int offsets[4];
+ struct gpiod_line *line;
+ int rv;
+
+ chip = gpiod_chip_open(test_chip_path(0));
+ TEST_ASSERT_NOT_NULL(chip);
+
+ offsets[0] = 1;
+ offsets[1] = 3;
+ offsets[2] = 4;
+ offsets[3] = 7;
+
+ rv = gpiod_chip_get_lines(chip, offsets, 4, &bulk);
+ TEST_ASSERT_RET_OK(rv);
+
+ TEST_ASSERT_EQ(gpiod_line_bulk_num_lines(&bulk), 4);
+ line = gpiod_line_bulk_get_line(&bulk, 0);
+ TEST_ASSERT_EQ(gpiod_line_offset(line), 1);
+ line = gpiod_line_bulk_get_line(&bulk, 1);
+ TEST_ASSERT_EQ(gpiod_line_offset(line), 3);
+ line = gpiod_line_bulk_get_line(&bulk, 2);
+ TEST_ASSERT_EQ(gpiod_line_offset(line), 4);
+ line = gpiod_line_bulk_get_line(&bulk, 3);
+ TEST_ASSERT_EQ(gpiod_line_offset(line), 7);
+}
+TEST_DEFINE(chip_get_lines,
+ "gpiod_chip_get_lines()",
+ 0, { 16 });
+
+static void chip_get_all_lines(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip = NULL;
+ struct gpiod_line_bulk bulk;
+ struct gpiod_line *line;
+ int rv;
+
+ chip = gpiod_chip_open(test_chip_path(0));
+ TEST_ASSERT_NOT_NULL(chip);
+
+ rv = gpiod_chip_get_all_lines(chip, &bulk);
+ TEST_ASSERT_RET_OK(rv);
+
+ TEST_ASSERT_EQ(gpiod_line_bulk_num_lines(&bulk), 4);
+ line = gpiod_line_bulk_get_line(&bulk, 0);
+ TEST_ASSERT_EQ(gpiod_line_offset(line), 0);
+ line = gpiod_line_bulk_get_line(&bulk, 1);
+ TEST_ASSERT_EQ(gpiod_line_offset(line), 1);
+ line = gpiod_line_bulk_get_line(&bulk, 2);
+ TEST_ASSERT_EQ(gpiod_line_offset(line), 2);
+ line = gpiod_line_bulk_get_line(&bulk, 3);
+ TEST_ASSERT_EQ(gpiod_line_offset(line), 3);
+}
+TEST_DEFINE(chip_get_all_lines,
+ "gpiod_chip_get_all_lines()",
+ 0, { 4 });
+
+static void chip_find_line_good(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip = NULL;
+ struct gpiod_line *line;
+
+ chip = gpiod_chip_open(test_chip_path(1));
+ TEST_ASSERT_NOT_NULL(chip);
+
+ line = gpiod_chip_find_line(chip, "gpio-mockup-B-4");
+ TEST_ASSERT_NOT_NULL(line);
+ TEST_ASSERT_EQ(gpiod_line_offset(line), 4);
+ TEST_ASSERT_STR_EQ(gpiod_line_name(line), "gpio-mockup-B-4");
+}
+TEST_DEFINE(chip_find_line_good,
+ "gpiod_chip_find_line() - good",
+ TEST_FLAG_NAMED_LINES, { 8, 8, 8 });
+
+static void chip_find_line_not_found(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip = NULL;
+ struct gpiod_line *line;
+
+ chip = gpiod_chip_open(test_chip_path(1));
+ TEST_ASSERT_NOT_NULL(chip);
+
+ line = gpiod_chip_find_line(chip, "nonexistent");
+ TEST_ASSERT_NULL(line);
+ TEST_ASSERT_ERRNO_IS(ENOENT);
+}
+TEST_DEFINE(chip_find_line_not_found,
+ "gpiod_chip_find_line() - not found",
+ TEST_FLAG_NAMED_LINES, { 8, 8, 8 });
+
+static void chip_find_lines_good(void)
+{
+ static const char *names[] = { "gpio-mockup-B-3",
+ "gpio-mockup-B-6",
+ "gpio-mockup-B-7",
+ NULL };
+
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip = NULL;
+ struct gpiod_line_bulk bulk;
+ struct gpiod_line *line;
+ int rv;
+
+ chip = gpiod_chip_open(test_chip_path(1));
+ TEST_ASSERT_NOT_NULL(chip);
+
+ rv = gpiod_chip_find_lines(chip, names, &bulk);
+ TEST_ASSERT_RET_OK(rv);
+ TEST_ASSERT_EQ(gpiod_line_bulk_num_lines(&bulk), 3);
+ line = gpiod_line_bulk_get_line(&bulk, 0);
+ TEST_ASSERT_EQ(gpiod_line_offset(line), 3);
+ line = gpiod_line_bulk_get_line(&bulk, 1);
+ TEST_ASSERT_EQ(gpiod_line_offset(line), 6);
+ line = gpiod_line_bulk_get_line(&bulk, 2);
+ TEST_ASSERT_EQ(gpiod_line_offset(line), 7);
+}
+TEST_DEFINE(chip_find_lines_good,
+ "gpiod_chip_find_lines() - good",
+ TEST_FLAG_NAMED_LINES, { 8, 8, 8 });
+
+static void chip_find_lines_not_found(void)
+{
+ static const char *names[] = { "gpio-mockup-B-3",
+ "nonexistent",
+ "gpio-mockup-B-7",
+ NULL };
+
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip = NULL;
+ struct gpiod_line_bulk bulk;
+ int rv;
+
+ chip = gpiod_chip_open(test_chip_path(1));
+ TEST_ASSERT_NOT_NULL(chip);
+
+ rv = gpiod_chip_find_lines(chip, names, &bulk);
+ TEST_ASSERT_EQ(rv, -1);
+ TEST_ASSERT_ERRNO_IS(ENOENT);
+}
+TEST_DEFINE(chip_find_lines_not_found,
+ "gpiod_chip_find_lines() - not found",
+ TEST_FLAG_NAMED_LINES, { 8, 8, 8 });
diff --git a/tests/tests-ctxless.c b/tests/tests-ctxless.c
new file mode 100644
index 0000000..638274f
--- /dev/null
+++ b/tests/tests-ctxless.c
@@ -0,0 +1,311 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+/* Test cases for the high-level API. */
+
+#include <errno.h>
+
+#include "gpiod-test.h"
+
+static void ctxless_set_get_value(void)
+{
+ int rv;
+
+ rv = gpiod_ctxless_get_value(test_chip_name(0), 3,
+ false, TEST_CONSUMER);
+ TEST_ASSERT_EQ(rv, 0);
+
+ rv = gpiod_ctxless_set_value(test_chip_name(0), 3, 1,
+ false, TEST_CONSUMER, NULL, NULL);
+ TEST_ASSERT_RET_OK(rv);
+
+ rv = gpiod_ctxless_get_value(test_chip_name(0), 3,
+ false, TEST_CONSUMER);
+ TEST_ASSERT_EQ(rv, 1);
+}
+TEST_DEFINE(ctxless_set_get_value,
+ "ctxless set/get value - single line",
+ 0, { 8 });
+
+static void ctxless_set_get_value_multiple(void)
+{
+ unsigned int offsets[] = { 0, 1, 2, 3, 4, 5, 6, 12, 13, 15 };
+ int values[10], rv;
+
+ rv = gpiod_ctxless_get_value_multiple(test_chip_name(0), offsets,
+ values, 10, false, TEST_CONSUMER);
+ TEST_ASSERT_RET_OK(rv);
+
+ TEST_ASSERT_EQ(values[0], 0);
+ TEST_ASSERT_EQ(values[1], 0);
+ TEST_ASSERT_EQ(values[2], 0);
+ TEST_ASSERT_EQ(values[3], 0);
+ TEST_ASSERT_EQ(values[4], 0);
+ TEST_ASSERT_EQ(values[5], 0);
+ TEST_ASSERT_EQ(values[6], 0);
+ TEST_ASSERT_EQ(values[7], 0);
+ TEST_ASSERT_EQ(values[8], 0);
+ TEST_ASSERT_EQ(values[9], 0);
+
+ values[0] = 1;
+ values[1] = 1;
+ values[2] = 1;
+ values[3] = 0;
+ values[4] = 0;
+ values[5] = 1;
+ values[6] = 0;
+ values[7] = 1;
+ values[8] = 0;
+ values[9] = 0;
+
+ rv = gpiod_ctxless_set_value_multiple(test_chip_name(0), offsets,
+ values, 10, false, TEST_CONSUMER,
+ NULL, NULL);
+ TEST_ASSERT_RET_OK(rv);
+
+ rv = gpiod_ctxless_get_value_multiple(test_chip_name(0), offsets,
+ values, 10, false, TEST_CONSUMER);
+ TEST_ASSERT_RET_OK(rv);
+
+ TEST_ASSERT_EQ(values[0], 1);
+ TEST_ASSERT_EQ(values[1], 1);
+ TEST_ASSERT_EQ(values[2], 1);
+ TEST_ASSERT_EQ(values[3], 0);
+ TEST_ASSERT_EQ(values[4], 0);
+ TEST_ASSERT_EQ(values[5], 1);
+ TEST_ASSERT_EQ(values[6], 0);
+ TEST_ASSERT_EQ(values[7], 1);
+ TEST_ASSERT_EQ(values[8], 0);
+ TEST_ASSERT_EQ(values[9], 0);
+}
+TEST_DEFINE(ctxless_set_get_value_multiple,
+ "ctxless set/get value - multiple lines",
+ 0, { 16 });
+
+static void ctxless_get_value_multiple_max_lines(void)
+{
+ unsigned int offsets[GPIOD_LINE_BULK_MAX_LINES + 1];
+ int values[GPIOD_LINE_BULK_MAX_LINES + 1], rv;
+
+ rv = gpiod_ctxless_get_value_multiple(test_chip_name(0), offsets,
+ values,
+ GPIOD_LINE_BULK_MAX_LINES + 1,
+ false, TEST_CONSUMER);
+ TEST_ASSERT_NOTEQ(rv, 0);
+ TEST_ASSERT_ERRNO_IS(EINVAL);
+}
+TEST_DEFINE(ctxless_get_value_multiple_max_lines,
+ "gpiod_ctxless_get_value_multiple() exceed max lines",
+ 0, { 128 });
+
+static void ctxless_set_value_multiple_max_lines(void)
+{
+ unsigned int offsets[GPIOD_LINE_BULK_MAX_LINES + 1];
+ int values[GPIOD_LINE_BULK_MAX_LINES + 1], rv;
+
+ rv = gpiod_ctxless_set_value_multiple(test_chip_name(0), offsets,
+ values,
+ GPIOD_LINE_BULK_MAX_LINES + 1,
+ false, TEST_CONSUMER,
+ NULL, NULL);
+ TEST_ASSERT_NOTEQ(rv, 0);
+ TEST_ASSERT_ERRNO_IS(EINVAL);
+}
+TEST_DEFINE(ctxless_set_value_multiple_max_lines,
+ "gpiod_ctxless_set_value_multiple() exceed max lines",
+ 0, { 128 });
+
+struct ctxless_event_data {
+ bool got_rising_edge;
+ bool got_falling_edge;
+ unsigned int offset;
+ unsigned int count;
+};
+
+static int ctxless_event_cb(int evtype, unsigned int offset,
+ const struct timespec *ts TEST_UNUSED, void *data)
+{
+ struct ctxless_event_data *evdata = data;
+
+ if (evtype == GPIOD_CTXLESS_EVENT_CB_RISING_EDGE)
+ evdata->got_rising_edge = true;
+ else if (evtype == GPIOD_CTXLESS_EVENT_CB_FALLING_EDGE)
+ evdata->got_falling_edge = true;
+
+ evdata->offset = offset;
+
+ return ++evdata->count == 2 ? GPIOD_CTXLESS_EVENT_CB_RET_STOP
+ : GPIOD_CTXLESS_EVENT_CB_RET_OK;
+}
+
+static void ctxless_event_monitor(void)
+{
+ struct ctxless_event_data evdata = { false, false, 0, 0 };
+ struct timespec ts = { 1, 0 };
+ int rv;
+
+ test_set_event(0, 3, TEST_EVENT_ALTERNATING, 100);
+
+ rv = gpiod_ctxless_event_monitor(test_chip_name(0),
+ GPIOD_CTXLESS_EVENT_BOTH_EDGES,
+ 3, false, TEST_CONSUMER, &ts,
+ NULL, ctxless_event_cb, &evdata);
+
+ TEST_ASSERT_RET_OK(rv);
+ TEST_ASSERT(evdata.got_rising_edge);
+ TEST_ASSERT(evdata.got_falling_edge);
+ TEST_ASSERT_EQ(evdata.count, 2);
+ TEST_ASSERT_EQ(evdata.offset, 3);
+}
+TEST_DEFINE(ctxless_event_monitor,
+ "gpiod_ctxless_event_monitor() - single event",
+ 0, { 8 });
+
+static void ctxless_event_monitor_single_event_type(void)
+{
+ struct ctxless_event_data evdata = { false, false, 0, 0 };
+ struct timespec ts = { 1, 0 };
+ int rv;
+
+ test_set_event(0, 3, TEST_EVENT_ALTERNATING, 100);
+
+ rv = gpiod_ctxless_event_monitor(test_chip_name(0),
+ GPIOD_CTXLESS_EVENT_FALLING_EDGE,
+ 3, false, TEST_CONSUMER, &ts,
+ NULL, ctxless_event_cb, &evdata);
+
+ TEST_ASSERT_RET_OK(rv);
+ TEST_ASSERT(evdata.got_falling_edge);
+ TEST_ASSERT_FALSE(evdata.got_rising_edge);
+ TEST_ASSERT_EQ(evdata.count, 2);
+ TEST_ASSERT_EQ(evdata.offset, 3);
+}
+TEST_DEFINE(ctxless_event_monitor_single_event_type,
+ "gpiod_ctxless_event_monitor() - specify event type",
+ 0, { 8 });
+
+static void ctxless_event_monitor_multiple(void)
+{
+ struct ctxless_event_data evdata = { false, false, 0, 0 };
+ struct timespec ts = { 1, 0 };
+ unsigned int offsets[4];
+ int rv;
+
+ offsets[0] = 2;
+ offsets[1] = 3;
+ offsets[2] = 5;
+ offsets[3] = 6;
+
+ test_set_event(0, 3, TEST_EVENT_ALTERNATING, 100);
+
+ rv = gpiod_ctxless_event_monitor_multiple(
+ test_chip_name(0),
+ GPIOD_CTXLESS_EVENT_BOTH_EDGES,
+ offsets, 4, false, TEST_CONSUMER,
+ &ts, NULL, ctxless_event_cb, &evdata);
+
+ TEST_ASSERT_RET_OK(rv);
+ TEST_ASSERT(evdata.got_rising_edge);
+ TEST_ASSERT(evdata.got_falling_edge);
+ TEST_ASSERT_EQ(evdata.count, 2);
+ TEST_ASSERT_EQ(evdata.offset, 3);
+}
+TEST_DEFINE(ctxless_event_monitor_multiple,
+ "gpiod_ctxless_event_monitor_multiple() - single event",
+ 0, { 8 });
+
+static int error_event_cb(int evtype TEST_UNUSED,
+ unsigned int offset TEST_UNUSED,
+ const struct timespec *ts TEST_UNUSED,
+ void *data TEST_UNUSED)
+{
+ errno = ENOTBLK;
+
+ return GPIOD_CTXLESS_EVENT_CB_RET_ERR;
+}
+
+static void ctxless_event_monitor_indicate_error(void)
+{
+ struct timespec ts = { 1, 0 };
+ int rv;
+
+ test_set_event(0, 3, TEST_EVENT_ALTERNATING, 100);
+
+ rv = gpiod_ctxless_event_monitor(test_chip_name(0),
+ GPIOD_CTXLESS_EVENT_BOTH_EDGES,
+ 3, false, TEST_CONSUMER, &ts,
+ NULL, error_event_cb, NULL);
+
+ TEST_ASSERT_EQ(rv, -1);
+ TEST_ASSERT_ERRNO_IS(ENOTBLK);
+}
+TEST_DEFINE(ctxless_event_monitor_indicate_error,
+ "gpiod_ctxless_event_monitor() - error in callback",
+ 0, { 8 });
+
+static void ctxless_event_monitor_indicate_error_timeout(void)
+{
+ struct timespec ts = { 0, 100000 };
+ int rv;
+
+ rv = gpiod_ctxless_event_monitor(test_chip_name(0),
+ GPIOD_CTXLESS_EVENT_BOTH_EDGES,
+ 3, false, TEST_CONSUMER, &ts,
+ NULL, error_event_cb, NULL);
+
+ TEST_ASSERT_EQ(rv, -1);
+ TEST_ASSERT_ERRNO_IS(ENOTBLK);
+}
+TEST_DEFINE(ctxless_event_monitor_indicate_error_timeout,
+ "gpiod_ctxless_event_monitor() - error in callback after timeout",
+ 0, { 8 });
+
+static void ctxless_find_line_good(void)
+{
+ unsigned int offset;
+ char chip[32];
+ int rv;
+
+ rv = gpiod_ctxless_find_line("gpio-mockup-C-14", chip,
+ sizeof(chip), &offset);
+ TEST_ASSERT_EQ(rv, 1);
+ TEST_ASSERT_EQ(offset, 14);
+ TEST_ASSERT_STR_EQ(chip, test_chip_name(2));
+}
+TEST_DEFINE(ctxless_find_line_good,
+ "gpiod_ctxless_find_line() - good",
+ TEST_FLAG_NAMED_LINES, { 8, 16, 16, 8 });
+
+static void ctxless_find_line_truncated(void)
+{
+ unsigned int offset;
+ char chip[6];
+ int rv;
+
+ rv = gpiod_ctxless_find_line("gpio-mockup-C-14", chip,
+ sizeof(chip), &offset);
+ TEST_ASSERT_EQ(rv, 1);
+ TEST_ASSERT_EQ(offset, 14);
+ TEST_ASSERT_STR_EQ(chip, "gpioc");
+}
+TEST_DEFINE(ctxless_find_line_truncated,
+ "gpiod_ctxless_find_line() - chip name truncated",
+ TEST_FLAG_NAMED_LINES, { 8, 16, 16, 8 });
+
+static void ctxless_find_line_not_found(void)
+{
+ unsigned int offset;
+ char chip[32];
+ int rv;
+
+ rv = gpiod_ctxless_find_line("nonexistent", chip,
+ sizeof(chip), &offset);
+ TEST_ASSERT_EQ(rv, 0);
+}
+TEST_DEFINE(ctxless_find_line_not_found,
+ "gpiod_ctxless_find_line() - not found",
+ TEST_FLAG_NAMED_LINES, { 8, 16, 16, 8 });
diff --git a/tests/tests-event.c b/tests/tests-event.c
new file mode 100644
index 0000000..93e53a4
--- /dev/null
+++ b/tests/tests-event.c
@@ -0,0 +1,335 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+/* Test cases for GPIO line events. */
+
+#include <errno.h>
+#include <unistd.h>
+
+#include "gpiod-test.h"
+
+static void event_rising_edge_good(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip = NULL;
+ struct timespec ts = { 1, 0 };
+ struct gpiod_line_event ev;
+ struct gpiod_line *line;
+ int rv;
+
+ chip = gpiod_chip_open(test_chip_path(0));
+ TEST_ASSERT_NOT_NULL(chip);
+
+ line = gpiod_chip_get_line(chip, 7);
+ TEST_ASSERT_NOT_NULL(line);
+
+ rv = gpiod_line_request_rising_edge_events(line, TEST_CONSUMER);
+ TEST_ASSERT_RET_OK(rv);
+
+ test_set_event(0, 7, TEST_EVENT_RISING, 100);
+
+ rv = gpiod_line_event_wait(line, &ts);
+ TEST_ASSERT_EQ(rv, 1);
+
+ rv = gpiod_line_event_read(line, &ev);
+ TEST_ASSERT_RET_OK(rv);
+
+ TEST_ASSERT_EQ(ev.event_type, GPIOD_LINE_EVENT_RISING_EDGE);
+}
+TEST_DEFINE(event_rising_edge_good,
+ "events - receive single rising edge event",
+ 0, { 8 });
+
+static void event_falling_edge_good(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip = NULL;
+ struct timespec ts = { 1, 0 };
+ struct gpiod_line_event ev;
+ struct gpiod_line *line;
+ int rv;
+
+ chip = gpiod_chip_open(test_chip_path(0));
+ TEST_ASSERT_NOT_NULL(chip);
+
+ line = gpiod_chip_get_line(chip, 7);
+ TEST_ASSERT_NOT_NULL(line);
+
+ rv = gpiod_line_request_falling_edge_events(line, TEST_CONSUMER);
+ TEST_ASSERT_RET_OK(rv);
+
+ test_set_event(0, 7, TEST_EVENT_FALLING, 100);
+
+ rv = gpiod_line_event_wait(line, &ts);
+ TEST_ASSERT_EQ(rv, 1);
+
+ rv = gpiod_line_event_read(line, &ev);
+ TEST_ASSERT_RET_OK(rv);
+
+ TEST_ASSERT_EQ(ev.event_type, GPIOD_LINE_EVENT_FALLING_EDGE);
+}
+TEST_DEFINE(event_falling_edge_good,
+ "events - receive single falling edge event",
+ 0, { 8 });
+
+static void event_rising_edge_ignore_falling(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip = NULL;
+ struct timespec ts = { 0, 300 };
+ struct gpiod_line *line;
+ int rv;
+
+ chip = gpiod_chip_open(test_chip_path(0));
+ TEST_ASSERT_NOT_NULL(chip);
+
+ line = gpiod_chip_get_line(chip, 7);
+ TEST_ASSERT_NOT_NULL(line);
+
+ rv = gpiod_line_request_rising_edge_events(line, TEST_CONSUMER);
+ TEST_ASSERT_RET_OK(rv);
+
+ test_set_event(0, 7, TEST_EVENT_FALLING, 100);
+
+ rv = gpiod_line_event_wait(line, &ts);
+ TEST_ASSERT_EQ(rv, 0);
+}
+TEST_DEFINE(event_rising_edge_ignore_falling,
+ "events - request rising edge & ignore falling edge events",
+ 0, { 8 });
+
+static void event_rising_edge_active_low(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip = NULL;
+ struct timespec ts = { 1, 0 };
+ struct gpiod_line_event ev;
+ struct gpiod_line *line;
+ int rv;
+
+ chip = gpiod_chip_open(test_chip_path(0));
+ TEST_ASSERT_NOT_NULL(chip);
+
+ line = gpiod_chip_get_line(chip, 7);
+ TEST_ASSERT_NOT_NULL(line);
+
+ rv = gpiod_line_request_rising_edge_events_flags(line, TEST_CONSUMER,
+ GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW);
+ TEST_ASSERT_RET_OK(rv);
+
+ test_set_event(0, 7, TEST_EVENT_RISING, 100);
+
+ rv = gpiod_line_event_wait(line, &ts);
+ TEST_ASSERT_EQ(rv, 1);
+
+ rv = gpiod_line_event_read(line, &ev);
+ TEST_ASSERT_RET_OK(rv);
+
+ TEST_ASSERT_EQ(ev.event_type, GPIOD_LINE_EVENT_RISING_EDGE);
+}
+TEST_DEFINE(event_rising_edge_active_low,
+ "events - single rising edge event with low active state",
+ 0, { 8 });
+
+static void event_get_value(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip = NULL;
+ struct timespec ts = { 1, 0 };
+ struct gpiod_line_event ev;
+ struct gpiod_line *line;
+ int rv;
+
+ chip = gpiod_chip_open(test_chip_path(0));
+ TEST_ASSERT_NOT_NULL(chip);
+
+ line = gpiod_chip_get_line(chip, 7);
+ TEST_ASSERT_NOT_NULL(line);
+
+ rv = gpiod_line_request_rising_edge_events(line, TEST_CONSUMER);
+ TEST_ASSERT_RET_OK(rv);
+
+ rv = gpiod_line_get_value(line);
+ TEST_ASSERT_EQ(rv, 0);
+
+ test_set_event(0, 7, TEST_EVENT_RISING, 100);
+
+ rv = gpiod_line_event_wait(line, &ts);
+ TEST_ASSERT_EQ(rv, 1);
+
+ rv = gpiod_line_event_read(line, &ev);
+ TEST_ASSERT_RET_OK(rv);
+
+ TEST_ASSERT_EQ(ev.event_type, GPIOD_LINE_EVENT_RISING_EDGE);
+
+ rv = gpiod_line_get_value(line);
+ TEST_ASSERT_EQ(rv, 1);
+}
+TEST_DEFINE(event_get_value,
+ "events - mixing events and gpiod_line_get_value()",
+ 0, { 8 });
+
+static void event_get_value_active_low(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip = NULL;
+ struct timespec ts = { 1, 0 };
+ struct gpiod_line_event ev;
+ struct gpiod_line *line;
+ int rv;
+
+ chip = gpiod_chip_open(test_chip_path(0));
+ TEST_ASSERT_NOT_NULL(chip);
+
+ line = gpiod_chip_get_line(chip, 7);
+ TEST_ASSERT_NOT_NULL(line);
+
+ rv = gpiod_line_request_falling_edge_events_flags(line, TEST_CONSUMER,
+ GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW);
+ TEST_ASSERT_RET_OK(rv);
+
+ rv = gpiod_line_get_value(line);
+ TEST_ASSERT_EQ(rv, 1);
+
+ test_set_event(0, 7, TEST_EVENT_FALLING, 100);
+
+ rv = gpiod_line_event_wait(line, &ts);
+ TEST_ASSERT_EQ(rv, 1);
+
+ rv = gpiod_line_event_read(line, &ev);
+ TEST_ASSERT_RET_OK(rv);
+
+ TEST_ASSERT_EQ(ev.event_type, GPIOD_LINE_EVENT_FALLING_EDGE);
+
+ rv = gpiod_line_get_value(line);
+ TEST_ASSERT_EQ(rv, 0);
+}
+TEST_DEFINE(event_get_value_active_low,
+ "events - mixing events and gpiod_line_get_value() (active-low flag)",
+ 0, { 8 });
+
+static void event_wait_multiple(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip = NULL;
+ struct gpiod_line_bulk bulk, event_bulk;
+ struct timespec ts = { 1, 0 };
+ struct gpiod_line *line;
+ int rv, i;
+
+ chip = gpiod_chip_open(test_chip_path(0));
+ TEST_ASSERT_NOT_NULL(chip);
+
+ gpiod_line_bulk_init(&bulk);
+
+ for (i = 0; i < 8; i++) {
+ line = gpiod_chip_get_line(chip, i);
+ TEST_ASSERT_NOT_NULL(line);
+
+ gpiod_line_bulk_add(&bulk, line);
+ }
+
+ rv = gpiod_line_request_bulk_both_edges_events(&bulk, TEST_CONSUMER);
+ TEST_ASSERT_RET_OK(rv);
+
+ test_set_event(0, 4, TEST_EVENT_RISING, 100);
+
+ rv = gpiod_line_event_wait_bulk(&bulk, &ts, &event_bulk);
+ TEST_ASSERT_EQ(rv, 1);
+
+ TEST_ASSERT_EQ(gpiod_line_bulk_num_lines(&event_bulk), 1);
+ line = gpiod_line_bulk_get_line(&event_bulk, 0);
+ TEST_ASSERT_EQ(gpiod_line_offset(line), 4);
+}
+TEST_DEFINE(event_wait_multiple,
+ "events - wait for events on multiple lines",
+ 0, { 8 });
+
+static void event_get_fd_when_values_requested(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip = NULL;
+ struct gpiod_line *line;
+ int rv, fd;
+
+ chip = gpiod_chip_open(test_chip_path(0));
+ TEST_ASSERT_NOT_NULL(chip);
+
+ line = gpiod_chip_get_line(chip, 3);
+ TEST_ASSERT_NOT_NULL(line);
+
+ rv = gpiod_line_request_input(line, TEST_CONSUMER);
+ TEST_ASSERT_RET_OK(rv);
+
+ fd = gpiod_line_event_get_fd(line);
+ TEST_ASSERT_EQ(fd, -1);
+ TEST_ASSERT_ERRNO_IS(EPERM);
+}
+TEST_DEFINE(event_get_fd_when_values_requested,
+ "events - gpiod_line_event_get_fd(): line requested for values",
+ 0, { 8 });
+
+static void event_request_bulk_fail(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip = NULL;
+ struct gpiod_line_bulk bulk = GPIOD_LINE_BULK_INITIALIZER;
+ struct gpiod_line *line;
+ int rv, i;
+
+ chip = gpiod_chip_open(test_chip_path(0));
+ TEST_ASSERT_NOT_NULL(chip);
+
+ line = gpiod_chip_get_line(chip, 5);
+ TEST_ASSERT_NOT_NULL(line);
+
+ rv = gpiod_line_request_input(line, TEST_CONSUMER);
+ TEST_ASSERT_RET_OK(rv);
+
+ for (i = 0; i < 8; i++) {
+ line = gpiod_chip_get_line(chip, i);
+ TEST_ASSERT_NOT_NULL(line);
+ gpiod_line_bulk_add(&bulk, line);
+ }
+
+ rv = gpiod_line_request_bulk_both_edges_events(&bulk, TEST_CONSUMER);
+ TEST_ASSERT_EQ(rv, -1);
+ TEST_ASSERT_ERRNO_IS(EBUSY);
+}
+TEST_DEFINE(event_request_bulk_fail,
+ "events - failed bulk request (test reversed release)",
+ 0, { 8 });
+
+static void event_invalid_fd(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip = NULL;
+ struct gpiod_line_bulk bulk = GPIOD_LINE_BULK_INITIALIZER;
+ struct gpiod_line_bulk ev_bulk;
+ struct timespec ts = { 1, 0 };
+ struct gpiod_line *line;
+ int rv, fd;
+
+ chip = gpiod_chip_open(test_chip_path(0));
+ TEST_ASSERT_NOT_NULL(chip);
+
+ line = gpiod_chip_get_line(chip, 5);
+ TEST_ASSERT_NOT_NULL(line);
+
+ rv = gpiod_line_request_both_edges_events(line, TEST_CONSUMER);
+ TEST_ASSERT_RET_OK(rv);
+
+ fd = gpiod_line_event_get_fd(line);
+ close(fd);
+
+ rv = gpiod_line_event_wait(line, &ts);
+ TEST_ASSERT_EQ(rv, -1);
+ TEST_ASSERT_ERRNO_IS(EINVAL);
+
+ /*
+ * The single line variant calls gpiod_line_event_wait_bulk() with the
+ * event_bulk argument set to NULL, so test this use case explicitly
+ * as well.
+ */
+ gpiod_line_bulk_add(&bulk, line);
+ rv = gpiod_line_event_wait_bulk(&bulk, &ts, &ev_bulk);
+ TEST_ASSERT_EQ(rv, -1);
+ TEST_ASSERT_ERRNO_IS(EINVAL);
+}
+TEST_DEFINE(event_invalid_fd,
+ "events - gpiod_line_event_wait() error on closed fd",
+ 0, { 8 });
diff --git a/tests/tests-gpiodetect.c b/tests/tests-gpiodetect.c
new file mode 100644
index 0000000..bd5eeb1
--- /dev/null
+++ b/tests/tests-gpiodetect.c
@@ -0,0 +1,50 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+/* Test cases for the gpiodetect program. */
+
+#include <stdio.h>
+
+#include "gpiod-test.h"
+
+static void gpiodetect_simple(void)
+{
+ test_tool_run("gpiodetect", (char *)NULL);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_RET_OK(test_tool_exit_status());
+ TEST_ASSERT_NOT_NULL(test_tool_stdout());
+ TEST_ASSERT_STR_CONTAINS(test_tool_stdout(),
+ test_build_str("%s [gpio-mockup-A] (4 lines)",
+ test_chip_name(0)));
+ TEST_ASSERT_STR_CONTAINS(test_tool_stdout(),
+ test_build_str("%s [gpio-mockup-B] (8 lines)",
+ test_chip_name(1)));
+ TEST_ASSERT_STR_CONTAINS(test_tool_stdout(),
+ test_build_str("%s [gpio-mockup-C] (16 lines)",
+ test_chip_name(2)));
+ TEST_ASSERT_NULL(test_tool_stderr());
+}
+TEST_DEFINE(gpiodetect_simple,
+ "tools: gpiodetect - simple",
+ 0, { 4, 8, 16 });
+
+static void gpiodetect_invalid_args(void)
+{
+ test_tool_run("gpiodetect", "unused argument", (char *)NULL);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_EQ(test_tool_exit_status(), 1);
+ TEST_ASSERT_NULL(test_tool_stdout());
+ TEST_ASSERT_NOT_NULL(test_tool_stderr());
+ TEST_ASSERT_STR_CONTAINS(test_tool_stderr(), "unrecognized argument");
+}
+TEST_DEFINE(gpiodetect_invalid_args,
+ "tools: gpiodetect - invalid arguments",
+ 0, { });
diff --git a/tests/tests-gpiofind.c b/tests/tests-gpiofind.c
new file mode 100644
index 0000000..6d8e12a
--- /dev/null
+++ b/tests/tests-gpiofind.c
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+/* Test cases for the gpiofind program. */
+
+#include <stdio.h>
+
+#include "gpiod-test.h"
+
+static void gpiofind_found(void)
+{
+ test_tool_run("gpiofind", "gpio-mockup-B-7", (char *)NULL);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_RET_OK(test_tool_exit_status());
+ TEST_ASSERT_NOT_NULL(test_tool_stdout());
+ TEST_ASSERT_STR_EQ(test_tool_stdout(),
+ test_build_str("%s 7\n", test_chip_name(1)));
+ TEST_ASSERT_NULL(test_tool_stderr());
+}
+TEST_DEFINE(gpiofind_found,
+ "tools: gpiofind - found",
+ TEST_FLAG_NAMED_LINES, { 4, 8 });
+
+static void gpiofind_not_found(void)
+{
+ test_tool_run("gpiofind", "nonexistent", (char *)NULL);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_EQ(test_tool_exit_status(), 1);
+ TEST_ASSERT_NULL(test_tool_stdout());
+ TEST_ASSERT_NULL(test_tool_stderr());
+}
+TEST_DEFINE(gpiofind_not_found,
+ "tools: gpiofind - not found",
+ TEST_FLAG_NAMED_LINES, { 4, 8 });
+
+static void gpiofind_invalid_args(void)
+{
+ test_tool_run("gpiofind", (char *)NULL);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_EQ(test_tool_exit_status(), 1);
+ TEST_ASSERT_NULL(test_tool_stdout());
+ TEST_ASSERT_NOT_NULL(test_tool_stderr());
+ TEST_ASSERT_STR_CONTAINS(test_tool_stderr(),
+ "exactly one GPIO line name must be specified");
+
+ test_tool_run("gpiofind", "first argument",
+ "second argument", (char *)NULL);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_EQ(test_tool_exit_status(), 1);
+ TEST_ASSERT_NULL(test_tool_stdout());
+ TEST_ASSERT_NOT_NULL(test_tool_stderr());
+ TEST_ASSERT_STR_CONTAINS(test_tool_stderr(),
+ "exactly one GPIO line name must be specified");
+}
+TEST_DEFINE(gpiofind_invalid_args,
+ "tools: gpiofind - invalid arguments",
+ 0, { });
diff --git a/tests/tests-gpioget.c b/tests/tests-gpioget.c
new file mode 100644
index 0000000..2c9bcb1
--- /dev/null
+++ b/tests/tests-gpioget.c
@@ -0,0 +1,181 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+/* Test cases for the gpioget program. */
+
+#include "gpiod-test.h"
+
+static void gpioget_read_all_lines(void)
+{
+ unsigned int offsets[4];
+ int rv, values[4];
+
+ test_tool_run("gpioget", test_chip_name(1),
+ "0", "1", "2", "3", "4", "5", "6", "7", (char *)NULL);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_RET_OK(test_tool_exit_status());
+ TEST_ASSERT_NOT_NULL(test_tool_stdout());
+ TEST_ASSERT_NULL(test_tool_stderr());
+ TEST_ASSERT_STR_EQ(test_tool_stdout(), "0 0 0 0 0 0 0 0\n");
+
+ offsets[0] = 2;
+ offsets[1] = 3;
+ offsets[2] = 5;
+ offsets[3] = 7;
+
+ values[0] = values[1] = values[2] = values[3] = 1;
+
+ rv = gpiod_ctxless_set_value_multiple(test_chip_name(1), offsets,
+ values, 4, false, TEST_CONSUMER,
+ NULL, NULL);
+ TEST_ASSERT_RET_OK(rv);
+
+ test_tool_run("gpioget", test_chip_name(1),
+ "0", "1", "2", "3", "4", "5", "6", "7", (char *)NULL);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_RET_OK(test_tool_exit_status());
+ TEST_ASSERT_NOT_NULL(test_tool_stdout());
+ TEST_ASSERT_NULL(test_tool_stderr());
+ TEST_ASSERT_STR_EQ(test_tool_stdout(), "0 0 1 1 0 1 0 1\n");
+}
+TEST_DEFINE(gpioget_read_all_lines,
+ "tools: gpioget - read all lines",
+ 0, { 8, 8, 8 });
+
+static void gpioget_read_all_lines_active_low(void)
+{
+ unsigned int offsets[4];
+ int rv, values[4];
+
+ test_tool_run("gpioget", "--active-low", test_chip_name(1),
+ "0", "1", "2", "3", "4", "5", "6", "7", (char *)NULL);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_RET_OK(test_tool_exit_status());
+ TEST_ASSERT_NOT_NULL(test_tool_stdout());
+ TEST_ASSERT_NULL(test_tool_stderr());
+ TEST_ASSERT_STR_EQ(test_tool_stdout(), "1 1 1 1 1 1 1 1\n");
+
+ offsets[0] = 2;
+ offsets[1] = 3;
+ offsets[2] = 5;
+ offsets[3] = 7;
+
+ values[0] = values[1] = values[2] = values[3] = 1;
+
+ rv = gpiod_ctxless_set_value_multiple(test_chip_name(1), offsets,
+ values, 4, false, TEST_CONSUMER,
+ NULL, NULL);
+ TEST_ASSERT_RET_OK(rv);
+
+ test_tool_run("gpioget", "--active-low", test_chip_name(1),
+ "0", "1", "2", "3", "4", "5", "6", "7", (char *)NULL);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_RET_OK(test_tool_exit_status());
+ TEST_ASSERT_NOT_NULL(test_tool_stdout());
+ TEST_ASSERT_NULL(test_tool_stderr());
+ TEST_ASSERT_STR_EQ(test_tool_stdout(), "1 1 0 0 1 0 1 0\n");
+}
+TEST_DEFINE(gpioget_read_all_lines_active_low,
+ "tools: gpioget - read all lines (active-low)",
+ 0, { 8, 8, 8 });
+
+static void gpioget_read_some_lines(void)
+{
+ unsigned int offsets[3];
+ int rv, values[3];
+
+ test_tool_run("gpioget", test_chip_name(1),
+ "0", "1", "4", "6", (char *)NULL);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_RET_OK(test_tool_exit_status());
+ TEST_ASSERT_NOT_NULL(test_tool_stdout());
+ TEST_ASSERT_NULL(test_tool_stderr());
+ TEST_ASSERT_STR_EQ(test_tool_stdout(), "0 0 0 0\n");
+
+ offsets[0] = 1;
+ offsets[1] = 4;
+ offsets[2] = 6;
+
+ values[0] = values[1] = values[2] = 1;
+
+ rv = gpiod_ctxless_set_value_multiple(test_chip_name(1), offsets,
+ values, 3, false, TEST_CONSUMER,
+ NULL, NULL);
+ TEST_ASSERT_RET_OK(rv);
+
+ test_tool_run("gpioget", test_chip_name(1),
+ "0", "1", "4", "6", (char *)NULL);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_RET_OK(test_tool_exit_status());
+ TEST_ASSERT_NOT_NULL(test_tool_stdout());
+ TEST_ASSERT_NULL(test_tool_stderr());
+ TEST_ASSERT_STR_EQ(test_tool_stdout(), "0 1 1 1\n");
+}
+TEST_DEFINE(gpioget_read_some_lines,
+ "tools: gpioget - read some lines",
+ 0, { 8, 8, 8 });
+
+static void gpioget_no_arguments(void)
+{
+ test_tool_run("gpioget", (char *)NULL);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_EQ(test_tool_exit_status(), 1);
+ TEST_ASSERT_NULL(test_tool_stdout());
+ TEST_ASSERT_NOT_NULL(test_tool_stderr());
+ TEST_ASSERT_STR_CONTAINS(test_tool_stderr(),
+ "gpiochip must be specified");
+}
+TEST_DEFINE(gpioget_no_arguments,
+ "tools: gpioget - no arguments",
+ 0, { });
+
+static void gpioget_no_lines_specified(void)
+{
+ test_tool_run("gpioget", test_chip_name(1), (char *)NULL);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_EQ(test_tool_exit_status(), 1);
+ TEST_ASSERT_NULL(test_tool_stdout());
+ TEST_ASSERT_NOT_NULL(test_tool_stderr());
+ TEST_ASSERT_STR_CONTAINS(test_tool_stderr(),
+ "at least one GPIO line offset must be specified");
+}
+TEST_DEFINE(gpioget_no_lines_specified,
+ "tools: gpioget - no lines specified",
+ 0, { 4, 4 });
+
+static void gpioget_too_many_lines_specified(void)
+{
+ test_tool_run("gpioget", test_chip_name(0),
+ "0", "1", "2", "3", "4", (char *)NULL);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_EQ(test_tool_exit_status(), 1);
+ TEST_ASSERT_NULL(test_tool_stdout());
+ TEST_ASSERT_NOT_NULL(test_tool_stderr());
+ TEST_ASSERT_STR_CONTAINS(test_tool_stderr(),
+ "error reading GPIO values");
+}
+TEST_DEFINE(gpioget_too_many_lines_specified,
+ "tools: gpioget - too many lines specified",
+ 0, { 4 });
diff --git a/tests/tests-gpioinfo.c b/tests/tests-gpioinfo.c
new file mode 100644
index 0000000..162896f
--- /dev/null
+++ b/tests/tests-gpioinfo.c
@@ -0,0 +1,141 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+/* Test cases for the gpioinfo program. */
+
+#include <stdio.h>
+
+#include "gpiod-test.h"
+
+static void gpioinfo_dump_all_chips(void)
+{
+ test_tool_run("gpioinfo", (char *)NULL);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_RET_OK(test_tool_exit_status());
+ TEST_ASSERT_NOT_NULL(test_tool_stdout());
+ TEST_ASSERT_STR_CONTAINS(test_tool_stdout(),
+ test_build_str("%s - 4 lines:",
+ test_chip_name(0)));
+ TEST_ASSERT_STR_CONTAINS(test_tool_stdout(),
+ test_build_str("%s - 8 lines:",
+ test_chip_name(1)));
+ TEST_ASSERT_REGEX_MATCH(test_tool_stdout(),
+ "\\s+line\\s+0:\\s+unnamed\\s+unused\\s+input\\s+active-high");
+ TEST_ASSERT_REGEX_MATCH(test_tool_stdout(),
+ "\\s+line\\s+7:\\s+unnamed\\s+unused\\s+input\\s+active-high");
+}
+TEST_DEFINE(gpioinfo_dump_all_chips,
+ "tools: gpioinfo - dump all chips",
+ 0, { 4, 8 });
+
+static void gpioinfo_dump_all_chips_one_exported(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip = NULL;
+ struct gpiod_line *line;
+ int rv;
+
+ chip = gpiod_chip_open(test_chip_path(1));
+ TEST_ASSERT_NOT_NULL(chip);
+
+ line = gpiod_chip_get_line(chip, 7);
+ TEST_ASSERT_NOT_NULL(line);
+
+ rv = gpiod_line_request_input_flags(line, TEST_CONSUMER,
+ GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW);
+ TEST_ASSERT_RET_OK(rv);
+
+ test_tool_run("gpioinfo", (char *)NULL);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_RET_OK(test_tool_exit_status());
+ TEST_ASSERT_NOT_NULL(test_tool_stdout());
+ TEST_ASSERT_STR_CONTAINS(test_tool_stdout(),
+ test_build_str("%s - 4 lines:",
+ test_chip_name(0)));
+ TEST_ASSERT_STR_CONTAINS(test_tool_stdout(),
+ test_build_str("%s - 8 lines:",
+ test_chip_name(1)));
+ TEST_ASSERT_REGEX_MATCH(test_tool_stdout(),
+ "\\s+line\\s+0:\\s+unnamed\\s+unused\\s+input\\s+active-high");
+ TEST_ASSERT_REGEX_MATCH(test_tool_stdout(),
+ "\\s+line\\s+7:\\s+unnamed\\s+\\\"" TEST_CONSUMER "\\\"\\s+input\\s+active-low");
+}
+TEST_DEFINE(gpioinfo_dump_all_chips_one_exported,
+ "tools: gpioinfo - dump all chips (one line exported)",
+ 0, { 4, 8 });
+
+static void gpioinfo_dump_one_chip(void)
+{
+ test_tool_run("gpioinfo", test_chip_name(1), (char *)NULL);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_RET_OK(test_tool_exit_status());
+ TEST_ASSERT_NOT_NULL(test_tool_stdout());
+ TEST_ASSERT_STR_NOTCONT(test_tool_stdout(),
+ test_build_str("%s - 8 lines:",
+ test_chip_name(0)));
+ TEST_ASSERT_STR_CONTAINS(test_tool_stdout(),
+ test_build_str("%s - 4 lines:",
+ test_chip_name(1)));
+ TEST_ASSERT_REGEX_MATCH(test_tool_stdout(),
+ "\\s+line\\s+0:\\s+unnamed\\s+unused\\s+input\\s+active-high");
+ TEST_ASSERT_REGEX_NOMATCH(test_tool_stdout(),
+ "\\s+line\\s+7:\\s+unnamed\\s+unused\\s+input\\s+active-high");
+}
+TEST_DEFINE(gpioinfo_dump_one_chip,
+ "tools: gpioinfo - dump one chip",
+ 0, { 8, 4 });
+
+static void gpioinfo_dump_all_but_one_chip(void)
+{
+ test_tool_run("gpioinfo", test_chip_name(0),
+ test_chip_name(1), test_chip_name(3), (char *)NULL);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_RET_OK(test_tool_exit_status());
+ TEST_ASSERT_NOT_NULL(test_tool_stdout());
+ TEST_ASSERT_STR_NOTCONT(test_tool_stdout(),
+ test_build_str("%s - 8 lines:",
+ test_chip_name(2)));
+ TEST_ASSERT_STR_CONTAINS(test_tool_stdout(),
+ test_build_str("%s - 4 lines:",
+ test_chip_name(0)));
+ TEST_ASSERT_STR_CONTAINS(test_tool_stdout(),
+ test_build_str("%s - 4 lines:",
+ test_chip_name(1)));
+ TEST_ASSERT_STR_CONTAINS(test_tool_stdout(),
+ test_build_str("%s - 4 lines:",
+ test_chip_name(3)));
+ TEST_ASSERT_REGEX_MATCH(test_tool_stdout(),
+ "\\s+line\\s+0:\\s+unnamed\\s+unused\\s+input\\s+active-high");
+ TEST_ASSERT_REGEX_NOMATCH(test_tool_stdout(),
+ "\\s+line\\s+7:\\s+unnamed\\s+unused\\s+input\\s+active-high");
+}
+TEST_DEFINE(gpioinfo_dump_all_but_one_chip,
+ "tools: gpioinfo - dump all but one chip",
+ 0, { 4, 4, 8, 4 });
+
+static void gpioinfo_inexistent_chip(void)
+{
+ test_tool_run("gpioinfo", "inexistent", (char *)NULL);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_EQ(test_tool_exit_status(), 1);
+ TEST_ASSERT_NULL(test_tool_stdout());
+ TEST_ASSERT_NOT_NULL(test_tool_stderr());
+ TEST_ASSERT_STR_CONTAINS(test_tool_stderr(),
+ "looking up chip inexistent");
+}
+TEST_DEFINE(gpioinfo_inexistent_chip,
+ "tools: gpioinfo - inexistent chip",
+ 0, { 8, 4 });
diff --git a/tests/tests-gpiomon.c b/tests/tests-gpiomon.c
new file mode 100644
index 0000000..fd21391
--- /dev/null
+++ b/tests/tests-gpiomon.c
@@ -0,0 +1,375 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+/* Test cases for the gpiomon program. */
+
+#include <signal.h>
+#include <unistd.h>
+
+#include "gpiod-test.h"
+
+static void gpiomon_single_rising_edge_event(void)
+{
+ test_tool_run("gpiomon", "--rising-edge", "--num-events=1",
+ test_chip_name(1), "4", (char *)NULL);
+ test_set_event(1, 4, TEST_EVENT_RISING, 200);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_RET_OK(test_tool_exit_status());
+ TEST_ASSERT_NOT_NULL(test_tool_stdout());
+ TEST_ASSERT_NULL(test_tool_stderr());
+ TEST_ASSERT_REGEX_MATCH(test_tool_stdout(),
+ "event\\:\\s+RISING\\s+EDGE\\s+offset\\:\\s+4\\s+timestamp:\\s+\\[[0-9]+\\.[0-9]+\\]");
+}
+TEST_DEFINE(gpiomon_single_rising_edge_event,
+ "tools: gpiomon - single rising edge event",
+ 0, { 8, 8 });
+
+static void gpiomon_single_rising_edge_event_active_low(void)
+{
+ test_tool_run("gpiomon", "--rising-edge", "--num-events=1",
+ "--active-low", test_chip_name(1), "4", (char *)NULL);
+ test_set_event(1, 4, TEST_EVENT_RISING, 200);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_RET_OK(test_tool_exit_status());
+ TEST_ASSERT_NOT_NULL(test_tool_stdout());
+ TEST_ASSERT_NULL(test_tool_stderr());
+ TEST_ASSERT_REGEX_MATCH(test_tool_stdout(),
+ "event\\:\\s+RISING\\s+EDGE\\s+offset\\:\\s+4\\s+timestamp:\\s+\\[[0-9]+\\.[0-9]+\\]");
+}
+TEST_DEFINE(gpiomon_single_rising_edge_event_active_low,
+ "tools: gpiomon - single rising edge event (active-low)",
+ 0, { 8, 8 });
+
+static void gpiomon_single_rising_edge_event_silent(void)
+{
+ test_tool_run("gpiomon", "--rising-edge", "--num-events=1",
+ "--silent", test_chip_name(1), "4", (char *)NULL);
+ test_set_event(1, 4, TEST_EVENT_RISING, 200);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_RET_OK(test_tool_exit_status());
+ TEST_ASSERT_NULL(test_tool_stdout());
+ TEST_ASSERT_NULL(test_tool_stderr());
+}
+TEST_DEFINE(gpiomon_single_rising_edge_event_silent,
+ "tools: gpiomon - single rising edge event (silent mode)",
+ 0, { 8, 8 });
+
+static void gpiomon_four_alternating_events(void)
+{
+ test_tool_run("gpiomon", "--num-events=4",
+ test_chip_name(1), "4", (char *)NULL);
+ test_set_event(1, 4, TEST_EVENT_ALTERNATING, 100);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_RET_OK(test_tool_exit_status());
+ TEST_ASSERT_NOT_NULL(test_tool_stdout());
+ TEST_ASSERT_NULL(test_tool_stderr());
+ TEST_ASSERT_REGEX_MATCH(test_tool_stdout(),
+ "event\\:\\s+FALLING\\s+EDGE\\s+offset\\:\\s+4\\s+timestamp:\\s+\\[[0-9]+\\.[0-9]+\\]");
+ TEST_ASSERT_REGEX_MATCH(test_tool_stdout(),
+ "event\\:\\s+RISING\\s+EDGE\\s+offset\\:\\s+4\\s+timestamp:\\s+\\[[0-9]+\\.[0-9]+\\]");
+}
+TEST_DEFINE(gpiomon_four_alternating_events,
+ "tools: gpiomon - four alternating events",
+ 0, { 8, 8 });
+
+static void gpiomon_falling_edge_events_sigint(void)
+{
+ test_tool_run("gpiomon", "--falling-edge",
+ test_chip_name(0), "4", (char *)NULL);
+ test_set_event(0, 4, TEST_EVENT_FALLING, 100);
+ usleep(200000);
+ test_tool_signal(SIGINT);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_RET_OK(test_tool_exit_status());
+ TEST_ASSERT_NOT_NULL(test_tool_stdout());
+ TEST_ASSERT_NULL(test_tool_stderr());
+ TEST_ASSERT_REGEX_MATCH(test_tool_stdout(),
+ "event\\:\\s+FALLING\\s+EDGE\\s+offset\\:\\s+4\\s+timestamp:\\s+\\[[0-9]+\\.[0-9]+\\]");
+}
+TEST_DEFINE(gpiomon_falling_edge_events_sigint,
+ "tools: gpiomon - receive falling edge events and kill with SIGINT",
+ 0, { 8, 8 });
+
+static void gpiomon_both_events_sigterm(void)
+{
+ test_tool_run("gpiomon", "--falling-edge", "--rising-edge",
+ test_chip_name(0), "4", (char *)NULL);
+ test_set_event(0, 4, TEST_EVENT_ALTERNATING, 100);
+ usleep(300000);
+ test_tool_signal(SIGTERM);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_RET_OK(test_tool_exit_status());
+ TEST_ASSERT_NOT_NULL(test_tool_stdout());
+ TEST_ASSERT_NULL(test_tool_stderr());
+ TEST_ASSERT_REGEX_MATCH(test_tool_stdout(),
+ "event\\:\\s+FALLING\\s+EDGE\\s+offset\\:\\s+4\\s+timestamp:\\s+\\[[0-9]+\\.[0-9]+\\]");
+ TEST_ASSERT_REGEX_MATCH(test_tool_stdout(),
+ "event\\:\\s+RISING\\s+EDGE\\s+offset\\:\\s+4\\s+timestamp:\\s+\\[[0-9]+\\.[0-9]+\\]");
+}
+TEST_DEFINE(gpiomon_both_events_sigterm,
+ "tools: gpiomon - receive both types of events and kill with SIGTERM",
+ 0, { 8, 8 });
+
+static void gpiomon_watch_multiple_lines(void)
+{
+ test_tool_run("gpiomon", "--format=%o", test_chip_name(0),
+ "1", "2", "3", "4", "5", (char *)NULL);
+ test_set_event(0, 2, TEST_EVENT_ALTERNATING, 100);
+ usleep(150000);
+ test_set_event(0, 3, TEST_EVENT_ALTERNATING, 100);
+ usleep(150000);
+ test_set_event(0, 4, TEST_EVENT_ALTERNATING, 100);
+ usleep(150000);
+ test_tool_signal(SIGTERM);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_RET_OK(test_tool_exit_status());
+ TEST_ASSERT_NULL(test_tool_stderr());
+ TEST_ASSERT_NOT_NULL(test_tool_stdout());
+ TEST_ASSERT_STR_EQ(test_tool_stdout(), "2\n3\n4\n");
+
+}
+TEST_DEFINE(gpiomon_watch_multiple_lines,
+ "tools: gpiomon - watch multiple lines",
+ 0, { 8, 8 });
+
+static void gpiomon_watch_multiple_lines_not_in_order(void)
+{
+ test_tool_run("gpiomon", "--format=%o", test_chip_name(0),
+ "5", "2", "7", "1", "6", (char *)NULL);
+ test_set_event(0, 2, TEST_EVENT_ALTERNATING, 100);
+ usleep(150000);
+ test_set_event(0, 1, TEST_EVENT_ALTERNATING, 100);
+ usleep(150000);
+ test_set_event(0, 6, TEST_EVENT_ALTERNATING, 100);
+ usleep(150000);
+ test_tool_signal(SIGTERM);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_RET_OK(test_tool_exit_status());
+ TEST_ASSERT_NULL(test_tool_stderr());
+ TEST_ASSERT_NOT_NULL(test_tool_stdout());
+ TEST_ASSERT_STR_EQ(test_tool_stdout(), "2\n1\n6\n");
+
+}
+TEST_DEFINE(gpiomon_watch_multiple_lines_not_in_order,
+ "tools: gpiomon - watch multiple lines (offsets not in order)",
+ 0, { 8, 8 });
+
+static void gpiomon_request_the_same_line_twice(void)
+{
+ test_tool_run("gpiomon", test_chip_name(0), "2", "2", (char *)NULL);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_EQ(test_tool_exit_status(), 1);
+ TEST_ASSERT_NULL(test_tool_stdout());
+ TEST_ASSERT_NOT_NULL(test_tool_stderr());
+ TEST_ASSERT_STR_CONTAINS(test_tool_stderr(),
+ "error waiting for events");
+}
+TEST_DEFINE(gpiomon_request_the_same_line_twice,
+ "tools: gpiomon - request the same line twice",
+ 0, { 8, 8 });
+
+static void gpiomon_no_arguments(void)
+{
+ test_tool_run("gpiomon", (char *)NULL);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_EQ(test_tool_exit_status(), 1);
+ TEST_ASSERT_NULL(test_tool_stdout());
+ TEST_ASSERT_NOT_NULL(test_tool_stderr());
+ TEST_ASSERT_STR_CONTAINS(test_tool_stderr(),
+ "gpiochip must be specified");
+}
+TEST_DEFINE(gpiomon_no_arguments,
+ "tools: gpiomon - no arguments",
+ 0, { });
+
+static void gpiomon_line_not_specified(void)
+{
+ test_tool_run("gpiomon", test_chip_name(1), (char *)NULL);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_EQ(test_tool_exit_status(), 1);
+ TEST_ASSERT_NULL(test_tool_stdout());
+ TEST_ASSERT_NOT_NULL(test_tool_stderr());
+ TEST_ASSERT_STR_CONTAINS(test_tool_stderr(),
+ "GPIO line offset must be specified");
+}
+TEST_DEFINE(gpiomon_line_not_specified,
+ "tools: gpiomon - line not specified",
+ 0, { 4, 4 });
+
+static void gpiomon_line_out_of_range(void)
+{
+ test_tool_run("gpiomon", test_chip_name(0), "4", (char *)NULL);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_EQ(test_tool_exit_status(), 1);
+ TEST_ASSERT_NULL(test_tool_stdout());
+ TEST_ASSERT_NOT_NULL(test_tool_stderr());
+ TEST_ASSERT_STR_CONTAINS(test_tool_stderr(),
+ "error waiting for events");
+}
+TEST_DEFINE(gpiomon_line_out_of_range,
+ "tools: gpiomon - line out of range",
+ 0, { 4 });
+
+static void gpiomon_custom_format_event_and_offset(void)
+{
+ test_tool_run("gpiomon", "--num-events=1", "--format=%e %o",
+ test_chip_name(0), "3", (char *)NULL);
+ test_set_event(0, 3, TEST_EVENT_RISING, 100);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_RET_OK(test_tool_exit_status());
+ TEST_ASSERT_NOT_NULL(test_tool_stdout());
+ TEST_ASSERT_NULL(test_tool_stderr());
+ TEST_ASSERT_STR_EQ(test_tool_stdout(), "1 3\n");
+}
+TEST_DEFINE(gpiomon_custom_format_event_and_offset,
+ "tools: gpiomon - custom output format: event and offset",
+ 0, { 8, 8 });
+
+static void gpiomon_custom_format_event_and_offset_joined(void)
+{
+ test_tool_run("gpiomon", "--num-events=1", "--format=%e%o",
+ test_chip_name(0), "3", (char *)NULL);
+ test_set_event(0, 3, TEST_EVENT_RISING, 100);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_RET_OK(test_tool_exit_status());
+ TEST_ASSERT_NOT_NULL(test_tool_stdout());
+ TEST_ASSERT_NULL(test_tool_stderr());
+ TEST_ASSERT_STR_EQ(test_tool_stdout(), "13\n");
+}
+TEST_DEFINE(gpiomon_custom_format_event_and_offset_joined,
+ "tools: gpiomon - custom output format: event and offset, joined strings",
+ 0, { 8, 8 });
+
+static void gpiomon_custom_format_timestamp(void)
+{
+ test_tool_run("gpiomon", "--num-events=1", "--format=%e %o %s.%n",
+ test_chip_name(0), "3", (char *)NULL);
+ test_set_event(0, 3, TEST_EVENT_RISING, 100);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_RET_OK(test_tool_exit_status());
+ TEST_ASSERT_NOT_NULL(test_tool_stdout());
+ TEST_ASSERT_NULL(test_tool_stderr());
+ TEST_ASSERT_REGEX_MATCH(test_tool_stdout(), "1 3 [0-9]+\\.[0-9]+");
+}
+TEST_DEFINE(gpiomon_custom_format_timestamp,
+ "tools: gpiomon - custom output format: timestamp",
+ 0, { 8, 8 });
+
+static void gpiomon_custom_format_double_percent_sign(void)
+{
+ test_tool_run("gpiomon", "--num-events=1", "--format=%%",
+ test_chip_name(0), "3", (char *)NULL);
+ test_set_event(0, 3, TEST_EVENT_RISING, 100);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_RET_OK(test_tool_exit_status());
+ TEST_ASSERT_NOT_NULL(test_tool_stdout());
+ TEST_ASSERT_NULL(test_tool_stderr());
+ TEST_ASSERT_STR_EQ(test_tool_stdout(), "%\n");
+}
+TEST_DEFINE(gpiomon_custom_format_double_percent_sign,
+ "tools: gpiomon - custom output format: double percent sign",
+ 0, { 8, 8 });
+
+static void gpiomon_custom_format_double_percent_sign_and_spec(void)
+{
+ test_tool_run("gpiomon", "--num-events=1", "--format=%%e",
+ test_chip_name(0), "3", (char *)NULL);
+ test_set_event(0, 3, TEST_EVENT_RISING, 100);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_RET_OK(test_tool_exit_status());
+ TEST_ASSERT_NOT_NULL(test_tool_stdout());
+ TEST_ASSERT_NULL(test_tool_stderr());
+ TEST_ASSERT_STR_EQ(test_tool_stdout(), "%e\n");
+}
+TEST_DEFINE(gpiomon_custom_format_double_percent_sign_and_spec,
+ "tools: gpiomon - custom output format: double percent sign with specifier",
+ 0, { 8, 8 });
+
+static void gpiomon_custom_format_single_percent_sign(void)
+{
+ test_tool_run("gpiomon", "--num-events=1", "--format=%",
+ test_chip_name(0), "3", (char *)NULL);
+ test_set_event(0, 3, TEST_EVENT_RISING, 100);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_RET_OK(test_tool_exit_status());
+ TEST_ASSERT_NOT_NULL(test_tool_stdout());
+ TEST_ASSERT_NULL(test_tool_stderr());
+ TEST_ASSERT_STR_EQ(test_tool_stdout(), "%\n");
+}
+TEST_DEFINE(gpiomon_custom_format_single_percent_sign,
+ "tools: gpiomon - custom output format: single percent sign",
+ 0, { 8, 8 });
+
+static void gpiomon_custom_format_single_percent_sign_between_chars(void)
+{
+ test_tool_run("gpiomon", "--num-events=1", "--format=foo % bar",
+ test_chip_name(0), "3", (char *)NULL);
+ test_set_event(0, 3, TEST_EVENT_RISING, 100);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_RET_OK(test_tool_exit_status());
+ TEST_ASSERT_NOT_NULL(test_tool_stdout());
+ TEST_ASSERT_NULL(test_tool_stderr());
+ TEST_ASSERT_STR_EQ(test_tool_stdout(), "foo % bar\n");
+}
+TEST_DEFINE(gpiomon_custom_format_single_percent_sign_between_chars,
+ "tools: gpiomon - custom output format: single percent sign between other characters",
+ 0, { 8, 8 });
+
+static void gpiomon_custom_format_unknown_specifier(void)
+{
+ test_tool_run("gpiomon", "--num-events=1", "--format=%x",
+ test_chip_name(0), "3", (char *)NULL);
+ test_set_event(0, 3, TEST_EVENT_RISING, 100);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_RET_OK(test_tool_exit_status());
+ TEST_ASSERT_NOT_NULL(test_tool_stdout());
+ TEST_ASSERT_NULL(test_tool_stderr());
+ TEST_ASSERT_STR_EQ(test_tool_stdout(), "%x\n");
+}
+TEST_DEFINE(gpiomon_custom_format_unknown_specifier,
+ "tools: gpiomon - custom output format: unknown specifier",
+ 0, { 8, 8 });
diff --git a/tests/tests-gpioset.c b/tests/tests-gpioset.c
new file mode 100644
index 0000000..ab7b514
--- /dev/null
+++ b/tests/tests-gpioset.c
@@ -0,0 +1,385 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+/* Test cases for the gpioset program. */
+
+#include <signal.h>
+#include <unistd.h>
+
+#include "gpiod-test.h"
+
+static void gpioset_set_lines_and_exit(void)
+{
+ unsigned int offsets[8];
+ int rv, values[8];
+
+ test_tool_run("gpioset", test_chip_name(2),
+ "0=0", "1=0", "2=1", "3=1",
+ "4=1", "5=1", "6=0", "7=1", (char *)NULL);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_RET_OK(test_tool_exit_status());
+ TEST_ASSERT_NULL(test_tool_stdout());
+ TEST_ASSERT_NULL(test_tool_stderr());
+
+ offsets[0] = 0;
+ offsets[1] = 1;
+ offsets[2] = 2;
+ offsets[3] = 3;
+ offsets[4] = 4;
+ offsets[5] = 5;
+ offsets[6] = 6;
+ offsets[7] = 7;
+
+ rv = gpiod_ctxless_get_value_multiple(test_chip_name(2), offsets,
+ values, 8, false, TEST_CONSUMER);
+ TEST_ASSERT_RET_OK(rv);
+
+ TEST_ASSERT_EQ(values[0], 0);
+ TEST_ASSERT_EQ(values[1], 0);
+ TEST_ASSERT_EQ(values[2], 1);
+ TEST_ASSERT_EQ(values[3], 1);
+ TEST_ASSERT_EQ(values[4], 1);
+ TEST_ASSERT_EQ(values[5], 1);
+ TEST_ASSERT_EQ(values[6], 0);
+ TEST_ASSERT_EQ(values[7], 1);
+}
+TEST_DEFINE(gpioset_set_lines_and_exit,
+ "tools: gpioset - set lines and exit",
+ 0, { 8, 8, 8 });
+
+static void gpioset_set_lines_and_exit_active_low(void)
+{
+ unsigned int offsets[8];
+ int rv, values[8];
+
+ test_tool_run("gpioset", "--active-low", test_chip_name(2),
+ "0=0", "1=0", "2=1", "3=1",
+ "4=1", "5=1", "6=0", "7=1", (char *)NULL);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_RET_OK(test_tool_exit_status());
+ TEST_ASSERT_NULL(test_tool_stdout());
+ TEST_ASSERT_NULL(test_tool_stderr());
+
+ offsets[0] = 0;
+ offsets[1] = 1;
+ offsets[2] = 2;
+ offsets[3] = 3;
+ offsets[4] = 4;
+ offsets[5] = 5;
+ offsets[6] = 6;
+ offsets[7] = 7;
+
+ rv = gpiod_ctxless_get_value_multiple(test_chip_name(2), offsets,
+ values, 8, false, TEST_CONSUMER);
+ TEST_ASSERT_RET_OK(rv);
+
+ TEST_ASSERT_EQ(values[0], 1);
+ TEST_ASSERT_EQ(values[1], 1);
+ TEST_ASSERT_EQ(values[2], 0);
+ TEST_ASSERT_EQ(values[3], 0);
+ TEST_ASSERT_EQ(values[4], 0);
+ TEST_ASSERT_EQ(values[5], 0);
+ TEST_ASSERT_EQ(values[6], 1);
+ TEST_ASSERT_EQ(values[7], 0);
+}
+TEST_DEFINE(gpioset_set_lines_and_exit_active_low,
+ "tools: gpioset - set lines and exit (active-low)",
+ 0, { 8, 8, 8 });
+
+static void gpioset_set_lines_and_exit_explicit_mode(void)
+{
+ unsigned int offsets[8];
+ int rv, values[8];
+
+ test_tool_run("gpioset", "--mode=exit", test_chip_name(2),
+ "0=0", "1=0", "2=1", "3=1",
+ "4=1", "5=1", "6=0", "7=1", (char *)NULL);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_RET_OK(test_tool_exit_status());
+ TEST_ASSERT_NULL(test_tool_stdout());
+ TEST_ASSERT_NULL(test_tool_stderr());
+
+ offsets[0] = 0;
+ offsets[1] = 1;
+ offsets[2] = 2;
+ offsets[3] = 3;
+ offsets[4] = 4;
+ offsets[5] = 5;
+ offsets[6] = 6;
+ offsets[7] = 7;
+
+ rv = gpiod_ctxless_get_value_multiple(test_chip_name(2), offsets,
+ values, 8, false, TEST_CONSUMER);
+ TEST_ASSERT_RET_OK(rv);
+
+ TEST_ASSERT_EQ(values[0], 0);
+ TEST_ASSERT_EQ(values[1], 0);
+ TEST_ASSERT_EQ(values[2], 1);
+ TEST_ASSERT_EQ(values[3], 1);
+ TEST_ASSERT_EQ(values[4], 1);
+ TEST_ASSERT_EQ(values[5], 1);
+ TEST_ASSERT_EQ(values[6], 0);
+ TEST_ASSERT_EQ(values[7], 1);
+}
+TEST_DEFINE(gpioset_set_lines_and_exit_explicit_mode,
+ "tools: gpioset - set lines and exit (explicit mode argument)",
+ 0, { 8, 8, 8 });
+
+static void gpioset_set_some_lines_and_wait_for_enter(void)
+{
+ unsigned int offsets[5];
+ int rv, values[5];
+
+ test_tool_run("gpioset", "--mode=wait", test_chip_name(2),
+ "1=0", "2=1", "5=1", "6=0", "7=1", (char *)NULL);
+ test_tool_stdin_write("\n");
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_RET_OK(test_tool_exit_status());
+ TEST_ASSERT_NULL(test_tool_stdout());
+ TEST_ASSERT_NULL(test_tool_stderr());
+
+ offsets[0] = 1;
+ offsets[1] = 2;
+ offsets[2] = 5;
+ offsets[3] = 6;
+ offsets[4] = 7;
+
+ rv = gpiod_ctxless_get_value_multiple(test_chip_name(2), offsets,
+ values, 5, false, TEST_CONSUMER);
+ TEST_ASSERT_RET_OK(rv);
+
+ TEST_ASSERT_EQ(values[0], 0);
+ TEST_ASSERT_EQ(values[1], 1);
+ TEST_ASSERT_EQ(values[2], 1);
+ TEST_ASSERT_EQ(values[3], 0);
+ TEST_ASSERT_EQ(values[4], 1);
+}
+TEST_DEFINE(gpioset_set_some_lines_and_wait_for_enter,
+ "tools: gpioset - set some lines and wait for enter",
+ 0, { 8, 8, 8 });
+
+static void gpioset_set_some_lines_and_wait_for_signal(void)
+{
+ static const int signals[] = { SIGTERM, SIGINT };
+
+ unsigned int offsets[5], i;
+ int rv, values[5];
+
+ for (i = 0; i < TEST_ARRAY_SIZE(signals); i++) {
+ test_tool_run("gpioset", "--mode=signal", test_chip_name(2),
+ "1=0", "2=1", "5=0", "6=0", "7=1", (char *)NULL);
+ usleep(200000);
+ test_tool_signal(signals[i]);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_RET_OK(test_tool_exit_status());
+ TEST_ASSERT_NULL(test_tool_stdout());
+ TEST_ASSERT_NULL(test_tool_stderr());
+
+ offsets[0] = 1;
+ offsets[1] = 2;
+ offsets[2] = 5;
+ offsets[3] = 6;
+ offsets[4] = 7;
+
+ rv = gpiod_ctxless_get_value_multiple(test_chip_name(2),
+ offsets, values,
+ 5, false, TEST_CONSUMER);
+ TEST_ASSERT_RET_OK(rv);
+
+ TEST_ASSERT_EQ(values[0], 0);
+ TEST_ASSERT_EQ(values[1], 1);
+ TEST_ASSERT_EQ(values[2], 0);
+ TEST_ASSERT_EQ(values[3], 0);
+ TEST_ASSERT_EQ(values[4], 1);
+ }
+}
+TEST_DEFINE(gpioset_set_some_lines_and_wait_for_signal,
+ "tools: gpioset - set some lines and wait for signal",
+ 0, { 8, 8, 8 });
+
+static void gpioset_set_some_lines_and_wait_time(void)
+{
+ unsigned int offsets[3];
+ int rv, values[3];
+
+ test_tool_run("gpioset", "--mode=time",
+ "--usec=100000", "--sec=0", test_chip_name(0),
+ "1=1", "2=0", "5=1", (char *)NULL);
+ usleep(200000);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_RET_OK(test_tool_exit_status());
+ TEST_ASSERT_NULL(test_tool_stdout());
+ TEST_ASSERT_NULL(test_tool_stderr());
+
+ offsets[0] = 1;
+ offsets[1] = 2;
+ offsets[2] = 5;
+
+ rv = gpiod_ctxless_get_value_multiple(test_chip_name(0), offsets,
+ values, 3, false, TEST_CONSUMER);
+ TEST_ASSERT_RET_OK(rv);
+
+ TEST_ASSERT_EQ(values[0], 1);
+ TEST_ASSERT_EQ(values[1], 0);
+ TEST_ASSERT_EQ(values[2], 1);
+}
+TEST_DEFINE(gpioset_set_some_lines_and_wait_time,
+ "tools: gpioset - set some lines and wait for specified time",
+ 0, { 8, 8, 8 });
+
+static void gpioset_no_arguments(void)
+{
+ test_tool_run("gpioset", (char *)NULL);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_EQ(test_tool_exit_status(), 1);
+ TEST_ASSERT_NULL(test_tool_stdout());
+ TEST_ASSERT_NOT_NULL(test_tool_stderr());
+ TEST_ASSERT_STR_CONTAINS(test_tool_stderr(),
+ "gpiochip must be specified");
+}
+TEST_DEFINE(gpioset_no_arguments,
+ "tools: gpioset - no arguments",
+ 0, { });
+
+static void gpioset_no_lines_specified(void)
+{
+ test_tool_run("gpioset", test_chip_name(1), (char *)NULL);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_EQ(test_tool_exit_status(), 1);
+ TEST_ASSERT_NULL(test_tool_stdout());
+ TEST_ASSERT_NOT_NULL(test_tool_stderr());
+ TEST_ASSERT_STR_CONTAINS(test_tool_stderr(),
+ "at least one GPIO line offset to value mapping must be specified");
+}
+TEST_DEFINE(gpioset_no_lines_specified,
+ "tools: gpioset - no lines specified",
+ 0, { 4, 4 });
+
+static void gpioset_too_many_lines_specified(void)
+{
+ test_tool_run("gpioset", test_chip_name(0),
+ "0=1", "1=1", "2=1", "3=1", "4=1", (char *)NULL);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_EQ(test_tool_exit_status(), 1);
+ TEST_ASSERT_NULL(test_tool_stdout());
+ TEST_ASSERT_NOT_NULL(test_tool_stderr());
+ TEST_ASSERT_STR_CONTAINS(test_tool_stderr(),
+ "error setting the GPIO line values");
+}
+TEST_DEFINE(gpioset_too_many_lines_specified,
+ "tools: gpioset - too many lines specified",
+ 0, { 4 });
+
+static void gpioset_sec_usec_without_time(void)
+{
+ test_tool_run("gpioset", "--mode=exit", "--sec=1",
+ test_chip_name(0), "0=1", (char *)NULL);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_EQ(test_tool_exit_status(), 1);
+ TEST_ASSERT_NULL(test_tool_stdout());
+ TEST_ASSERT_NOT_NULL(test_tool_stderr());
+ TEST_ASSERT_STR_CONTAINS(test_tool_stderr(),
+ "can't specify wait time in this mode");
+
+ test_tool_run("gpioset", "--mode=exit", "--usec=100",
+ test_chip_name(0), "0=1", (char *)NULL);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_EQ(test_tool_exit_status(), 1);
+ TEST_ASSERT_NULL(test_tool_stdout());
+ TEST_ASSERT_NOT_NULL(test_tool_stderr());
+ TEST_ASSERT_STR_CONTAINS(test_tool_stderr(),
+ "can't specify wait time in this mode");
+}
+TEST_DEFINE(gpioset_sec_usec_without_time,
+ "tools: gpioset - using --sec/--usec with mode other than 'time'",
+ 0, { 4 });
+
+static void gpioset_invalid_mapping(void)
+{
+ test_tool_run("gpioset", test_chip_name(0), "0=c", (char *)NULL);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_EQ(test_tool_exit_status(), 1);
+ TEST_ASSERT_NULL(test_tool_stdout());
+ TEST_ASSERT_NOT_NULL(test_tool_stderr());
+ TEST_ASSERT_STR_CONTAINS(test_tool_stderr(),
+ "invalid offset<->value mapping");
+}
+TEST_DEFINE(gpioset_invalid_mapping,
+ "tools: gpioset - invalid offset<->value mapping",
+ 0, { 4 });
+
+static void gpioset_invalid_value(void)
+{
+ test_tool_run("gpioset", test_chip_name(0), "0=3", (char *)NULL);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_EQ(test_tool_exit_status(), 1);
+ TEST_ASSERT_NULL(test_tool_stdout());
+ TEST_ASSERT_NOT_NULL(test_tool_stderr());
+ TEST_ASSERT_STR_CONTAINS(test_tool_stderr(), "value must be 0 or 1");
+}
+TEST_DEFINE(gpioset_invalid_value,
+ "tools: gpioset - value different than 0 or 1",
+ 0, { 4 });
+
+static void gpioset_invalid_offset(void)
+{
+ test_tool_run("gpioset", test_chip_name(0),
+ "4000000000=1", (char *)NULL);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_EQ(test_tool_exit_status(), 1);
+ TEST_ASSERT_NULL(test_tool_stdout());
+ TEST_ASSERT_NOT_NULL(test_tool_stderr());
+ TEST_ASSERT_STR_CONTAINS(test_tool_stderr(), "invalid offset");
+}
+TEST_DEFINE(gpioset_invalid_offset,
+ "tools: gpioset - invalid offset",
+ 0, { 4 });
+
+static void gpioset_daemonize_in_wrong_mode(void)
+{
+ test_tool_run("gpioset", "--background",
+ test_chip_name(0), "0=1", (char *)NULL);
+ test_tool_wait();
+
+ TEST_ASSERT(test_tool_exited());
+ TEST_ASSERT_EQ(test_tool_exit_status(), 1);
+ TEST_ASSERT_NULL(test_tool_stdout());
+ TEST_ASSERT_NOT_NULL(test_tool_stderr());
+ TEST_ASSERT_STR_CONTAINS(test_tool_stderr(),
+ "can't daemonize in this mode");
+}
+TEST_DEFINE(gpioset_daemonize_in_wrong_mode,
+ "tools: gpioset - daemonize in wrong mode",
+ 0, { 4 });
diff --git a/tests/tests-iter.c b/tests/tests-iter.c
new file mode 100644
index 0000000..f4aff42
--- /dev/null
+++ b/tests/tests-iter.c
@@ -0,0 +1,136 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+/* Iterator test cases. */
+
+#include "gpiod-test.h"
+
+static void chip_iter(void)
+{
+ TEST_CLEANUP(test_free_chip_iter) struct gpiod_chip_iter *iter = NULL;
+ struct gpiod_chip *chip;
+ bool A, B, C;
+
+ A = B = C = false;
+
+ iter = gpiod_chip_iter_new();
+ TEST_ASSERT_NOT_NULL(iter);
+
+ gpiod_foreach_chip(iter, chip) {
+ if (strcmp(gpiod_chip_label(chip), "gpio-mockup-A") == 0)
+ A = true;
+ else if (strcmp(gpiod_chip_label(chip), "gpio-mockup-B") == 0)
+ B = true;
+ else if (strcmp(gpiod_chip_label(chip), "gpio-mockup-C") == 0)
+ C = true;
+ }
+
+ TEST_ASSERT(A);
+ TEST_ASSERT(B);
+ TEST_ASSERT(C);
+}
+TEST_DEFINE(chip_iter,
+ "gpiod_chip_iter - simple loop",
+ 0, { 8, 8, 8 });
+
+static void chip_iter_noclose(void)
+{
+ TEST_CLEANUP(test_free_chip_iter_noclose)
+ struct gpiod_chip_iter *iter = NULL;
+ TEST_CLEANUP_CHIP struct gpiod_chip *chipA = NULL;
+ TEST_CLEANUP_CHIP struct gpiod_chip *chipB = NULL;
+ TEST_CLEANUP_CHIP struct gpiod_chip *chipC = NULL;
+ struct gpiod_chip *chip;
+ bool A, B, C;
+
+ A = B = C = false;
+
+ iter = gpiod_chip_iter_new();
+ TEST_ASSERT_NOT_NULL(iter);
+
+ gpiod_foreach_chip_noclose(iter, chip) {
+ if (strcmp(gpiod_chip_label(chip), "gpio-mockup-A") == 0) {
+ A = true;
+ chipA = chip;
+ } else if (strcmp(gpiod_chip_label(chip),
+ "gpio-mockup-B") == 0) {
+ B = true;
+ chipB = chip;
+ } else if (strcmp(gpiod_chip_label(chip),
+ "gpio-mockup-C") == 0) {
+ C = true;
+ chipC = chip;
+ }
+ }
+
+ TEST_ASSERT(A);
+ TEST_ASSERT(B);
+ TEST_ASSERT(C);
+
+ gpiod_chip_iter_free_noclose(iter);
+ iter = NULL;
+
+ /* See if the chips are still open and usable. */
+ TEST_ASSERT_STR_EQ(gpiod_chip_label(chipA), "gpio-mockup-A");
+ TEST_ASSERT_STR_EQ(gpiod_chip_label(chipB), "gpio-mockup-B");
+ TEST_ASSERT_STR_EQ(gpiod_chip_label(chipC), "gpio-mockup-C");
+}
+TEST_DEFINE(chip_iter_noclose,
+ "gpiod_chip_iter - simple loop, noclose variant",
+ 0, { 8, 8, 8 });
+
+static void chip_iter_break(void)
+{
+ TEST_CLEANUP(test_free_chip_iter) struct gpiod_chip_iter *iter = NULL;
+ struct gpiod_chip *chip;
+ int i = 0;
+
+ iter = gpiod_chip_iter_new();
+ TEST_ASSERT_NOT_NULL(iter);
+
+ gpiod_foreach_chip(iter, chip) {
+ if ((strcmp(gpiod_chip_label(chip), "gpio-mockup-A") == 0) ||
+ (strcmp(gpiod_chip_label(chip), "gpio-mockup-B") == 0) ||
+ (strcmp(gpiod_chip_label(chip), "gpio-mockup-C") == 0))
+ i++;
+
+ if (i == 3)
+ break;
+ }
+
+ gpiod_chip_iter_free(iter);
+ iter = NULL;
+
+ TEST_ASSERT_EQ(i, 3);
+}
+TEST_DEFINE(chip_iter_break,
+ "gpiod_chip_iter - break",
+ 0, { 8, 8, 8, 8, 8 });
+
+static void line_iter(void)
+{
+ TEST_CLEANUP(test_free_line_iter) struct gpiod_line_iter *iter = NULL;
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip = NULL;
+ struct gpiod_line *line;
+ unsigned int i = 0;
+
+ chip = gpiod_chip_open(test_chip_path(0));
+ TEST_ASSERT_NOT_NULL(chip);
+
+ iter = gpiod_line_iter_new(chip);
+ TEST_ASSERT_NOT_NULL(iter);
+
+ gpiod_foreach_line(iter, line) {
+ TEST_ASSERT_EQ(i, gpiod_line_offset(line));
+ i++;
+ }
+
+ TEST_ASSERT_EQ(8, i);
+}
+TEST_DEFINE(line_iter,
+ "gpiod_line_iter - simple loop, check offsets",
+ 0, { 8 });
diff --git a/tests/tests-line.c b/tests/tests-line.c
new file mode 100644
index 0000000..32b575a
--- /dev/null
+++ b/tests/tests-line.c
@@ -0,0 +1,721 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+/* GPIO line test cases. */
+
+#include <errno.h>
+
+#include "gpiod-test.h"
+
+static void line_request_output(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip = NULL;
+ struct gpiod_line *line_0;
+ struct gpiod_line *line_1;
+ int rv;
+
+ chip = gpiod_chip_open(test_chip_path(0));
+ TEST_ASSERT_NOT_NULL(chip);
+
+ line_0 = gpiod_chip_get_line(chip, 2);
+ line_1 = gpiod_chip_get_line(chip, 5);
+ TEST_ASSERT_NOT_NULL(line_0);
+ TEST_ASSERT_NOT_NULL(line_1);
+
+ rv = gpiod_line_request_output(line_0, TEST_CONSUMER, 0);
+ TEST_ASSERT_RET_OK(rv);
+ rv = gpiod_line_request_output(line_1, TEST_CONSUMER, 1);
+ TEST_ASSERT_RET_OK(rv);
+
+ TEST_ASSERT_EQ(gpiod_line_get_value(line_0), 0);
+ TEST_ASSERT_EQ(gpiod_line_get_value(line_1), 1);
+}
+TEST_DEFINE(line_request_output,
+ "gpiod_line_request_output() - good",
+ 0, { 8 });
+
+static void line_request_already_requested(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip = NULL;
+ struct gpiod_line *line;
+ int rv;
+
+ chip = gpiod_chip_open(test_chip_path(0));
+ TEST_ASSERT_NOT_NULL(chip);
+
+ line = gpiod_chip_get_line(chip, 0);
+ TEST_ASSERT_NOT_NULL(line);
+
+ rv = gpiod_line_request_input(line, TEST_CONSUMER);
+ TEST_ASSERT_RET_OK(rv);
+
+ rv = gpiod_line_request_input(line, TEST_CONSUMER);
+ TEST_ASSERT_NOTEQ(rv, 0);
+ TEST_ASSERT_ERRNO_IS(EBUSY);
+}
+TEST_DEFINE(line_request_already_requested,
+ "gpiod_line_request() - already requested",
+ 0, { 8 });
+
+static void line_consumer(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip = NULL;
+ struct gpiod_line *line;
+ int rv;
+
+ chip = gpiod_chip_open(test_chip_path(0));
+ TEST_ASSERT_NOT_NULL(chip);
+
+ line = gpiod_chip_get_line(chip, 0);
+ TEST_ASSERT_NOT_NULL(line);
+
+ TEST_ASSERT_NULL(gpiod_line_consumer(line));
+
+ rv = gpiod_line_request_input(line, TEST_CONSUMER);
+ TEST_ASSERT_RET_OK(rv);
+
+ TEST_ASSERT(!gpiod_line_needs_update(line));
+ TEST_ASSERT_STR_EQ(gpiod_line_consumer(line), TEST_CONSUMER);
+}
+TEST_DEFINE(line_consumer,
+ "gpiod_line_consumer() - good",
+ 0, { 8 });
+
+static void line_consumer_long_string(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip = NULL;
+ struct gpiod_line *line;
+ int rv;
+
+ chip = gpiod_chip_open(test_chip_path(0));
+ TEST_ASSERT_NOT_NULL(chip);
+
+ line = gpiod_chip_get_line(chip, 0);
+ TEST_ASSERT_NOT_NULL(line);
+
+ TEST_ASSERT_NULL(gpiod_line_consumer(line));
+
+ rv = gpiod_line_request_input(line,
+ "consumer string over 32 characters long");
+ TEST_ASSERT_RET_OK(rv);
+
+ TEST_ASSERT(!gpiod_line_needs_update(line));
+ TEST_ASSERT_STR_EQ(gpiod_line_consumer(line),
+ "consumer string over 32 charact");
+ TEST_ASSERT_EQ(strlen(gpiod_line_consumer(line)), 31);
+}
+TEST_DEFINE(line_consumer_long_string,
+ "gpiod_line_consumer() - long consumer string",
+ 0, { 8 });
+
+static void line_request_bulk_output(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chipA = NULL;
+ TEST_CLEANUP_CHIP struct gpiod_chip *chipB = NULL;
+ struct gpiod_line_bulk bulkB = GPIOD_LINE_BULK_INITIALIZER;
+ struct gpiod_line_bulk bulkA;
+ struct gpiod_line *lineA0;
+ struct gpiod_line *lineA1;
+ struct gpiod_line *lineA2;
+ struct gpiod_line *lineA3;
+ struct gpiod_line *lineB0;
+ struct gpiod_line *lineB1;
+ struct gpiod_line *lineB2;
+ struct gpiod_line *lineB3;
+ int valA[4], valB[4];
+ int rv;
+
+ chipA = gpiod_chip_open(test_chip_path(0));
+ chipB = gpiod_chip_open(test_chip_path(1));
+ TEST_ASSERT_NOT_NULL(chipA);
+ TEST_ASSERT_NOT_NULL(chipB);
+
+ gpiod_line_bulk_init(&bulkA);
+
+ lineA0 = gpiod_chip_get_line(chipA, 0);
+ lineA1 = gpiod_chip_get_line(chipA, 1);
+ lineA2 = gpiod_chip_get_line(chipA, 2);
+ lineA3 = gpiod_chip_get_line(chipA, 3);
+ lineB0 = gpiod_chip_get_line(chipB, 0);
+ lineB1 = gpiod_chip_get_line(chipB, 1);
+ lineB2 = gpiod_chip_get_line(chipB, 2);
+ lineB3 = gpiod_chip_get_line(chipB, 3);
+
+ TEST_ASSERT_NOT_NULL(lineA0);
+ TEST_ASSERT_NOT_NULL(lineA1);
+ TEST_ASSERT_NOT_NULL(lineA2);
+ TEST_ASSERT_NOT_NULL(lineA3);
+ TEST_ASSERT_NOT_NULL(lineB0);
+ TEST_ASSERT_NOT_NULL(lineB1);
+ TEST_ASSERT_NOT_NULL(lineB2);
+ TEST_ASSERT_NOT_NULL(lineB3);
+
+ gpiod_line_bulk_add(&bulkA, lineA0);
+ gpiod_line_bulk_add(&bulkA, lineA1);
+ gpiod_line_bulk_add(&bulkA, lineA2);
+ gpiod_line_bulk_add(&bulkA, lineA3);
+ gpiod_line_bulk_add(&bulkB, lineB0);
+ gpiod_line_bulk_add(&bulkB, lineB1);
+ gpiod_line_bulk_add(&bulkB, lineB2);
+ gpiod_line_bulk_add(&bulkB, lineB3);
+
+ valA[0] = 1;
+ valA[1] = 0;
+ valA[2] = 0;
+ valA[3] = 1;
+ rv = gpiod_line_request_bulk_output(&bulkA, TEST_CONSUMER, valA);
+ TEST_ASSERT_RET_OK(rv);
+
+ valB[0] = 0;
+ valB[1] = 1;
+ valB[2] = 0;
+ valB[3] = 1;
+ rv = gpiod_line_request_bulk_output(&bulkB, TEST_CONSUMER, valB);
+ TEST_ASSERT_RET_OK(rv);
+
+ memset(valA, 0, sizeof(valA));
+ memset(valB, 0, sizeof(valB));
+
+ rv = gpiod_line_get_value_bulk(&bulkA, valA);
+ TEST_ASSERT_RET_OK(rv);
+ TEST_ASSERT_EQ(valA[0], 1);
+ TEST_ASSERT_EQ(valA[1], 0);
+ TEST_ASSERT_EQ(valA[2], 0);
+ TEST_ASSERT_EQ(valA[3], 1);
+
+ rv = gpiod_line_get_value_bulk(&bulkB, valB);
+ TEST_ASSERT_RET_OK(rv);
+ TEST_ASSERT_EQ(valB[0], 0);
+ TEST_ASSERT_EQ(valB[1], 1);
+ TEST_ASSERT_EQ(valB[2], 0);
+ TEST_ASSERT_EQ(valB[3], 1);
+}
+TEST_DEFINE(line_request_bulk_output,
+ "gpiod_line_request_bulk_output() - good",
+ 0, { 8, 8 });
+
+static void line_request_bulk_different_chips(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chipA = NULL;
+ TEST_CLEANUP_CHIP struct gpiod_chip *chipB = NULL;
+ struct gpiod_line_request_config req;
+ struct gpiod_line_bulk bulk;
+ struct gpiod_line *lineA0;
+ struct gpiod_line *lineA1;
+ struct gpiod_line *lineB0;
+ struct gpiod_line *lineB1;
+ int rv;
+
+ chipA = gpiod_chip_open(test_chip_path(0));
+ chipB = gpiod_chip_open(test_chip_path(1));
+ TEST_ASSERT_NOT_NULL(chipA);
+ TEST_ASSERT_NOT_NULL(chipB);
+
+ lineA0 = gpiod_chip_get_line(chipA, 0);
+ lineA1 = gpiod_chip_get_line(chipA, 1);
+ lineB0 = gpiod_chip_get_line(chipB, 0);
+ lineB1 = gpiod_chip_get_line(chipB, 1);
+
+ TEST_ASSERT_NOT_NULL(lineA0);
+ TEST_ASSERT_NOT_NULL(lineA1);
+ TEST_ASSERT_NOT_NULL(lineB0);
+ TEST_ASSERT_NOT_NULL(lineB1);
+
+ gpiod_line_bulk_init(&bulk);
+ gpiod_line_bulk_add(&bulk, lineA0);
+ gpiod_line_bulk_add(&bulk, lineA1);
+ gpiod_line_bulk_add(&bulk, lineB0);
+ gpiod_line_bulk_add(&bulk, lineB1);
+
+ req.consumer = TEST_CONSUMER;
+ req.request_type = GPIOD_LINE_REQUEST_DIRECTION_INPUT;
+ req.flags = GPIOD_LINE_ACTIVE_STATE_HIGH;
+
+ rv = gpiod_line_request_bulk(&bulk, &req, NULL);
+ TEST_ASSERT_NOTEQ(rv, 0);
+ TEST_ASSERT_ERRNO_IS(EINVAL);
+}
+TEST_DEFINE(line_request_bulk_different_chips,
+ "gpiod_line_request_bulk() - different chips",
+ 0, { 8, 8 });
+
+static void line_request_null_default_vals_for_output(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip = NULL;
+ struct gpiod_line_bulk bulk = GPIOD_LINE_BULK_INITIALIZER;
+ struct gpiod_line *line;
+ int rv, vals[3];
+
+ chip = gpiod_chip_open(test_chip_path(0));
+ TEST_ASSERT_NOT_NULL(chip);
+
+ line = gpiod_chip_get_line(chip, 0);
+ gpiod_line_bulk_add(&bulk, line);
+ line = gpiod_chip_get_line(chip, 1);
+ gpiod_line_bulk_add(&bulk, line);
+ line = gpiod_chip_get_line(chip, 2);
+ gpiod_line_bulk_add(&bulk, line);
+
+ rv = gpiod_line_request_bulk_output(&bulk, TEST_CONSUMER, NULL);
+ TEST_ASSERT_RET_OK(rv);
+
+ gpiod_line_release_bulk(&bulk);
+
+ rv = gpiod_line_request_bulk_input(&bulk, TEST_CONSUMER);
+ TEST_ASSERT_RET_OK(rv);
+
+ memset(vals, 0, sizeof(vals));
+
+ rv = gpiod_line_get_value_bulk(&bulk, vals);
+ TEST_ASSERT_RET_OK(rv);
+
+ TEST_ASSERT_EQ(vals[0], 0);
+ TEST_ASSERT_EQ(vals[1], 0);
+ TEST_ASSERT_EQ(vals[2], 0);
+}
+TEST_DEFINE(line_request_null_default_vals_for_output,
+ "gpiod_line_request_bulk() - null default vals for output",
+ 0, { 8 });
+
+static void line_set_value(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip = NULL;
+ struct gpiod_line *line;
+ int rv;
+
+ chip = gpiod_chip_open(test_chip_path(0));
+ TEST_ASSERT_NOT_NULL(chip);
+
+ line = gpiod_chip_get_line(chip, 2);
+ TEST_ASSERT_NOT_NULL(line);
+
+ rv = gpiod_line_request_output(line, TEST_CONSUMER, 0);
+ TEST_ASSERT_RET_OK(rv);
+
+ TEST_ASSERT_RET_OK(gpiod_line_set_value(line, 1));
+ TEST_ASSERT_EQ(gpiod_line_get_value(line), 1);
+ TEST_ASSERT_RET_OK(gpiod_line_set_value(line, 0));
+ TEST_ASSERT_EQ(gpiod_line_get_value(line), 0);
+}
+TEST_DEFINE(line_set_value,
+ "gpiod_line_set_value() - good",
+ 0, { 8 });
+
+static void line_get_value_different_chips(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chipA = NULL;
+ TEST_CLEANUP_CHIP struct gpiod_chip *chipB = NULL;
+ struct gpiod_line *lineA1, *lineA2, *lineB1, *lineB2;
+ struct gpiod_line_bulk bulkA, bulkB, bulk;
+ int rv, vals[4];
+
+ chipA = gpiod_chip_open(test_chip_path(0));
+ TEST_ASSERT_NOT_NULL(chipA);
+
+ chipB = gpiod_chip_open(test_chip_path(1));
+ TEST_ASSERT_NOT_NULL(chipB);
+
+ lineA1 = gpiod_chip_get_line(chipA, 3);
+ lineA2 = gpiod_chip_get_line(chipA, 4);
+ lineB1 = gpiod_chip_get_line(chipB, 5);
+ lineB2 = gpiod_chip_get_line(chipB, 6);
+ TEST_ASSERT_NOT_NULL(lineA1);
+ TEST_ASSERT_NOT_NULL(lineA2);
+ TEST_ASSERT_NOT_NULL(lineB1);
+ TEST_ASSERT_NOT_NULL(lineB2);
+
+ gpiod_line_bulk_init(&bulkA);
+ gpiod_line_bulk_init(&bulkB);
+ gpiod_line_bulk_init(&bulk);
+
+ gpiod_line_bulk_add(&bulk, lineA1);
+ gpiod_line_bulk_add(&bulk, lineA2);
+ gpiod_line_bulk_add(&bulk, lineB1);
+ gpiod_line_bulk_add(&bulk, lineB2);
+
+ gpiod_line_bulk_add(&bulkA, lineA1);
+ gpiod_line_bulk_add(&bulkA, lineA2);
+ gpiod_line_bulk_add(&bulkB, lineB1);
+ gpiod_line_bulk_add(&bulkB, lineB2);
+ gpiod_line_bulk_add(&bulk, lineA1);
+ gpiod_line_bulk_add(&bulk, lineA2);
+ gpiod_line_bulk_add(&bulk, lineB1);
+ gpiod_line_bulk_add(&bulk, lineB2);
+
+ rv = gpiod_line_request_bulk_input(&bulkA, TEST_CONSUMER);
+ TEST_ASSERT_RET_OK(rv);
+
+ rv = gpiod_line_request_bulk_input(&bulkB, TEST_CONSUMER);
+ TEST_ASSERT_RET_OK(rv);
+
+ rv = gpiod_line_get_value_bulk(&bulk, vals);
+ TEST_ASSERT_EQ(rv, -1);
+ TEST_ASSERT_ERRNO_IS(EINVAL);
+}
+TEST_DEFINE(line_get_value_different_chips,
+ "gpiod_line_get_value_bulk() - different chips",
+ 0, { 8, 8 });
+
+static void line_get_good(void)
+{
+ TEST_CLEANUP(test_line_close_chip) struct gpiod_line *line = NULL;
+
+ line = gpiod_line_get(test_chip_name(2), 18);
+ TEST_ASSERT_NOT_NULL(line);
+ TEST_ASSERT_EQ(gpiod_line_offset(line), 18);
+}
+TEST_DEFINE(line_get_good,
+ "gpiod_line_get() - good",
+ TEST_FLAG_NAMED_LINES, { 16, 16, 32, 16 });
+
+static void line_get_invalid_offset(void)
+{
+ TEST_CLEANUP(test_line_close_chip) struct gpiod_line *line = NULL;
+
+ line = gpiod_line_get(test_chip_name(3), 18);
+ TEST_ASSERT_NULL(line);
+ TEST_ASSERT_ERRNO_IS(EINVAL);
+}
+TEST_DEFINE(line_get_invalid_offset,
+ "gpiod_line_get() - invalid offset",
+ TEST_FLAG_NAMED_LINES, { 16, 16, 32, 16 });
+
+static void line_find_good(void)
+{
+ TEST_CLEANUP(test_line_close_chip) struct gpiod_line *line = NULL;
+
+ line = gpiod_line_find("gpio-mockup-C-12");
+ TEST_ASSERT_NOT_NULL(line);
+
+ TEST_ASSERT_EQ(gpiod_line_offset(line), 12);
+}
+TEST_DEFINE(line_find_good,
+ "gpiod_line_find() - good",
+ TEST_FLAG_NAMED_LINES, { 16, 16, 32, 16 });
+
+static void line_find_not_found(void)
+{
+ TEST_CLEANUP(test_line_close_chip) struct gpiod_line *line = NULL;
+
+ line = gpiod_line_find("nonexistent");
+ TEST_ASSERT_NULL(line);
+ TEST_ASSERT_ERRNO_IS(ENOENT);
+}
+TEST_DEFINE(line_find_not_found,
+ "gpiod_line_find() - not found",
+ TEST_FLAG_NAMED_LINES, { 16, 16, 32, 16 });
+
+static void line_find_unnamed_lines(void)
+{
+ TEST_CLEANUP(test_line_close_chip) struct gpiod_line *line = NULL;
+
+ line = gpiod_line_find("gpio-mockup-C-12");
+ TEST_ASSERT_NULL(line);
+ TEST_ASSERT_ERRNO_IS(ENOENT);
+}
+TEST_DEFINE(line_find_unnamed_lines,
+ "gpiod_line_find() - unnamed lines",
+ 0, { 16, 16, 32, 16 });
+
+static void line_direction(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip = NULL;
+ struct gpiod_line *line;
+ int rv;
+
+ chip = gpiod_chip_open(test_chip_path(0));
+ TEST_ASSERT_NOT_NULL(chip);
+
+ line = gpiod_chip_get_line(chip, 5);
+ TEST_ASSERT_NOT_NULL(line);
+
+ rv = gpiod_line_request_output(line, TEST_CONSUMER, 0);
+ TEST_ASSERT_RET_OK(rv);
+
+ TEST_ASSERT_EQ(gpiod_line_direction(line), GPIOD_LINE_DIRECTION_OUTPUT);
+
+ gpiod_line_release(line);
+
+ rv = gpiod_line_request_input(line, TEST_CONSUMER);
+ TEST_ASSERT_RET_OK(rv);
+
+ TEST_ASSERT_EQ(gpiod_line_direction(line), GPIOD_LINE_DIRECTION_INPUT);
+}
+TEST_DEFINE(line_direction,
+ "gpiod_line_direction() - set & get",
+ 0, { 8 });
+
+static void line_active_state(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip = NULL;
+ struct gpiod_line *line;
+ int rv;
+
+ chip = gpiod_chip_open(test_chip_path(0));
+ TEST_ASSERT_NOT_NULL(chip);
+
+ line = gpiod_chip_get_line(chip, 5);
+ TEST_ASSERT_NOT_NULL(line);
+
+ rv = gpiod_line_request_input(line, TEST_CONSUMER);
+ TEST_ASSERT_RET_OK(rv);
+
+ TEST_ASSERT_EQ(gpiod_line_active_state(line),
+ GPIOD_LINE_ACTIVE_STATE_HIGH);
+
+ gpiod_line_release(line);
+
+ rv = gpiod_line_request_input_flags(line, TEST_CONSUMER,
+ GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW);
+ TEST_ASSERT_RET_OK(rv);
+
+ TEST_ASSERT_EQ(gpiod_line_direction(line), GPIOD_LINE_DIRECTION_INPUT);
+}
+TEST_DEFINE(line_active_state,
+ "gpiod_line_active_state() - set & get",
+ 0, { 8 });
+
+static void line_misc_flags(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip = NULL;
+ struct gpiod_line_request_config config;
+ struct gpiod_line *line;
+ int rv;
+
+ chip = gpiod_chip_open(test_chip_path(0));
+ TEST_ASSERT_NOT_NULL(chip);
+
+ line = gpiod_chip_get_line(chip, 2);
+ TEST_ASSERT_NOT_NULL(line);
+
+ TEST_ASSERT_FALSE(gpiod_line_is_used(line));
+ TEST_ASSERT_FALSE(gpiod_line_is_open_drain(line));
+ TEST_ASSERT_FALSE(gpiod_line_is_open_source(line));
+
+ config.request_type = GPIOD_LINE_REQUEST_DIRECTION_OUTPUT;
+ config.consumer = TEST_CONSUMER;
+ config.flags = GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN;
+
+ rv = gpiod_line_request(line, &config, 0);
+ TEST_ASSERT_RET_OK(rv);
+
+ TEST_ASSERT(gpiod_line_is_used(line));
+ TEST_ASSERT(gpiod_line_is_open_drain(line));
+ TEST_ASSERT_FALSE(gpiod_line_is_open_source(line));
+
+ gpiod_line_release(line);
+
+ config.flags = GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE;
+
+ rv = gpiod_line_request(line, &config, 0);
+ TEST_ASSERT_RET_OK(rv);
+
+ TEST_ASSERT(gpiod_line_is_used(line));
+ TEST_ASSERT_FALSE(gpiod_line_is_open_drain(line));
+ TEST_ASSERT(gpiod_line_is_open_source(line));
+}
+TEST_DEFINE(line_misc_flags,
+ "gpiod_line - misc flags",
+ 0, { 8 });
+
+static void line_open_source_open_drain_input_invalid(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip = NULL;
+ struct gpiod_line *line;
+ int rv;
+
+ chip = gpiod_chip_open(test_chip_path(0));
+ TEST_ASSERT_NOT_NULL(chip);
+
+ line = gpiod_chip_get_line(chip, 2);
+ TEST_ASSERT_NOT_NULL(line);
+
+ rv = gpiod_line_request_input_flags(line, TEST_CONSUMER,
+ GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN);
+ TEST_ASSERT_EQ(rv, -1);
+ TEST_ASSERT_ERRNO_IS(EINVAL);
+
+ rv = gpiod_line_request_input_flags(line, TEST_CONSUMER,
+ GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE);
+ TEST_ASSERT_EQ(rv, -1);
+ TEST_ASSERT_ERRNO_IS(EINVAL);
+}
+TEST_DEFINE(line_open_source_open_drain_input_invalid,
+ "gpiod_line - open-source & open-drain for input mode (invalid)",
+ 0, { 8 });
+
+static void line_open_source_open_drain_simultaneously(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip = NULL;
+ struct gpiod_line *line;
+ int rv;
+
+ chip = gpiod_chip_open(test_chip_path(0));
+ TEST_ASSERT_NOT_NULL(chip);
+
+ line = gpiod_chip_get_line(chip, 2);
+ TEST_ASSERT_NOT_NULL(line);
+
+ rv = gpiod_line_request_output_flags(line, TEST_CONSUMER,
+ GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE |
+ GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN, 1);
+ TEST_ASSERT_EQ(rv, -1);
+ TEST_ASSERT_ERRNO_IS(EINVAL);
+}
+TEST_DEFINE(line_open_source_open_drain_simultaneously,
+ "gpiod_line - open-source & open-drain flags simultaneously",
+ 0, { 8 });
+
+/* Verify that the reference counting of the line fd handle works correctly. */
+static void line_release_one_use_another(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip = NULL;
+ struct gpiod_line_bulk bulk;
+ struct gpiod_line *line1;
+ struct gpiod_line *line2;
+ int rv, vals[2];
+
+ chip = gpiod_chip_open(test_chip_path(0));
+ TEST_ASSERT_NOT_NULL(chip);
+
+ line1 = gpiod_chip_get_line(chip, 2);
+ TEST_ASSERT_NOT_NULL(line1);
+ line2 = gpiod_chip_get_line(chip, 3);
+ TEST_ASSERT_NOT_NULL(line2);
+
+ gpiod_line_bulk_init(&bulk);
+ gpiod_line_bulk_add(&bulk, line1);
+ gpiod_line_bulk_add(&bulk, line2);
+
+ vals[0] = vals[1] = 1;
+
+ rv = gpiod_line_request_bulk_output(&bulk, TEST_CONSUMER, vals);
+ TEST_ASSERT_RET_OK(rv);
+
+ gpiod_line_release(line1);
+
+ rv = gpiod_line_get_value(line1);
+ TEST_ASSERT_EQ(rv, -1);
+ TEST_ASSERT_ERRNO_IS(EPERM);
+
+ rv = gpiod_line_get_value(line2);
+ TEST_ASSERT_EQ(rv, 1);
+}
+TEST_DEFINE(line_release_one_use_another,
+ "gpiod_line - request two, release one, use the other one",
+ 0, { 8 });
+
+static void line_null_consumer(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip = NULL;
+ struct gpiod_line_request_config config;
+ struct gpiod_line *line;
+ int rv;
+
+ chip = gpiod_chip_open(test_chip_path(0));
+ TEST_ASSERT_NOT_NULL(chip);
+
+ line = gpiod_chip_get_line(chip, 2);
+ TEST_ASSERT_NOT_NULL(line);
+
+ config.request_type = GPIOD_LINE_REQUEST_DIRECTION_INPUT;
+ config.consumer = NULL;
+ config.flags = 0;
+
+ rv = gpiod_line_request(line, &config, 0);
+ TEST_ASSERT_RET_OK(rv);
+ TEST_ASSERT_STR_EQ(gpiod_line_consumer(line), "?");
+
+ gpiod_line_release(line);
+
+ /*
+ * Internally we use different structures for event requests, so we
+ * need to test that explicitly too.
+ */
+ config.request_type = GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES;
+
+ rv = gpiod_line_request(line, &config, 0);
+ TEST_ASSERT_RET_OK(rv);
+ TEST_ASSERT_STR_EQ(gpiod_line_consumer(line), "?");
+}
+TEST_DEFINE(line_null_consumer,
+ "line request - NULL consumer string",
+ 0, { 8 });
+
+static void line_empty_consumer(void)
+{
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip = NULL;
+ struct gpiod_line_request_config config;
+ struct gpiod_line *line;
+ int rv;
+
+ chip = gpiod_chip_open(test_chip_path(0));
+ TEST_ASSERT_NOT_NULL(chip);
+
+ line = gpiod_chip_get_line(chip, 2);
+ TEST_ASSERT_NOT_NULL(line);
+
+ config.request_type = GPIOD_LINE_REQUEST_DIRECTION_INPUT;
+ config.consumer = "";
+ config.flags = 0;
+
+ rv = gpiod_line_request(line, &config, 0);
+ TEST_ASSERT_RET_OK(rv);
+ TEST_ASSERT_STR_EQ(gpiod_line_consumer(line), "?");
+
+ gpiod_line_release(line);
+
+ /*
+ * Internally we use different structures for event requests, so we
+ * need to test that explicitly too.
+ */
+ config.request_type = GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES;
+
+ rv = gpiod_line_request(line, &config, 0);
+ TEST_ASSERT_RET_OK(rv);
+ TEST_ASSERT_STR_EQ(gpiod_line_consumer(line), "?");
+}
+TEST_DEFINE(line_empty_consumer,
+ "line request - empty consumer string",
+ 0, { 8 });
+
+static void line_bulk_foreach(void)
+{
+ static const char *const line_names[] = { "gpio-mockup-A-0",
+ "gpio-mockup-A-1",
+ "gpio-mockup-A-2",
+ "gpio-mockup-A-3" };
+
+ TEST_CLEANUP_CHIP struct gpiod_chip *chip = NULL;
+ struct gpiod_line_bulk bulk = GPIOD_LINE_BULK_INITIALIZER;
+ struct gpiod_line *line, **lineptr;
+ int i;
+
+ chip = gpiod_chip_open(test_chip_path(0));
+ TEST_ASSERT_NOT_NULL(chip);
+
+ for (i = 0; i < 4; i++) {
+ line = gpiod_chip_get_line(chip, i);
+ TEST_ASSERT_NOT_NULL(line);
+
+ gpiod_line_bulk_add(&bulk, line);
+ }
+
+ i = 0;
+ gpiod_line_bulk_foreach_line(&bulk, line, lineptr)
+ TEST_ASSERT_STR_EQ(gpiod_line_name(line), line_names[i++]);
+
+ i = 0;
+ gpiod_line_bulk_foreach_line(&bulk, line, lineptr) {
+ TEST_ASSERT_STR_EQ(gpiod_line_name(line), line_names[i++]);
+ if (i == 2)
+ break;
+ }
+}
+TEST_DEFINE(line_bulk_foreach,
+ "line bulk - iterate over all lines",
+ TEST_FLAG_NAMED_LINES, { 8 });
diff --git a/tests/tests-misc.c b/tests/tests-misc.c
new file mode 100644
index 0000000..99a80a7
--- /dev/null
+++ b/tests/tests-misc.c
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+/* Misc test cases. */
+
+#include <errno.h>
+
+#include "gpiod-test.h"
+
+static void version_string(void)
+{
+ /* Check that gpiod_version_string() returns an actual string. */
+ TEST_ASSERT_NOT_NULL(gpiod_version_string());
+ TEST_ASSERT(strlen(gpiod_version_string()) > 0);
+ TEST_ASSERT_REGEX_MATCH(gpiod_version_string(),
+ "^[0-9]+\\.[0-9]+[0-9a-zA-Z\\.]*$");
+}
+TEST_DEFINE(version_string,
+ "gpiod_version_string()",
+ 0, { });
diff --git a/tools/Makefile.am b/tools/Makefile.am
new file mode 100644
index 0000000..4198dba
--- /dev/null
+++ b/tools/Makefile.am
@@ -0,0 +1,29 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+#
+# This file is part of libgpiod.
+#
+# Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+#
+
+AM_CFLAGS = -I$(top_srcdir)/include/ -include $(top_builddir)/config.h
+AM_CFLAGS += -Wall -Wextra -g
+
+noinst_LTLIBRARIES = libtools-common.la
+libtools_common_la_SOURCES = tools-common.c tools-common.h
+
+LDADD = libtools-common.la $(top_builddir)/lib/libgpiod.la
+
+bin_PROGRAMS = gpiodetect gpioinfo gpioget gpioset gpiomon gpiofind
+
+gpiodetect_SOURCES = gpiodetect.c
+
+gpioinfo_SOURCES = gpioinfo.c
+
+gpioget_SOURCES = gpioget.c
+
+gpioset_SOURCES = gpioset.c
+
+gpiomon_SOURCES = gpiomon.c
+
+gpiofind_SOURCES = gpiofind.c
diff --git a/tools/gpiodetect.c b/tools/gpiodetect.c
new file mode 100644
index 0000000..aa86568
--- /dev/null
+++ b/tools/gpiodetect.c
@@ -0,0 +1,78 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+#include <getopt.h>
+#include <gpiod.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "tools-common.h"
+
+static const struct option longopts[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'v' },
+ { GETOPT_NULL_LONGOPT },
+};
+
+static const char *const shortopts = "+hv";
+
+static void print_help(void)
+{
+ printf("Usage: %s [OPTIONS]\n", get_progname());
+ printf("List all GPIO chips, print their labels and number of GPIO lines.\n");
+ printf("\n");
+ printf("Options:\n");
+ printf(" -h, --help:\t\tdisplay this message and exit\n");
+ printf(" -v, --version:\tdisplay the version and exit\n");
+}
+
+int main(int argc, char **argv)
+{
+ struct gpiod_chip_iter *iter;
+ struct gpiod_chip *chip;
+ int optc, opti;
+
+ for (;;) {
+ optc = getopt_long(argc, argv, shortopts, longopts, &opti);
+ if (optc < 0)
+ break;
+
+ switch (optc) {
+ case 'h':
+ print_help();
+ return EXIT_SUCCESS;
+ case 'v':
+ print_version();
+ return EXIT_SUCCESS;
+ case '?':
+ die("try %s --help", get_progname());
+ default:
+ abort();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc > 0)
+ die("unrecognized argument: %s", argv[0]);
+
+ iter = gpiod_chip_iter_new();
+ if (!iter)
+ die_perror("unable to access GPIO chips");
+
+ gpiod_foreach_chip(iter, chip) {
+ printf("%s [%s] (%u lines)\n",
+ gpiod_chip_name(chip),
+ gpiod_chip_label(chip),
+ gpiod_chip_num_lines(chip));
+ }
+
+ gpiod_chip_iter_free(iter);
+
+ return EXIT_SUCCESS;
+}
diff --git a/tools/gpiofind.c b/tools/gpiofind.c
new file mode 100644
index 0000000..35cc491
--- /dev/null
+++ b/tools/gpiofind.c
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+#include <getopt.h>
+#include <gpiod.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "tools-common.h"
+
+static const struct option longopts[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'v' },
+ { GETOPT_NULL_LONGOPT },
+};
+
+static const char *const shortopts = "+hv";
+
+static void print_help(void)
+{
+ printf("Usage: %s [OPTIONS] <name>\n", get_progname());
+ printf("Find a GPIO line by name. The output of this command can be used as input for gpioget/set.\n");
+ printf("\n");
+ printf("Options:\n");
+ printf(" -h, --help:\t\tdisplay this message and exit\n");
+ printf(" -v, --version:\tdisplay the version and exit\n");
+}
+
+int main(int argc, char **argv)
+{
+ unsigned int offset;
+ int optc, opti, rv;
+ char chip[32];
+
+ for (;;) {
+ optc = getopt_long(argc, argv, shortopts, longopts, &opti);
+ if (optc < 0)
+ break;
+
+ switch (optc) {
+ case 'h':
+ print_help();
+ return EXIT_SUCCESS;
+ case 'v':
+ print_version();
+ return EXIT_SUCCESS;
+ case '?':
+ die("try %s --help", get_progname());
+ default:
+ abort();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1)
+ die("exactly one GPIO line name must be specified");
+
+ rv = gpiod_ctxless_find_line(argv[0], chip, sizeof(chip), &offset);
+ if (rv < 0)
+ die_perror("error performing the line lookup");
+ else if (rv == 0)
+ return EXIT_FAILURE;
+
+ printf("%s %u\n", chip, offset);
+
+ return EXIT_SUCCESS;
+}
diff --git a/tools/gpioget.c b/tools/gpioget.c
new file mode 100644
index 0000000..196ebeb
--- /dev/null
+++ b/tools/gpioget.c
@@ -0,0 +1,106 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+#include <getopt.h>
+#include <gpiod.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "tools-common.h"
+
+static const struct option longopts[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'v' },
+ { "active-low", no_argument, NULL, 'l' },
+ { GETOPT_NULL_LONGOPT },
+};
+
+static const char *const shortopts = "+hvl";
+
+static void print_help(void)
+{
+ printf("Usage: %s [OPTIONS] <chip name/number> <offset 1> <offset 2> ...\n",
+ get_progname());
+ printf("Read line value(s) from a GPIO chip\n");
+ printf("\n");
+ printf("Options:\n");
+ printf(" -h, --help:\t\tdisplay this message and exit\n");
+ printf(" -v, --version:\tdisplay the version and exit\n");
+ printf(" -l, --active-low:\tset the line active state to low\n");
+}
+
+int main(int argc, char **argv)
+{
+ unsigned int *offsets, i, num_lines;
+ int *values, optc, opti, rv;
+ bool active_low = false;
+ char *device, *end;
+
+ for (;;) {
+ optc = getopt_long(argc, argv, shortopts, longopts, &opti);
+ if (optc < 0)
+ break;
+
+ switch (optc) {
+ case 'h':
+ print_help();
+ return EXIT_SUCCESS;
+ case 'v':
+ print_version();
+ return EXIT_SUCCESS;
+ case 'l':
+ active_low = true;
+ break;
+ case '?':
+ die("try %s --help", get_progname());
+ default:
+ abort();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1)
+ die("gpiochip must be specified");
+
+ if (argc < 2)
+ die("at least one GPIO line offset must be specified");
+
+ device = argv[0];
+ num_lines = argc - 1;
+
+ values = malloc(sizeof(*values) * num_lines);
+ offsets = malloc(sizeof(*offsets) * num_lines);
+ if (!values || !offsets)
+ die("out of memory");
+
+ for (i = 0; i < num_lines; i++) {
+ offsets[i] = strtoul(argv[i + 1], &end, 10);
+ if (*end != '\0' || offsets[i] > INT_MAX)
+ die("invalid GPIO offset: %s", argv[i + 1]);
+ }
+
+ rv = gpiod_ctxless_get_value_multiple(device, offsets, values,
+ num_lines, active_low,
+ "gpioget");
+ if (rv < 0)
+ die_perror("error reading GPIO values");
+
+ for (i = 0; i < num_lines; i++) {
+ printf("%d", values[i]);
+ if (i != num_lines - 1)
+ printf(" ");
+ }
+ printf("\n");
+
+ free(values);
+ free(offsets);
+
+ return EXIT_SUCCESS;
+}
diff --git a/tools/gpioinfo.c b/tools/gpioinfo.c
new file mode 100644
index 0000000..bb17262
--- /dev/null
+++ b/tools/gpioinfo.c
@@ -0,0 +1,203 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <gpiod.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "tools-common.h"
+
+typedef bool (*is_set_func)(struct gpiod_line *);
+
+struct flag {
+ const char *name;
+ is_set_func is_set;
+};
+
+static const struct flag flags[] = {
+ {
+ .name = "used",
+ .is_set = gpiod_line_is_used,
+ },
+ {
+ .name = "open-drain",
+ .is_set = gpiod_line_is_open_drain,
+ },
+ {
+ .name = "open-source",
+ .is_set = gpiod_line_is_open_source,
+ },
+};
+
+static const struct option longopts[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'v' },
+ { GETOPT_NULL_LONGOPT },
+};
+
+static const char *const shortopts = "+hv";
+
+static void print_help(void)
+{
+ printf("Usage: %s [OPTIONS] <gpiochip1> ...\n", get_progname());
+ printf("Print information about all lines of the specified GPIO chip(s) (or all gpiochips if none are specified).\n");
+ printf("\n");
+ printf("Options:\n");
+ printf(" -h, --help:\t\tdisplay this message and exit\n");
+ printf(" -v, --version:\tdisplay the version and exit\n");
+}
+
+static PRINTF(3, 4) void prinfo(bool *of,
+ unsigned int prlen, const char *fmt, ...)
+{
+ char *buf, *buffmt = NULL;
+ size_t len;
+ va_list va;
+ int rv;
+
+ va_start(va, fmt);
+ rv = vasprintf(&buf, fmt, va);
+ va_end(va);
+ if (rv < 0)
+ die("vasprintf: %s\n", strerror(errno));
+
+ len = strlen(buf) - 1;
+
+ if (len >= prlen || *of) {
+ *of = true;
+ printf("%s", buf);
+ } else {
+ rv = asprintf(&buffmt, "%%%us", prlen);
+ if (rv < 0)
+ die("asprintf: %s\n", strerror(errno));
+
+ printf(buffmt, buf);
+ }
+
+ free(buf);
+ if (fmt)
+ free(buffmt);
+}
+
+static void list_lines(struct gpiod_chip *chip)
+{
+ struct gpiod_line_iter *iter;
+ int direction, active_state;
+ const char *name, *consumer;
+ struct gpiod_line *line;
+ unsigned int i, offset;
+ bool flag_printed, of;
+
+ iter = gpiod_line_iter_new(chip);
+ if (!iter)
+ die_perror("error creating line iterator");
+
+ printf("%s - %u lines:\n",
+ gpiod_chip_name(chip), gpiod_chip_num_lines(chip));
+
+ gpiod_foreach_line(iter, line) {
+ offset = gpiod_line_offset(line);
+ name = gpiod_line_name(line);
+ consumer = gpiod_line_consumer(line);
+ direction = gpiod_line_direction(line);
+ active_state = gpiod_line_active_state(line);
+
+ of = false;
+
+ printf("\tline ");
+ prinfo(&of, 3, "%u", offset);
+ printf(": ");
+
+ name ? prinfo(&of, 12, "\"%s\"", name)
+ : prinfo(&of, 12, "unnamed");
+ printf(" ");
+
+ consumer ? prinfo(&of, 12, "\"%s\"", consumer)
+ : prinfo(&of, 12, "unused");
+ printf(" ");
+
+ prinfo(&of, 8, "%s ", direction == GPIOD_LINE_DIRECTION_INPUT
+ ? "input" : "output");
+ prinfo(&of, 13, "%s ",
+ active_state == GPIOD_LINE_ACTIVE_STATE_LOW
+ ? "active-low"
+ : "active-high");
+
+ flag_printed = false;
+ for (i = 0; i < ARRAY_SIZE(flags); i++) {
+ if (flags[i].is_set(line)) {
+ if (flag_printed)
+ printf(" ");
+ else
+ printf("[");
+ printf("%s", flags[i].name);
+ flag_printed = true;
+ }
+ }
+ if (flag_printed)
+ printf("]");
+
+ printf("\n");
+ }
+
+ gpiod_line_iter_free(iter);
+}
+
+int main(int argc, char **argv)
+{
+ struct gpiod_chip_iter *chip_iter;
+ struct gpiod_chip *chip;
+ int i, optc, opti;
+
+ for (;;) {
+ optc = getopt_long(argc, argv, shortopts, longopts, &opti);
+ if (optc < 0)
+ break;
+
+ switch (optc) {
+ case 'h':
+ print_help();
+ return EXIT_SUCCESS;
+ case 'v':
+ print_version();
+ return EXIT_SUCCESS;
+ case '?':
+ die("try %s --help", get_progname());
+ default:
+ abort();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0) {
+ chip_iter = gpiod_chip_iter_new();
+ if (!chip_iter)
+ die_perror("error accessing GPIO chips");
+
+ gpiod_foreach_chip(chip_iter, chip)
+ list_lines(chip);
+
+ gpiod_chip_iter_free(chip_iter);
+ } else {
+ for (i = 0; i < argc; i++) {
+ chip = gpiod_chip_open_lookup(argv[i]);
+ if (!chip)
+ die_perror("looking up chip %s", argv[i]);
+
+ list_lines(chip);
+
+ gpiod_chip_close(chip);
+ }
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/tools/gpiomon.c b/tools/gpiomon.c
new file mode 100644
index 0000000..9a1843b
--- /dev/null
+++ b/tools/gpiomon.c
@@ -0,0 +1,332 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <gpiod.h>
+#include <limits.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/signalfd.h>
+#include <unistd.h>
+
+#include "tools-common.h"
+
+static const struct option longopts[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'v' },
+ { "active-low", no_argument, NULL, 'l' },
+ { "num-events", required_argument, NULL, 'n' },
+ { "silent", no_argument, NULL, 's' },
+ { "rising-edge", no_argument, NULL, 'r' },
+ { "falling-edge", no_argument, NULL, 'f' },
+ { "line-buffered", no_argument, NULL, 'b' },
+ { "format", required_argument, NULL, 'F' },
+ { GETOPT_NULL_LONGOPT },
+};
+
+static const char *const shortopts = "+hvln:srfbF:";
+
+static void print_help(void)
+{
+ printf("Usage: %s [OPTIONS] <chip name/number> <offset 1> <offset 2> ...\n",
+ get_progname());
+ printf("Wait for events on GPIO lines and print them to standard output\n");
+ printf("\n");
+ printf("Options:\n");
+ printf(" -h, --help:\t\tdisplay this message and exit\n");
+ printf(" -v, --version:\tdisplay the version and exit\n");
+ printf(" -l, --active-low:\tset the line active state to low\n");
+ printf(" -n, --num-events=NUM:\texit after processing NUM events\n");
+ printf(" -s, --silent:\t\tdon't print event info\n");
+ printf(" -r, --rising-edge:\tonly process rising edge events\n");
+ printf(" -f, --falling-edge:\tonly process falling edge events\n");
+ printf(" -b, --line-buffered:\tset standard output as line buffered\n");
+ printf(" -F, --format=FMT\tspecify custom output format\n");
+ printf("\n");
+ printf("Format specifiers:\n");
+ printf(" %%o: GPIO line offset\n");
+ printf(" %%e: event type (0 - falling edge, 1 rising edge)\n");
+ printf(" %%s: seconds part of the event timestamp\n");
+ printf(" %%n: nanoseconds part of the event timestamp\n");
+}
+
+struct mon_ctx {
+ unsigned int offset;
+ unsigned int events_wanted;
+ unsigned int events_done;
+
+ bool silent;
+ char *fmt;
+
+ int sigfd;
+};
+
+static void event_print_custom(unsigned int offset,
+ const struct timespec *ts,
+ int event_type,
+ struct mon_ctx *ctx)
+{
+ char *prev, *curr, fmt;
+
+ for (prev = curr = ctx->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 'o':
+ printf("%u", offset);
+ break;
+ case 'e':
+ if (event_type == GPIOD_CTXLESS_EVENT_CB_RISING_EDGE)
+ fputc('1', stdout);
+ else
+ fputc('0', stdout);
+ break;
+ case 's':
+ printf("%ld", ts->tv_sec);
+ break;
+ case 'n':
+ printf("%ld", ts->tv_nsec);
+ 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(unsigned int offset,
+ const struct timespec *ts,
+ int event_type)
+{
+ char *evname;
+
+ if (event_type == GPIOD_CTXLESS_EVENT_CB_RISING_EDGE)
+ evname = " RISING EDGE";
+ else
+ evname = "FALLING EDGE";
+
+ printf("event: %s offset: %u timestamp: [%8ld.%09ld]\n",
+ evname, offset, ts->tv_sec, ts->tv_nsec);
+}
+
+static int poll_callback(unsigned int num_lines,
+ struct gpiod_ctxless_event_poll_fd *fds,
+ const struct timespec *timeout, void *data)
+{
+ struct pollfd pfds[GPIOD_LINE_BULK_MAX_LINES + 1];
+ struct mon_ctx *ctx = data;
+ int cnt, ts, rv;
+ unsigned int i;
+
+ for (i = 0; i < num_lines; i++) {
+ pfds[i].fd = fds[i].fd;
+ pfds[i].events = POLLIN | POLLPRI;
+ }
+
+ pfds[i].fd = ctx->sigfd;
+ pfds[i].events = POLLIN | POLLPRI;
+
+ ts = timeout->tv_sec * 1000 + timeout->tv_nsec / 1000000;
+
+ cnt = poll(pfds, num_lines + 1, ts);
+ if (cnt < 0)
+ return GPIOD_CTXLESS_EVENT_POLL_RET_ERR;
+ else if (cnt == 0)
+ return GPIOD_CTXLESS_EVENT_POLL_RET_TIMEOUT;
+
+ rv = cnt;
+ for (i = 0; i < num_lines; i++) {
+ if (pfds[i].revents) {
+ fds[i].event = true;
+ if (!--cnt)
+ return rv;
+ }
+ }
+
+ /*
+ * If we're here, then there's a signal pending. No need to read it,
+ * we know we should quit now.
+ */
+ close(ctx->sigfd);
+
+ return GPIOD_CTXLESS_EVENT_POLL_RET_STOP;
+}
+
+static void handle_event(struct mon_ctx *ctx, int event_type,
+ unsigned int line_offset,
+ const struct timespec *timestamp)
+{
+ if (!ctx->silent) {
+ if (ctx->fmt)
+ event_print_custom(line_offset, timestamp,
+ event_type, ctx);
+ else
+ event_print_human_readable(line_offset,
+ timestamp, event_type);
+ }
+
+ ctx->events_done++;
+}
+
+static int event_callback(int event_type, unsigned int line_offset,
+ const struct timespec *timestamp, void *data)
+{
+ struct mon_ctx *ctx = data;
+
+ switch (event_type) {
+ case GPIOD_CTXLESS_EVENT_CB_RISING_EDGE:
+ case GPIOD_CTXLESS_EVENT_CB_FALLING_EDGE:
+ handle_event(ctx, event_type, line_offset, timestamp);
+ break;
+ default:
+ /*
+ * REVISIT: This happening would indicate a problem in the
+ * library.
+ */
+ return GPIOD_CTXLESS_EVENT_CB_RET_OK;
+ }
+
+ if (ctx->events_wanted && ctx->events_done >= ctx->events_wanted)
+ return GPIOD_CTXLESS_EVENT_CB_RET_STOP;
+
+ return GPIOD_CTXLESS_EVENT_CB_RET_OK;
+}
+
+static int make_signalfd(void)
+{
+ sigset_t sigmask;
+ int sigfd, rv;
+
+ sigemptyset(&sigmask);
+ sigaddset(&sigmask, SIGTERM);
+ sigaddset(&sigmask, SIGINT);
+
+ rv = sigprocmask(SIG_BLOCK, &sigmask, NULL);
+ if (rv < 0)
+ die("error masking signals: %s", strerror(errno));
+
+ sigfd = signalfd(-1, &sigmask, 0);
+ if (sigfd < 0)
+ die("error creating signalfd: %s", strerror(errno));
+
+ return sigfd;
+}
+
+int main(int argc, char **argv)
+{
+ unsigned int offsets[GPIOD_LINE_BULK_MAX_LINES], num_lines = 0, offset;
+ bool active_low = false, watch_rising = false, watch_falling = false;
+ struct timespec timeout = { 10, 0 };
+ int optc, opti, rv, i, event_type;
+ struct mon_ctx ctx;
+ char *end;
+
+ memset(&ctx, 0, sizeof(ctx));
+
+ for (;;) {
+ optc = getopt_long(argc, argv, shortopts, longopts, &opti);
+ if (optc < 0)
+ break;
+
+ switch (optc) {
+ case 'h':
+ print_help();
+ return EXIT_SUCCESS;
+ case 'v':
+ print_version();
+ return EXIT_SUCCESS;
+ case 'l':
+ active_low = true;
+ break;
+ case 'n':
+ ctx.events_wanted = strtoul(optarg, &end, 10);
+ if (*end != '\0')
+ die("invalid number: %s", optarg);
+ break;
+ case 's':
+ ctx.silent = true;
+ break;
+ case 'r':
+ watch_rising = true;
+ break;
+ case 'f':
+ watch_falling = true;
+ break;
+ case 'b':
+ setlinebuf(stdout);
+ break;
+ case 'F':
+ ctx.fmt = optarg;
+ break;
+ case '?':
+ die("try %s --help", get_progname());
+ default:
+ abort();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (watch_rising && !watch_falling)
+ event_type = GPIOD_CTXLESS_EVENT_RISING_EDGE;
+ else if (watch_falling && !watch_rising)
+ event_type = GPIOD_CTXLESS_EVENT_FALLING_EDGE;
+ else
+ event_type = GPIOD_CTXLESS_EVENT_BOTH_EDGES;
+
+ if (argc < 1)
+ die("gpiochip must be specified");
+
+ if (argc < 2)
+ die("at least one GPIO line offset must be specified");
+
+ for (i = 1; i < argc; i++) {
+ offset = strtoul(argv[i], &end, 10);
+ if (*end != '\0' || offset > INT_MAX)
+ die("invalid GPIO offset: %s", argv[i]);
+
+ offsets[i - 1] = offset;
+ num_lines++;
+ }
+
+ ctx.sigfd = make_signalfd();
+
+ rv = gpiod_ctxless_event_monitor_multiple(argv[0], event_type,
+ offsets, num_lines,
+ active_low, "gpiomon",
+ &timeout, poll_callback,
+ event_callback, &ctx);
+ if (rv)
+ die_perror("error waiting for events");
+
+ return EXIT_SUCCESS;
+}
diff --git a/tools/gpioset.c b/tools/gpioset.c
new file mode 100644
index 0000000..d9977a7
--- /dev/null
+++ b/tools/gpioset.c
@@ -0,0 +1,281 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+#include <errno.h>
+#include <gpiod.h>
+#include <getopt.h>
+#include <limits.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/select.h>
+#include <sys/signalfd.h>
+#include <unistd.h>
+
+#include "tools-common.h"
+
+static const struct option longopts[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'v' },
+ { "active-low", no_argument, NULL, 'l' },
+ { "mode", required_argument, NULL, 'm' },
+ { "sec", required_argument, NULL, 's' },
+ { "usec", required_argument, NULL, 'u' },
+ { "background", no_argument, NULL, 'b' },
+ { GETOPT_NULL_LONGOPT },
+};
+
+static const char *const shortopts = "+hvlm:s:u:b";
+
+static void print_help(void)
+{
+ printf("Usage: %s [OPTIONS] <chip name/number> <offset1>=<value1> <offset2>=<value2> ...\n",
+ get_progname());
+ printf("Set GPIO line values of a GPIO chip and maintain the state until the process exits\n");
+ printf("\n");
+ printf("Options:\n");
+ printf(" -h, --help:\t\tdisplay this message and exit\n");
+ printf(" -v, --version:\tdisplay the version and exit\n");
+ printf(" -l, --active-low:\tset the line active state to low\n");
+ printf(" -m, --mode=[exit|wait|time|signal] (defaults to 'exit'):\n");
+ printf(" tell the program what to do after setting values\n");
+ printf(" -s, --sec=SEC:\tspecify the number of seconds to wait (only valid for --mode=time)\n");
+ printf(" -u, --usec=USEC:\tspecify the number of microseconds to wait (only valid for --mode=time)\n");
+ printf(" -b, --background:\tafter setting values: detach from the controlling terminal\n");
+ printf("\n");
+ printf("Modes:\n");
+ printf(" exit:\t\tset values and exit immediately\n");
+ printf(" wait:\t\tset values and wait for user to press ENTER\n");
+ printf(" time:\t\tset values and sleep for a specified amount of time\n");
+ printf(" signal:\tset values and wait for SIGINT or SIGTERM\n");
+ printf("\n");
+ printf("Note: 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");
+}
+
+struct callback_data {
+ /* Replace with a union once we have more modes using callback data. */
+ struct timeval tv;
+ bool daemonize;
+};
+
+static void maybe_daemonize(bool daemonize)
+{
+ int rv;
+
+ if (daemonize) {
+ rv = daemon(0, 0);
+ if (rv < 0)
+ die("unable to daemonize: %s", strerror(errno));
+ }
+}
+
+static void wait_enter(void *data GPIOD_UNUSED)
+{
+ getchar();
+}
+
+static void wait_time(void *data)
+{
+ struct callback_data *cbdata = data;
+
+ maybe_daemonize(cbdata->daemonize);
+ select(0, NULL, NULL, NULL, &cbdata->tv);
+}
+
+static void wait_signal(void *data)
+{
+ struct callback_data *cbdata = data;
+ struct pollfd pfd;
+ sigset_t sigmask;
+ int sigfd, rv;
+
+ sigemptyset(&sigmask);
+ sigaddset(&sigmask, SIGTERM);
+ sigaddset(&sigmask, SIGINT);
+
+ rv = sigprocmask(SIG_BLOCK, &sigmask, NULL);
+ if (rv < 0)
+ die("error blocking signals: %s", strerror(errno));
+
+ sigfd = signalfd(-1, &sigmask, 0);
+ if (sigfd < 0)
+ die("error creating signalfd: %s", strerror(errno));
+
+ memset(&pfd, 0, sizeof(pfd));
+ pfd.fd = sigfd;
+ pfd.events = POLLIN | POLLPRI;
+
+ maybe_daemonize(cbdata->daemonize);
+
+ for (;;) {
+ rv = poll(&pfd, 1, 1000 /* one second */);
+ if (rv < 0)
+ die("error polling for signals: %s", strerror(errno));
+ else if (rv > 0)
+ break;
+ }
+
+ /*
+ * Don't bother reading siginfo - it's enough to know that we
+ * received any signal.
+ */
+ close(sigfd);
+}
+
+enum {
+ MODE_EXIT = 0,
+ MODE_WAIT,
+ MODE_TIME,
+ MODE_SIGNAL,
+};
+
+struct mode_mapping {
+ int id;
+ const char *name;
+ gpiod_ctxless_set_value_cb callback;
+};
+
+static const struct mode_mapping modes[] = {
+ [MODE_EXIT] = {
+ .id = MODE_EXIT,
+ .name = "exit",
+ .callback = NULL,
+ },
+ [MODE_WAIT] = {
+ .id = MODE_WAIT,
+ .name = "wait",
+ .callback = wait_enter,
+ },
+ [MODE_TIME] = {
+ .id = MODE_TIME,
+ .name = "time",
+ .callback = wait_time,
+ },
+ [MODE_SIGNAL] = {
+ .id = MODE_SIGNAL,
+ .name = "signal",
+ .callback = wait_signal,
+ },
+};
+
+static const struct mode_mapping *parse_mode(const char *mode)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(modes); i++)
+ if (strcmp(mode, modes[i].name) == 0)
+ return &modes[i];
+
+ return NULL;
+}
+
+int main(int argc, char **argv)
+{
+ const struct mode_mapping *mode = &modes[MODE_EXIT];
+ unsigned int *offsets, num_lines, i;
+ int *values, rv, optc, opti;
+ struct callback_data cbdata;
+ bool active_low = false;
+ char *device, *end;
+
+ memset(&cbdata, 0, sizeof(cbdata));
+
+ for (;;) {
+ optc = getopt_long(argc, argv, shortopts, longopts, &opti);
+ if (optc < 0)
+ break;
+
+ switch (optc) {
+ case 'h':
+ print_help();
+ return EXIT_SUCCESS;
+ case 'v':
+ print_version();
+ return EXIT_SUCCESS;
+ case 'l':
+ active_low = true;
+ break;
+ case 'm':
+ mode = parse_mode(optarg);
+ if (!mode)
+ die("invalid mode: %s", optarg);
+ break;
+ case 's':
+ cbdata.tv.tv_sec = strtoul(optarg, &end, 10);
+ if (*end != '\0')
+ die("invalid time value in seconds: %s", optarg);
+ break;
+ case 'u':
+ cbdata.tv.tv_usec = strtoul(optarg, &end, 10);
+ if (*end != '\0')
+ die("invalid time value in microseconds: %s",
+ optarg);
+ break;
+ case 'b':
+ cbdata.daemonize = true;
+ break;
+ case '?':
+ die("try %s --help", get_progname());
+ default:
+ abort();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (mode->id != MODE_TIME && (cbdata.tv.tv_sec || cbdata.tv.tv_usec))
+ die("can't specify wait time in this mode");
+
+ if (mode->id != MODE_SIGNAL &&
+ mode->id != MODE_TIME &&
+ cbdata.daemonize)
+ die("can't daemonize in this mode");
+
+ if (argc < 1)
+ die("gpiochip must be specified");
+
+ if (argc < 2)
+ die("at least one GPIO line offset to value mapping must be specified");
+
+ device = argv[0];
+
+ num_lines = argc - 1;
+
+ offsets = malloc(sizeof(*offsets) * num_lines);
+ values = malloc(sizeof(*values) * num_lines);
+ if (!values || !offsets)
+ die("out of memory");
+
+ for (i = 0; i < num_lines; i++) {
+ rv = sscanf(argv[i + 1], "%u=%d", &offsets[i], &values[i]);
+ if (rv != 2)
+ die("invalid offset<->value mapping: %s", argv[i + 1]);
+
+ if (values[i] != 0 && values[i] != 1)
+ die("value must be 0 or 1: %s", argv[i + 1]);
+
+ if (offsets[i] > INT_MAX)
+ die("invalid offset: %s", argv[i + 1]);
+ }
+
+ rv = gpiod_ctxless_set_value_multiple(device, offsets, values,
+ num_lines, active_low, "gpioset",
+ mode->callback, &cbdata);
+ if (rv < 0)
+ die_perror("error setting the GPIO line values");
+
+ free(offsets);
+ free(values);
+
+ return EXIT_SUCCESS;
+}
diff --git a/tools/tools-common.c b/tools/tools-common.c
new file mode 100644
index 0000000..a6e38e5
--- /dev/null
+++ b/tools/tools-common.c
@@ -0,0 +1,59 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+/* Common code for GPIO tools. */
+
+#include <errno.h>
+#include <gpiod.h>
+#include <libgen.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "tools-common.h"
+
+const char *get_progname(void)
+{
+ return program_invocation_name;
+}
+
+void die(const char *fmt, ...)
+{
+ va_list va;
+
+ va_start(va, fmt);
+ fprintf(stderr, "%s: ", program_invocation_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: ", program_invocation_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",
+ program_invocation_short_name, gpiod_version_string());
+ printf("Copyright (C) 2017-2018 Bartosz Golaszewski\n");
+ printf("License: LGPLv2.1\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");
+}
diff --git a/tools/tools-common.h b/tools/tools-common.h
new file mode 100644
index 0000000..ace3b46
--- /dev/null
+++ b/tools/tools-common.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+#ifndef __GPIOD_TOOLS_COMMON_H__
+#define __GPIOD_TOOLS_COMMON_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 ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x)))
+
+#define GETOPT_NULL_LONGOPT NULL, 0, NULL, 0
+
+const char *get_progname(void);
+void die(const char *fmt, ...) NORETURN PRINTF(1, 2);
+void die_perror(const char *fmt, ...) NORETURN PRINTF(1, 2);
+void print_version(void);
+
+#endif /* __GPIOD_TOOLS_COMMON_H__ */