From d6196ebb5ea65bdf7e16dae01d83b584212935dd Mon Sep 17 00:00:00 2001 From: =?utf8?q?SZ=20Lin=20=28=E6=9E=97=E4=B8=8A=E6=99=BA=29?= Date: Tue, 13 Nov 2018 03:42:05 +0000 Subject: [PATCH 1/1] Import libgpiod_1.2.orig.tar.gz [dgit import orig libgpiod_1.2.orig.tar.gz] --- .gitignore | 44 + COPYING | 508 +++++ Doxyfile | 109 + Makefile.am | 32 + NEWS | 162 ++ README | 160 ++ autogen.sh | 22 + bindings/Makefile.am | 21 + bindings/cxx/Makefile.am | 21 + bindings/cxx/chip.cpp | 202 ++ bindings/cxx/examples/Makefile.am | 33 + bindings/cxx/examples/gpiod_cxx_tests.cpp | 487 +++++ bindings/cxx/examples/gpiodetectcxx.cpp | 29 + bindings/cxx/examples/gpiofindcxx.cpp | 29 + bindings/cxx/examples/gpiogetcxx.cpp | 43 + bindings/cxx/examples/gpioinfocxx.cpp | 51 + bindings/cxx/examples/gpiomoncxx.cpp | 69 + bindings/cxx/examples/gpiosetcxx.cpp | 52 + bindings/cxx/gpiod.hpp | 971 +++++++++ bindings/cxx/iter.cpp | 142 ++ bindings/cxx/libgpiodcxx.pc.in | 11 + bindings/cxx/line.cpp | 243 +++ bindings/cxx/line_bulk.cpp | 270 +++ bindings/python/Makefile.am | 18 + bindings/python/examples/Makefile.am | 15 + bindings/python/examples/gpiod_tests.py | 437 ++++ bindings/python/examples/gpiodetect.py | 18 + bindings/python/examples/gpiofind.py | 20 + bindings/python/examples/gpioget.py | 29 + bindings/python/examples/gpioinfo.py | 31 + bindings/python/examples/gpiomon.py | 44 + bindings/python/examples/gpioset.py | 28 + bindings/python/gpiodmodule.c | 2354 +++++++++++++++++++++ configure.ac | 208 ++ include/Makefile.am | 9 + include/gpiod.h | 1404 ++++++++++++ libgpiod.pc.in | 11 + src/Makefile.am | 15 + src/lib/Makefile.am | 14 + src/lib/core.c | 863 ++++++++ src/lib/ctxless.c | 369 ++++ src/lib/helpers.c | 445 ++++ src/lib/iter.c | 172 ++ src/lib/misc.c | 15 + src/tools/Makefile.am | 29 + src/tools/gpiodetect.c | 78 + src/tools/gpiofind.c | 73 + src/tools/gpioget.c | 106 + src/tools/gpioinfo.c | 203 ++ src/tools/gpiomon.c | 327 +++ src/tools/gpioset.c | 276 +++ src/tools/tools-common.c | 59 + src/tools/tools-common.h | 29 + tests/Makefile.am | 46 + tests/gpiod-test.c | 1176 ++++++++++ tests/gpiod-test.h | 164 ++ tests/tests-chip.c | 336 +++ tests/tests-ctxless.c | 311 +++ tests/tests-event.c | 335 +++ tests/tests-gpiodetect.c | 50 + tests/tests-gpiofind.c | 69 + tests/tests-gpioget.c | 181 ++ tests/tests-gpioinfo.c | 141 ++ tests/tests-gpiomon.c | 393 ++++ tests/tests-gpioset.c | 385 ++++ tests/tests-iter.c | 136 ++ tests/tests-line.c | 721 +++++++ tests/tests-misc.c | 24 + 68 files changed, 15878 insertions(+) create mode 100644 .gitignore create mode 100644 COPYING create mode 100644 Doxyfile create mode 100644 Makefile.am create mode 100644 NEWS create mode 100644 README create mode 100755 autogen.sh create mode 100644 bindings/Makefile.am create mode 100644 bindings/cxx/Makefile.am create mode 100644 bindings/cxx/chip.cpp create mode 100644 bindings/cxx/examples/Makefile.am create mode 100644 bindings/cxx/examples/gpiod_cxx_tests.cpp create mode 100644 bindings/cxx/examples/gpiodetectcxx.cpp create mode 100644 bindings/cxx/examples/gpiofindcxx.cpp create mode 100644 bindings/cxx/examples/gpiogetcxx.cpp create mode 100644 bindings/cxx/examples/gpioinfocxx.cpp create mode 100644 bindings/cxx/examples/gpiomoncxx.cpp create mode 100644 bindings/cxx/examples/gpiosetcxx.cpp create mode 100644 bindings/cxx/gpiod.hpp create mode 100644 bindings/cxx/iter.cpp create mode 100644 bindings/cxx/libgpiodcxx.pc.in create mode 100644 bindings/cxx/line.cpp create mode 100644 bindings/cxx/line_bulk.cpp create mode 100644 bindings/python/Makefile.am create mode 100644 bindings/python/examples/Makefile.am create mode 100755 bindings/python/examples/gpiod_tests.py create mode 100755 bindings/python/examples/gpiodetect.py create mode 100755 bindings/python/examples/gpiofind.py create mode 100755 bindings/python/examples/gpioget.py create mode 100755 bindings/python/examples/gpioinfo.py create mode 100755 bindings/python/examples/gpiomon.py create mode 100755 bindings/python/examples/gpioset.py create mode 100644 bindings/python/gpiodmodule.c create mode 100644 configure.ac create mode 100644 include/Makefile.am create mode 100644 include/gpiod.h create mode 100644 libgpiod.pc.in create mode 100644 src/Makefile.am create mode 100644 src/lib/Makefile.am create mode 100644 src/lib/core.c create mode 100644 src/lib/ctxless.c create mode 100644 src/lib/helpers.c create mode 100644 src/lib/iter.c create mode 100644 src/lib/misc.c create mode 100644 src/tools/Makefile.am create mode 100644 src/tools/gpiodetect.c create mode 100644 src/tools/gpiofind.c create mode 100644 src/tools/gpioget.c create mode 100644 src/tools/gpioinfo.c create mode 100644 src/tools/gpiomon.c create mode 100644 src/tools/gpioset.c create mode 100644 src/tools/tools-common.c create mode 100644 src/tools/tools-common.h create mode 100644 tests/Makefile.am create mode 100644 tests/gpiod-test.c create mode 100644 tests/gpiod-test.h create mode 100644 tests/tests-chip.c create mode 100644 tests/tests-ctxless.c create mode 100644 tests/tests-event.c create mode 100644 tests/tests-gpiodetect.c create mode 100644 tests/tests-gpiofind.c create mode 100644 tests/tests-gpioget.c create mode 100644 tests/tests-gpioinfo.c create mode 100644 tests/tests-gpiomon.c create mode 100644 tests/tests-gpioset.c create mode 100644 tests/tests-iter.c create mode 100644 tests/tests-line.c create mode 100644 tests/tests-misc.c 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, { }); -- 2.30.2