pax_global_header00006660000000000000000000000064132366025630014520gustar00rootroot0000000000000052 comment=68b516e4d199305016bd34ddbdb45acd558ec23b libgpiod-v1.0.x/000077500000000000000000000000001323660256300136055ustar00rootroot00000000000000libgpiod-v1.0.x/.gitignore000066400000000000000000000005101323660256300155710ustar00rootroot00000000000000!.gitignore libgpiod.la libtools-common.la gpiodetect gpioinfo gpioget gpioset gpiomon gpiofind *.o *.lo doc libgpiod.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 libgpiod-v1.0.x/COPYING000066400000000000000000000636351323660256300146550ustar00rootroot00000000000000 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! libgpiod-v1.0.x/Doxyfile000066400000000000000000000055221323660256300153170ustar00rootroot00000000000000# # Copyright (C) 2017-2018 Bartosz Golaszewski # # This program 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. # # 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 = libgpiod-v1.0.x/Makefile.am000066400000000000000000000011531323660256300156410ustar00rootroot00000000000000# # Copyright (C) 2017-2018 Bartosz Golaszewski # # This program 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. # ACLOCAL_AMFLAGS = -I m4 AUTOMAKE_OPTIONS = foreign SUBDIRS = include src pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = libgpiod.pc if WITH_TESTS SUBDIRS += tests endif if HAS_DOXYGEN doc: @(cat Doxyfile; echo PROJECT_NUMBER = $(VERSION_STR)) | doxygen - .PHONY: doc endif libgpiod-v1.0.x/NEWS000066400000000000000000000075151323660256300143140ustar00rootroot00000000000000libgpiod 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 libgpiod-v1.0.x/README000066400000000000000000000114021323660256300144630ustar00rootroot00000000000000libgpiod ======== 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. 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 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. libgpiod-v1.0.x/autogen.sh000077500000000000000000000003451323660256300156100ustar00rootroot00000000000000#!/bin/sh 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 libgpiod-v1.0.x/configure.ac000066400000000000000000000071471323660256300161040ustar00rootroot00000000000000# # Copyright (C) 2017-2018 Bartosz Golaszewski # # This program 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. # AC_PREREQ(2.61) AC_INIT([libgpiod], 1.0) 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]) 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])]) 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_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([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_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]) AC_OUTPUT libgpiod-v1.0.x/include/000077500000000000000000000000001323660256300152305ustar00rootroot00000000000000libgpiod-v1.0.x/include/Makefile.am000066400000000000000000000005471323660256300172720ustar00rootroot00000000000000# # Copyright (C) 2017-2018 Bartosz Golaszewski # # This program 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. # include_HEADERS = gpiod.h libgpiod-v1.0.x/include/gpiod.h000066400000000000000000001260261323660256300165120ustar00rootroot00000000000000/* * This file is part of libgpiod. * * Copyright (C) 2017-2018 Bartosz Golaszewski * * This program 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. */ #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; /** * @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)) /** * @} * * @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 can be passed to the ctxless event callback. */ enum { GPIOD_CTXLESS_EVENT_CB_TIMEOUT, /**< 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; /** * @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; /** * @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 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; /** * @} * * @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, /**< 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, /**< 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, /**< 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, /**< 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 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__ */ libgpiod-v1.0.x/libgpiod.pc.in000066400000000000000000000003741323660256300163330ustar00rootroot00000000000000prefix=@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} libgpiod-v1.0.x/src/000077500000000000000000000000001323660256300143745ustar00rootroot00000000000000libgpiod-v1.0.x/src/Makefile.am000066400000000000000000000006031323660256300164270ustar00rootroot00000000000000# # Copyright (C) 2017-2018 Bartosz Golaszewski # # This program 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. # SUBDIRS = lib if WITH_TOOLS SUBDIRS += tools endif libgpiod-v1.0.x/src/lib/000077500000000000000000000000001323660256300151425ustar00rootroot00000000000000libgpiod-v1.0.x/src/lib/Makefile.am000066400000000000000000000012211323660256300171720ustar00rootroot00000000000000# # Copyright (C) 2017-2018 Bartosz Golaszewski # # This program 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. # 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-number $(subst .,:,$(PACKAGE_VERSION)) libgpiod-v1.0.x/src/lib/core.c000066400000000000000000000361661323660256300162520ustar00rootroot00000000000000/* * This file is part of libgpiod. * * Copyright (C) 2017-2018 Bartosz Golaszewski * * This program 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. */ /* Low-level, core library code. */ #include #include #include #include #include #include #include #include #include #include #include enum { LINE_FREE = 0, LINE_REQUESTED_VALUES, LINE_REQUESTED_EVENTS, }; 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; int fd; 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]; }; 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; 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 = -1; 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 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 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); for (i = 0; i < gpiod_line_bulk_num_lines(bulk); i++) { line = gpiod_line_bulk_get_line(bulk, i); req.lineoffsets[i] = gpiod_line_offset(line); if (config->request_type == GPIOD_LINE_REQUEST_DIRECTION_OUTPUT) 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; gpiod_line_bulk_foreach_line(bulk, line, lineptr) { line->state = LINE_REQUESTED_VALUES; line->fd = req.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 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->state = LINE_REQUESTED_EVENTS; line->fd = req.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) { close(line->fd); 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 = first->fd; 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; 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); status = ioctl(line->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->fd; 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) { 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) { if (line->state != LINE_REQUESTED_EVENTS) { errno = EPERM; return -1; } return gpiod_line_event_read_fd(line->fd, event); } int gpiod_line_event_get_fd(struct gpiod_line *line) { if (line->state != LINE_REQUESTED_EVENTS) { errno = EPERM; return -1; } return line->fd; } 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; } libgpiod-v1.0.x/src/lib/ctxless.c000066400000000000000000000165311323660256300170010ustar00rootroot00000000000000/* * This file is part of libgpiod. * * Copyright (C) 2017-2018 Bartosz Golaszewski * * This program 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. */ /* 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_loop_multiple(device, &offset, 1, 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) { struct gpiod_ctxless_event_poll_fd fds[GPIOD_LINE_BULK_MAX_LINES]; int rv, ret, flags, evtype, cnt; struct gpiod_line_event event; struct gpiod_line_bulk bulk; 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); } flags = active_low ? GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW : 0; rv = gpiod_line_request_bulk_both_edges_events_flags(&bulk, consumer, flags); 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; } libgpiod-v1.0.x/src/lib/helpers.c000066400000000000000000000215701323660256300167550ustar00rootroot00000000000000/* * This file is part of libgpiod. * * Copyright (C) 2017-2018 Bartosz Golaszewski * * This program 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. */ /* * 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; } 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_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); } libgpiod-v1.0.x/src/lib/iter.c000066400000000000000000000067211323660256300162570ustar00rootroot00000000000000/* * This file is part of libgpiod. * * Copyright (C) 2017-2018 Bartosz Golaszewski * * This program 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. */ /* 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; } libgpiod-v1.0.x/src/lib/misc.c000066400000000000000000000010051323660256300162350ustar00rootroot00000000000000/* * This file is part of libgpiod. * * Copyright (C) 2017-2018 Bartosz Golaszewski * * This program 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. */ /* Misc code that didn't fit anywhere else. */ #include const char *gpiod_version_string(void) { return GPIOD_VERSION_STR; } libgpiod-v1.0.x/src/tools/000077500000000000000000000000001323660256300155345ustar00rootroot00000000000000libgpiod-v1.0.x/src/tools/Makefile.am000066400000000000000000000015311323660256300175700ustar00rootroot00000000000000# # Copyright (C) 2017-2018 Bartosz Golaszewski # # This program 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. # 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 libgpiod-v1.0.x/src/tools/gpiodetect.c000066400000000000000000000034361323660256300200350ustar00rootroot00000000000000/* * This file is part of libgpiod. * * Copyright (C) 2017-2018 Bartosz Golaszewski * * This program 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. */ #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("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; } libgpiod-v1.0.x/src/tools/gpiofind.c000066400000000000000000000033471323660256300175060ustar00rootroot00000000000000/* * This file is part of libgpiod. * * Copyright (C) 2017-2018 Bartosz Golaszewski * * This program 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. */ #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("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; } libgpiod-v1.0.x/src/tools/gpioget.c000066400000000000000000000047561323660256300173520ustar00rootroot00000000000000/* * This file is part of libgpiod. * * Copyright (C) 2017-2018 Bartosz Golaszewski * * This program 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. */ #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("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; } libgpiod-v1.0.x/src/tools/gpioinfo.c000066400000000000000000000103761323660256300175210ustar00rootroot00000000000000/* * This file is part of libgpiod. * * Copyright (C) 2017-2018 Bartosz Golaszewski * * This program 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. */ #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("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; } libgpiod-v1.0.x/src/tools/gpiomon.c000066400000000000000000000166101323660256300173540ustar00rootroot00000000000000/* * This file is part of libgpiod. * * Copyright (C) 2017-2018 Bartosz Golaszewski * * This program 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. */ #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("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 { bool watch_rising; bool watch_falling; 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: if (ctx->watch_rising) handle_event(ctx, event_type, line_offset, timestamp); break; case GPIOD_CTXLESS_EVENT_CB_FALLING_EDGE: if (ctx->watch_falling) handle_event(ctx, event_type, line_offset, timestamp); break; default: 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]; struct timespec timeout = { 10, 0 }; unsigned int num_lines = 0, offset; bool active_low = false; int optc, opti, ret, i; 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': ctx.watch_rising = true; break; case 'f': ctx.watch_falling = true; break; case 'F': ctx.fmt = optarg; break; case '?': die("try %s --help", get_progname()); default: abort(); } } argc -= optind; argv += optind; if (!ctx.watch_rising && !ctx.watch_falling) ctx.watch_rising = ctx.watch_falling = true; 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_loop_multiple(argv[0], offsets, num_lines, active_low, "gpiomon", &timeout, poll_callback, event_callback, &ctx); if (ret) die_perror("error waiting for events"); return EXIT_SUCCESS; } libgpiod-v1.0.x/src/tools/gpioset.c000066400000000000000000000150521323660256300173550ustar00rootroot00000000000000/* * This file is part of libgpiod. * * Copyright (C) 2017-2018 Bartosz Golaszewski * * This program 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. */ #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("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; } libgpiod-v1.0.x/src/tools/tools-common.c000066400000000000000000000030221323660256300203230ustar00rootroot00000000000000/* * This file is part of libgpiod. * * Copyright (C) 2017-2018-2018 Bartosz Golaszewski * * This program 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. */ /* Common code for GPIO tools. */ #include #include "tools-common.h" #include #include #include #include #include #include #define NORETURN __attribute__((noreturn)) const char * get_progname(void) { return program_invocation_name; } void NORETURN PRINTF(1, 2) 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 NORETURN PRINTF(1, 2) 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) %s\n", program_invocation_short_name, gpiod_version_string()); printf("Copyright (C) 2017-2018-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"); } libgpiod-v1.0.x/src/tools/tools-common.h000066400000000000000000000016261323660256300203400ustar00rootroot00000000000000/* * This file is part of libgpiod. * * Copyright (C) 2017-2018 Bartosz Golaszewski * * This program 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. */ #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 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, ...); void die_perror(const char *fmt, ...); void print_version(void); #endif /* __GPIOD_TOOLS_COMMON_H__ */ libgpiod-v1.0.x/tests/000077500000000000000000000000001323660256300147475ustar00rootroot00000000000000libgpiod-v1.0.x/tests/Makefile.am000066400000000000000000000030731323660256300170060ustar00rootroot00000000000000# # Copyright (C) 2017-2018 Bartosz Golaszewski # # This program 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. # 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 linux kernel version >=v4.12.6 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 " ********************************************************" libgpiod-v1.0.x/tests/gpiod-test.c000066400000000000000000000576111323660256300172040ustar00rootroot00000000000000/* * This file is part of libgpiod. * * Copyright (C) 2017-2018 Bartosz Golaszewski * * This program 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. */ #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 = 12; static const unsigned int min_kern_release = 6; 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; } libgpiod-v1.0.x/tests/gpiod-test.h000066400000000000000000000132071323660256300172020ustar00rootroot00000000000000/* * This file is part of libgpiod. * * Copyright (C) 2017-2018 Bartosz Golaszewski * * This program 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. */ /* 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); 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__ */ libgpiod-v1.0.x/tests/tests-chip.c000066400000000000000000000156551323660256300172120ustar00rootroot00000000000000/* * This file is part of libgpiod. * * Copyright (C) 2017-2018 Bartosz Golaszewski * * This program 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. */ /* Test cases for GPIO chip handling. */ #include "gpiod-test.h" #include #include static void chip_open_good(void) { TEST_CLEANUP(test_close_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(test_close_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(test_close_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(test_close_chip) struct gpiod_chip *chip_by_label = NULL; TEST_CLEANUP(test_close_chip) struct gpiod_chip *chip_by_name = NULL; TEST_CLEANUP(test_close_chip) struct gpiod_chip *chip_by_path = NULL; TEST_CLEANUP(test_close_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(test_close_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(test_close_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(test_close_chip) struct gpiod_chip *chip0 = NULL; TEST_CLEANUP(test_close_chip) struct gpiod_chip *chip1 = NULL; TEST_CLEANUP(test_close_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(test_close_chip) struct gpiod_chip *chip0 = NULL; TEST_CLEANUP(test_close_chip) struct gpiod_chip *chip1 = NULL; TEST_CLEANUP(test_close_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(test_close_chip) struct gpiod_chip *chip0 = NULL; TEST_CLEANUP(test_close_chip) struct gpiod_chip *chip1 = NULL; TEST_CLEANUP(test_close_chip) struct gpiod_chip *chip2 = NULL; TEST_CLEANUP(test_close_chip) struct gpiod_chip *chip3 = NULL; TEST_CLEANUP(test_close_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_find_line_good(void) { TEST_CLEANUP(test_close_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(test_close_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 }); libgpiod-v1.0.x/tests/tests-ctxless.c000066400000000000000000000171031323660256300177420ustar00rootroot00000000000000/* * This file is part of libgpiod. * * Copyright (C) 2017-2018 Bartosz Golaszewski * * This program 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. */ /* 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_loop(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_loop(test_chip_name(0), 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_loop, "gpiod_ctxless_event_loop() - single event", 0, { 8 }); static void ctxless_event_loop_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_loop_multiple(test_chip_name(0), 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_loop_multiple, "gpiod_ctxless_event_loop_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_loop_indicate_error(void) { struct timespec ts = { 1, 0 }; int rv; test_set_event(0, 3, TEST_EVENT_ALTERNATING, 100); rv = gpiod_ctxless_event_loop(test_chip_name(0), 3, false, TEST_CONSUMER, &ts, NULL, error_event_cb, NULL); TEST_ASSERT_EQ(rv, -1); TEST_ASSERT_ERRNO_IS(ENOTBLK); } TEST_DEFINE(ctxless_event_loop_indicate_error, "gpiod_ctxless_event_loop() - error in callback", 0, { 8 }); static void ctxless_event_loop_indicate_error_timeout(void) { struct timespec ts = { 0, 100000 }; int rv; rv = gpiod_ctxless_event_loop(test_chip_name(0), 3, false, TEST_CONSUMER, &ts, NULL, error_event_cb, NULL); TEST_ASSERT_EQ(rv, -1); TEST_ASSERT_ERRNO_IS(ENOTBLK); } TEST_DEFINE(ctxless_event_loop_indicate_error_timeout, "gpiod_ctxless_event_loop() - 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 }); libgpiod-v1.0.x/tests/tests-event.c000066400000000000000000000152311323660256300173760ustar00rootroot00000000000000/* * This file is part of libgpiod. * * Copyright (C) 2017-2018 Bartosz Golaszewski * * This program 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. */ /* Test cases for GPIO line events. */ #include "gpiod-test.h" #include static void event_rising_edge_good(void) { TEST_CLEANUP(test_close_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(test_close_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(test_close_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(test_close_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(test_close_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_wait_multiple(void) { TEST_CLEANUP(test_close_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(test_close_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(test_close_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 }); libgpiod-v1.0.x/tests/tests-gpiodetect.c000066400000000000000000000031751323660256300204100ustar00rootroot00000000000000/* * This file is part of libgpiod. * * Copyright (C) 2017-2018 Bartosz Golaszewski * * This program 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. */ /* 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, { }); libgpiod-v1.0.x/tests/tests-gpiofind.c000066400000000000000000000041451323660256300200560ustar00rootroot00000000000000/* * This file is part of libgpiod. * * Copyright (C) 2017-2018 Bartosz Golaszewski * * This program 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. */ /* 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, { }); libgpiod-v1.0.x/tests/tests-gpioget.c000066400000000000000000000124571323660256300177220ustar00rootroot00000000000000/* * This file is part of libgpiod. * * Copyright (C) 2017-2018 Bartosz Golaszewski * * This program 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. */ /* 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 }); libgpiod-v1.0.x/tests/tests-gpioinfo.c000066400000000000000000000113231323660256300200650ustar00rootroot00000000000000/* * This file is part of libgpiod. * * Copyright (C) 2017-2018 Bartosz Golaszewski * * This program 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. */ /* 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+output\\s+active-high"); TEST_ASSERT_REGEX_MATCH(test_tool_stdout(), "\\s+line\\s+7:\\s+unnamed\\s+unused\\s+output\\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(test_close_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+output\\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+output\\s+active-high"); TEST_ASSERT_REGEX_NOMATCH(test_tool_stdout(), "\\s+line\\s+7:\\s+unnamed\\s+unused\\s+output\\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+output\\s+active-high"); TEST_ASSERT_REGEX_NOMATCH(test_tool_stdout(), "\\s+line\\s+7:\\s+unnamed\\s+unused\\s+output\\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 }); libgpiod-v1.0.x/tests/tests-gpiomon.c000066400000000000000000000322671323660256300177350ustar00rootroot00000000000000/* * This file is part of libgpiod. * * Copyright (C) 2017-2018 Bartosz Golaszewski * * This program 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. */ /* 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 }); libgpiod-v1.0.x/tests/tests-gpioset.c000066400000000000000000000254251323660256300177350ustar00rootroot00000000000000/* * This file is part of libgpiod. * * Copyright (C) 2017-2018 Bartosz Golaszewski * * This program 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. */ /* 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 }); libgpiod-v1.0.x/tests/tests-iter.c000066400000000000000000000066461323660256300172320ustar00rootroot00000000000000/* * This file is part of libgpiod. * * Copyright (C) 2017-2018 Bartosz Golaszewski * * This program 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. */ /* 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(test_close_chip) struct gpiod_chip *chipA = NULL; TEST_CLEANUP(test_close_chip) struct gpiod_chip *chipB = NULL; TEST_CLEANUP(test_close_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(test_close_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 }); libgpiod-v1.0.x/tests/tests-line.c000066400000000000000000000432371323660256300172130ustar00rootroot00000000000000/* * This file is part of libgpiod. * * Copyright (C) 2017-2018 Bartosz Golaszewski * * This program 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. */ /* GPIO line test cases. */ #include "gpiod-test.h" #include static void line_request_output(void) { TEST_CLEANUP(test_close_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(test_close_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(test_close_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(test_close_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(test_close_chip) struct gpiod_chip *chipA = NULL; TEST_CLEANUP(test_close_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(test_close_chip) struct gpiod_chip *chipA = NULL; TEST_CLEANUP(test_close_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_set_value(void) { TEST_CLEANUP(test_close_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(test_close_chip) struct gpiod_chip *chipA = NULL; TEST_CLEANUP(test_close_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(test_close_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(test_close_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(test_close_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(test_close_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(test_close_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 }); static void line_null_consumer(void) { TEST_CLEANUP(test_close_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(test_close_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(test_close_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 }); libgpiod-v1.0.x/tests/tests-misc.c000066400000000000000000000014541323660256300172120ustar00rootroot00000000000000/* * This file is part of libgpiod. * * Copyright (C) 2017-2018 Bartosz Golaszewski * * This program 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. */ /* 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, { });