From: SZ Lin (林上智) Date: Tue, 13 Nov 2018 03:42:05 +0000 (+0000) Subject: Import libgpiod_1.2.orig.tar.gz X-Git-Tag: archive/raspbian/1.4.1-2+rpi1~4 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=d6196ebb5ea65bdf7e16dae01d83b584212935dd;p=libgpiod.git Import libgpiod_1.2.orig.tar.gz [dgit import orig libgpiod_1.2.orig.tar.gz] --- d6196ebb5ea65bdf7e16dae01d83b584212935dd diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f16a9df --- /dev/null +++ b/.gitignore @@ -0,0 +1,44 @@ +!.gitignore +libgpiod.la +libgpiodcxx.la +libtools-common.la +gpiodetect +gpioinfo +gpioget +gpioset +gpiomon +gpiofind +gpiod_cxx_tests +gpiodetectcxx +gpiofindcxx +gpiogetcxx +gpioinfocxx +gpiomoncxx +gpiosetcxx +*.o +*.lo +*.la +doc +libgpiod.pc +libgpiodcxx.pc + +# autotools stuff +.deps/ +.libs/ +Makefile +Makefile.in +aclocal.m4 +autom4te.cache/ +autostuff/ +config.h +config.h.in +config.h.in~ +config.log +config.status +configure +libtool +m4/ +stamp-h1 + +# unit tests +gpiod-test 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. + + + + Copyright (C) + + 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. + + , 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 +# + +# 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..fee02c3 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,32 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +# +# This file is part of libgpiod. +# +# Copyright (C) 2017-2018 Bartosz Golaszewski +# + +ACLOCAL_AMFLAGS = -I m4 +AUTOMAKE_OPTIONS = foreign +SUBDIRS = include src bindings + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = libgpiod.pc + +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 + +EXTRA_DIST = Doxyfile + +endif diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..bb15b4d --- /dev/null +++ b/NEWS @@ -0,0 +1,162 @@ +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 instead of 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 for struct timespec +- include instead of +- 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/README b/README new file mode 100644 index 0000000..dae812a --- /dev/null +++ b/README @@ -0,0 +1,160 @@ +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= + 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.11. 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. + +Both bindings are extensively documented: with doxygen markup in case of C++ +and inline pydoc comments for python. + +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 +# 2017 Thierry Reding +# + +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 +# + +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..16a2a2c --- /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 +# + +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)/src/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..f4c92ef --- /dev/null +++ b/bindings/cxx/chip.cpp @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of libgpiod. + * + * Copyright (C) 2017-2018 Bartosz Golaszewski + */ + +#include + +#include +#include +#include +#include + +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 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& 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 +# + +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 + */ + +/* + * 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 + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace { + +using test_func = ::std::function; + +::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 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 + */ + +/* C++ reimplementation of the gpiodetect tool. */ + +#include + +#include +#include + +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 + */ + +/* C++ reimplementation of the gpiofind tool. */ + +#include + +#include +#include + +int main(int argc, char **argv) +{ + if (argc != 2) { + ::std::cerr << "usage: " << argv[0] << " " << ::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 + */ + +/* Simplified C++ reimplementation of the gpioget tool. */ + +#include + +#include +#include + +int main(int argc, char **argv) +{ + if (argc < 3) { + ::std::cerr << "usage: " << argv[0] << " ..." << ::std::endl; + return EXIT_FAILURE; + } + + ::std::vector 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 + */ + +/* Simplified C++ reimplementation of the gpioinfo tool. */ + +#include + +#include +#include + +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 + */ + +/* Simplified C++ reimplementation of the gpiomon tool. */ + +#include + +#include +#include + +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] << " ..." << ::std::endl; + return EXIT_FAILURE; + } + + ::std::vector 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 + */ + +/* Simplified C++ reimplementation of the gpioset tool. */ + +#include + +#include +#include + +int main(int argc, char **argv) +{ + if (argc < 3) { + ::std::cerr << "usage: " << argv[0] << " = ..." << ::std::endl; + return EXIT_FAILURE; + } + + ::std::vector offsets; + ::std::vector 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..fd08b17 --- /dev/null +++ b/bindings/cxx/gpiod.hpp @@ -0,0 +1,971 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libgpiod. + * + * Copyright (C) 2017-2018 Bartosz Golaszewski + */ + +#ifndef __LIBGPIOD_GPIOD_CXX_HPP__ +#define __LIBGPIOD_GPIOD_CXX_HPP__ + +#include + +#include +#include +#include +#include +#include + +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& 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& 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 default_vals = std::vector()) 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 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& 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::iterator& it); + + ::std::vector::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 _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..0f11057 --- /dev/null +++ b/bindings/cxx/iter.cpp @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of libgpiod. + * + * Copyright (C) 2017-2018 Bartosz Golaszewski + */ + +#include + +#include + +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), + _m_current(chip(::gpiod_chip_iter_next_noclose(this->_m_iter.get()))) +{ + +} + +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..35fef8b --- /dev/null +++ b/bindings/cxx/line.cpp @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of libgpiod. + * + * Copyright (C) 2017-2018 Bartosz Golaszewski + */ + +#include + +#include + +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..c93f364 --- /dev/null +++ b/bindings/cxx/line_bulk.cpp @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of libgpiod. + * + * Copyright (C) 2017-2018 Bartosz Golaszewski + */ + +#include + +#include +#include + +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 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) + { + 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& 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 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 line_bulk::get_values(void) const +{ + this->throw_if_empty(); + + ::std::vector 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& 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::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..506b733 --- /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 +# + +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)/src/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 +# + +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 +# + +'''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 +# + +'''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 +# + +'''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 +# + +'''Simplified reimplementation of the gpioget tool in Python.''' + +import gpiod +import sys + +if len(sys.argv) < 3: + raise TypeError('usage: gpioget.py ...') + +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 +# + +'''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 +# + +'''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 ...') + +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 +# + +'''Simplified reimplementation of the gpioset tool in Python.''' + +import gpiod +import sys + +if len(sys.argv) < 3: + raise TypeError('usage: gpioset.py = ...') + +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..6cd8011 --- /dev/null +++ b/bindings/python/gpiodmodule.c @@ -0,0 +1,2354 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of libgpiod. + * + * Copyright (C) 2017-2018 Bartosz Golaszewski + */ + +#include +#include + +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 ret; + + if (self->event.event_type == GPIOD_LINE_EVENT_RISING_EDGE) + ret = gpiod_RISING_EDGE; + else + ret = gpiod_FALLING_EDGE; + + return Py_BuildValue("I", ret); +} + +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_vals\n" +" Default value of this line (as a sequence, example: default_vals=[ 1 ])."); + +/* + * TODO: add support for 'default_val' argument which will allow users to pass + * a single default value directly rather than wrapping it in a sequence. + */ +static PyObject *gpiod_Line_request(gpiod_LineObject *self, + PyObject *args, PyObject *kwds) +{ + gpiod_LineBulkObject *bulk_obj; + PyObject *ret; + + 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..03ba3af --- /dev/null +++ b/configure.ac @@ -0,0 +1,208 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +# +# This file is part of libgpiod. +# +# Copyright (C) 2017-2018 Bartosz Golaszewski +# + +AC_PREREQ(2.61) + +AC_INIT([libgpiod], 1.2) +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.0.1]) +# Have a separate ABI version for C++ bindings: +AC_SUBST(ABI_CXX_VERSION, [1.0.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([src]) +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 + else + with_tools=false + 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_HEADERS([sys/signalfd.h], [], [HEADER_NOT_FOUND_TOOLS([sys/signalfd.h])]) +fi + +AC_ARG_ENABLE([tests], + [AC_HELP_STRING([--enable-tests], + [enable libgpiod tests [default=no]])], + [ + if test "x$enableval" = xyes + then + with_tests=true + else + with_tests=false + 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 + else + with_bindings_cxx=false + 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 + else + with_bindings_python=false + 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 + +AC_CONFIG_FILES([libgpiod.pc + Makefile + include/Makefile + src/Makefile + src/lib/Makefile + src/tools/Makefile + tests/Makefile + bindings/cxx/libgpiodcxx.pc + bindings/Makefile + bindings/cxx/Makefile + bindings/cxx/examples/Makefile + bindings/python/Makefile + bindings/python/examples/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 +# + +include_HEADERS = gpiod.h diff --git a/include/gpiod.h b/include/gpiod.h new file mode 100644 index 0000000..ccff977 --- /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 + */ + +#ifndef __LIBGPIOD_GPIOD_H__ +#define __LIBGPIOD_GPIOD_H__ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @mainpage libgpiod public API + * + * This is the complete documentation of the public API made available to + * users of libgpiod. + * + *

The 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. + * + *

The low-level API is further logically split into several parts such + * as: GPIO chip & line operators, iterators, GPIO events handling etc. + * + *

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 callack 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_DIRECTION_INPUT or GPIOD_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_ACTIVE_STATE_HIGH or GPIOD_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/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/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..e8c955d --- /dev/null +++ b/src/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 +# + +SUBDIRS = lib + +if WITH_TOOLS + +SUBDIRS += tools + +endif diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am new file mode 100644 index 0000000..3f797c4 --- /dev/null +++ b/src/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 +# + +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/src/lib/core.c b/src/lib/core.c new file mode 100644 index 0000000..4f273e3 --- /dev/null +++ b/src/lib/core.c @@ -0,0 +1,863 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of libgpiod. + * + * Copyright (C) 2017-2018 Bartosz Golaszewski + */ + +/* Low-level, core library code. */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 status, 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)); + + status = ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &info); + if (status < 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 status; + + 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->fd_handle = NULL; + + line->offset = offset; + line->chip = chip; + + chip->lines[offset] = line; + } else { + line = chip->lines[offset]; + } + + status = gpiod_line_update(line); + if (status < 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 status; + + status = gpiod_line_update(line); + if (status < 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 status, value; + + gpiod_line_bulk_init(&bulk); + gpiod_line_bulk_add(&bulk, line); + + status = gpiod_line_get_value_bulk(&bulk, &value); + if (status < 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 status, 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); + + status = ioctl(fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data); + if (status < 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 status, 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); + + status = ioctl(fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data); + if (status < 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/src/lib/ctxless.c b/src/lib/ctxless.c new file mode 100644 index 0000000..0009504 --- /dev/null +++ b/src/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 + */ + +/* Implementation of the high-level API. */ + +#include + +#include +#include +#include +#include + +int gpiod_ctxless_get_value(const char *device, unsigned int offset, + bool active_low, const char *consumer) +{ + int value, status; + + status = gpiod_ctxless_get_value_multiple(device, &offset, &value, + 1, active_low, consumer); + if (status < 0) + return status; + + 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; + int status, flags; + unsigned int i; + + if (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; + + status = gpiod_line_request_bulk_input_flags(&bulk, consumer, flags); + if (status < 0) { + gpiod_chip_close(chip); + return -1; + } + + memset(values, 0, sizeof(*values) * num_lines); + status = gpiod_line_get_value_bulk(&bulk, values); + + gpiod_chip_close(chip); + + return status; +} + +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; + int status, flags; + unsigned int i; + + if (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; + + status = gpiod_line_request_bulk_output_flags(&bulk, consumer, + flags, values); + if (status < 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 > 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 > 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/src/lib/helpers.c b/src/lib/helpers.c new file mode 100644 index 0000000..80b8eff --- /dev/null +++ b/src/lib/helpers.c @@ -0,0 +1,445 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of libgpiod. + * + * Copyright (C) 2017-2018 Bartosz Golaszewski + */ + +/* + * 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 + +#include +#include +#include +#include +#include + +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 status; + + status = asprintf(&path, "/dev/%s", name); + if (status < 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 status; + + status = asprintf(&path, "/dev/gpiochip%u", num); + if (!status) + 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/src/lib/iter.c b/src/lib/iter.c new file mode 100644 index 0000000..5b3d378 --- /dev/null +++ b/src/lib/iter.c @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of libgpiod. + * + * Copyright (C) 2017-2018 Bartosz Golaszewski + */ + +/* GPIO chip and line iterators. */ + +#include + +#include +#include + +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/src/lib/misc.c b/src/lib/misc.c new file mode 100644 index 0000000..b812416 --- /dev/null +++ b/src/lib/misc.c @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of libgpiod. + * + * Copyright (C) 2017-2018 Bartosz Golaszewski + */ + +/* Misc code that didn't fit anywhere else. */ + +#include + +const char *gpiod_version_string(void) +{ + return GPIOD_VERSION_STR; +} diff --git a/src/tools/Makefile.am b/src/tools/Makefile.am new file mode 100644 index 0000000..f72a8c1 --- /dev/null +++ b/src/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 +# + +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)/src/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/src/tools/gpiodetect.c b/src/tools/gpiodetect.c new file mode 100644 index 0000000..98e26cd --- /dev/null +++ b/src/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 + */ + +#include +#include "tools-common.h" + +#include +#include +#include + +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/src/tools/gpiofind.c b/src/tools/gpiofind.c new file mode 100644 index 0000000..76f0889 --- /dev/null +++ b/src/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 + */ + +#include +#include "tools-common.h" + +#include +#include +#include + +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("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/src/tools/gpioget.c b/src/tools/gpioget.c new file mode 100644 index 0000000..b468c05 --- /dev/null +++ b/src/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 + */ + +#include +#include "tools-common.h" + +#include +#include +#include +#include + +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] ...\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, status; + 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]); + } + + status = gpiod_ctxless_get_value_multiple(device, offsets, values, + num_lines, active_low, + "gpioget"); + if (status < 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/src/tools/gpioinfo.c b/src/tools/gpioinfo.c new file mode 100644 index 0000000..e631e1c --- /dev/null +++ b/src/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 + */ + +#include +#include "tools-common.h" + +#include +#include +#include +#include +#include + +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] ...\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 status; + + va_start(va, fmt); + status = vasprintf(&buf, fmt, va); + va_end(va); + if (status < 0) + die("vasprintf: %s\n", strerror(errno)); + + len = strlen(buf) - 1; + + if (len >= prlen || *of) { + *of = true; + printf("%s", buf); + } else { + status = asprintf(&buffmt, "%%%us", prlen); + if (status < 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/src/tools/gpiomon.c b/src/tools/gpiomon.c new file mode 100644 index 0000000..c13ab38 --- /dev/null +++ b/src/tools/gpiomon.c @@ -0,0 +1,327 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of libgpiod. + * + * Copyright (C) 2017-2018 Bartosz Golaszewski + */ + +#include +#include "tools-common.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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' }, + { "format", required_argument, NULL, 'F' }, + { GETOPT_NULL_LONGOPT }, +}; + +static const char *const shortopts = "+hvln:srfF:"; + +static void print_help(void) +{ + printf("Usage: %s [OPTIONS] ...\n", + get_progname()); + printf("Wait for events on 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"); + 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(" -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, ret; + 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; + + ret = cnt; + for (i = 0; i < num_lines; i++) { + if (pfds[i].revents) { + fds[i].event = true; + if (!--cnt) + return ret; + } + } + + /* + * 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, ret, 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 '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(); + + ret = gpiod_ctxless_event_monitor_multiple(argv[0], event_type, + offsets, num_lines, + active_low, "gpiomon", + &timeout, poll_callback, + event_callback, &ctx); + if (ret) + die_perror("error waiting for events"); + + return EXIT_SUCCESS; +} diff --git a/src/tools/gpioset.c b/src/tools/gpioset.c new file mode 100644 index 0000000..fb012fa --- /dev/null +++ b/src/tools/gpioset.c @@ -0,0 +1,276 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of libgpiod. + * + * Copyright (C) 2017-2018 Bartosz Golaszewski + */ + +#include +#include "tools-common.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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] = = ...\n", + get_progname()); + printf("Set GPIO line values of 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"); + 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"); +} + +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 status; + + if (daemonize) { + status = daemon(0, 0); + if (status < 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; + int sigfd, status; + struct pollfd pfd; + sigset_t sigmask; + + sigemptyset(&sigmask); + sigaddset(&sigmask, SIGTERM); + sigaddset(&sigmask, SIGINT); + + status = sigprocmask(SIG_BLOCK, &sigmask, NULL); + if (status < 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 (;;) { + status = poll(&pfd, 1, 1000 /* one second */); + if (status < 0) + die("error polling for signals: %s", strerror(errno)); + else if (status > 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, status, 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++) { + status = sscanf(argv[i + 1], "%u=%d", &offsets[i], &values[i]); + if (status != 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]); + } + + status = gpiod_ctxless_set_value_multiple(device, offsets, values, + num_lines, active_low, + "gpioset", mode->callback, + &cbdata); + if (status < 0) + die_perror("error setting the GPIO line values"); + + free(offsets); + free(values); + + return EXIT_SUCCESS; +} diff --git a/src/tools/tools-common.c b/src/tools/tools-common.c new file mode 100644 index 0000000..4a7a776 --- /dev/null +++ b/src/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 + */ + +/* Common code for GPIO tools. */ + +#include +#include "tools-common.h" + +#include +#include +#include +#include +#include +#include + +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/src/tools/tools-common.h b/src/tools/tools-common.h new file mode 100644 index 0000000..08a9522 --- /dev/null +++ b/src/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 + */ + +#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__ */ diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 0000000..a9319a7 --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,46 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +# +# This file is part of libgpiod. +# +# Copyright (C) 2017-2018 Bartosz Golaszewski +# + +AM_CFLAGS = -I$(top_srcdir)/include/ -include $(top_builddir)/config.h +AM_CFLAGS += -Wall -Wextra -g $(KMOD_CFLAGS) $(UDEV_CFLAGS) +AM_LDFLAGS = -pthread +LDADD = ../src/lib/libgpiod.la $(KMOD_LIBS) $(UDEV_LIBS) + +check_PROGRAMS = gpiod-test + +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..92d6b78 --- /dev/null +++ b/tests/gpiod-test.c @@ -0,0 +1,1176 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of libgpiod. + * + * Copyright (C) 2017-2018 Bartosz Golaszewski + */ + +#include "gpiod-test.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} 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 status; + + if (proc->running) { + kill(proc->pid, SIGKILL); + waitpid(proc->pid, &status, 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 status; + + va_start(va, fmt); + status = vasprintf(&new, fmt, va); + va_end(va); + if (status < 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 status, 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; + + status = pthread_cond_timedwait(&ev->cond, &ev->lock, &ts); + if (status == 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 (status != 0) { + die("error waiting for conditional variable: %s", + strerror(status)); + } + + event_unlock(); + } + + return NULL; +} + +static void gpiotool_proc_make_pipes(int *in_fds, int *out_fds, int *err_fds) +{ + int status, i, *fds[3]; + + fds[0] = in_fds; + fds[1] = out_fds; + fds[2] = err_fds; + + for (i = 0; i < 3; i++) { + status = pipe(fds[i]); + if (status < 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, status; + + 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++) { + status = dup2(old_fds[i], new_fds[i]); + if (status < 0) + die_perr("unable to duplicate a file descriptor"); + + close(old_fds[i]); + } +} + +static char *gpiotool_proc_get_path(const char *tool) +{ + char *path, *progpath, *progdir; + + progpath = xstrdup(program_invocation_name); + progdir = dirname(progpath); + path = xappend(NULL, "%s/../../src/tools/%s", progdir, tool); + free(progpath); + + return path; +} + +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], status; + 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"); + + gpiotool_proc_make_pipes(in_fds, out_fds, err_fds); + path = gpiotool_proc_get_path(tool); + + status = access(path, R_OK | X_OK); + if (status) + die_perr("unable to execute '%s'", path); + + sigemptyset(&sigmask); + sigaddset(&sigmask, SIGCHLD); + + status = sigprocmask(SIG_BLOCK, &sigmask, NULL); + if (status) + 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; + int status; + ssize_t rd; + + proc = &globals.test_ctx.tool_proc; + + if (!proc->running) + die("gpiotool process not running"); + + pfd.fd = proc->sig_fd; + pfd.events = POLLIN | POLLPRI; + + status = poll(&pfd, 1, 5000); + if (status == 0) + die("tool program is taking too long to terminate"); + else if (status < 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); + + status = sigprocmask(SIG_UNBLOCK, &sigmask, NULL); + if (status) + 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 status; + + msg("checking gpio-mockup availability"); + + globals.module_ctx = kmod_new(NULL, NULL); + if (!globals.module_ctx) + die_perr("error creating kernel module context"); + + status = kmod_module_new_from_name(globals.module_ctx, + "gpio-mockup", &globals.module); + if (status) + 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. */ + status = kmod_module_probe_insert_module(globals.module, 0, + "gpio_mockup_ranges=-1,4", + NULL, NULL, NULL); + if (status) + die_perr("unable to load gpio-mockup"); + + status = kmod_module_remove_module(globals.module, 0); + if (status) + die_perr("unable to remove gpio-mockup"); + + msg("gpio-mockup ok"); +} + +static void load_module(struct _test_chip_descr *descr) +{ + unsigned int i; + char *modarg; + int status; + + 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"); + + status = kmod_module_probe_insert_module(globals.module, 0, + modarg, NULL, NULL, NULL); + if (status) + 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; + 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 status; + + 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"); + + status = udev_monitor_filter_add_match_subsystem_devtype(monitor, + "gpio", NULL); + if (status < 0) + die_perr("error adding udev filters"); + + status = udev_monitor_enable_receiving(monitor); + if (status < 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) { + status = poll(&pfd, 1, 5000); + if (status < 0) + die_perr("error polling for uevents"); + else if (status == 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); + + if (!devpath || !devnode || !sysname || + !devpath_is_mockup(devpath)) { + udev_device_unref(dev); + continue; + } + + chip = xzalloc(sizeof(*chip)); + chip->name = xstrdup(sysname); + chip->path = xstrdup(devnode); + status = sscanf(sysname, "gpiochip%u", &chip->number); + if (status != 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 status; + + event_lock(); + ev = &globals.test_ctx.event; + + if (ev->running) { + ev->should_stop = true; + pthread_cond_broadcast(&ev->cond); + event_unlock(); + + status = pthread_join(globals.test_ctx.event.thread_id, NULL); + if (status != 0) + die("error joining event thread: %s", + strerror(status)); + + 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()) { + status = kmod_module_remove_module(globals.module, 0); + if (status) + 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(); + + 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, ...) +{ + int status; + va_list va; + + va_start(va, fmt); + status = vasprintf(&globals.test_ctx.failed_msg, fmt, va); + va_end(va); + if (status < 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 status; + + event_lock(); + + if (!ev->running) { + status = pthread_create(&ev->thread_id, NULL, + event_worker, NULL); + if (status != 0) + die("error creating event thread: %s", + strerror(status)); + + 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 + */ + +/* Testing framework - functions and definitions used by test cases. */ + +#ifndef __GPIOD_TEST_H__ +#define __GPIOD_TEST_H__ + +#include +#include + +#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..e199ce7 --- /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 + */ + +/* Test cases for GPIO chip handling. */ + +#include "gpiod-test.h" + +#include +#include + +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..ea9403d --- /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 + */ + +/* Test cases for the high-level API. */ + +#include "gpiod-test.h" + +#include + +static void ctxless_set_get_value(void) +{ + int ret; + + ret = gpiod_ctxless_get_value(test_chip_name(0), 3, + false, TEST_CONSUMER); + TEST_ASSERT_EQ(ret, 0); + + ret = gpiod_ctxless_set_value(test_chip_name(0), 3, 1, + false, TEST_CONSUMER, NULL, NULL); + TEST_ASSERT_RET_OK(ret); + + ret = gpiod_ctxless_get_value(test_chip_name(0), 3, + false, TEST_CONSUMER); + TEST_ASSERT_EQ(ret, 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], ret; + + ret = gpiod_ctxless_get_value_multiple(test_chip_name(0), offsets, + values, + GPIOD_LINE_BULK_MAX_LINES + 1, + false, TEST_CONSUMER); + TEST_ASSERT_NOTEQ(ret, 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], ret; + + ret = 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(ret, 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 status; + + test_set_event(0, 3, TEST_EVENT_ALTERNATING, 100); + + status = 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(status); + 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 status; + + offsets[0] = 2; + offsets[1] = 3; + offsets[2] = 5; + offsets[3] = 6; + + test_set_event(0, 3, TEST_EVENT_ALTERNATING, 100); + + status = 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(status); + 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..f91d80f --- /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 + */ + +/* Test cases for GPIO line events. */ + +#include "gpiod-test.h" + +#include +#include + +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..d7e7f20 --- /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 + */ + +/* Test cases for the gpiodetect program. */ + +#include "gpiod-test.h" + +#include + +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..a5a23fe --- /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 + */ + +/* Test cases for the gpiofind program. */ + +#include "gpiod-test.h" + +#include + +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 + */ + +/* 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..a7e592c --- /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 + */ + +/* Test cases for the gpioinfo program. */ + +#include "gpiod-test.h" + +#include + +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..83d5770 --- /dev/null +++ b/tests/tests-gpiomon.c @@ -0,0 +1,393 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of libgpiod. + * + * Copyright (C) 2017-2018 Bartosz Golaszewski + */ + +/* Test cases for the gpiomon program. */ + +#include "gpiod-test.h" + +#include +#include + +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_ignore_falling_edge(void) +{ + test_tool_run("gpiomon", "--rising-edge", + test_chip_name(0), "4", (char *)NULL); + test_set_event(0, 4, TEST_EVENT_FALLING, 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_NULL(test_tool_stdout()); + TEST_ASSERT_NULL(test_tool_stderr()); +} +TEST_DEFINE(gpiomon_ignore_falling_edge, + "tools: gpiomon - wait for rising edge events, ignore falling edge", + 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..64ca99a --- /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 + */ + +/* Test cases for the gpioset program. */ + +#include "gpiod-test.h" + +#include +#include + +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 + */ + +/* 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..1b73b86 --- /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 + */ + +/* GPIO line test cases. */ + +#include "gpiod-test.h" + +#include + +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 status; + + 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); + + status = gpiod_line_request_output(line_0, TEST_CONSUMER, 0); + TEST_ASSERT_RET_OK(status); + status = gpiod_line_request_output(line_1, TEST_CONSUMER, 1); + TEST_ASSERT_RET_OK(status); + + 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 status; + + 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); + + status = gpiod_line_request_input(line, TEST_CONSUMER); + TEST_ASSERT_RET_OK(status); + + status = gpiod_line_request_input(line, TEST_CONSUMER); + TEST_ASSERT_NOTEQ(status, 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 status; + + 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)); + + status = gpiod_line_request_input(line, TEST_CONSUMER); + TEST_ASSERT_RET_OK(status); + + 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 status; + + 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)); + + status = gpiod_line_request_input(line, + "consumer string over 32 characters long"); + TEST_ASSERT_RET_OK(status); + + 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 status; + + 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; + status = gpiod_line_request_bulk_output(&bulkA, TEST_CONSUMER, valA); + TEST_ASSERT_RET_OK(status); + + valB[0] = 0; + valB[1] = 1; + valB[2] = 0; + valB[3] = 1; + status = gpiod_line_request_bulk_output(&bulkB, TEST_CONSUMER, valB); + TEST_ASSERT_RET_OK(status); + + memset(valA, 0, sizeof(valA)); + memset(valB, 0, sizeof(valB)); + + status = gpiod_line_get_value_bulk(&bulkA, valA); + TEST_ASSERT_RET_OK(status); + TEST_ASSERT_EQ(valA[0], 1); + TEST_ASSERT_EQ(valA[1], 0); + TEST_ASSERT_EQ(valA[2], 0); + TEST_ASSERT_EQ(valA[3], 1); + + status = gpiod_line_get_value_bulk(&bulkB, valB); + TEST_ASSERT_RET_OK(status); + 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 status; + + 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; + + status = gpiod_line_request_bulk(&bulk, &req, NULL); + TEST_ASSERT_NOTEQ(status, 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 status; + + 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); + + status = gpiod_line_request_output(line, TEST_CONSUMER, 0); + TEST_ASSERT_RET_OK(status); + + 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 status; + + 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); + + status = gpiod_line_request_output(line, TEST_CONSUMER, 0); + TEST_ASSERT_RET_OK(status); + + TEST_ASSERT_EQ(gpiod_line_direction(line), GPIOD_LINE_DIRECTION_OUTPUT); + + gpiod_line_release(line); + + status = gpiod_line_request_input(line, TEST_CONSUMER); + TEST_ASSERT_RET_OK(status); + + 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 status; + + 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); + + status = gpiod_line_request_input(line, TEST_CONSUMER); + TEST_ASSERT_RET_OK(status); + + TEST_ASSERT_EQ(gpiod_line_active_state(line), + GPIOD_LINE_ACTIVE_STATE_HIGH); + + gpiod_line_release(line); + + status = gpiod_line_request_input_flags(line, TEST_CONSUMER, + GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW); + TEST_ASSERT_RET_OK(status); + + 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 status; + + 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; + + status = gpiod_line_request(line, &config, 0); + TEST_ASSERT_RET_OK(status); + + 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; + + status = gpiod_line_request(line, &config, 0); + TEST_ASSERT_RET_OK(status); + + 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..89ec64a --- /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 + */ + +/* Misc test cases. */ + +#include "gpiod-test.h" + +#include + +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, { });