config.log
config.status
configure
+configure~
libtool
*-libtool
m4/
stamp-h1
+
+# profiling
+*.gcda
+*.gcno
WARN_IF_UNDOCUMENTED = YES
WARN_FORMAT =
WARN_LOGFILE =
-INPUT = @top_srcdir@/include/gpiod.h @top_srcdir@/bindings/cxx/gpiod.hpp
+INPUT = @top_srcdir@/include/gpiod.h \
+ @top_srcdir@/bindings/cxx/gpiod.hpp \
+ @top_srcdir@/bindings/cxx/gpiodcxx/
SOURCE_BROWSER = YES
INLINE_SOURCES = NO
REFERENCED_BY_RELATION = YES
# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
lib_LTLIBRARIES = libgpiodcxx.la
-libgpiodcxx_la_SOURCES = chip.cpp internal.h iter.cpp line.cpp line_bulk.cpp
-libgpiodcxx_la_CPPFLAGS = -Wall -Wextra -g -std=gnu++11
-libgpiodcxx_la_CPPFLAGS += -fvisibility=hidden -I$(top_srcdir)/include/
+libgpiodcxx_la_SOURCES = \
+ chip.cpp \
+ chip-info.cpp \
+ edge-event-buffer.cpp \
+ edge-event.cpp \
+ exception.cpp \
+ info-event.cpp \
+ internal.cpp \
+ internal.hpp \
+ line.cpp \
+ line-config.cpp \
+ line-info.cpp \
+ line-request.cpp \
+ line-settings.cpp \
+ misc.cpp \
+ request-builder.cpp \
+ request-config.cpp
+
+libgpiodcxx_la_CXXFLAGS = -Wall -Wextra -g -std=gnu++17
+libgpiodcxx_la_CXXFLAGS += -fvisibility=hidden -I$(top_srcdir)/include/
+libgpiodcxx_la_CXXFLAGS += $(PROFILING_CFLAGS)
+libgpiodcxx_la_CXXFLAGS += -DGPIOD_CXX_BUILD
libgpiodcxx_la_LDFLAGS = -version-info $(subst .,:,$(ABI_CXX_VERSION))
libgpiodcxx_la_LDFLAGS += -lgpiod -L$(top_builddir)/lib
+libgpiodcxx_la_LDFLAGS += $(PROFILING_LDFLAGS)
include_HEADERS = gpiod.hpp
pkgconfigdir = $(libdir)/pkgconfig
pkgconfig_DATA = libgpiodcxx.pc
-SUBDIRS = .
+SUBDIRS = gpiodcxx .
if WITH_TESTS
--- /dev/null
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <ostream>
+#include <utility>
+
+#include "internal.hpp"
+
+namespace gpiod {
+
+void chip_info::impl::set_info_ptr(chip_info_ptr& new_info)
+{
+ this->info = ::std::move(new_info);
+}
+
+GPIOD_CXX_API chip_info::chip_info()
+ : _m_priv(new impl)
+{
+
+}
+
+GPIOD_CXX_API chip_info::chip_info(const chip_info& other)
+ : _m_priv(other._m_priv)
+{
+
+}
+
+GPIOD_CXX_API chip_info::chip_info(chip_info&& other) noexcept
+ : _m_priv(::std::move(other._m_priv))
+{
+
+}
+
+GPIOD_CXX_API chip_info::~chip_info()
+{
+
+}
+
+GPIOD_CXX_API chip_info& chip_info::operator=(const chip_info& other)
+{
+ this->_m_priv = other._m_priv;
+
+ return *this;
+}
+
+GPIOD_CXX_API chip_info& chip_info::operator=(chip_info&& other) noexcept
+{
+ this->_m_priv = ::std::move(other._m_priv);
+
+ return *this;
+}
+
+GPIOD_CXX_API ::std::string chip_info::name() const noexcept
+{
+ return ::gpiod_chip_info_get_name(this->_m_priv->info.get());
+}
+
+GPIOD_CXX_API ::std::string chip_info::label() const noexcept
+{
+ return ::gpiod_chip_info_get_label(this->_m_priv->info.get());
+}
+
+GPIOD_CXX_API ::std::size_t chip_info::num_lines() const noexcept
+{
+ return ::gpiod_chip_info_get_num_lines(this->_m_priv->info.get());
+}
+
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const chip_info& info)
+{
+ out << "gpiod::chip_info(name=\"" << info.name() <<
+ "\", label=\"" << info.label() <<
+ "\", num_lines=" << info.num_lines() << ")";
+
+ return out;
+}
+
+} /* namespace gpiod */
// SPDX-License-Identifier: LGPL-3.0-or-later
-// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
-#include <functional>
-#include <gpiod.hpp>
-#include <map>
-#include <system_error>
+#include <ostream>
#include <utility>
#include "internal.hpp"
namespace {
-GPIOD_CXX_API void chip_deleter(::gpiod_chip* chip)
+chip_ptr open_chip(const ::std::filesystem::path& path)
{
- ::gpiod_chip_unref(chip);
+ chip_ptr chip(::gpiod_chip_open(path.c_str()));
+ if (!chip)
+ throw_from_errno("unable to open the GPIO device " + path.string());
+
+ return chip;
}
} /* namespace */
-GPIOD_CXX_API bool is_gpiochip_device(const ::std::string& path)
+chip::impl::impl(const ::std::filesystem::path& path)
+ : chip(open_chip(path))
{
- return ::gpiod_is_gpiochip_device(path.c_str());
+
}
-GPIOD_CXX_API chip::chip(const ::std::string& path)
- : _m_chip()
+void chip::impl::throw_if_closed() const
{
- this->open(path);
+ if (!this->chip)
+ throw chip_closed("GPIO chip has been closed");
}
-GPIOD_CXX_API chip::chip(::gpiod_chip* chip)
- : _m_chip(chip, chip_deleter)
+GPIOD_CXX_API chip::chip(const ::std::filesystem::path& path)
+ : _m_priv(new impl(path))
{
}
-GPIOD_CXX_API chip::chip(const ::std::weak_ptr<::gpiod_chip>& chip_ptr)
- : _m_chip(chip_ptr)
+chip::chip(const chip& other)
+ : _m_priv(other._m_priv)
{
}
-GPIOD_CXX_API void chip::open(const ::std::string& path)
+GPIOD_CXX_API chip::chip(chip&& other) noexcept
+ : _m_priv(::std::move(other._m_priv))
{
- ::gpiod_chip *chip = ::gpiod_chip_open(path.c_str());
- if (!chip)
- throw ::std::system_error(errno, ::std::system_category(),
- "cannot open GPIO device " + path);
- this->_m_chip.reset(chip, chip_deleter);
}
-GPIOD_CXX_API void chip::reset(void) noexcept
+GPIOD_CXX_API chip::~chip()
{
- this->_m_chip.reset();
+
}
-GPIOD_CXX_API ::std::string chip::name(void) const
+GPIOD_CXX_API chip& chip::operator=(chip&& other) noexcept
{
- this->throw_if_noref();
+ this->_m_priv = ::std::move(other._m_priv);
+
+ return *this;
+}
- return ::std::string(::gpiod_chip_get_name(this->_m_chip.get()));
+GPIOD_CXX_API chip::operator bool() const noexcept
+{
+ return this->_m_priv->chip.get() != nullptr;
}
-GPIOD_CXX_API ::std::string chip::label(void) const
+GPIOD_CXX_API void chip::close()
{
- this->throw_if_noref();
+ this->_m_priv->throw_if_closed();
- return ::std::string(::gpiod_chip_get_label(this->_m_chip.get()));
+ this->_m_priv->chip.reset();
}
-GPIOD_CXX_API unsigned int chip::num_lines(void) const
+GPIOD_CXX_API ::std::filesystem::path chip::path() const
{
- this->throw_if_noref();
+ this->_m_priv->throw_if_closed();
- return ::gpiod_chip_get_num_lines(this->_m_chip.get());
+ return ::gpiod_chip_get_path(this->_m_priv->chip.get());
}
-GPIOD_CXX_API line chip::get_line(unsigned int offset) const
+GPIOD_CXX_API chip_info chip::get_info() const
{
- this->throw_if_noref();
+ this->_m_priv->throw_if_closed();
+
+ chip_info_ptr info(::gpiod_chip_get_info(this->_m_priv->chip.get()));
+ if (!info)
+ throw_from_errno("failed to retrieve GPIO chip info");
- if (offset >= this->num_lines())
- throw ::std::out_of_range("line offset greater than the number of lines");
+ chip_info ret;
- ::gpiod_line* line_handle = ::gpiod_chip_get_line(this->_m_chip.get(), offset);
- if (!line_handle)
- throw ::std::system_error(errno, ::std::system_category(),
- "error getting GPIO line from chip");
+ ret._m_priv->set_info_ptr(info);
- return line(line_handle, *this);
+ return ret;
}
-GPIOD_CXX_API int chip::find_line(const ::std::string& name) const
+GPIOD_CXX_API line_info chip::get_line_info(line::offset offset) const
{
- this->throw_if_noref();
+ this->_m_priv->throw_if_closed();
- for (unsigned int offset = 0; offset < this->num_lines(); offset++) {
- auto line = this->get_line(offset);
+ line_info_ptr info(::gpiod_chip_get_line_info(this->_m_priv->chip.get(), offset));
+ if (!info)
+ throw_from_errno("unable to retrieve GPIO line info");
- if (line.name() == name)
- return offset;
- }
+ line_info ret;
+
+ ret._m_priv->set_info_ptr(info);
- return -1;
+ return ret;
}
-GPIOD_CXX_API line_bulk chip::get_lines(const ::std::vector<unsigned int>& offsets) const
+GPIOD_CXX_API line_info chip::watch_line_info(line::offset offset) const
{
- line_bulk lines;
+ this->_m_priv->throw_if_closed();
- for (auto& it: offsets)
- lines.append(this->get_line(it));
+ line_info_ptr info(::gpiod_chip_watch_line_info(this->_m_priv->chip.get(), offset));
+ if (!info)
+ throw_from_errno("unable to start watching GPIO line info changes");
- return lines;
+ line_info ret;
+
+ ret._m_priv->set_info_ptr(info);
+
+ return ret;
}
-GPIOD_CXX_API line_bulk chip::get_all_lines(void) const
+GPIOD_CXX_API void chip::unwatch_line_info(line::offset offset) const
{
- line_bulk lines;
+ this->_m_priv->throw_if_closed();
- for (unsigned int i = 0; i < this->num_lines(); i++)
- lines.append(this->get_line(i));
+ int ret = ::gpiod_chip_unwatch_line_info(this->_m_priv->chip.get(), offset);
+ if (ret)
+ throw_from_errno("unable to unwatch line status changes");
+}
- return lines;
+GPIOD_CXX_API int chip::fd() const
+{
+ this->_m_priv->throw_if_closed();
+
+ return ::gpiod_chip_get_fd(this->_m_priv->chip.get());
}
-GPIOD_CXX_API bool chip::operator==(const chip& rhs) const noexcept
+GPIOD_CXX_API bool chip::wait_info_event(const ::std::chrono::nanoseconds& timeout) const
{
- return this->_m_chip.get() == rhs._m_chip.get();
+ this->_m_priv->throw_if_closed();
+
+ int ret = ::gpiod_chip_wait_info_event(this->_m_priv->chip.get(), timeout.count());
+ if (ret < 0)
+ throw_from_errno("error waiting for info events");
+
+ return ret;
}
-GPIOD_CXX_API bool chip::operator!=(const chip& rhs) const noexcept
+GPIOD_CXX_API info_event chip::read_info_event() const
{
- return this->_m_chip.get() != rhs._m_chip.get();
+ this->_m_priv->throw_if_closed();
+
+ info_event_ptr event(gpiod_chip_read_info_event(this->_m_priv->chip.get()));
+ if (!event)
+ throw_from_errno("error reading the line info event_handle");
+
+ info_event ret;
+ ret._m_priv->set_info_event_ptr(event);
+
+ return ret;
}
-GPIOD_CXX_API chip::operator bool(void) const noexcept
+GPIOD_CXX_API int chip::get_line_offset_from_name(const ::std::string& name) const
{
- return this->_m_chip.get() != nullptr;
+ this->_m_priv->throw_if_closed();
+
+ int ret = ::gpiod_chip_get_line_offset_from_name(this->_m_priv->chip.get(), name.c_str());
+ if (ret < 0) {
+ if (errno == ENOENT)
+ return -1;
+
+ throw_from_errno("error looking up line by name");
+ }
+
+ return ret;
}
-GPIOD_CXX_API bool chip::operator!(void) const noexcept
+GPIOD_CXX_API request_builder chip::prepare_request()
{
- return this->_m_chip.get() == nullptr;
+ return request_builder(*this);
}
-GPIOD_CXX_API void chip::throw_if_noref(void) const
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const chip& chip)
{
- if (!this->_m_chip.get())
- throw ::std::logic_error("object not associated with an open GPIO chip");
+ if (!chip)
+ out << "gpiod::chip(closed)";
+ else
+ out << "gpiod::chip(path=" << chip.path() <<
+ ", info=" << chip.get_info() << ")";
+
+ return out;
}
} /* namespace gpiod */
--- /dev/null
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <iterator>
+#include <ostream>
+#include <utility>
+
+#include "internal.hpp"
+
+namespace gpiod {
+
+namespace {
+
+::gpiod_edge_event_buffer* make_edge_event_buffer(unsigned int capacity)
+{
+ ::gpiod_edge_event_buffer* buffer = ::gpiod_edge_event_buffer_new(capacity);
+ if (!buffer)
+ throw_from_errno("unable to allocate the edge event buffer");
+
+ return buffer;
+}
+
+} /* namespace */
+
+edge_event_buffer::impl::impl(unsigned int capacity)
+ : buffer(make_edge_event_buffer(capacity)),
+ events()
+{
+ events.reserve(capacity);
+
+ for (unsigned int i = 0; i < capacity; i++) {
+ events.push_back(edge_event());
+ events.back()._m_priv.reset(new edge_event::impl_external);
+ }
+}
+
+int edge_event_buffer::impl::read_events(const line_request_ptr& request, unsigned int max_events)
+{
+ int ret = ::gpiod_line_request_read_edge_event(request.get(),
+ this->buffer.get(), max_events);
+ if (ret < 0)
+ throw_from_errno("error reading edge events from file descriptor");
+
+ for (int i = 0; i < ret; i++) {
+ ::gpiod_edge_event* event = ::gpiod_edge_event_buffer_get_event(this->buffer.get(), i);
+
+ dynamic_cast<edge_event::impl_external&>(*this->events[i]._m_priv).event = event;
+ }
+
+ return ret;
+}
+
+GPIOD_CXX_API edge_event_buffer::edge_event_buffer(::std::size_t capacity)
+ : _m_priv(new impl(capacity))
+{
+
+}
+
+GPIOD_CXX_API edge_event_buffer::edge_event_buffer(edge_event_buffer&& other) noexcept
+ : _m_priv(::std::move(other._m_priv))
+{
+
+}
+
+GPIOD_CXX_API edge_event_buffer::~edge_event_buffer()
+{
+
+}
+
+GPIOD_CXX_API edge_event_buffer& edge_event_buffer::operator=(edge_event_buffer&& other) noexcept
+{
+ this->_m_priv = ::std::move(other._m_priv);
+
+ return *this;
+}
+
+GPIOD_CXX_API const edge_event& edge_event_buffer::get_event(unsigned int index) const
+{
+ return this->_m_priv->events.at(index);
+}
+
+GPIOD_CXX_API ::std::size_t edge_event_buffer::num_events() const
+{
+ return ::gpiod_edge_event_buffer_get_num_events(this->_m_priv->buffer.get());
+}
+
+GPIOD_CXX_API ::std::size_t edge_event_buffer::capacity() const noexcept
+{
+ return ::gpiod_edge_event_buffer_get_capacity(this->_m_priv->buffer.get());
+}
+
+GPIOD_CXX_API edge_event_buffer::const_iterator edge_event_buffer::begin() const noexcept
+{
+ return this->_m_priv->events.begin();
+}
+
+GPIOD_CXX_API edge_event_buffer::const_iterator edge_event_buffer::end() const noexcept
+{
+ return this->_m_priv->events.begin() + this->num_events();
+}
+
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const edge_event_buffer& buf)
+{
+ out << "gpiod::edge_event_buffer(num_events=" << buf.num_events() <<
+ ", capacity=" << buf.capacity() <<
+ ", events=[";
+
+ ::std::copy(buf.begin(), ::std::prev(buf.end()),
+ ::std::ostream_iterator<edge_event>(out, ", "));
+ out << *(::std::prev(buf.end()));
+
+ out << "])";
+
+ return out;
+}
+
+} /* namespace gpiod */
--- /dev/null
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <map>
+#include <ostream>
+#include <utility>
+
+#include "internal.hpp"
+
+namespace gpiod {
+
+namespace {
+
+const ::std::map<int, edge_event::event_type> event_type_mapping = {
+ { GPIOD_EDGE_EVENT_RISING_EDGE, edge_event::event_type::RISING_EDGE },
+ { GPIOD_EDGE_EVENT_FALLING_EDGE, edge_event::event_type::FALLING_EDGE }
+};
+
+const ::std::map<edge_event::event_type, ::std::string> event_type_names = {
+ { edge_event::event_type::RISING_EDGE, "RISING_EDGE" },
+ { edge_event::event_type::FALLING_EDGE, "FALLING_EDGE" }
+};
+
+} /* namespace */
+
+::gpiod_edge_event* edge_event::impl_managed::get_event_ptr() const noexcept
+{
+ return this->event.get();
+}
+
+::std::shared_ptr<edge_event::impl>
+edge_event::impl_managed::copy(const ::std::shared_ptr<impl>& self) const
+{
+ return self;
+}
+
+edge_event::impl_external::impl_external()
+ : impl(),
+ event(nullptr)
+{
+
+}
+
+::gpiod_edge_event* edge_event::impl_external::get_event_ptr() const noexcept
+{
+ return this->event;
+}
+
+::std::shared_ptr<edge_event::impl>
+edge_event::impl_external::copy(const ::std::shared_ptr<impl>& self GPIOD_CXX_UNUSED) const
+{
+ ::std::shared_ptr<impl> ret(new impl_managed);
+ impl_managed& managed = dynamic_cast<impl_managed&>(*ret);
+
+ managed.event.reset(::gpiod_edge_event_copy(this->event));
+ if (!managed.event)
+ throw_from_errno("unable to copy the edge event object");
+
+ return ret;
+}
+
+edge_event::edge_event()
+ : _m_priv()
+{
+
+}
+
+GPIOD_CXX_API edge_event::edge_event(const edge_event& other)
+ : _m_priv(other._m_priv->copy(other._m_priv))
+{
+
+}
+
+GPIOD_CXX_API edge_event::edge_event(edge_event&& other) noexcept
+ : _m_priv(::std::move(other._m_priv))
+{
+
+}
+
+GPIOD_CXX_API edge_event::~edge_event()
+{
+
+}
+
+GPIOD_CXX_API edge_event& edge_event::operator=(const edge_event& other)
+{
+ this->_m_priv = other._m_priv->copy(other._m_priv);
+
+ return *this;
+}
+
+GPIOD_CXX_API edge_event& edge_event::operator=(edge_event&& other) noexcept
+{
+ this->_m_priv = ::std::move(other._m_priv);
+
+ return *this;
+}
+
+GPIOD_CXX_API edge_event::event_type edge_event::type() const
+{
+ int evtype = ::gpiod_edge_event_get_event_type(this->_m_priv->get_event_ptr());
+
+ return map_int_to_enum(evtype, event_type_mapping);
+}
+
+GPIOD_CXX_API timestamp edge_event::timestamp_ns() const noexcept
+{
+ return ::gpiod_edge_event_get_timestamp_ns(this->_m_priv->get_event_ptr());
+}
+
+GPIOD_CXX_API line::offset edge_event::line_offset() const noexcept
+{
+ return ::gpiod_edge_event_get_line_offset(this->_m_priv->get_event_ptr());
+}
+
+GPIOD_CXX_API unsigned long edge_event::global_seqno() const noexcept
+{
+ return ::gpiod_edge_event_get_global_seqno(this->_m_priv->get_event_ptr());
+}
+
+GPIOD_CXX_API unsigned long edge_event::line_seqno() const noexcept
+{
+ return ::gpiod_edge_event_get_line_seqno(this->_m_priv->get_event_ptr());
+}
+
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const edge_event& event)
+{
+ out << "gpiod::edge_event(type='" << event_type_names.at(event.type()) <<
+ "', timestamp=" << event.timestamp_ns() <<
+ ", line_offset=" << event.line_offset() <<
+ ", global_seqno=" << event.global_seqno() <<
+ ", line_seqno=" << event.line_seqno() << ")";
+
+ return out;
+}
+
+} /* namespace gpiod */
# SPDX-License-Identifier: GPL-2.0-or-later
# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
-AM_CPPFLAGS = -I$(top_srcdir)/bindings/cxx/ -I$(top_srcdir)/include
-AM_CPPFLAGS += -Wall -Wextra -g -std=gnu++17
-AM_LDFLAGS = -lgpiodcxx -L$(top_builddir)/bindings/cxx/ -lstdc++fs
-
-noinst_PROGRAMS = \
- gpiodetectcxx \
- gpiofindcxx \
- gpiogetcxx \
- gpioinfocxx \
- gpiomoncxx \
+AM_CXXFLAGS = -I$(top_srcdir)/bindings/cxx/ -I$(top_srcdir)/include
+AM_CXXFLAGS += -Wall -Wextra -g -std=gnu++17
+AM_LDFLAGS = -lgpiodcxx -L$(top_builddir)/bindings/cxx/
+
+noinst_PROGRAMS = \
+ gpiodetectcxx \
+ gpiofindcxx \
+ gpiogetcxx \
+ gpioinfocxx \
+ gpiomoncxx \
gpiosetcxx
gpiodetectcxx_SOURCES = gpiodetectcxx.cpp
/* C++ reimplementation of the gpiodetect tool. */
-#include <gpiod.hpp>
-
#include <cstdlib>
#include <filesystem>
+#include <gpiod.hpp>
#include <iostream>
int main(int argc, char **argv)
for (const auto& entry: ::std::filesystem::directory_iterator("/dev/")) {
if (::gpiod::is_gpiochip_device(entry.path())) {
::gpiod::chip chip(entry.path());
+ auto info = chip.get_info();
- ::std::cout << chip.name() << " ["
- << chip.label() << "] ("
- << chip.num_lines() << " lines)" << ::std::endl;
+ ::std::cout << info.name() << " [" <<
+ info.label() << "] (" <<
+ info.num_lines() << " lines)" << ::std::endl;
}
}
if (::gpiod::is_gpiochip_device(entry.path())) {
::gpiod::chip chip(entry.path());
- auto offset = chip.find_line(argv[1]);
+ auto offset = chip.get_line_offset_from_name(argv[1]);
if (offset >= 0) {
- ::std::cout << chip.name() << " " << offset << ::std::endl;
+ ::std::cout << chip.get_info().name() << " " << offset << ::std::endl;
return EXIT_SUCCESS;
}
}
return EXIT_FAILURE;
}
- ::std::vector<unsigned int> offsets;
+ ::gpiod::line::offsets offsets;
for (int i = 2; i < argc; i++)
offsets.push_back(::std::stoul(argv[i]));
- ::gpiod::chip chip(argv[1]);
- auto lines = chip.get_lines(offsets);
+ auto request = ::gpiod::chip(argv[1])
+ .prepare_request()
+ .set_consumer("gpiogetcxx")
+ .add_line_settings(
+ offsets,
+ ::gpiod::line_settings()
+ .set_direction(::gpiod::line::direction::INPUT)
+ )
+ .do_request();
- lines.request({
- argv[0],
- ::gpiod::line_request::DIRECTION_INPUT,
- 0
- });
-
- auto vals = lines.get_values();
+ auto vals = request.get_values();
for (auto& it: vals)
- ::std::cout << it << ' ';
+ ::std::cout << (it == ::gpiod::line::value::ACTIVE ? "1" : "0") << ' ';
::std::cout << ::std::endl;
return EXIT_SUCCESS;
#include <filesystem>
#include <iostream>
-int main(int argc, char **argv)
+namespace {
+
+void show_chip(const ::gpiod::chip& chip)
{
- if (argc != 1) {
- ::std::cerr << "usage: " << argv[0] << ::std::endl;
- return EXIT_FAILURE;
- }
+ auto info = chip.get_info();
- for (const auto& entry: ::std::filesystem::directory_iterator("/dev/")) {
- if (::gpiod::is_gpiochip_device(entry.path())) {
- ::gpiod::chip chip(entry.path());
+ ::std::cout << info.name() << " - " << info.num_lines() << " lines:" << ::std::endl;
+
+ for (unsigned int offset = 0; offset < info.num_lines(); offset++) {
+ auto info = chip.get_line_info(offset);
- ::std::cout << chip.name() << " - " << chip.num_lines() << " lines:" << ::std::endl;
+ ::std::cout << "\tline ";
+ ::std::cout.width(3);
+ ::std::cout << info.offset() << ": ";
- for (auto& lit: ::gpiod::line_iter(chip)) {
- ::std::cout << "\tline ";
- ::std::cout.width(3);
- ::std::cout << lit.offset() << ": ";
+ ::std::cout.width(12);
+ ::std::cout << (info.name().empty() ? "unnamed" : info.name());
+ ::std::cout << " ";
- ::std::cout.width(12);
- ::std::cout << (lit.name().empty() ? "unnamed" : lit.name());
- ::std::cout << " ";
+ ::std::cout.width(12);
+ ::std::cout << (info.consumer().empty() ? "unused" : info.consumer());
+ ::std::cout << " ";
- ::std::cout.width(12);
- ::std::cout << (lit.consumer().empty() ? "unused" : lit.consumer());
- ::std::cout << " ";
+ ::std::cout.width(8);
+ ::std::cout << (info.direction() == ::gpiod::line::direction::INPUT ? "input" : "output");
+ ::std::cout << " ";
- ::std::cout.width(8);
- ::std::cout << (lit.direction() == ::gpiod::line::DIRECTION_INPUT ? "input" : "output");
- ::std::cout << " ";
+ ::std::cout.width(10);
+ ::std::cout << (info.active_low() ? "active-low" : "active-high");
- ::std::cout.width(10);
- ::std::cout << (lit.is_active_low() ? "active-low" : "active-high");
+ ::std::cout << ::std::endl;
+ }
+}
- ::std::cout << ::std::endl;
- }
- }
+} /* namespace */
+
+int main(int argc, char **argv)
+{
+ if (argc != 1) {
+ ::std::cerr << "usage: " << argv[0] << ::std::endl;
+ return EXIT_FAILURE;
+ }
+
+ for (const auto& entry: ::std::filesystem::directory_iterator("/dev/")) {
+ if (::gpiod::is_gpiochip_device(entry.path()))
+ show_chip(::gpiod::chip(entry.path()));
}
return EXIT_SUCCESS;
/* Simplified C++ reimplementation of the gpiomon tool. */
-#include <gpiod.hpp>
-
+#include <chrono>
#include <cstdlib>
+#include <gpiod.hpp>
#include <iostream>
namespace {
-void print_event(const ::gpiod::line_event& event)
+void print_event(const ::gpiod::edge_event& event)
{
- if (event.event_type == ::gpiod::line_event::RISING_EDGE)
+ if (event.type() == ::gpiod::edge_event::event_type::RISING_EDGE)
::std::cout << " RISING EDGE";
- else if (event.event_type == ::gpiod::line_event::FALLING_EDGE)
- ::std::cout << "FALLING EDGE";
else
- throw ::std::logic_error("invalid event type");
+ ::std::cout << "FALLING EDGE";
::std::cout << " ";
- ::std::cout << ::std::chrono::duration_cast<::std::chrono::seconds>(event.timestamp).count();
+ ::std::cout << event.timestamp_ns() / 1000000000;
::std::cout << ".";
- ::std::cout << event.timestamp.count() % 1000000000;
+ ::std::cout << event.timestamp_ns() % 1000000000;
- ::std::cout << " line: " << event.source.offset();
+ ::std::cout << " line: " << event.line_offset();
::std::cout << ::std::endl;
}
return EXIT_FAILURE;
}
- ::std::vector<unsigned int> offsets;
+ ::gpiod::line::offsets offsets;
offsets.reserve(argc);
for (int i = 2; i < argc; i++)
offsets.push_back(::std::stoul(argv[i]));
- ::gpiod::chip chip(argv[1]);
- auto lines = chip.get_lines(offsets);
+ auto request = ::gpiod::chip(argv[1])
+ .prepare_request()
+ .set_consumer("gpiomoncxx")
+ .add_line_settings(
+ offsets,
+ ::gpiod::line_settings()
+ .set_direction(::gpiod::line::direction::INPUT)
+ .set_edge_detection(::gpiod::line::edge::BOTH)
+ )
+ .do_request();
- lines.request({
- argv[0],
- ::gpiod::line_request::EVENT_BOTH_EDGES,
- 0,
- });
+ ::gpiod::edge_event_buffer buffer;
for (;;) {
- auto events = lines.event_wait(::std::chrono::seconds(1));
- if (events) {
- for (auto& it: events)
- print_event(it.event_read());
- }
+ request.read_edge_event(buffer);
+
+ for (const auto& event: buffer)
+ print_event(event);
}
return EXIT_SUCCESS;
int main(int argc, char **argv)
{
if (argc < 3) {
- ::std::cerr << "usage: " << argv[0] << " <chip> <line_offset0>=<value0> ..." << ::std::endl;
+ ::std::cerr << "usage: " << argv[0] <<
+ " <chip> <line_offset0>=<value0> ..." << ::std::endl;
return EXIT_FAILURE;
}
- ::std::vector<unsigned int> offsets;
- ::std::vector<int> values;
+ ::gpiod::line::offsets offsets;
+ ::gpiod::line::values values;
for (int i = 2; i < argc; i++) {
::std::string arg(argv[i]);
::std::string value(arg.substr(pos + 1, ::std::string::npos));
if (offset.empty() || value.empty())
- throw ::std::invalid_argument("invalid argument: " + ::std::string(argv[i]));
+ throw ::std::invalid_argument("invalid offset=value mapping: " +
+ ::std::string(argv[i]));
offsets.push_back(::std::stoul(offset));
- values.push_back(::std::stoul(value));
+ values.push_back(::std::stoul(value) ? ::gpiod::line::value::ACTIVE :
+ ::gpiod::line::value::INACTIVE);
}
- ::gpiod::chip chip(argv[1]);
- auto lines = chip.get_lines(offsets);
-
- lines.request({
- argv[0],
- ::gpiod::line_request::DIRECTION_OUTPUT,
- 0
- }, values);
+ auto request = ::gpiod::chip(argv[1])
+ .prepare_request()
+ .set_consumer("gpiosetcxx")
+ .add_line_settings(
+ offsets,
+ ::gpiod::line_settings()
+ .set_direction(::gpiod::line::direction::OUTPUT)
+ )
+ .do_request();
+
+ request.set_values(values);
::std::cin.get();
--- /dev/null
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include "internal.hpp"
+
+namespace gpiod {
+
+GPIOD_CXX_API chip_closed::chip_closed(const ::std::string& what)
+ : ::std::logic_error(what)
+{
+
+}
+
+GPIOD_CXX_API chip_closed::chip_closed(const chip_closed& other) noexcept
+ : ::std::logic_error(other)
+{
+
+}
+
+GPIOD_CXX_API chip_closed::chip_closed(chip_closed&& other) noexcept
+ : ::std::logic_error(other)
+{
+
+}
+
+GPIOD_CXX_API chip_closed& chip_closed::operator=(const chip_closed& other) noexcept
+{
+ ::std::logic_error::operator=(other);
+
+ return *this;
+}
+
+GPIOD_CXX_API chip_closed& chip_closed::operator=(chip_closed&& other) noexcept
+{
+ ::std::logic_error::operator=(other);
+
+ return *this;
+}
+
+GPIOD_CXX_API chip_closed::~chip_closed()
+{
+
+}
+
+GPIOD_CXX_API request_released::request_released(const ::std::string& what)
+ : ::std::logic_error(what)
+{
+
+}
+
+GPIOD_CXX_API request_released::request_released(const request_released& other) noexcept
+ : ::std::logic_error(other)
+{
+
+}
+
+GPIOD_CXX_API request_released::request_released(request_released&& other) noexcept
+ : ::std::logic_error(other)
+{
+
+}
+
+GPIOD_CXX_API request_released& request_released::operator=(const request_released& other) noexcept
+{
+ ::std::logic_error::operator=(other);
+
+ return *this;
+}
+
+GPIOD_CXX_API request_released& request_released::operator=(request_released&& other) noexcept
+{
+ ::std::logic_error::operator=(other);
+
+ return *this;
+}
+
+GPIOD_CXX_API request_released::~request_released()
+{
+
+}
+
+GPIOD_CXX_API bad_mapping::bad_mapping(const ::std::string& what)
+ : ::std::runtime_error(what)
+{
+
+}
+
+GPIOD_CXX_API bad_mapping::bad_mapping(const bad_mapping& other) noexcept
+ : ::std::runtime_error(other)
+{
+
+}
+
+GPIOD_CXX_API bad_mapping::bad_mapping(bad_mapping&& other) noexcept
+ : ::std::runtime_error(other)
+{
+
+}
+
+GPIOD_CXX_API bad_mapping& bad_mapping::operator=(const bad_mapping& other) noexcept
+{
+ ::std::runtime_error::operator=(other);
+
+ return *this;
+}
+
+GPIOD_CXX_API bad_mapping& bad_mapping::operator=(bad_mapping&& other) noexcept
+{
+ ::std::runtime_error::operator=(other);
+
+ return *this;
+}
+
+GPIOD_CXX_API bad_mapping::~bad_mapping()
+{
+
+}
+
+} /* namespace gpiod */
/* SPDX-License-Identifier: LGPL-3.0-or-later */
-/* SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com> */
-
-#ifndef __LIBGPIOD_GPIOD_CXX_HPP__
-#define __LIBGPIOD_GPIOD_CXX_HPP__
-
-#include <bitset>
-#include <chrono>
-#include <gpiod.h>
-#include <memory>
-#include <string>
-#include <vector>
-
-namespace gpiod {
-
-class line;
-class line_bulk;
-class line_iter;
-class chip_iter;
-struct line_event;
+/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
/**
* @file gpiod.hpp
*/
-/**
- * @defgroup gpiod_cxx C++ bindings
- * @{
- */
-
-/**
- * @brief Check if the file pointed to by path is a GPIO chip character device.
- * @param path Path to check.
- * @return True if the file exists and is a GPIO chip character device or a
- * symbolic link to it.
- */
-bool is_gpiochip_device(const ::std::string& path);
-
-/**
- * @brief Represents a GPIO chip.
- *
- * Internally this class holds a smart pointer to an open GPIO chip descriptor.
- * Multiple objects of this class can reference the same chip. The chip is
- * closed and all resources freed when the last reference is dropped.
- */
-class chip
-{
-public:
-
- /**
- * @brief Default constructor. Creates an empty GPIO chip object.
- */
- chip(void) = default;
-
- /**
- * @brief Constructor. Opens the chip using chip::open.
- * @param path Path to the GPIO chip device.
- */
- chip(const ::std::string& path);
-
- /**
- * @brief Copy constructor. References the object held by other.
- * @param other Other chip object.
- */
- chip(const chip& other) = default;
-
- /**
- * @brief Move constructor. References the object held by other.
- * @param other Other chip object.
- */
- chip(chip&& other) = default;
-
- /**
- * @brief Assignment operator. References the object held by other.
- * @param other Other chip object.
- * @return Reference to this object.
- */
- chip& operator=(const chip& other) = default;
-
- /**
- * @brief Move assignment operator. References the object held by other.
- * @param other Other chip object.
- * @return Reference to this object.
- */
- chip& operator=(chip&& other) = default;
-
- /**
- * @brief Destructor. Unreferences the internal chip object.
- */
- ~chip(void) = default;
-
- /**
- * @brief Open a GPIO chip.
- * @param path Path to the GPIO chip device.
- *
- * If the object already holds a reference to an open chip, it will be
- * closed and the reference reset.
- */
- void open(const ::std::string &path);
-
- /**
- * @brief Reset the internal smart pointer owned by this object.
- */
- void reset(void) noexcept;
-
- /**
- * @brief Return the name of the chip held by this object.
- * @return Name of the GPIO chip.
- */
- ::std::string name(void) const;
-
- /**
- * @brief Return the label of the chip held by this object.
- * @return Label of the GPIO chip.
- */
- ::std::string label(void) const;
-
- /**
- * @brief Return the number of lines exposed by this chip.
- * @return Number of lines.
- */
- unsigned int num_lines(void) const;
-
- /**
- * @brief Get the line exposed by this chip at given offset.
- * @param offset Offset of the line.
- * @return Line object.
- */
- line get_line(unsigned int offset) const;
-
- /**
- * @brief Map a GPIO line's name to its offset within the chip.
- * @param name Name of the GPIO line to map.
- * @return Offset of the line within the chip or -1 if a line with
- * given name is not exposed by the chip.
- */
- int find_line(const ::std::string& name) const;
-
- /**
- * @brief Get a set of lines exposed by this chip at given offsets.
- * @param offsets Vector of line offsets.
- * @return Set of lines held by a line_bulk object.
- */
- line_bulk get_lines(const ::std::vector<unsigned int>& offsets) const;
-
- /**
- * @brief Get all lines exposed by this chip.
- * @return All lines exposed by this chip held by a line_bulk object.
- */
- line_bulk get_all_lines(void) const;
-
- /**
- * @brief Equality operator.
- * @param rhs Right-hand side of the equation.
- * @return True if rhs references the same chip. False otherwise.
- */
- bool operator==(const chip& rhs) const noexcept;
-
- /**
- * @brief Inequality operator.
- * @param rhs Right-hand side of the equation.
- * @return False if rhs references the same chip. True otherwise.
- */
- bool operator!=(const chip& rhs) const noexcept;
-
- /**
- * @brief Check if this object holds a reference to a GPIO chip.
- * @return True if this object references a GPIO chip, false otherwise.
- */
- explicit operator bool(void) const noexcept;
-
- /**
- * @brief Check if this object doesn't hold a reference to a GPIO chip.
- * @return False if this object references a GPIO chip, true otherwise.
- */
- bool operator!(void) const noexcept;
-
-private:
-
- chip(::gpiod_chip* chip);
- chip(const ::std::weak_ptr<::gpiod_chip>& chip_ptr);
-
- void throw_if_noref(void) const;
-
- ::std::shared_ptr<::gpiod_chip> _m_chip;
-
- friend line;
- friend chip_iter;
- friend line_iter;
-};
-
-/**
- * @brief Stores the configuration for line requests.
- */
-struct line_request
-{
- /**
- * @brief Request types.
- */
- enum : int {
- DIRECTION_AS_IS = 1,
- /**< Request for values, don't change the direction. */
- DIRECTION_INPUT,
- /**< Request for reading line values. */
- DIRECTION_OUTPUT,
- /**< Request for driving the GPIO lines. */
- EVENT_FALLING_EDGE,
- /**< Listen for falling edge events. */
- EVENT_RISING_EDGE,
- /**< Listen for rising edge events. */
- EVENT_BOTH_EDGES,
- /**< Listen for all types of events. */
- };
-
- static const ::std::bitset<32> FLAG_ACTIVE_LOW;
- /**< Set the active state to 'low' (high is the default). */
- static const ::std::bitset<32> FLAG_OPEN_SOURCE;
- /**< The line is an open-source port. */
- static const ::std::bitset<32> FLAG_OPEN_DRAIN;
- /**< The line is an open-drain port. */
- static const ::std::bitset<32> FLAG_BIAS_DISABLED;
- /**< The line has neither pull-up nor pull-down resistor enabled. */
- static const ::std::bitset<32> FLAG_BIAS_PULL_DOWN;
- /**< The line has a configurable pull-down resistor enabled. */
- static const ::std::bitset<32> FLAG_BIAS_PULL_UP;
- /**< The line has a configurable pull-up resistor enabled. */
-
- ::std::string consumer;
- /**< Consumer name to pass to the request. */
- int request_type;
- /**< Type of the request. */
- ::std::bitset<32> flags;
- /**< Additional request flags. */
-};
-
-/**
- * @brief Represents a single GPIO line.
- *
- * Internally this class holds a raw pointer to a GPIO line descriptor and a
- * reference to the parent chip. All line resources are freed when the last
- * reference to the parent chip is dropped.
- */
-class line
-{
-public:
-
- /**
- * @brief Default constructor. Creates an empty line object.
- */
- line(void);
-
- /**
- * @brief Copy constructor.
- * @param other Other line object.
- */
- line(const line& other) = default;
-
- /**
- * @brief Move constructor.
- * @param other Other line object.
- */
- line(line&& other) = default;
-
- /**
- * @brief Assignment operator.
- * @param other Other line object.
- * @return Reference to this object.
- */
- line& operator=(const line& other) = default;
-
- /**
- * @brief Move assignment operator.
- * @param other Other line object.
- * @return Reference to this object.
- */
- line& operator=(line&& other) = default;
-
- /**
- * @brief Destructor.
- */
- ~line(void) = default;
-
- /**
- * @brief Get the offset of this line.
- * @return Offet of this line.
- */
- unsigned int offset(void) const;
-
- /**
- * @brief Get the name of this line (if any).
- * @return Name of this line or an empty string if it is unnamed.
- */
- ::std::string name(void) const;
-
- /**
- * @brief Get the consumer of this line (if any).
- * @return Name of the consumer of this line or an empty string if it
- * is unused.
- */
- ::std::string consumer(void) const;
-
- /**
- * @brief Get current direction of this line.
- * @return Current direction setting.
- */
- int direction(void) const;
-
- /**
- * @brief Check if this line's signal is inverted.
- * @return True if this line is "active-low", false otherwise.
- */
- bool is_active_low(void) const;
-
- /**
- * @brief Get current bias of this line.
- * @return Current bias setting.
- */
- int bias(void) const;
-
- /**
- * @brief Check if this line is used by the kernel or other user space
- * process.
- * @return True if this line is in use, false otherwise.
- */
- bool is_used(void) const;
-
- /**
- * @brief Get current drive setting of this line.
- * @return Current drive setting.
- */
- int drive(void) const;
-
- /**
- * @brief Request this line.
- * @param config Request config (see gpiod::line_request).
- * @param default_val Default value - only matters for OUTPUT direction.
- */
- void request(const line_request& config, int default_val = 0) const;
-
- /**
- * @brief Release the line if it was previously requested.
- */
- void release(void) const;
-
- /**
- * @brief Read the line value.
- * @return Current value (0 or 1).
- */
- int get_value(void) const;
-
- /**
- * @brief Set the value of this line.
- * @param val New value (0 or 1).
- */
- void set_value(int val) const;
-
- /**
- * @brief Set configuration of this line.
- * @param direction New direction.
- * @param flags Replacement flags.
- * @param value New value (0 or 1) - only matters for OUTPUT direction.
- */
- void set_config(int direction, ::std::bitset<32> flags, int value = 0) const;
-
- /**
- * @brief Set configuration flags of this line.
- * @param flags Replacement flags.
- */
- void set_flags(::std::bitset<32> flags) const;
-
- /**
- * @brief Change the direction this line to input.
- */
- void set_direction_input() const;
-
- /**
- * @brief Change the direction this lines to output.
- * @param value New value (0 or 1).
- */
- void set_direction_output(int value = 0) const;
-
- /**
- * @brief Wait for an event on this line.
- * @param timeout Time to wait before returning if no event occurred.
- * @return True if an event occurred and can be read, false if the wait
- * timed out.
- */
- bool event_wait(const ::std::chrono::nanoseconds& timeout) const;
-
- /**
- * @brief Read a line event.
- * @return Line event object.
- */
- line_event event_read(void) const;
-
- /**
- * @brief Read multiple line events.
- * @return Vector of line event objects.
- */
- ::std::vector<line_event> event_read_multiple(void) const;
-
- /**
- * @brief Get the event file descriptor associated with this line.
- * @return File descriptor number.
- */
- int event_get_fd(void) const;
-
- /**
- * @brief Get the parent chip.
- * @return Parent chip of this line.
- */
- const chip get_chip(void) const;
-
- /**
- * @brief Reset the state of this object.
- *
- * This is useful when the user needs to e.g. keep the line_event object
- * but wants to drop the reference to the GPIO chip indirectly held by
- * the line being the source of the event.
- */
- void reset(void);
-
- /**
- * @brief Check if two line objects reference the same GPIO line.
- * @param rhs Right-hand side of the equation.
- * @return True if both objects reference the same line, fale otherwise.
- */
- bool operator==(const line& rhs) const noexcept;
-
- /**
- * @brief Check if two line objects reference different GPIO lines.
- * @param rhs Right-hand side of the equation.
- * @return False if both objects reference the same line, true otherwise.
- */
- bool operator!=(const line& rhs) const noexcept;
-
- /**
- * @brief Check if this object holds a reference to any GPIO line.
- * @return True if this object references a GPIO line, false otherwise.
- */
- explicit operator bool(void) const noexcept;
-
- /**
- * @brief Check if this object doesn't reference any GPIO line.
- * @return True if this object doesn't reference any GPIO line, true
- * otherwise.
- */
- bool operator!(void) const noexcept;
-
- /**
- * @brief Possible direction settings.
- */
- enum : int {
- DIRECTION_INPUT = 1,
- /**< Line's direction setting is input. */
- DIRECTION_OUTPUT,
- /**< Line's direction setting is output. */
- };
-
- /**
- * @brief Possible drive settings.
- */
- enum : int {
- DRIVE_PUSH_PULL = 1,
- /**< Drive setting is unknown. */
- DRIVE_OPEN_DRAIN,
- /**< Line output is open-drain. */
- DRIVE_OPEN_SOURCE,
- /**< Line output is open-source. */
- };
-
- /**
- * @brief Possible bias settings.
- */
- enum : int {
- BIAS_UNKNOWN = 1,
- /**< Line's bias state is unknown. */
- BIAS_DISABLED,
- /**< Line's internal bias is disabled. */
- BIAS_PULL_UP,
- /**< Line's internal pull-up bias is enabled. */
- BIAS_PULL_DOWN,
- /**< Line's internal pull-down bias is enabled. */
- };
-
-private:
-
- line(::gpiod_line* line, const chip& owner);
-
- void throw_if_null(void) const;
- line_event make_line_event(const ::gpiod_line_event& event) const noexcept;
-
- ::gpiod_line* _m_line;
- ::std::weak_ptr<::gpiod_chip> _m_owner;
-
- class chip_guard
- {
- public:
- chip_guard(const line& line);
- ~chip_guard(void) = default;
-
- chip_guard(const chip_guard& other) = delete;
- chip_guard(chip_guard&& other) = delete;
- chip_guard& operator=(const chip_guard&& other) = delete;
- chip_guard& operator=(chip_guard&& other) = delete;
-
- private:
- ::std::shared_ptr<::gpiod_chip> _m_chip;
- };
-
- friend chip;
- friend line_bulk;
- friend line_iter;
-};
-
-/**
- * @brief Describes a single GPIO line event.
- */
-struct line_event
-{
- /**
- * @brief Possible event types.
- */
- enum : int {
- RISING_EDGE = 1,
- /**< Rising edge event. */
- FALLING_EDGE,
- /**< Falling edge event. */
- };
-
- ::std::chrono::nanoseconds timestamp;
- /**< Best estimate of time of event occurrence in nanoseconds. */
- int event_type;
- /**< Type of the event that occurred. */
- line source;
- /**< Line object referencing the GPIO line on which the event occurred. */
-};
+#ifndef __LIBGPIOD_GPIOD_CXX_HPP__
+#define __LIBGPIOD_GPIOD_CXX_HPP__
/**
- * @brief Represents a set of GPIO lines.
+ * @defgroup gpiod_cxx C++ bindings
*
- * Internally an object of this class stores an array of line objects
- * owned by a single chip.
+ * C++ bindings for libgpiod.
*/
-class line_bulk
-{
-public:
-
- /**
- * @brief Default constructor. Creates an empty line_bulk object.
- */
- line_bulk(void) = default;
-
- /**
- * @brief Construct a line_bulk from a vector of lines.
- * @param lines Vector of gpiod::line objects.
- * @note All lines must be owned by the same GPIO chip.
- */
- line_bulk(const ::std::vector<line>& lines);
-
- /**
- * @brief Copy constructor.
- * @param other Other line_bulk object.
- */
- line_bulk(const line_bulk& other) = default;
-
- /**
- * @brief Move constructor.
- * @param other Other line_bulk object.
- */
- line_bulk(line_bulk&& other) = default;
-
- /**
- * @brief Assignment operator.
- * @param other Other line_bulk object.
- * @return Reference to this object.
- */
- line_bulk& operator=(const line_bulk& other) = default;
-
- /**
- * @brief Move assignment operator.
- * @param other Other line_bulk object.
- * @return Reference to this object.
- */
- line_bulk& operator=(line_bulk&& other) = default;
-
- /**
- * @brief Destructor.
- */
- ~line_bulk(void) = default;
-
- /**
- * @brief Add a line to this line_bulk object.
- * @param new_line Line to add.
- * @note The new line must be owned by the same chip as all the other
- * lines already held by this line_bulk object.
- */
- void append(const line& new_line);
-
- /**
- * @brief Get the line at given offset.
- * @param index Index of the line to get.
- * @return Reference to the line object.
- * @note This method will throw if index is equal or greater than the
- * number of lines currently held by this bulk.
- */
- line& get(unsigned int index);
-
- /**
- * @brief Get the line at given offset without bounds checking.
- * @param index Offset of the line to get.
- * @return Reference to the line object.
- * @note No bounds checking is performed.
- */
- line& operator[](unsigned int index);
-
- /**
- * @brief Get the number of lines currently held by this object.
- * @return Number of elements in this line_bulk.
- */
- unsigned int size(void) const noexcept;
-
- /**
- * @brief Check if this line_bulk doesn't hold any lines.
- * @return True if this object is empty, false otherwise.
- */
- bool empty(void) const noexcept;
-
- /**
- * @brief Remove all lines from this object.
- */
- void clear(void);
-
- /**
- * @brief Request all lines held by this object.
- * @param config Request config (see gpiod::line_request).
- * @param default_vals Vector of default values. Only relevant for
- * output direction requests.
- */
- void request(const line_request& config,
- const ::std::vector<int> default_vals = ::std::vector<int>()) const;
-
- /**
- * @brief Release all lines held by this object.
- */
- void release(void) const;
-
- /**
- * @brief Read values from all lines held by this object.
- * @return Vector containing line values the order of which corresponds
- * with the order of lines in the internal array.
- */
- ::std::vector<int> get_values(void) const;
-
- /**
- * @brief Set values of all lines held by this object.
- * @param values Vector of values to set. Must be the same size as the
- * number of lines held by this line_bulk.
- */
- void set_values(const ::std::vector<int>& values) const;
-
- /**
- * @brief Set configuration of all lines held by this object.
- * @param direction New direction.
- * @param flags Replacement flags.
- * @param values Vector of values to set. Must be the same size as the
- * number of lines held by this line_bulk.
- * Only relevant for output direction requests.
- */
- void set_config(int direction, ::std::bitset<32> flags,
- const ::std::vector<int> values = ::std::vector<int>()) const;
-
- /**
- * @brief Set configuration flags of all lines held by this object.
- * @param flags Replacement flags.
- */
- void set_flags(::std::bitset<32> flags) const;
-
- /**
- * @brief Change the direction all lines held by this object to input.
- */
- void set_direction_input() const;
-
- /**
- * @brief Change the direction all lines held by this object to output.
- * @param values Vector of values to set. Must be the same size as the
- * number of lines held by this line_bulk.
- */
- void set_direction_output(const ::std::vector<int>& values) const;
-
- /**
- * @brief Poll the set of lines for line events.
- * @param timeout Number of nanoseconds to wait before returning an
- * empty line_bulk.
- * @return Returns a line_bulk object containing lines on which events
- * occurred.
- */
- line_bulk event_wait(const ::std::chrono::nanoseconds& timeout) const;
-
- /**
- * @brief Check if this object holds any lines.
- * @return True if this line_bulk holds at least one line, false otherwise.
- */
- explicit operator bool(void) const noexcept;
-
- /**
- * @brief Check if this object doesn't hold any lines.
- * @return True if this line_bulk is empty, false otherwise.
- */
- bool operator!(void) const noexcept;
-
- /**
- * @brief Max number of lines that this object can hold.
- */
- static const unsigned int MAX_LINES;
-
- /**
- * @brief Iterator for iterating over lines held by line_bulk.
- */
- class iterator
- {
- public:
-
- /**
- * @brief Default constructor. Builds an empty iterator object.
- */
- iterator(void) = default;
-
- /**
- * @brief Copy constructor.
- * @param other Other line_bulk iterator.
- */
- iterator(const iterator& other) = default;
-
- /**
- * @brief Move constructor.
- * @param other Other line_bulk iterator.
- */
- iterator(iterator&& other) = default;
-
- /**
- * @brief Assignment operator.
- * @param other Other line_bulk iterator.
- * @return Reference to this iterator.
- */
- iterator& operator=(const iterator& other) = default;
-
- /**
- * @brief Move assignment operator.
- * @param other Other line_bulk iterator.
- * @return Reference to this iterator.
- */
- iterator& operator=(iterator&& other) = default;
-
- /**
- * @brief Destructor.
- */
- ~iterator(void) = default;
-
- /**
- * @brief Advance the iterator by one element.
- * @return Reference to this iterator.
- */
- iterator& operator++(void);
-
- /**
- * @brief Dereference current element.
- * @return Current GPIO line by reference.
- */
- const line& operator*(void) const;
-
- /**
- * @brief Member access operator.
- * @return Current GPIO line by pointer.
- */
- const line* operator->(void) const;
-
- /**
- * @brief Check if this operator points to the same element.
- * @param rhs Right-hand side of the equation.
- * @return True if this iterator points to the same GPIO line,
- * false otherwise.
- */
- bool operator==(const iterator& rhs) const noexcept;
-
- /**
- * @brief Check if this operator doesn't point to the same element.
- * @param rhs Right-hand side of the equation.
- * @return True if this iterator doesn't point to the same GPIO
- * line, false otherwise.
- */
- bool operator!=(const iterator& rhs) const noexcept;
-
- private:
-
- iterator(const ::std::vector<line>::iterator& it);
-
- ::std::vector<line>::iterator _m_iter;
-
- friend line_bulk;
- };
-
- /**
- * @brief Returns an iterator to the first line.
- * @return A line_bulk iterator.
- */
- iterator begin(void) noexcept;
-
- /**
- * @brief Returns an iterator to the element following the last line.
- * @return A line_bulk iterator.
- */
- iterator end(void) noexcept;
-
-private:
-
- struct line_bulk_deleter
- {
- void operator()(::gpiod_line_bulk *bulk);
- };
-
- void throw_if_empty(void) const;
-
- using line_bulk_ptr = ::std::unique_ptr<::gpiod_line_bulk, line_bulk_deleter>;
-
- line_bulk_ptr make_line_bulk_ptr(void) const;
- line_bulk_ptr to_line_bulk(void) const;
-
- ::std::vector<line> _m_bulk;
-};
-
-/**
- * @brief Support for range-based loops for line iterators.
- * @param iter A line iterator.
- * @return Iterator unchanged.
- */
-line_iter begin(line_iter iter) noexcept;
/**
- * @brief Support for range-based loops for line iterators.
- * @param iter A line iterator.
- * @return New end iterator.
+ * @cond
*/
-line_iter end(const line_iter& iter) noexcept;
-/**
- * @brief Allows to iterate over all lines owned by a GPIO chip.
+/*
+ * We don't make this symbol private because it needs to be accessible by
+ * the declarations in exception.hpp in order to expose the symbols of classes
+ * inheriting from standard exceptions.
*/
-class line_iter
-{
-public:
-
- /**
- * @brief Default constructor. Creates the end iterator.
- */
- line_iter(void) = default;
-
- /**
- * @brief Constructor. Creates the begin iterator.
- * @param owner Chip owning the GPIO lines over which we want to iterate.
- */
- line_iter(const chip& owner);
-
- /**
- * @brief Copy constructor.
- * @param other Other line iterator.
- */
- line_iter(const line_iter& other) = default;
-
- /**
- * @brief Move constructor.
- * @param other Other line iterator.
- */
- line_iter(line_iter&& other) = default;
+#ifdef GPIOD_CXX_BUILD
+#define GPIOD_CXX_API __attribute__((visibility("default")))
+#else
+#define GPIOD_CXX_API __attribute__((visibility("hidden")))
+#endif
- /**
- * @brief Assignment operator.
- * @param other Other line iterator.
- * @return Reference to this line_iter.
- */
- line_iter& operator=(const line_iter& other) = default;
-
- /**
- * @brief Move assignment operator.
- * @param other Other line iterator.
- * @return Reference to this line_iter.
- */
- line_iter& operator=(line_iter&& other) = default;
-
- /**
- * @brief Destructor.
- */
- ~line_iter(void) = default;
-
- /**
- * @brief Advance the iterator by one element.
- * @return Reference to this iterator.
- */
- line_iter& operator++(void);
-
- /**
- * @brief Dereference current element.
- * @return Current GPIO line by reference.
- */
- const line& operator*(void) const;
-
- /**
- * @brief Member access operator.
- * @return Current GPIO line by pointer.
- */
- const line* operator->(void) const;
-
- /**
- * @brief Check if this operator points to the same element.
- * @param rhs Right-hand side of the equation.
- * @return True if this iterator points to the same line_iter,
- * false otherwise.
- */
- bool operator==(const line_iter& rhs) const noexcept;
-
- /**
- * @brief Check if this operator doesn't point to the same element.
- * @param rhs Right-hand side of the equation.
- * @return True if this iterator doesn't point to the same line_iter,
- * false otherwise.
- */
- bool operator!=(const line_iter& rhs) const noexcept;
-
-private:
-
- line _m_current;
-};
+#define __LIBGPIOD_GPIOD_CXX_INSIDE__
+#include "gpiodcxx/chip.hpp"
+#include "gpiodcxx/chip-info.hpp"
+#include "gpiodcxx/edge-event.hpp"
+#include "gpiodcxx/edge-event-buffer.hpp"
+#include "gpiodcxx/exception.hpp"
+#include "gpiodcxx/info-event.hpp"
+#include "gpiodcxx/line.hpp"
+#include "gpiodcxx/line-config.hpp"
+#include "gpiodcxx/line-info.hpp"
+#include "gpiodcxx/line-request.hpp"
+#include "gpiodcxx/line-settings.hpp"
+#include "gpiodcxx/request-builder.hpp"
+#include "gpiodcxx/request-config.hpp"
+#undef __LIBGPIOD_GPIOD_CXX_INSIDE__
/**
- * @}
+ * @endcond
*/
-} /* namespace gpiod */
-
#endif /* __LIBGPIOD_GPIOD_CXX_HPP__ */
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
+
+otherincludedir = $(includedir)/gpiodcxx
+otherinclude_HEADERS = \
+ chip.hpp \
+ chip-info.hpp \
+ edge-event-buffer.hpp \
+ edge-event.hpp \
+ exception.hpp \
+ info-event.hpp \
+ line.hpp \
+ line-config.hpp \
+ line-info.hpp \
+ line-request.hpp \
+ line-settings.hpp \
+ misc.hpp \
+ request-builder.hpp \
+ request-config.hpp \
+ timestamp.hpp
--- /dev/null
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/* SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+/**
+ * @file chip-info.hpp
+ */
+
+#ifndef __LIBGPIOD_CXX_CHIP_INFO_HPP__
+#define __LIBGPIOD_CXX_CHIP_INFO_HPP__
+
+#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
+#error "Only gpiod.hpp can be included directly."
+#endif
+
+#include <memory>
+#include <ostream>
+
+namespace gpiod {
+
+class chip;
+
+/**
+ * @ingroup gpiod_cxx
+ * @{
+ */
+
+/**
+ * @brief Represents an immutable snapshot of GPIO chip information.
+ */
+class chip_info
+{
+public:
+
+ /**
+ * @brief Copy constructor.
+ * @param other Object to copy.
+ */
+ chip_info(const chip_info& other);
+
+ /**
+ * @brief Move constructor.
+ * @param other Object to move.
+ */
+ chip_info(chip_info&& other) noexcept;
+
+ ~chip_info();
+
+ /**
+ * @brief Assignment operator.
+ * @param other Object to copy.
+ * @return Reference to self.
+ */
+ chip_info& operator=(const chip_info& other);
+
+ /**
+ * @brief Move assignment operator.
+ * @param other Object to move.
+ * @return Reference to self.
+ */
+ chip_info& operator=(chip_info&& other) noexcept;
+
+ /**
+ * @brief Get the name of this GPIO chip.
+ * @return String containing the chip name.
+ */
+ ::std::string name() const noexcept;
+
+ /**
+ * @brief Get the label of this GPIO chip.
+ * @return String containing the chip name.
+ */
+ ::std::string label() const noexcept;
+
+ /**
+ * @brief Return the number of lines exposed by this chip.
+ * @return Number of lines.
+ */
+ ::std::size_t num_lines() const noexcept;
+
+private:
+
+ chip_info();
+
+ struct impl;
+
+ ::std::shared_ptr<impl> _m_priv;
+
+ friend chip;
+};
+
+/**
+ * @brief Stream insertion operator for GPIO chip objects.
+ * @param out Output stream to write to.
+ * @param chip GPIO chip to insert into the output stream.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, const chip_info& chip);
+
+/**
+ * @}
+ */
+
+} /* namespace gpiod */
+
+#endif /* __LIBGPIOD_CXX_CHIP_INFO_HPP__ */
--- /dev/null
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+/**
+ * @file chip.hpp
+ */
+
+#ifndef __LIBGPIOD_CXX_CHIP_HPP__
+#define __LIBGPIOD_CXX_CHIP_HPP__
+
+#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
+#error "Only gpiod.hpp can be included directly."
+#endif
+
+#include <chrono>
+#include <cstddef>
+#include <iostream>
+#include <filesystem>
+#include <memory>
+
+#include "line.hpp"
+
+namespace gpiod {
+
+class chip_info;
+class info_event;
+class line_config;
+class line_info;
+class line_request;
+class request_builder;
+class request_config;
+
+/**
+ * @ingroup gpiod_cxx
+ * @{
+ */
+
+/**
+ * @brief Represents a GPIO chip.
+ */
+class chip
+{
+public:
+
+ /**
+ * @brief Instantiates a new chip object by opening the device file
+ * indicated by the path argument.
+ * @param path Path to the device file to open.
+ */
+ explicit chip(const ::std::filesystem::path& path);
+
+ /**
+ * @brief Move constructor.
+ * @param other Object to move.
+ */
+ chip(chip&& other) noexcept;
+
+ ~chip();
+
+ chip& operator=(const chip& other) = delete;
+
+ /**
+ * @brief Move assignment operator.
+ * @param other Object to move.
+ * @return Reference to self.
+ */
+ chip& operator=(chip&& other) noexcept;
+
+ /**
+ * @brief Check if this object is valid.
+ * @return True if this object's methods can be used, false otherwise.
+ * False usually means the chip was closed. If the user calls
+ * any of the methods of this class on an object for which this
+ * operator returned false, a logic_error will be thrown.
+ */
+ explicit operator bool() const noexcept;
+
+ /**
+ * @brief Close the GPIO chip device file and free associated resources.
+ * @note The chip object can live after calling this method but any of
+ * the chip's mutators will throw a logic_error exception.
+ */
+ void close();
+
+ /**
+ * @brief Get the filesystem path that was used to open this GPIO chip.
+ * @return Path to the underlying character device file.
+ */
+ ::std::filesystem::path path() const;
+
+ /**
+ * @brief Get information about the chip.
+ * @return New chip_info object.
+ */
+ chip_info get_info() const;
+
+ /**
+ * @brief Retrieve the current snapshot of line information for a
+ * single line.
+ * @param offset Offset of the line to get the info for.
+ * @return New ::gpiod::line_info object.
+ */
+ line_info get_line_info(line::offset offset) const;
+
+ /**
+ * @brief Wrapper around ::gpiod::chip::get_line_info that retrieves
+ * the line info and starts watching the line for changes.
+ * @param offset Offset of the line to get the info for.
+ * @return New ::gpiod::line_info object.
+ */
+ line_info watch_line_info(line::offset offset) const;
+
+ /**
+ * @brief Stop watching the line at given offset for info events.
+ * @param offset Offset of the line to get the info for.
+ */
+ void unwatch_line_info(line::offset offset) const;
+
+ /**
+ * @brief Get the file descriptor associated with this chip.
+ * @return File descriptor number.
+ */
+ int fd() const;
+
+ /**
+ * @brief Wait for line status events on any of the watched lines
+ * exposed by this chip.
+ * @param timeout Wait time limit in nanoseconds.
+ * @return True if at least one event is ready to be read. False if the
+ * wait timed out.
+ */
+ bool wait_info_event(const ::std::chrono::nanoseconds& timeout) const;
+
+ /**
+ * @brief Read a single line status change event from this chip.
+ * @return New info_event object.
+ */
+ info_event read_info_event() const;
+
+ /**
+ * @brief Map a GPIO line's name to its offset within the chip.
+ * @param name Name of the GPIO line to map.
+ * @return Offset of the line within the chip or -1 if the line with
+ * given name is not exposed by this chip.
+ */
+ int get_line_offset_from_name(const ::std::string& name) const;
+
+ /**
+ * @brief Create a request_builder associated with this chip.
+ * @return New request_builder object.
+ */
+ request_builder prepare_request();
+
+private:
+
+ struct impl;
+
+ ::std::shared_ptr<impl> _m_priv;
+
+ chip(const chip& other);
+
+ friend request_builder;
+};
+
+/**
+ * @brief Stream insertion operator for GPIO chip objects.
+ * @param out Output stream to write to.
+ * @param chip GPIO chip to insert into the output stream.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, const chip& chip);
+
+/**
+ * @}
+ */
+
+} /* namespace gpiod */
+
+#endif /* __LIBGPIOD_CXX_CHIP_HPP__ */
--- /dev/null
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+/**
+ * @file edge-event-buffer.hpp
+ */
+
+#ifndef __LIBGPIOD_CXX_EDGE_EVENT_BUFFER_HPP__
+#define __LIBGPIOD_CXX_EDGE_EVENT_BUFFER_HPP__
+
+#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
+#error "Only gpiod.hpp can be included directly."
+#endif
+
+#include <cstddef>
+#include <iostream>
+#include <memory>
+#include <vector>
+
+namespace gpiod {
+
+class edge_event;
+class line_request;
+
+/**
+ * @ingroup gpiod_cxx
+ * @{
+ */
+
+/**
+ * @brief Object into which edge events are read for better performance.
+ *
+ * The edge_event_buffer allows reading edge_event objects into an existing
+ * buffer which improves the performance by avoiding needless memory
+ * allocations.
+ */
+class edge_event_buffer
+{
+public:
+
+ /**
+ * @brief Constant iterator for iterating over edge events stored in
+ * the buffer.
+ */
+ using const_iterator = ::std::vector<edge_event>::const_iterator;
+
+ /**
+ * @brief Constructor. Creates a new edge event buffer with given
+ * capacity.
+ * @param capacity Capacity of the new buffer.
+ */
+ explicit edge_event_buffer(::std::size_t capacity = 64);
+
+ edge_event_buffer(const edge_event_buffer& other) = delete;
+
+ /**
+ * @brief Move constructor.
+ * @param other Object to move.
+ */
+ edge_event_buffer(edge_event_buffer&& other) noexcept;
+
+ ~edge_event_buffer();
+
+ edge_event_buffer& operator=(const edge_event_buffer& other) = delete;
+
+ /**
+ * @brief Move assignment operator.
+ * @param other Object to move.
+ * @return Reference to self.
+ */
+ edge_event_buffer& operator=(edge_event_buffer&& other) noexcept;
+
+ /**
+ * @brief Get the constant reference to the edge event at given index.
+ * @param index Index of the event in the buffer.
+ * @return Constant reference to the edge event.
+ */
+ const edge_event& get_event(unsigned int index) const;
+
+ /**
+ * @brief Get the number of edge events currently stored in the buffer.
+ * @return Number of edge events in the buffer.
+ */
+ ::std::size_t num_events() const;
+
+ /**
+ * @brief Maximum capacity of the buffer.
+ * @return Buffer capacity.
+ */
+ ::std::size_t capacity() const noexcept;
+
+ /**
+ * @brief Get a constant iterator to the first edge event currently
+ * stored in the buffer.
+ * @return Constant iterator to the first element.
+ */
+ const_iterator begin() const noexcept;
+
+ /**
+ * @brief Get a constant iterator to the element after the last edge
+ * event in the buffer.
+ * @return Constant iterator to the element after the last edge event.
+ */
+ const_iterator end() const noexcept;
+
+private:
+
+ struct impl;
+
+ ::std::unique_ptr<impl> _m_priv;
+
+ friend line_request;
+};
+
+/**
+ * @brief Stream insertion operator for GPIO edge event buffer objects.
+ * @param out Output stream to write to.
+ * @param buf GPIO edge event buffer object to insert into the output stream.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, const edge_event_buffer& buf);
+
+/**
+ * @}
+ */
+
+} /* namespace gpiod */
+
+#endif /* __LIBGPIOD_CXX_EDGE_EVENT_BUFFER_HPP__ */
--- /dev/null
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+/**
+ * @file edge-event.hpp
+ */
+
+#ifndef __LIBGPIOD_CXX_EDGE_EVENT_HPP__
+#define __LIBGPIOD_CXX_EDGE_EVENT_HPP__
+
+#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
+#error "Only gpiod.hpp can be included directly."
+#endif
+
+#include <cstdint>
+#include <iostream>
+#include <memory>
+
+#include "timestamp.hpp"
+
+namespace gpiod {
+
+class edge_event_buffer;
+
+/**
+ * @ingroup gpiod_cxx
+ * @{
+ */
+
+/**
+ * @brief Immutable object containing data about a single edge event.
+ */
+class edge_event
+{
+public:
+
+ /**
+ * @brief Edge event types.
+ */
+ enum class event_type
+ {
+ RISING_EDGE = 1,
+ /**< This is a rising edge event. */
+ FALLING_EDGE
+ /**< This is falling edge event. */
+ };
+
+ /**
+ * @brief Copy constructor.
+ * @param other Object to copy.
+ */
+ edge_event(const edge_event& other);
+
+ /**
+ * @brief Move constructor.
+ * @param other Object to move.
+ */
+ edge_event(edge_event&& other) noexcept;
+
+ ~edge_event();
+
+ /**
+ * @brief Copy assignment operator.
+ * @param other Object to copy.
+ * @return Reference to self.
+ */
+ edge_event& operator=(const edge_event& other);
+
+ /**
+ * @brief Move assignment operator.
+ * @param other Object to move.
+ * @return Reference to self.
+ */
+ edge_event& operator=(edge_event&& other) noexcept;
+
+ /**
+ * @brief Retrieve the event type.
+ * @return Event type (rising or falling edge).
+ */
+ event_type type() const;
+
+ /**
+ * @brief Retrieve the event time-stamp.
+ * @return Time-stamp in nanoseconds as registered by the kernel using
+ * the configured edge event clock.
+ */
+ timestamp timestamp_ns() const noexcept;
+
+ /**
+ * @brief Read the offset of the line on which this event was
+ * registered.
+ * @return Line offset.
+ */
+ line::offset line_offset() const noexcept;
+
+ /**
+ * @brief Get the global sequence number of this event.
+ * @return Sequence number of the event relative to all lines in the
+ * associated line request.
+ */
+ unsigned long global_seqno() const noexcept;
+
+ /**
+ * @brief Get the event sequence number specific to the concerned line.
+ * @return Sequence number of the event relative to this line within
+ * the lifetime of the associated line request.
+ */
+ unsigned long line_seqno() const noexcept;
+
+private:
+
+ edge_event();
+
+ struct impl;
+ struct impl_managed;
+ struct impl_external;
+
+ ::std::shared_ptr<impl> _m_priv;
+
+ friend edge_event_buffer;
+};
+
+/**
+ * @brief Stream insertion operator for edge events.
+ * @param out Output stream to write to.
+ * @param event Edge event to insert into the output stream.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, const edge_event& event);
+
+/**
+ * @}
+ */
+
+} /* namespace gpiod */
+
+#endif /* __LIBGPIOD_CXX_EDGE_EVENT_HPP__ */
--- /dev/null
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+/**
+ * @file exception.hpp
+ */
+
+#ifndef __LIBGPIOD_CXX_EXCEPTION_HPP__
+#define __LIBGPIOD_CXX_EXCEPTION_HPP__
+
+#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
+#error "Only gpiod.hpp can be included directly."
+#endif
+
+#include <stdexcept>
+#include <string>
+
+namespace gpiod {
+
+/**
+ * @ingroup gpiod_cxx
+ * @{
+ */
+
+/**
+ * @brief Exception thrown when an already closed chip is used.
+ */
+class GPIOD_CXX_API chip_closed : public ::std::logic_error
+{
+public:
+
+ /**
+ * @brief Constructor.
+ * @param what Human readable reason for error.
+ */
+ explicit chip_closed(const ::std::string& what);
+
+ /**
+ * @brief Copy constructor.
+ * @param other Object to copy from.
+ */
+ chip_closed(const chip_closed& other) noexcept;
+
+ /**
+ * @brief Move constructor.
+ * @param other Object to move.
+ */
+ chip_closed(chip_closed&& other) noexcept;
+
+ /**
+ * @brief Assignment operator.
+ * @param other Object to copy from.
+ * @return Reference to self.
+ */
+ chip_closed& operator=(const chip_closed& other) noexcept;
+
+ /**
+ * @brief Move assignment operator.
+ * @param other Object to move.
+ * @return Reference to self.
+ */
+ chip_closed& operator=(chip_closed&& other) noexcept;
+
+ virtual ~chip_closed();
+};
+
+/**
+ * @brief Exception thrown when an already released line request is used.
+ */
+class GPIOD_CXX_API request_released : public ::std::logic_error
+{
+public:
+
+ /**
+ * @brief Constructor.
+ * @param what Human readable reason for error.
+ */
+ explicit request_released(const ::std::string& what);
+
+ /**
+ * @brief Copy constructor.
+ * @param other Object to copy from.
+ */
+ request_released(const request_released& other) noexcept;
+
+ /**
+ * @brief Move constructor.
+ * @param other Object to move.
+ */
+ request_released(request_released&& other) noexcept;
+
+ /**
+ * @brief Assignment operator.
+ * @param other Object to copy from.
+ * @return Reference to self.
+ */
+ request_released& operator=(const request_released& other) noexcept;
+
+ /**
+ * @brief Move assignment operator.
+ * @param other Object to move.
+ * @return Reference to self.
+ */
+ request_released& operator=(request_released&& other) noexcept;
+
+ virtual ~request_released();
+};
+
+/**
+ * @brief Exception thrown when the core C library returns an invalid value
+ * for any of the line_info properties.
+ */
+class GPIOD_CXX_API bad_mapping : public ::std::runtime_error
+{
+public:
+
+ /**
+ * @brief Constructor.
+ * @param what Human readable reason for error.
+ */
+ explicit bad_mapping(const ::std::string& what);
+
+ /**
+ * @brief Copy constructor.
+ * @param other Object to copy from.
+ */
+ bad_mapping(const bad_mapping& other) noexcept;
+
+ /**
+ * @brief Move constructor.
+ * @param other Object to move.
+ */
+ bad_mapping(bad_mapping&& other) noexcept;
+
+ /**
+ * @brief Assignment operator.
+ * @param other Object to copy from.
+ * @return Reference to self.
+ */
+ bad_mapping& operator=(const bad_mapping& other) noexcept;
+
+ /**
+ * @brief Move assignment operator.
+ * @param other Object to move.
+ * @return Reference to self.
+ */
+ bad_mapping& operator=(bad_mapping&& other) noexcept;
+
+ virtual ~bad_mapping();
+};
+
+/**
+ * @}
+ */
+
+} /* namespace gpiod */
+
+#endif /* __LIBGPIOD_CXX_EXCEPTION_HPP__ */
--- /dev/null
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+/**
+ * @file gpiod.h
+ */
+
+#ifndef __LIBGPIOD_CXX_INFO_EVENT_HPP__
+#define __LIBGPIOD_CXX_INFO_EVENT_HPP__
+
+#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
+#error "Only gpiod.hpp can be included directly."
+#endif
+
+#include <cstdint>
+#include <iostream>
+#include <memory>
+
+#include "timestamp.hpp"
+
+namespace gpiod {
+
+class chip;
+class line_info;
+
+/**
+ * @ingroup gpiod_cxx
+ * @{
+ */
+
+/**
+ * @brief Immutable object containing data about a single line info event.
+ */
+class info_event
+{
+public:
+
+ /**
+ * @brief Types of info events.
+ */
+ enum class event_type
+ {
+ LINE_REQUESTED = 1,
+ /**< Line has been requested. */
+ LINE_RELEASED,
+ /**< Previously requested line has been released. */
+ LINE_CONFIG_CHANGED
+ /**< Line configuration has changed. */
+ };
+
+ /**
+ * @brief Copy constructor.
+ * @param other Object to copy.
+ */
+ info_event(const info_event& other);
+
+ /**
+ * @brief Move constructor.
+ * @param other Object to move.
+ */
+ info_event(info_event&& other) noexcept;
+
+ ~info_event();
+
+ /**
+ * @brief Copy assignment operator.
+ * @param other Object to copy.
+ * @return Reference to self.
+ */
+ info_event& operator=(const info_event& other);
+
+ /**
+ * @brief Move assignment operator.
+ * @param other Object to move.
+ * @return Reference to self.
+ */
+ info_event& operator=(info_event&& other) noexcept;
+
+ /**
+ * @brief Type of this event.
+ * @return Event type.
+ */
+ event_type type() const;
+
+ /**
+ * @brief Timestamp of the event as returned by the kernel.
+ * @return Timestamp as a 64-bit unsigned integer.
+ */
+ ::std::uint64_t timestamp_ns() const noexcept;
+
+ /**
+ * @brief Get the new line information.
+ * @return Constant reference to the line info object containing the
+ * line data as read at the time of the info event.
+ */
+ const line_info& get_line_info() const noexcept;
+
+private:
+
+ info_event();
+
+ struct impl;
+
+ ::std::shared_ptr<impl> _m_priv;
+
+ friend chip;
+};
+
+/**
+ * @brief Stream insertion operator for info events.
+ * @param out Output stream to write to.
+ * @param event GPIO line info event to insert into the output stream.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, const info_event& event);
+
+/**
+ * @}
+ */
+
+} /* namespace gpiod */
+
+#endif /* __LIBGPIOD_CXX_INFO_EVENT_HPP__ */
--- /dev/null
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+/**
+ * @file line-config.hpp
+ */
+
+#ifndef __LIBGPIOD_CXX_LINE_CONFIG_HPP__
+#define __LIBGPIOD_CXX_LINE_CONFIG_HPP__
+
+#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
+#error "Only gpiod.hpp can be included directly."
+#endif
+
+#include <map>
+#include <memory>
+
+namespace gpiod {
+
+class chip;
+class line_request;
+class line_settings;
+
+/**
+ * @ingroup gpiod_cxx
+ * @{
+ */
+
+/**
+ * @brief Contains a set of line config options used in line requests and
+ * reconfiguration.
+ */
+class line_config
+{
+public:
+
+
+ line_config();
+
+ line_config(const line_config& other) = delete;
+
+ /**
+ * @brief Move constructor.
+ * @param other Object to move.
+ */
+ line_config(line_config&& other) noexcept;
+
+ ~line_config();
+
+ /**
+ * @brief Move assignment operator.
+ * @param other Object to move.
+ * @return Reference to self.
+ */
+ line_config& operator=(line_config&& other) noexcept;
+
+ /**
+ * @brief Reset the line config object.
+ * @return Reference to self.
+ */
+ line_config& reset() noexcept;
+
+ /**
+ * @brief Add line settings for a single offset.
+ * @param offset Offset for which to add settings.
+ * @param settings Line settings to add.
+ * @return Reference to self.
+ */
+ line_config& add_line_settings(line::offset offset, const line_settings& settings);
+
+ /**
+ * @brief Add line settings for a set of offsets.
+ * @param offsets Offsets for which to add settings.
+ * @param settings Line settings to add.
+ * @return Reference to self.
+ */
+ line_config& add_line_settings(const line::offsets& offsets, const line_settings& settings);
+
+ /**
+ * @brief Get a mapping of offsets to line settings stored by this
+ * object.
+ * @return Map in which keys represent line offsets and values are
+ * the settings corresponding with them.
+ */
+ ::std::map<line::offset, line_settings> get_line_settings() const;
+
+private:
+
+ struct impl;
+
+ ::std::shared_ptr<impl> _m_priv;
+
+ line_config& operator=(const line_config& other);
+
+ friend line_request;
+ friend request_builder;
+};
+
+/**
+ * @brief Stream insertion operator for GPIO line config objects.
+ * @param out Output stream to write to.
+ * @param config Line config object to insert into the output stream.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, const line_config& config);
+
+/**
+ * @}
+ */
+
+} /* namespace gpiod */
+
+#endif /* __LIBGPIOD_CXX_LINE_CONFIG_HPP__ */
--- /dev/null
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+/**
+ * @file line-info.hpp
+ */
+
+#ifndef __LIBGPIOD_CXX_LINE_INFO_HPP__
+#define __LIBGPIOD_CXX_LINE_INFO_HPP__
+
+#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
+#error "Only gpiod.hpp can be included directly."
+#endif
+
+#include <chrono>
+#include <iostream>
+#include <memory>
+#include <string>
+
+namespace gpiod {
+
+class chip;
+class info_event;
+
+/**
+ * @ingroup gpiod_cxx
+ * @{
+ */
+
+/**
+ * @brief Contains an immutable snapshot of the line's state at the
+ * time when the object of this class was instantiated.
+ */
+class line_info
+{
+public:
+
+ /**
+ * @brief Copy constructor.
+ * @param other Object to copy.
+ */
+ line_info(const line_info& other) noexcept;
+
+ /**
+ * @brief Move constructor.
+ * @param other Object to move.
+ */
+ line_info(line_info&& other) noexcept;
+
+ ~line_info();
+
+ /**
+ * @brief Copy assignment operator.
+ * @param other Object to copy.
+ * @return Reference to self.
+ */
+ line_info& operator=(const line_info& other) noexcept;
+
+ /**
+ * @brief Move assignment operator.
+ * @param other Object to move.
+ * @return Reference to self.
+ */
+ line_info& operator=(line_info&& other) noexcept;
+
+ /**
+ * @brief Get the hardware offset of the line.
+ * @return Offset of the line within the parent chip.
+ */
+ line::offset offset() const noexcept;
+
+ /**
+ * @brief Get the GPIO line name.
+ * @return Name of the GPIO line as it is represented in the kernel.
+ * This routine returns an empty string if the line is unnamed.
+ */
+ ::std::string name() const noexcept;
+
+ /**
+ * @brief Check if the line is currently in use.
+ * @return True if the line is in use, false otherwise.
+ *
+ * The user space can't know exactly why a line is busy. It may have
+ * been requested by another process or hogged by the kernel. It only
+ * matters that the line is used and we can't request it.
+ */
+ bool used() const noexcept;
+
+ /**
+ * @brief Read the GPIO line consumer name.
+ * @return Name of the GPIO consumer name as it is represented in the
+ * kernel. This routine returns an empty string if the line is
+ * not used.
+ */
+ ::std::string consumer() const noexcept;
+
+ /**
+ * @brief Read the GPIO line direction setting.
+ * @return Returns DIRECTION_INPUT or DIRECTION_OUTPUT.
+ */
+ line::direction direction() const;
+
+ /**
+ * @brief Read the current edge detection setting of this line.
+ * @return Returns EDGE_NONE, EDGE_RISING, EDGE_FALLING or EDGE_BOTH.
+ */
+ line::edge edge_detection() const;
+
+ /**
+ * @brief Read the GPIO line bias setting.
+ * @return Returns BIAS_PULL_UP, BIAS_PULL_DOWN, BIAS_DISABLE or
+ * BIAS_UNKNOWN.
+ */
+ line::bias bias() const;
+
+ /**
+ * @brief Read the GPIO line drive setting.
+ * @return Returns DRIVE_PUSH_PULL, DRIVE_OPEN_DRAIN or
+ * DRIVE_OPEN_SOURCE.
+ */
+ line::drive drive() const;
+
+ /**
+ * @brief Check if the signal of this line is inverted.
+ * @return True if this line is "active-low", false otherwise.
+ */
+ bool active_low() const noexcept;
+
+ /**
+ * @brief Check if this line is debounced (either by hardware or by the
+ * kernel software debouncer).
+ * @return True if the line is debounced, false otherwise.
+ */
+ bool debounced() const noexcept;
+
+ /**
+ * @brief Read the current debounce period in microseconds.
+ * @return Current debounce period in microseconds, 0 if the line is
+ * not debounced.
+ */
+ ::std::chrono::microseconds debounce_period() const noexcept;
+
+ /**
+ * @brief Read the current event clock setting used for edge event
+ * timestamps.
+ * @return Returns MONOTONIC, REALTIME or HTE.
+ */
+ line::clock event_clock() const;
+
+private:
+
+ line_info();
+
+ struct impl;
+
+ ::std::shared_ptr<impl> _m_priv;
+
+ friend chip;
+ friend info_event;
+};
+
+/**
+ * @brief Stream insertion operator for GPIO line info objects.
+ * @param out Output stream to write to.
+ * @param info GPIO line info object to insert into the output stream.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, const line_info& info);
+
+/**
+ * @}
+ */
+
+} /* namespace gpiod */
+
+#endif /* __LIBGPIOD_CXX_LINE_INFO_HPP__ */
--- /dev/null
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+/**
+ * @file line-request.hpp
+ */
+
+#ifndef __LIBGPIOD_CXX_LINE_REQUEST_HPP__
+#define __LIBGPIOD_CXX_LINE_REQUEST_HPP__
+
+#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
+#error "Only gpiod.hpp can be included directly."
+#endif
+
+#include <chrono>
+#include <cstddef>
+#include <iostream>
+#include <memory>
+
+#include "misc.hpp"
+
+namespace gpiod {
+
+class chip;
+class edge_event;
+class edge_event_buffer;
+class line_config;
+
+/**
+ * @ingroup gpiod_cxx
+ * @{
+ */
+
+/**
+ * @brief Stores the context of a set of requested GPIO lines.
+ */
+class line_request
+{
+public:
+
+ line_request(const line_request& other) = delete;
+
+ /**
+ * @brief Move constructor.
+ * @param other Object to move.
+ */
+ line_request(line_request&& other) noexcept;
+
+ ~line_request();
+
+ line_request& operator=(const line_request& other) = delete;
+
+ /**
+ * @brief Move assignment operator.
+ * @param other Object to move.
+ * @return Reference to self.
+ */
+ line_request& operator=(line_request&& other) noexcept;
+
+ /**
+ * @brief Check if this object is valid.
+ * @return True if this object's methods can be used, false otherwise.
+ * False usually means the request was released. If the user
+ * calls any of the methods of this class on an object for
+ * which this operator returned false, a logic_error will be
+ * thrown.
+ */
+ explicit operator bool() const noexcept;
+
+ /**
+ * @brief Release the GPIO chip and free all associated resources.
+ * @note The object can still be used after this method is called but
+ * using any of the mutators will result in throwing
+ * a logic_error exception.
+ */
+ void release();
+
+ /**
+ * @brief Get the number of requested lines.
+ * @return Number of lines in this request.
+ */
+ ::std::size_t num_lines() const;
+
+ /**
+ * @brief Get the list of offsets of requested lines.
+ * @return List of hardware offsets of the lines in this request.
+ */
+ line::offsets offsets() const;
+
+ /**
+ * @brief Get the value of a single requested line.
+ * @param offset Offset of the line to read within the chip.
+ * @return Current line value.
+ */
+ line::value get_value(line::offset offset);
+
+ /**
+ * @brief Get the values of a subset of requested lines.
+ * @param offsets Vector of line offsets
+ * @return Vector of lines values with indexes of values corresponding
+ * to those of the offsets.
+ */
+ line::values get_values(const line::offsets& offsets);
+
+ /**
+ * @brief Get the values of all requested lines.
+ * @return List of read values.
+ */
+ line::values get_values();
+
+ /**
+ * @brief Get the values of a subset of requested lines into a vector
+ * supplied by the caller.
+ * @param offsets Vector of line offsets.
+ * @param values Vector for storing the values. Its size must be at
+ * least that of the offsets vector. The indexes of read
+ * values will correspond with those in the offsets
+ * vector.
+ */
+ void get_values(const line::offsets& offsets, line::values& values);
+
+ /**
+ * @brief Get the values of all requested lines.
+ * @param values Array in which the values will be stored. Must hold
+ * at least the number of elements returned by
+ * line_request::num_lines.
+ */
+ void get_values(line::values& values);
+
+ /**
+ * @brief Set the value of a single requested line.
+ * @param offset Offset of the line to set within the chip.
+ * @param value New line value.
+ * @return Reference to self.
+ */
+ line_request& set_value(line::offset offset, line::value value);
+
+ /**
+ * @brief Set the values of a subset of requested lines.
+ * @param values Vector containing a set of offset->value mappings.
+ * @return Reference to self.
+ */
+ line_request& set_values(const line::value_mappings& values);
+
+ /**
+ * @brief Set the values of a subset of requested lines.
+ * @param offsets Vector containing the offsets of lines to set.
+ * @param values Vector containing new values with indexes
+ * corresponding with those in the offsets vector.
+ * @return Reference to self.
+ */
+ line_request& set_values(const line::offsets& offsets, const line::values& values);
+
+ /**
+ * @brief Set the values of all requested lines.
+ * @param values Array of new line values. The size must be equal to
+ * the value returned by line_request::num_lines.
+ * @return Reference to self.
+ */
+ line_request& set_values(const line::values& values);
+
+ /**
+ * @brief Apply new config options to requested lines.
+ * @param config New configuration.
+ * @return Reference to self.
+ */
+ line_request& reconfigure_lines(const line_config& config);
+
+ /**
+ * @brief Get the file descriptor number associated with this line
+ * request.
+ * @return File descriptor number.
+ */
+ int fd() const;
+
+ /**
+ * @brief Wait for edge events on any of the lines requested with edge
+ * detection enabled.
+ * @param timeout Wait time limit in nanoseconds.
+ * @return True if at least one event is ready to be read. False if the
+ * wait timed out.
+ */
+ bool wait_edge_event(const ::std::chrono::nanoseconds& timeout) const;
+
+ /**
+ * @brief Read a number of edge events from this request up to the
+ * maximum capacity of the buffer.
+ * @param buffer Edge event buffer to read events into.
+ * @return Number of events read.
+ */
+ ::std::size_t read_edge_event(edge_event_buffer& buffer);
+
+ /**
+ * @brief Read a number of edge events from this request.
+ * @param buffer Edge event buffer to read events into.
+ * @param max_events Maximum number of events to read. Limited by the
+ * capacity of the buffer.
+ * @return Number of events read.
+ */
+ ::std::size_t read_edge_event(edge_event_buffer& buffer, ::std::size_t max_events);
+
+private:
+
+ line_request();
+
+ struct impl;
+
+ ::std::unique_ptr<impl> _m_priv;
+
+ friend request_builder;
+};
+
+/**
+ * @brief Stream insertion operator for line requests.
+ * @param out Output stream to write to.
+ * @param request Line request object to insert into the output stream.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, const line_request& request);
+
+/**
+ * @}
+ */
+
+} /* namespace gpiod */
+
+#endif /* __LIBGPIOD_CXX_LINE_REQUEST_HPP__ */
--- /dev/null
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/* SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+/**
+ * @file request-config.hpp
+ */
+
+#ifndef __LIBGPIOD_CXX_LINE_SETTINGS_HPP__
+#define __LIBGPIOD_CXX_LINE_SETTINGS_HPP__
+
+#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
+#error "Only gpiod.hpp can be included directly."
+#endif
+
+#include <chrono>
+#include <memory>
+
+#include "line.hpp"
+
+namespace gpiod {
+
+class line_config;
+
+/**
+ * @ingroup gpiod_cxx
+ * @{
+ */
+
+/**
+ * @brief Stores GPIO line settings.
+ */
+class line_settings
+{
+public:
+
+ /**
+ * @brief Initializes the line_settings object with default values.
+ */
+ line_settings();
+
+ line_settings(const line_settings& other) = delete;
+
+ /**
+ * @brief Move constructor.
+ * @param other Object to move.
+ */
+ line_settings(line_settings&& other) noexcept;
+
+ ~line_settings();
+
+ line_settings& operator=(const line_settings& other) = delete;
+
+ /**
+ * @brief Move assignment operator.
+ * @param other Object to move.
+ * @return Reference to self.
+ */
+ line_settings& operator=(line_settings&& other);
+
+ /**
+ * @brief Reset the line settings to default values.
+ * @return Reference to self.
+ */
+ line_settings& reset(void) noexcept;
+
+ /**
+ * @brief Set direction.
+ * @param direction New direction.
+ * @return Reference to self.
+ */
+ line_settings& set_direction(line::direction direction);
+
+ /**
+ * @brief Get direction.
+ * @return Current direction setting.
+ */
+ line::direction direction() const;
+
+ /**
+ * @brief Set edge detection.
+ * @param edge New edge detection setting.
+ * @return Reference to self.
+ */
+ line_settings& set_edge_detection(line::edge edge);
+
+ /**
+ * @brief Get edge detection.
+ * @return Current edge detection setting.
+ */
+ line::edge edge_detection() const;
+
+ /**
+ * @brief Set bias setting.
+ * @param bias New bias.
+ * @return Reference to self.
+ */
+ line_settings& set_bias(line::bias bias);
+
+ /**
+ * @brief Get bias setting.
+ * @return Current bias.
+ */
+ line::bias bias() const;
+
+ /**
+ * @brief Set drive setting.
+ * @param drive New drive.
+ * @return Reference to self.
+ */
+ line_settings& set_drive(line::drive drive);
+
+ /**
+ * @brief Get drive setting.
+ * @return Current drive.
+ */
+ line::drive drive() const;
+
+ /**
+ * @brief Set the active-low setting.
+ * @param active_low New active-low setting.
+ * @return Reference to self.
+ */
+ line_settings& set_active_low(bool active_low);
+
+ /**
+ * @brief Get the active-low setting.
+ * @return Current active-low setting.
+ */
+ bool active_low() const noexcept;
+
+ /**
+ * @brief Set debounce period.
+ * @param period New debounce period in microseconds.
+ * @return Reference to self.
+ */
+ line_settings& set_debounce_period(const ::std::chrono::microseconds& period);
+
+ /**
+ * @brief Get debounce period.
+ * @return Current debounce period.
+ */
+ ::std::chrono::microseconds debounce_period() const noexcept;
+
+ /**
+ * @brief Set the event clock to use for edge event timestamps.
+ * @param event_clock Clock to use.
+ * @return Reference to self.
+ */
+ line_settings& set_event_clock(line::clock event_clock);
+
+ /**
+ * @brief Get the event clock used for edge event timestamps.
+ * @return Current event clock type.
+ */
+ line::clock event_clock() const;
+
+ /**
+ * @brief Set the output value.
+ * @param value New output value.
+ * @return Reference to self.
+ */
+ line_settings& set_output_value(line::value value);
+
+ /**
+ * @brief Get the output value.
+ * @return Current output value.
+ */
+ line::value output_value() const;
+
+private:
+
+ struct impl;
+
+ ::std::unique_ptr<impl> _m_priv;
+
+ friend line_config;
+};
+
+/**
+ * @brief Stream insertion operator for line settings.
+ * @param out Output stream to write to.
+ * @param settings Line settings object to insert into the output stream.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, const line_settings& settings);
+
+/**
+ * @}
+ */
+
+} /* namespace gpiod */
+
+#endif /* __LIBGPIOD_CXX_LINE_SETTINGS_HPP__ */
--- /dev/null
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+/**
+ * @file line.hpp
+ */
+
+#ifndef __LIBGPIOD_CXX_LINE_HPP__
+#define __LIBGPIOD_CXX_LINE_HPP__
+
+#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
+#error "Only gpiod.hpp can be included directly."
+#endif
+
+#include <ostream>
+#include <utility>
+#include <vector>
+
+namespace gpiod {
+
+/**
+ * @brief Namespace containing various type definitions for GPIO lines.
+ */
+namespace line {
+
+/**
+ * @ingroup gpiod_cxx
+ * @{
+ */
+
+/**
+ * @brief Wrapper around unsigned int for representing line offsets.
+ */
+class offset
+{
+public:
+ /**
+ * @brief Constructor with implicit conversion from unsigned int.
+ * @param off Line offset.
+ */
+ offset(unsigned int off = 0) : _m_offset(off) { }
+
+ /**
+ * @brief Copy constructor.
+ * @param other Object to copy.
+ */
+ offset(const offset& other) = default;
+
+ /**
+ * @brief Move constructor.
+ * @param other Object to move.
+ */
+ offset(offset&& other) = default;
+
+ ~offset() = default;
+
+ /**
+ * @brief Assignment operator.
+ * @param other Object to copy.
+ * @return Reference to self.
+ */
+ offset& operator=(const offset& other) = default;
+
+ /**
+ * @brief Move assignment operator.
+ * @param other Object to move.
+ * @return Reference to self.
+ */
+ offset& operator=(offset&& other) noexcept = default;
+
+ /**
+ * @brief Conversion operator to `unsigned int`.
+ */
+ operator unsigned int() const noexcept
+ {
+ return this->_m_offset;
+ }
+
+private:
+ unsigned int _m_offset;
+};
+
+/**
+ * @brief Logical line states.
+ */
+enum class value
+{
+ INACTIVE = 0,
+ /**< Line is inactive. */
+ ACTIVE = 1,
+ /**< Line is active. */
+};
+
+/**
+ * @brief Direction settings.
+ */
+enum class direction
+{
+ AS_IS = 1,
+ /**< Request the line(s), but don't change current direction. */
+ INPUT,
+ /**< Direction is input - we're reading the state of a GPIO line. */
+ OUTPUT
+ /**< Direction is output - we're driving the GPIO line. */
+};
+
+/**
+ * @brief Edge detection settings.
+ */
+enum class edge
+{
+ NONE = 1,
+ /**< Line edge detection is disabled. */
+ RISING,
+ /**< Line detects rising edge events. */
+ FALLING,
+ /**< Line detect falling edge events. */
+ BOTH
+ /**< Line detects both rising and falling edge events. */
+};
+
+/**
+ * @brief Internal bias settings.
+ */
+enum class bias
+{
+ AS_IS = 1,
+ /**< Don't change the bias setting when applying line config. */
+ UNKNOWN,
+ /**< The internal bias state is unknown. */
+ DISABLED,
+ /**< The internal bias is disabled. */
+ PULL_UP,
+ /**< The internal pull-up bias is enabled. */
+ PULL_DOWN
+ /**< The internal pull-down bias is enabled. */
+};
+
+/**
+ * @brief Drive settings.
+ */
+enum class drive
+{
+ PUSH_PULL = 1,
+ /**< Drive setting is push-pull. */
+ OPEN_DRAIN,
+ /**< Line output is open-drain. */
+ OPEN_SOURCE
+ /**< Line output is open-source. */
+};
+
+/**
+ * @brief Event clock settings.
+ */
+enum class clock
+{
+ MONOTONIC = 1,
+ /**< Line uses the monotonic clock for edge event timestamps. */
+ REALTIME,
+ /**< Line uses the realtime clock for edge event timestamps. */
+ HTE,
+ /*<< Line uses the hardware timestamp engine for event timestamps. */
+};
+
+/**
+ * @brief Vector of line offsets.
+ */
+using offsets = ::std::vector<offset>;
+
+/**
+ * @brief Vector of line values.
+ */
+using values = ::std::vector<value>;
+
+/**
+ * @brief Represents a mapping of a line offset to line logical state.
+ */
+using value_mapping = ::std::pair<offset, value>;
+
+/**
+ * @brief Vector of offset->value mappings. Each mapping is defined as a pair
+ * of an unsigned and signed integers.
+ */
+using value_mappings = ::std::vector<value_mapping>;
+
+/**
+ * @brief Stream insertion operator for logical line values.
+ * @param out Output stream.
+ * @param val Value to insert into the output stream in a human-readable form.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, value val);
+
+/**
+ * @brief Stream insertion operator for direction values.
+ * @param out Output stream.
+ * @param dir Value to insert into the output stream in a human-readable form.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, direction dir);
+
+/**
+ * @brief Stream insertion operator for edge detection values.
+ * @param out Output stream.
+ * @param edge Value to insert into the output stream in a human-readable form.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, edge edge);
+
+/**
+ * @brief Stream insertion operator for bias values.
+ * @param out Output stream.
+ * @param bias Value to insert into the output stream in a human-readable form.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, bias bias);
+
+/**
+ * @brief Stream insertion operator for drive values.
+ * @param out Output stream.
+ * @param drive Value to insert into the output stream in a human-readable form.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, drive drive);
+
+/**
+ * @brief Stream insertion operator for event clock values.
+ * @param out Output stream.
+ * @param clock Value to insert into the output stream in a human-readable form.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, clock clock);
+
+/**
+ * @brief Stream insertion operator for the list of output values.
+ * @param out Output stream.
+ * @param vals Object to insert into the output stream in a human-readable form.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, const values& vals);
+
+/**
+ * @brief Stream insertion operator for the list of line offsets.
+ * @param out Output stream.
+ * @param offs Object to insert into the output stream in a human-readable form.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, const offsets& offs);
+
+/**
+ * @brief Stream insertion operator for the offset-to-value mapping.
+ * @param out Output stream.
+ * @param mapping Value to insert into the output stream in a human-readable
+ * form.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, const value_mapping& mapping);
+
+/**
+ * @brief Stream insertion operator for the list of offset-to-value mappings.
+ * @param out Output stream.
+ * @param mappings Object to insert into the output stream in a human-readable
+ * form.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, const value_mappings& mappings);
+
+/**
+ * @}
+ */
+
+} /* namespace line */
+
+} /* namespace gpiod */
+
+#endif /* __LIBGPIOD_CXX_LINE_HPP__ */
--- /dev/null
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/* SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+/**
+ * @file misc.hpp
+ */
+
+#ifndef __LIBGPIOD_CXX_MISC_HPP__
+#define __LIBGPIOD_CXX_MISC_HPP__
+
+#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
+#error "Only gpiod.hpp can be included directly."
+#endif
+
+#include <string>
+
+namespace gpiod {
+
+/**
+ * @ingroup gpiod_cxx
+ * @{
+ */
+
+/**
+ * @brief Check if the file pointed to by path is a GPIO chip character device.
+ * @param path Path to check.
+ * @return True if the file exists and is a GPIO chip character device or a
+ * symbolic link to it.
+ */
+bool is_gpiochip_device(const ::std::filesystem::path& path);
+
+/**
+ * @brief Get the human readable version string for libgpiod API
+ * @return String containing the library version.
+ */
+const ::std::string& version_string();
+
+/**
+ * @}
+ */
+
+} /* namespace gpiod */
+
+#endif /* __LIBGPIOD_CXX_MISC_HPP__ */
--- /dev/null
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/* SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+/**
+ * @file request-builder.hpp
+ */
+
+#ifndef __LIBGPIOD_CXX_REQUEST_BUILDER_HPP__
+#define __LIBGPIOD_CXX_REQUEST_BUILDER_HPP__
+
+#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
+#error "Only gpiod.hpp can be included directly."
+#endif
+
+#include <memory>
+#include <ostream>
+
+namespace gpiod {
+
+class chip;
+class line_config;
+class line_request;
+class request_config;
+
+/**
+ * @ingroup gpiod_cxx
+ * @{
+ */
+
+/**
+ * @brief Intermediate object storing the configuration for a line request.
+ */
+class request_builder
+{
+public:
+
+ request_builder(const request_builder& other) = delete;
+
+ /**
+ * @brief Move constructor.
+ * @param other Object to be moved.
+ */
+ request_builder(request_builder&& other) noexcept;
+
+ ~request_builder();
+
+ request_builder& operator=(const request_builder& other) = delete;
+
+ /**
+ * @brief Move assignment operator.
+ * @param other Object to be moved.
+ * @return Reference to self.
+ */
+ request_builder& operator=(request_builder&& other) noexcept;
+
+ /**
+ * @brief Set the request config for the request.
+ * @param req_cfg Request config to use.
+ * @return Reference to self.
+ */
+ request_builder& set_request_config(request_config& req_cfg);
+
+ /**
+ * @brief Get the current request config.
+ * @return Const reference to the current request config stored by this
+ * object.
+ */
+ const request_config& get_request_config() const noexcept;
+
+ /**
+ * @brief Set consumer in the request config stored by this object.
+ * @param consumer New consumer string.
+ * @return Reference to self.
+ */
+ request_builder& set_consumer(const ::std::string& consumer) noexcept;
+
+ /**
+ * @brief Set the event buffer size in the request config stored by
+ * this object.
+ * @param event_buffer_size New event buffer size.
+ * @return Reference to self.
+ */
+ request_builder& set_event_buffer_size(::std::size_t event_buffer_size) noexcept;
+
+ /**
+ * @brief Set the line config for this request.
+ * @param line_cfg Line config to use.
+ * @return Reference to self.
+ */
+ request_builder& set_line_config(line_config &line_cfg);
+
+ /**
+ * @brief Get the current line config.
+ * @return Const reference to the current line config stored by this
+ * object.
+ */
+ const line_config& get_line_config() const noexcept;
+
+ /**
+ * @brief Add line settings to the line config stored by this object
+ * for a single offset.
+ * @param offset Offset for which to add settings.
+ * @param settings Line settings to use.
+ * @return Reference to self.
+ */
+ request_builder& add_line_settings(line::offset offset, const line_settings& settings);
+
+ /**
+ * @brief Add line settings to the line config stored by this object
+ * for a set of offsets.
+ * @param offsets Offsets for which to add settings.
+ * @param settings Settings to add.
+ * @return Reference to self.
+ */
+ request_builder& add_line_settings(const line::offsets& offsets, const line_settings& settings);
+
+ /**
+ * @brief Make the line request.
+ * @return New line_request object.
+ */
+ line_request do_request();
+
+private:
+
+ struct impl;
+
+ request_builder(chip& chip);
+
+ ::std::unique_ptr<impl> _m_priv;
+
+ friend chip;
+ friend ::std::ostream& operator<<(::std::ostream& out, const request_builder& builder);
+};
+
+/**
+ * @brief Stream insertion operator for GPIO request builder objects.
+ * @param out Output stream to write to.
+ * @param builder Request builder object to insert into the output stream.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, const request_builder& builder);
+
+/**
+ * @}
+ */
+
+} /* namespace gpiod */
+
+#endif /* __LIBGPIOD_CXX_REQUEST_BUILDER_HPP__ */
--- /dev/null
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+/**
+ * @file request-config.hpp
+ */
+
+#ifndef __LIBGPIOD_CXX_REQUEST_CONFIG_HPP__
+#define __LIBGPIOD_CXX_REQUEST_CONFIG_HPP__
+
+#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
+#error "Only gpiod.hpp can be included directly."
+#endif
+
+#include <cstddef>
+#include <iostream>
+#include <memory>
+#include <string>
+
+#include "line.hpp"
+
+namespace gpiod {
+
+class chip;
+
+/**
+ * @ingroup gpiod_cxx
+ * @{
+ */
+
+/**
+ * @brief Stores a set of options passed to the kernel when making a line
+ * request.
+ */
+class request_config
+{
+public:
+
+ /**
+ * @brief Constructor.
+ */
+ request_config();
+
+ request_config(const request_config& other) = delete;
+
+ /**
+ * @brief Move constructor.
+ * @param other Object to move.
+ */
+ request_config(request_config&& other) noexcept;
+
+ ~request_config();
+
+ /**
+ * @brief Move assignment operator.
+ * @param other Object to move.
+ * @return Reference to self.
+ */
+ request_config& operator=(request_config&& other) noexcept;
+
+ /**
+ * @brief Set the consumer name.
+ * @param consumer New consumer name.
+ * @return Reference to self.
+ */
+ request_config& set_consumer(const ::std::string& consumer) noexcept;
+
+ /**
+ * @brief Get the consumer name.
+ * @return Currently configured consumer name. May be an empty string.
+ */
+ ::std::string consumer() const noexcept;
+
+ /**
+ * @brief Set the size of the kernel event buffer.
+ * @param event_buffer_size New event buffer size.
+ * @return Reference to self.
+ * @note The kernel may adjust the value if it's too high. If set to 0,
+ * the default value will be used.
+ */
+ request_config& set_event_buffer_size(::std::size_t event_buffer_size) noexcept;
+
+ /**
+ * @brief Get the edge event buffer size from this request config.
+ * @return Current edge event buffer size setting.
+ */
+ ::std::size_t event_buffer_size() const noexcept;
+
+private:
+
+ struct impl;
+
+ ::std::shared_ptr<impl> _m_priv;
+
+ request_config& operator=(const request_config& other);
+
+ friend request_builder;
+};
+
+/**
+ * @brief Stream insertion operator for request_config objects.
+ * @param out Output stream to write to.
+ * @param config request_config to insert into the output stream.
+ * @return Reference to out.
+ */
+::std::ostream& operator<<(::std::ostream& out, const request_config& config);
+
+/**
+ * @}
+ */
+
+} /* namespace gpiod */
+
+#endif /* __LIBGPIOD_CXX_REQUEST_CONFIG_HPP__ */
--- /dev/null
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/* SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+/**
+ * @file timestamp.hpp
+ */
+
+#ifndef __LIBGPIOD_CXX_TIMESTAMP_HPP__
+#define __LIBGPIOD_CXX_TIMESTAMP_HPP__
+
+#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
+#error "Only gpiod.hpp can be included directly."
+#endif
+
+#include <chrono>
+#include <cstdint>
+
+namespace gpiod {
+
+/**
+ * @ingroup gpiod_cxx
+ * @{
+ */
+
+/**
+ * @brief Stores the edge and info event timestamps as returned by the kernel
+ * and allows to convert them to std::chrono::time_point.
+ */
+class timestamp
+{
+public:
+
+ /**
+ * @brief Monotonic time_point.
+ */
+ using time_point_monotonic = ::std::chrono::time_point<::std::chrono::steady_clock>;
+
+ /**
+ * @brief Real-time time_point.
+ */
+ using time_point_realtime = ::std::chrono::time_point<::std::chrono::system_clock>;
+
+ /**
+ * @brief Constructor with implicit conversion from `uint64_t`.
+ * @param ns Timestamp in nanoseconds.
+ */
+ timestamp(::std::uint64_t ns) : _m_ns(ns) { }
+
+ /**
+ * @brief Copy constructor.
+ * @param other Object to copy.
+ */
+ timestamp(const timestamp& other) noexcept = default;
+
+ /**
+ * @brief Move constructor.
+ * @param other Object to move.
+ */
+ timestamp(timestamp&& other) noexcept = default;
+
+ /**
+ * @brief Assignment operator.
+ * @param other Object to copy.
+ * @return Reference to self.
+ */
+ timestamp& operator=(const timestamp& other) noexcept = default;
+
+ /**
+ * @brief Move assignment operator.
+ * @param other Object to move.
+ * @return Reference to self.
+ */
+ timestamp& operator=(timestamp&& other) noexcept = default;
+
+ ~timestamp() = default;
+
+ /**
+ * @brief Conversion operator to `std::uint64_t`.
+ */
+ operator ::std::uint64_t() noexcept
+ {
+ return this->ns();
+ }
+
+ /**
+ * @brief Get the timestamp in nanoseconds.
+ * @return Timestamp in nanoseconds.
+ */
+ ::std::uint64_t ns() const noexcept
+ {
+ return this->_m_ns;
+ }
+
+ /**
+ * @brief Convert the timestamp to a monotonic time_point.
+ * @return time_point associated with the steady clock.
+ */
+ time_point_monotonic to_time_point_monotonic() const
+ {
+ return time_point_monotonic(::std::chrono::nanoseconds(this->ns()));
+ }
+
+ /**
+ * @brief Convert the timestamp to a real-time time_point.
+ * @return time_point associated with the system clock.
+ */
+ time_point_realtime to_time_point_realtime() const
+ {
+ return time_point_realtime(::std::chrono::nanoseconds(this->ns()));
+ }
+
+private:
+ ::std::uint64_t _m_ns;
+};
+
+/**
+ * @}
+ */
+
+} /* namespace gpiod */
+
+#endif /* __LIBGPIOD_CXX_TIMESTAMP_HPP__ */
--- /dev/null
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <map>
+#include <ostream>
+
+#include "internal.hpp"
+
+namespace gpiod {
+
+namespace {
+
+const ::std::map<int, info_event::event_type> event_type_mapping = {
+ { GPIOD_INFO_EVENT_LINE_REQUESTED, info_event::event_type::LINE_REQUESTED },
+ { GPIOD_INFO_EVENT_LINE_RELEASED, info_event::event_type::LINE_RELEASED },
+ { GPIOD_INFO_EVENT_LINE_CONFIG_CHANGED, info_event::event_type::LINE_CONFIG_CHANGED }
+};
+
+const ::std::map<info_event::event_type, ::std::string> event_type_names = {
+ { info_event::event_type::LINE_REQUESTED, "LINE_REQUESTED" },
+ { info_event::event_type::LINE_RELEASED, "LINE_RELEASED" },
+ { info_event::event_type::LINE_CONFIG_CHANGED, "LINE_CONFIG_CHANGED" }
+};
+
+} /* namespace */
+
+void info_event::impl::set_info_event_ptr(info_event_ptr& new_event)
+{
+ ::gpiod_line_info* info = ::gpiod_info_event_get_line_info(new_event.get());
+
+ line_info_ptr copy(::gpiod_line_info_copy(info));
+ if (!copy)
+ throw_from_errno("unable to copy the line info object");
+
+ this->event = ::std::move(new_event);
+ this->info._m_priv->set_info_ptr(copy);
+}
+
+info_event::info_event()
+ : _m_priv(new impl)
+{
+
+}
+
+GPIOD_CXX_API info_event::info_event(const info_event& other)
+ : _m_priv(other._m_priv)
+{
+
+}
+
+GPIOD_CXX_API info_event::info_event(info_event&& other) noexcept
+ : _m_priv(::std::move(other._m_priv))
+{
+
+}
+
+GPIOD_CXX_API info_event::~info_event()
+{
+
+}
+
+GPIOD_CXX_API info_event& info_event::operator=(const info_event& other)
+{
+ this->_m_priv = other._m_priv;
+
+ return *this;
+}
+
+GPIOD_CXX_API info_event& info_event::operator=(info_event&& other) noexcept
+{
+ this->_m_priv = ::std::move(other._m_priv);
+
+ return *this;
+}
+
+GPIOD_CXX_API info_event::event_type info_event::type() const
+{
+ int type = ::gpiod_info_event_get_event_type(this->_m_priv->event.get());
+
+ return map_int_to_enum(type, event_type_mapping);
+}
+
+GPIOD_CXX_API ::std::uint64_t info_event::timestamp_ns() const noexcept
+{
+ return ::gpiod_info_event_get_timestamp_ns(this->_m_priv->event.get());
+}
+
+GPIOD_CXX_API const line_info& info_event::get_line_info() const noexcept
+{
+ return this->_m_priv->info;
+}
+
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const info_event& event)
+{
+ out << "gpiod::info_event(event_type='" << event_type_names.at(event.type()) <<
+ "', timestamp=" << event.timestamp_ns() <<
+ ", line_info=" << event.get_line_info() <<
+ ")";
+
+ return out;
+}
+
+} /* namespace gpiod */
--- /dev/null
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <map>
+#include <stdexcept>
+#include <system_error>
+
+#include "internal.hpp"
+
+namespace gpiod {
+
+void throw_from_errno(const ::std::string& what)
+{
+ switch (errno) {
+ case EINVAL:
+ throw ::std::invalid_argument(what);
+ case E2BIG:
+ throw ::std::length_error(what);
+ case ENOMEM:
+ throw ::std::bad_alloc();
+ case EDOM:
+ throw ::std::domain_error(what);
+ default:
+ throw ::std::system_error(errno, ::std::system_category(), what);
+ }
+}
+
+} /* namespace gpiod */
/* SPDX-License-Identifier: LGPL-3.0-or-later */
-/* SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <bgolaszewski@baylibre.com> */
+/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
-#ifndef __LIBGPIOD_GPIOD_CXX_INTERNAL_HPP__
-#define __LIBGPIOD_GPIOD_CXX_INTERNAL_HPP__
+#ifndef __LIBGPIOD_CXX_INTERNAL_HPP__
+#define __LIBGPIOD_CXX_INTERNAL_HPP__
-#define GPIOD_CXX_API __attribute__((visibility("default")))
+#include <gpiod.h>
+#include <map>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
-#endif /* __LIBGPIOD_GPIOD_CXX_INTERNAL_HPP__ */
+#include "gpiod.hpp"
+
+#define GPIOD_CXX_UNUSED __attribute__((unused))
+#define GPIOD_CXX_NORETURN __attribute__((noreturn))
+
+namespace gpiod {
+
+template<class enum_type> enum_type
+map_int_to_enum(int value, const ::std::map<int, enum_type>& mapping)
+{
+ try {
+ return mapping.at(value);
+ } catch (const ::std::out_of_range& err) {
+ /* FIXME Demangle the name. */
+ throw bad_mapping(::std::string("invalid value for ") +
+ typeid(enum_type).name());
+ }
+}
+
+void throw_from_errno(const ::std::string& what);
+
+template<class T, void F(T*)> struct deleter
+{
+ void operator()(T* ptr)
+ {
+ F(ptr);
+ }
+};
+
+using chip_deleter = deleter<::gpiod_chip, ::gpiod_chip_close>;
+using chip_info_deleter = deleter<::gpiod_chip_info, ::gpiod_chip_info_free>;
+using line_info_deleter = deleter<::gpiod_line_info, ::gpiod_line_info_free>;
+using info_event_deleter = deleter<::gpiod_info_event, ::gpiod_info_event_free>;
+using line_settings_deleter = deleter<::gpiod_line_settings, ::gpiod_line_settings_free>;
+using line_config_deleter = deleter<::gpiod_line_config, ::gpiod_line_config_free>;
+using request_config_deleter = deleter<::gpiod_request_config, ::gpiod_request_config_free>;
+using line_request_deleter = deleter<::gpiod_line_request, ::gpiod_line_request_release>;
+using edge_event_deleter = deleter<::gpiod_edge_event, ::gpiod_edge_event_free>;
+using edge_event_buffer_deleter = deleter<::gpiod_edge_event_buffer,
+ ::gpiod_edge_event_buffer_free>;
+
+using chip_ptr = ::std::unique_ptr<::gpiod_chip, chip_deleter>;
+using chip_info_ptr = ::std::unique_ptr<::gpiod_chip_info, chip_info_deleter>;
+using line_info_ptr = ::std::unique_ptr<::gpiod_line_info, line_info_deleter>;
+using info_event_ptr = ::std::unique_ptr<::gpiod_info_event, info_event_deleter>;
+using line_settings_ptr = ::std::unique_ptr<::gpiod_line_settings, line_settings_deleter>;
+using line_config_ptr = ::std::unique_ptr<::gpiod_line_config, line_config_deleter>;
+using request_config_ptr = ::std::unique_ptr<::gpiod_request_config, request_config_deleter>;
+using line_request_ptr = ::std::unique_ptr<::gpiod_line_request, line_request_deleter>;
+using edge_event_ptr = ::std::unique_ptr<::gpiod_edge_event, edge_event_deleter>;
+using edge_event_buffer_ptr = ::std::unique_ptr<::gpiod_edge_event_buffer,
+ edge_event_buffer_deleter>;
+
+struct chip::impl
+{
+ impl(const ::std::filesystem::path& path);
+ impl(const impl& other) = delete;
+ impl(impl&& other) = delete;
+ impl& operator=(const impl& other) = delete;
+ impl& operator=(impl&& other) = delete;
+
+ void throw_if_closed() const;
+
+ chip_ptr chip;
+};
+
+struct chip_info::impl
+{
+ impl() = default;
+ impl(const impl& other) = delete;
+ impl(impl&& other) = delete;
+ impl& operator=(const impl& other) = delete;
+ impl& operator=(impl&& other) = delete;
+
+ void set_info_ptr(chip_info_ptr& new_info);
+
+ chip_info_ptr info;
+};
+
+struct line_info::impl
+{
+ impl() = default;
+ impl(const impl& other) = delete;
+ impl(impl&& other) = delete;
+ impl& operator=(const impl& other) = delete;
+ impl& operator=(impl&& other) = delete;
+
+ void set_info_ptr(line_info_ptr& new_info);
+
+ line_info_ptr info;
+};
+
+struct info_event::impl
+{
+ impl() = default;
+ impl(const impl& other) = delete;
+ impl(impl&& other) = delete;
+ impl& operator=(const impl& other) = delete;
+ impl& operator=(impl&& other) = delete;
+
+ void set_info_event_ptr(info_event_ptr& new_event);
+
+ info_event_ptr event;
+ line_info info;
+};
+
+struct line_settings::impl
+{
+ impl();
+ impl(const impl& other) = delete;
+ impl(impl&& other) = delete;
+ impl& operator=(const impl& other) = delete;
+ impl& operator=(impl&& other) = delete;
+
+ line_settings_ptr settings;
+};
+
+struct line_config::impl
+{
+ impl();
+ impl(const impl& other) = delete;
+ impl(impl&& other) = delete;
+ impl& operator=(const impl& other) = delete;
+ impl& operator=(impl&& other) = delete;
+
+ line_config_ptr config;
+};
+
+struct request_config::impl
+{
+ impl();
+ impl(const impl& other) = delete;
+ impl(impl&& other) = delete;
+ impl& operator=(const impl& other) = delete;
+ impl& operator=(impl&& other) = delete;
+
+ request_config_ptr config;
+};
+
+struct line_request::impl
+{
+ impl() = default;
+ impl(const impl& other) = delete;
+ impl(impl&& other) = delete;
+ impl& operator=(const impl& other) = delete;
+ impl& operator=(impl&& other) = delete;
+
+ void throw_if_released() const;
+ void set_request_ptr(line_request_ptr& ptr);
+ void fill_offset_buf(const line::offsets& offsets);
+
+ line_request_ptr request;
+
+ /*
+ * Used when reading/setting the line values in order to avoid
+ * allocating a new buffer on every call. We're not doing it for
+ * offsets in the line & request config structures because they don't
+ * require high performance unlike the set/get value calls.
+ */
+ ::std::vector<unsigned int> offset_buf;
+};
+
+struct edge_event::impl
+{
+ impl() = default;
+ impl(const impl& other) = delete;
+ impl(impl&& other) = delete;
+ virtual ~impl() = default;
+ impl& operator=(const impl& other) = delete;
+ impl& operator=(impl&& other) = delete;
+
+ virtual ::gpiod_edge_event* get_event_ptr() const noexcept = 0;
+ virtual ::std::shared_ptr<impl> copy(const ::std::shared_ptr<impl>& self) const = 0;
+};
+
+struct edge_event::impl_managed : public edge_event::impl
+{
+ impl_managed() = default;
+ impl_managed(const impl_managed& other) = delete;
+ impl_managed(impl_managed&& other) = delete;
+ virtual ~impl_managed() = default;
+ impl_managed& operator=(const impl_managed& other) = delete;
+ impl_managed& operator=(impl_managed&& other) = delete;
+
+ ::gpiod_edge_event* get_event_ptr() const noexcept override;
+ ::std::shared_ptr<impl> copy(const ::std::shared_ptr<impl>& self) const override;
+
+ edge_event_ptr event;
+};
+
+struct edge_event::impl_external : public edge_event::impl
+{
+ impl_external();
+ impl_external(const impl_external& other) = delete;
+ impl_external(impl_external&& other) = delete;
+ virtual ~impl_external() = default;
+ impl_external& operator=(const impl_external& other) = delete;
+ impl_external& operator=(impl_external&& other) = delete;
+
+ ::gpiod_edge_event* get_event_ptr() const noexcept override;
+ ::std::shared_ptr<impl> copy(const ::std::shared_ptr<impl>& self) const override;
+
+ ::gpiod_edge_event *event;
+};
+
+struct edge_event_buffer::impl
+{
+ impl(unsigned int capacity);
+ impl(const impl& other) = delete;
+ impl(impl&& other) = delete;
+ impl& operator=(const impl& other) = delete;
+ impl& operator=(impl&& other) = delete;
+
+ int read_events(const line_request_ptr& request, unsigned int max_events);
+
+ edge_event_buffer_ptr buffer;
+ ::std::vector<edge_event> events;
+};
+
+} /* namespace gpiod */
+
+#endif /* __LIBGPIOD_CXX_INTERNAL_HPP__ */
+++ /dev/null
-// SPDX-License-Identifier: LGPL-3.0-or-later
-// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
-
-#include <gpiod.hpp>
-#include <system_error>
-
-#include "internal.hpp"
-
-namespace gpiod {
-
-GPIOD_CXX_API line_iter begin(line_iter iter) noexcept
-{
- return iter;
-}
-
-GPIOD_CXX_API line_iter end(const line_iter&) noexcept
-{
- return line_iter();
-}
-
-GPIOD_CXX_API line_iter::line_iter(const chip& owner)
- : _m_current(owner.get_line(0))
-{
-
-}
-
-GPIOD_CXX_API line_iter& line_iter::operator++(void)
-{
- unsigned int offset = this->_m_current.offset() + 1;
- chip owner = this->_m_current.get_chip();
-
- if (offset == owner.num_lines())
- this->_m_current = line(); /* Last element */
- else
- this->_m_current = owner.get_line(offset);
-
- return *this;
-}
-
-GPIOD_CXX_API const line& line_iter::operator*(void) const
-{
- return this->_m_current;
-}
-
-GPIOD_CXX_API const line* line_iter::operator->(void) const
-{
- return ::std::addressof(this->_m_current);
-}
-
-GPIOD_CXX_API bool line_iter::operator==(const line_iter& rhs) const noexcept
-{
- return this->_m_current._m_line == rhs._m_current._m_line;
-}
-
-GPIOD_CXX_API bool line_iter::operator!=(const line_iter& rhs) const noexcept
-{
- return this->_m_current._m_line != rhs._m_current._m_line;
-}
-
-} /* namespace gpiod */
--- /dev/null
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <cstdlib>
+#include <iterator>
+#include <ostream>
+#include <sstream>
+#include <utility>
+#include <vector>
+
+#include "internal.hpp"
+
+namespace gpiod {
+
+namespace {
+
+::gpiod_line_config* make_line_config()
+{
+ ::gpiod_line_config *config = ::gpiod_line_config_new();
+ if (!config)
+ throw_from_errno("Unable to allocate the line config object");
+
+ return config;
+}
+
+struct malloc_deleter
+{
+ void operator()(void* ptr)
+ {
+ ::free(ptr);
+ }
+};
+
+} /* namespace */
+
+line_config::impl::impl()
+ : config(make_line_config())
+{
+
+}
+
+GPIOD_CXX_API line_config::line_config()
+ : _m_priv(new impl)
+{
+
+}
+
+GPIOD_CXX_API line_config::line_config(line_config&& other) noexcept
+ : _m_priv(::std::move(other._m_priv))
+{
+
+}
+
+GPIOD_CXX_API line_config::~line_config()
+{
+
+}
+
+line_config& line_config::operator=(const line_config& other)
+{
+ this->_m_priv = other._m_priv;
+
+ return *this;
+}
+
+GPIOD_CXX_API line_config& line_config::operator=(line_config&& other) noexcept
+{
+ this->_m_priv = ::std::move(other._m_priv);
+
+ return *this;
+}
+
+GPIOD_CXX_API line_config& line_config::reset() noexcept
+{
+ ::gpiod_line_config_reset(this->_m_priv->config.get());
+
+ return *this;
+}
+
+GPIOD_CXX_API line_config& line_config::add_line_settings(line::offset offset,
+ const line_settings& settings)
+{
+ return this->add_line_settings(line::offsets({offset}), settings);
+}
+
+GPIOD_CXX_API line_config& line_config::add_line_settings(const line::offsets& offsets,
+ const line_settings& settings)
+{
+ ::std::vector<unsigned int> raw_offsets(offsets.size());
+
+ for (unsigned int i = 0; i < offsets.size(); i++)
+ raw_offsets[i] = offsets[i];
+
+ auto ret = ::gpiod_line_config_add_line_settings(this->_m_priv->config.get(),
+ raw_offsets.data(), raw_offsets.size(),
+ settings._m_priv->settings.get());
+ if (ret)
+ throw_from_errno("unable to add line settings");
+
+ return *this;
+}
+
+GPIOD_CXX_API ::std::map<line::offset, line_settings> line_config::get_line_settings() const
+{
+ ::std::map<line::offset, line_settings> settings_map;
+ ::std::size_t num_offsets;
+ unsigned int *offsets_ptr;
+ int ret;
+
+ ret = ::gpiod_line_config_get_offsets(this->_m_priv->config.get(),
+ &num_offsets, &offsets_ptr);
+ if (ret)
+ throw_from_errno("unable to retrieve line offsets");
+
+ if (num_offsets == 0)
+ return settings_map;
+
+ ::std::unique_ptr<unsigned int, malloc_deleter> offsets(offsets_ptr);
+
+ for (size_t i = 0; i < num_offsets; i++) {
+ line_settings settings;
+
+ settings._m_priv->settings.reset(::gpiod_line_config_get_line_settings(
+ this->_m_priv->config.get(),
+ offsets.get()[i]));
+ if (!settings._m_priv->settings)
+ throw_from_errno("unable to retrieve line settings");
+
+ settings_map[offsets.get()[i]] = ::std::move(settings);
+ }
+
+ return settings_map;
+}
+
+GPIOD_CXX_API ::std::ostream&
+operator<<(::std::ostream& out, const line_config& config)
+{
+ auto settings_map = config.get_line_settings();
+ ::std::vector<::std::string> vec;
+
+ out << "gpiod::line_config(num_settings=" << settings_map.size();
+
+ if (settings_map.size() == 0) {
+ out << ")";
+ return out;
+ }
+
+ for (const auto& [offset, settings]: settings_map) {
+ ::std::stringstream str;
+
+ str << offset << ": " << settings;
+ vec.push_back(str.str());
+ }
+
+ out << ", settings=[";
+ ::std::copy(vec.begin(), ::std::prev(vec.end()),
+ ::std::ostream_iterator<::std::string>(out, ", "));
+ out << vec.back();
+ out << "])";
+
+ return out;
+}
+
+} /* namespace gpiod */
--- /dev/null
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <map>
+#include <ostream>
+#include <utility>
+
+#include "internal.hpp"
+
+namespace gpiod {
+
+namespace {
+
+const ::std::map<int, line::direction> direction_mapping = {
+ { GPIOD_LINE_DIRECTION_INPUT, line::direction::INPUT },
+ { GPIOD_LINE_DIRECTION_OUTPUT, line::direction::OUTPUT }
+};
+
+const ::std::map<int, line::bias> bias_mapping = {
+ { GPIOD_LINE_BIAS_UNKNOWN, line::bias::UNKNOWN },
+ { GPIOD_LINE_BIAS_DISABLED, line::bias::DISABLED },
+ { GPIOD_LINE_BIAS_PULL_UP, line::bias::PULL_UP },
+ { GPIOD_LINE_BIAS_PULL_DOWN, line::bias::PULL_DOWN }
+};
+
+const ::std::map<int, line::drive> drive_mapping = {
+ { GPIOD_LINE_DRIVE_PUSH_PULL, line::drive::PUSH_PULL },
+ { GPIOD_LINE_DRIVE_OPEN_DRAIN, line::drive::OPEN_DRAIN },
+ { GPIOD_LINE_DRIVE_OPEN_SOURCE, line::drive::OPEN_SOURCE }
+};
+
+const ::std::map<int, line::edge> edge_mapping = {
+ { GPIOD_LINE_EDGE_NONE, line::edge::NONE },
+ { GPIOD_LINE_EDGE_RISING, line::edge::RISING },
+ { GPIOD_LINE_EDGE_FALLING, line::edge::FALLING },
+ { GPIOD_LINE_EDGE_BOTH, line::edge::BOTH }
+};
+
+const ::std::map<int, line::clock> clock_mapping = {
+ { GPIOD_LINE_EVENT_CLOCK_MONOTONIC, line::clock::MONOTONIC },
+ { GPIOD_LINE_EVENT_CLOCK_REALTIME, line::clock::REALTIME },
+ { GPIOD_LINE_EVENT_CLOCK_HTE, line::clock::HTE }
+};
+
+} /* namespace */
+
+void line_info::impl::set_info_ptr(line_info_ptr& new_info)
+{
+ this->info = ::std::move(new_info);
+}
+
+line_info::line_info()
+ : _m_priv(new impl)
+{
+
+}
+
+GPIOD_CXX_API line_info::line_info(const line_info& other) noexcept
+ : _m_priv(other._m_priv)
+{
+
+}
+
+GPIOD_CXX_API line_info::line_info(line_info&& other) noexcept
+ : _m_priv(::std::move(other._m_priv))
+{
+
+}
+
+GPIOD_CXX_API line_info::~line_info()
+{
+
+}
+
+GPIOD_CXX_API line_info& line_info::operator=(const line_info& other) noexcept
+{
+ this->_m_priv = other._m_priv;
+
+ return *this;
+}
+
+GPIOD_CXX_API line_info& line_info::operator=(line_info&& other) noexcept
+{
+ this->_m_priv = ::std::move(other._m_priv);
+
+ return *this;
+}
+
+GPIOD_CXX_API line::offset line_info::offset() const noexcept
+{
+ return ::gpiod_line_info_get_offset(this->_m_priv->info.get());
+}
+
+GPIOD_CXX_API ::std::string line_info::name() const noexcept
+{
+ const char* name = ::gpiod_line_info_get_name(this->_m_priv->info.get());
+
+ return name ?: "";
+}
+
+GPIOD_CXX_API bool line_info::used() const noexcept
+{
+ return ::gpiod_line_info_is_used(this->_m_priv->info.get());
+}
+
+GPIOD_CXX_API ::std::string line_info::consumer() const noexcept
+{
+ const char* consumer = ::gpiod_line_info_get_consumer(this->_m_priv->info.get());
+
+ return consumer ?: "";
+}
+
+GPIOD_CXX_API line::direction line_info::direction() const
+{
+ int direction = ::gpiod_line_info_get_direction(this->_m_priv->info.get());
+
+ return map_int_to_enum(direction, direction_mapping);
+}
+
+GPIOD_CXX_API bool line_info::active_low() const noexcept
+{
+ return ::gpiod_line_info_is_active_low(this->_m_priv->info.get());
+}
+
+GPIOD_CXX_API line::bias line_info::bias() const
+{
+ int bias = ::gpiod_line_info_get_bias(this->_m_priv->info.get());
+
+ return bias_mapping.at(bias);
+}
+
+GPIOD_CXX_API line::drive line_info::drive() const
+{
+ int drive = ::gpiod_line_info_get_drive(this->_m_priv->info.get());
+
+ return drive_mapping.at(drive);
+}
+
+GPIOD_CXX_API line::edge line_info::edge_detection() const
+{
+ int edge = ::gpiod_line_info_get_edge_detection(this->_m_priv->info.get());
+
+ return edge_mapping.at(edge);
+}
+
+GPIOD_CXX_API line::clock line_info::event_clock() const
+{
+ int clock = ::gpiod_line_info_get_event_clock(this->_m_priv->info.get());
+
+ return clock_mapping.at(clock);
+}
+
+GPIOD_CXX_API bool line_info::debounced() const noexcept
+{
+ return ::gpiod_line_info_is_debounced(this->_m_priv->info.get());
+}
+
+GPIOD_CXX_API ::std::chrono::microseconds line_info::debounce_period() const noexcept
+{
+ return ::std::chrono::microseconds(
+ ::gpiod_line_info_get_debounce_period_us(this->_m_priv->info.get()));
+}
+
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const line_info& info)
+{
+ ::std::string name, consumer;
+
+ name = info.name().empty() ? "unnamed" : ::std::string("'") + info.name() + "'";
+ consumer = info.consumer().empty() ? "unused" : ::std::string("'") + info.name() + "'";
+
+ out << "gpiod::line_info(offset=" << info.offset() <<
+ ", name=" << name <<
+ ", used=" << ::std::boolalpha << info.used() <<
+ ", consumer=" << consumer <<
+ ", direction=" << info.direction() <<
+ ", active_low=" << ::std::boolalpha << info.active_low() <<
+ ", bias=" << info.bias() <<
+ ", drive=" << info.drive() <<
+ ", edge_detection=" << info.edge_detection() <<
+ ", event_clock=" << info.event_clock() <<
+ ", debounced=" << ::std::boolalpha << info.debounced();
+
+ if (info.debounced())
+ out << ", debounce_period=" << info.debounce_period().count() << "us";
+
+ out << ")";
+
+ return out;
+}
+
+} /* namespace gpiod */
--- /dev/null
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <iterator>
+#include <ostream>
+#include <utility>
+
+#include "internal.hpp"
+
+namespace gpiod {
+
+void line_request::impl::throw_if_released() const
+{
+ if (!this->request)
+ throw request_released("GPIO lines have been released");
+}
+
+void line_request::impl::set_request_ptr(line_request_ptr& ptr)
+{
+ this->request = ::std::move(ptr);
+ this->offset_buf.resize(::gpiod_line_request_get_num_lines(this->request.get()));
+}
+
+void line_request::impl::fill_offset_buf(const line::offsets& offsets)
+{
+ for (unsigned int i = 0; i < offsets.size(); i++)
+ this->offset_buf[i] = offsets[i];
+}
+
+line_request::line_request()
+ : _m_priv(new impl)
+{
+
+}
+
+GPIOD_CXX_API line_request::line_request(line_request&& other) noexcept
+ : _m_priv(::std::move(other._m_priv))
+{
+
+}
+
+GPIOD_CXX_API line_request::~line_request()
+{
+
+}
+
+GPIOD_CXX_API line_request& line_request::operator=(line_request&& other) noexcept
+{
+ this->_m_priv = ::std::move(other._m_priv);
+
+ return *this;
+}
+
+GPIOD_CXX_API line_request::operator bool() const noexcept
+{
+ return this->_m_priv->request.get() != nullptr;
+}
+
+GPIOD_CXX_API void line_request::release()
+{
+ this->_m_priv->throw_if_released();
+
+ this->_m_priv->request.reset();
+}
+
+GPIOD_CXX_API ::std::size_t line_request::num_lines() const
+{
+ this->_m_priv->throw_if_released();
+
+ return ::gpiod_line_request_get_num_lines(this->_m_priv->request.get());
+}
+
+GPIOD_CXX_API line::offsets line_request::offsets() const
+{
+ this->_m_priv->throw_if_released();
+
+ auto num_lines = this->num_lines();
+ ::std::vector<unsigned int> buf(num_lines);
+ line::offsets offsets(num_lines);
+
+ ::gpiod_line_request_get_offsets(this->_m_priv->request.get(), buf.data());
+
+ for (unsigned int i = 0; i < num_lines; i++)
+ offsets[i] = buf[i];
+
+ return offsets;
+}
+
+GPIOD_CXX_API line::value line_request::get_value(line::offset offset)
+{
+ return this->get_values({ offset }).front();
+}
+
+GPIOD_CXX_API line::values
+line_request::get_values(const line::offsets& offsets)
+{
+ line::values vals(offsets.size());
+
+ this->get_values(offsets, vals);
+
+ return vals;
+}
+
+GPIOD_CXX_API line::values line_request::get_values()
+{
+ return this->get_values(this->offsets());
+}
+
+GPIOD_CXX_API void line_request::get_values(const line::offsets& offsets, line::values& values)
+{
+ this->_m_priv->throw_if_released();
+
+ if (offsets.size() != values.size())
+ throw ::std::invalid_argument("values must have the same size as the offsets");
+
+ this->_m_priv->fill_offset_buf(offsets);
+
+ int ret = ::gpiod_line_request_get_values_subset(this->_m_priv->request.get(),
+ offsets.size(), this->_m_priv->offset_buf.data(),
+ reinterpret_cast<int*>(values.data()));
+ if (ret)
+ throw_from_errno("unable to retrieve line values");
+}
+
+GPIOD_CXX_API void line_request::get_values(line::values& values)
+{
+ this->get_values(this->offsets(), values);
+}
+
+GPIOD_CXX_API line_request&
+line_request::line_request::set_value(line::offset offset, line::value value)
+{
+ return this->set_values({ offset }, { value });
+}
+
+GPIOD_CXX_API line_request&
+line_request::set_values(const line::value_mappings& values)
+{
+ line::offsets offsets(values.size());
+ line::values vals(values.size());
+
+ for (unsigned int i = 0; i < values.size(); i++) {
+ offsets[i] = values[i].first;
+ vals[i] = values[i].second;
+ }
+
+ return this->set_values(offsets, vals);
+}
+
+GPIOD_CXX_API line_request& line_request::set_values(const line::offsets& offsets,
+ const line::values& values)
+{
+ this->_m_priv->throw_if_released();
+
+ if (offsets.size() != values.size())
+ throw ::std::invalid_argument("values must have the same size as the offsets");
+
+ this->_m_priv->fill_offset_buf(offsets);
+
+ int ret = ::gpiod_line_request_set_values_subset(this->_m_priv->request.get(),
+ offsets.size(), this->_m_priv->offset_buf.data(),
+ reinterpret_cast<const int*>(values.data()));
+ if (ret)
+ throw_from_errno("unable to set line values");
+
+ return *this;
+}
+
+GPIOD_CXX_API line_request& line_request::set_values(const line::values& values)
+{
+ return this->set_values(this->offsets(), values);
+}
+
+GPIOD_CXX_API line_request& line_request::reconfigure_lines(const line_config& config)
+{
+ this->_m_priv->throw_if_released();
+
+ int ret = ::gpiod_line_request_reconfigure_lines(this->_m_priv->request.get(),
+ config._m_priv->config.get());
+ if (ret)
+ throw_from_errno("unable to reconfigure GPIO lines");
+
+ return *this;
+}
+
+GPIOD_CXX_API int line_request::fd() const
+{
+ this->_m_priv->throw_if_released();
+
+ return ::gpiod_line_request_get_fd(this->_m_priv->request.get());
+}
+
+GPIOD_CXX_API bool line_request::wait_edge_event(const ::std::chrono::nanoseconds& timeout) const
+{
+ this->_m_priv->throw_if_released();
+
+ int ret = ::gpiod_line_request_wait_edge_event(this->_m_priv->request.get(),
+ timeout.count());
+ if (ret < 0)
+ throw_from_errno("error waiting for edge events");
+
+ return ret;
+}
+
+GPIOD_CXX_API ::std::size_t line_request::read_edge_event(edge_event_buffer& buffer)
+{
+ return this->read_edge_event(buffer, buffer.capacity());
+}
+
+GPIOD_CXX_API ::std::size_t
+line_request::read_edge_event(edge_event_buffer& buffer, ::std::size_t max_events)
+{
+ this->_m_priv->throw_if_released();
+
+ return buffer._m_priv->read_events(this->_m_priv->request, max_events);
+}
+
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const line_request& request)
+{
+ if (!request)
+ out << "gpiod::line_request(released)";
+ else
+ out << "gpiod::line_request(num_lines=" << request.num_lines() <<
+ ", line_offsets=" << request.offsets() <<
+ ", fd=" << request.fd() <<
+ ")";
+
+ return out;
+}
+
+} /* namespace gpiod */
--- /dev/null
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <map>
+#include <ostream>
+
+#include "internal.hpp"
+
+namespace gpiod {
+
+namespace {
+
+template<class enum_type>
+::std::map<int, enum_type> make_reverse_maping(const ::std::map<enum_type, int>& mapping)
+{
+ ::std::map<int, enum_type> ret;
+
+ for (const auto &item: mapping)
+ ret[item.second] = item.first;
+
+ return ret;
+}
+
+const ::std::map<line::direction, int> direction_mapping = {
+ { line::direction::AS_IS, GPIOD_LINE_DIRECTION_AS_IS },
+ { line::direction::INPUT, GPIOD_LINE_DIRECTION_INPUT },
+ { line::direction::OUTPUT, GPIOD_LINE_DIRECTION_OUTPUT }
+};
+
+const ::std::map<int, line::direction> reverse_direction_mapping = make_reverse_maping(direction_mapping);
+
+const ::std::map<line::edge, int> edge_mapping = {
+ { line::edge::NONE, GPIOD_LINE_EDGE_NONE },
+ { line::edge::FALLING, GPIOD_LINE_EDGE_FALLING },
+ { line::edge::RISING, GPIOD_LINE_EDGE_RISING },
+ { line::edge::BOTH, GPIOD_LINE_EDGE_BOTH }
+};
+
+const ::std::map<int, line::edge> reverse_edge_mapping = make_reverse_maping(edge_mapping);
+
+const ::std::map<line::bias, int> bias_mapping = {
+ { line::bias::AS_IS, GPIOD_LINE_BIAS_AS_IS },
+ { line::bias::DISABLED, GPIOD_LINE_BIAS_DISABLED },
+ { line::bias::PULL_UP, GPIOD_LINE_BIAS_PULL_UP },
+ { line::bias::PULL_DOWN, GPIOD_LINE_BIAS_PULL_DOWN }
+};
+
+const ::std::map<int, line::bias> reverse_bias_mapping = make_reverse_maping(bias_mapping);
+
+const ::std::map<line::drive, int> drive_mapping = {
+ { line::drive::PUSH_PULL, GPIOD_LINE_DRIVE_PUSH_PULL },
+ { line::drive::OPEN_DRAIN, GPIOD_LINE_DRIVE_OPEN_DRAIN },
+ { line::drive::OPEN_SOURCE, GPIOD_LINE_DRIVE_OPEN_SOURCE }
+};
+
+const ::std::map<int, line::drive> reverse_drive_mapping = make_reverse_maping(drive_mapping);
+
+const ::std::map<line::clock, int> clock_mapping = {
+ { line::clock::MONOTONIC, GPIOD_LINE_EVENT_CLOCK_MONOTONIC },
+ { line::clock::REALTIME, GPIOD_LINE_EVENT_CLOCK_REALTIME },
+ { line::clock::HTE, GPIOD_LINE_EVENT_CLOCK_HTE }
+};
+
+const ::std::map<int, line::clock> reverse_clock_mapping = make_reverse_maping(clock_mapping);
+
+const ::std::map<line::value, int> value_mapping = {
+ { line::value::INACTIVE, GPIOD_LINE_VALUE_INACTIVE },
+ { line::value::ACTIVE, GPIOD_LINE_VALUE_ACTIVE }
+};
+
+const ::std::map<int, line::value> reverse_value_mapping = make_reverse_maping(value_mapping);
+
+line_settings_ptr make_line_settings()
+{
+ line_settings_ptr settings(::gpiod_line_settings_new());
+ if (!settings)
+ throw_from_errno("Unable to allocate the line settings object");
+
+ return settings;
+}
+
+template<class key_type, class value_type, class exception_type>
+value_type map_setting(const key_type& key, const ::std::map<key_type, value_type>& mapping)
+{
+ value_type ret;
+
+ try {
+ ret = mapping.at(key);
+ } catch (const ::std::out_of_range& err) {
+ /* FIXME Demangle the name. */
+ throw exception_type(::std::string("invalid value for ") +
+ typeid(key_type).name());
+ }
+
+ return ret;
+}
+
+template<class enum_type>
+int do_map_value(enum_type value, const ::std::map<enum_type, int>& mapping)
+{
+ return map_setting<enum_type, int, ::std::invalid_argument>(value, mapping);
+}
+
+template<class enum_type, int set_func(::gpiod_line_settings*, int)>
+void set_mapped_value(::gpiod_line_settings* settings, enum_type value,
+ const ::std::map<enum_type, int>& mapping)
+{
+ auto mapped_val = do_map_value(value, mapping);
+
+ auto ret = set_func(settings, mapped_val);
+ if (ret)
+ throw_from_errno("unable to set property");
+}
+
+template<class ret_type, int get_func(::gpiod_line_settings*)>
+ret_type get_mapped_value(::gpiod_line_settings* settings,
+ const ::std::map<int, ret_type>& mapping)
+{
+ int mapped_val = get_func(settings);
+
+ return map_int_to_enum(mapped_val, mapping);
+}
+
+} /* namespace */
+
+line_settings::impl::impl()
+ : settings(make_line_settings())
+{
+
+}
+
+GPIOD_CXX_API line_settings::line_settings()
+ : _m_priv(new impl)
+{
+
+}
+
+GPIOD_CXX_API line_settings::line_settings(line_settings&& other) noexcept
+ : _m_priv(::std::move(other._m_priv))
+{
+
+}
+
+GPIOD_CXX_API line_settings::~line_settings()
+{
+
+}
+
+GPIOD_CXX_API line_settings& line_settings::operator=(line_settings&& other)
+{
+ this->_m_priv = ::std::move(other._m_priv);
+
+ return *this;
+}
+
+GPIOD_CXX_API line_settings& line_settings::reset(void) noexcept
+{
+ ::gpiod_line_settings_reset(this->_m_priv->settings.get());
+
+ return *this;
+}
+
+GPIOD_CXX_API line_settings& line_settings::set_direction(line::direction direction)
+{
+ set_mapped_value<line::direction,
+ ::gpiod_line_settings_set_direction>(this->_m_priv->settings.get(),
+ direction, direction_mapping);
+
+ return *this;
+}
+
+GPIOD_CXX_API line::direction line_settings::direction() const
+{
+ return get_mapped_value<line::direction,
+ ::gpiod_line_settings_get_direction>(
+ this->_m_priv->settings.get(),
+ reverse_direction_mapping);
+}
+
+GPIOD_CXX_API line_settings& line_settings::set_edge_detection(line::edge edge)
+{
+ set_mapped_value<line::edge,
+ ::gpiod_line_settings_set_edge_detection>(this->_m_priv->settings.get(),
+ edge, edge_mapping);
+
+ return *this;
+}
+
+GPIOD_CXX_API line::edge line_settings::edge_detection() const
+{
+ return get_mapped_value<line::edge,
+ ::gpiod_line_settings_get_edge_detection>(
+ this->_m_priv->settings.get(),
+ reverse_edge_mapping);
+}
+
+GPIOD_CXX_API line_settings& line_settings::set_bias(line::bias bias)
+{
+ set_mapped_value<line::bias,
+ ::gpiod_line_settings_set_bias>(this->_m_priv->settings.get(),
+ bias, bias_mapping);
+
+ return *this;
+}
+
+GPIOD_CXX_API line::bias line_settings::bias() const
+{
+ return get_mapped_value<line::bias,
+ ::gpiod_line_settings_get_bias>(this->_m_priv->settings.get(),
+ reverse_bias_mapping);
+}
+
+GPIOD_CXX_API line_settings& line_settings::set_drive(line::drive drive)
+{
+ set_mapped_value<line::drive,
+ ::gpiod_line_settings_set_drive>(this->_m_priv->settings.get(),
+ drive, drive_mapping);
+
+ return *this;
+}
+
+GPIOD_CXX_API line::drive line_settings::drive() const
+{
+ return get_mapped_value<line::drive,
+ ::gpiod_line_settings_get_drive>(this->_m_priv->settings.get(),
+ reverse_drive_mapping);
+}
+
+GPIOD_CXX_API line_settings& line_settings::set_active_low(bool active_low)
+{
+ ::gpiod_line_settings_set_active_low(this->_m_priv->settings.get(), active_low);
+
+ return *this;
+}
+
+GPIOD_CXX_API bool line_settings::active_low() const noexcept
+{
+ return ::gpiod_line_settings_get_active_low(this->_m_priv->settings.get());
+}
+
+GPIOD_CXX_API line_settings&
+line_settings::set_debounce_period(const ::std::chrono::microseconds& period)
+{
+ ::gpiod_line_settings_set_debounce_period_us(this->_m_priv->settings.get(), period.count());
+
+ return *this;
+}
+
+GPIOD_CXX_API ::std::chrono::microseconds line_settings::debounce_period() const noexcept
+{
+ return ::std::chrono::microseconds(
+ ::gpiod_line_settings_get_debounce_period_us(this->_m_priv->settings.get()));
+}
+
+GPIOD_CXX_API line_settings& line_settings::set_event_clock(line::clock event_clock)
+{
+ set_mapped_value<line::clock,
+ ::gpiod_line_settings_set_event_clock>(this->_m_priv->settings.get(),
+ event_clock, clock_mapping);
+
+ return *this;
+}
+
+GPIOD_CXX_API line::clock line_settings::event_clock() const
+{
+ return get_mapped_value<line::clock,
+ ::gpiod_line_settings_get_event_clock>(
+ this->_m_priv->settings.get(),
+ reverse_clock_mapping);
+}
+
+GPIOD_CXX_API line_settings& line_settings::set_output_value(line::value value)
+{
+ set_mapped_value<line::value,
+ ::gpiod_line_settings_set_output_value>(this->_m_priv->settings.get(),
+ value, value_mapping);
+
+ return *this;
+}
+
+GPIOD_CXX_API line::value line_settings::output_value() const
+{
+ return get_mapped_value<line::value,
+ ::gpiod_line_settings_get_output_value>(
+ this->_m_priv->settings.get(),
+ reverse_value_mapping);
+}
+
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const line_settings& settings)
+{
+ out << "gpiod::line_settings(direction=" << settings.direction() <<
+ ", edge_detection=" << settings.edge_detection() <<
+ ", bias=" << settings.bias() <<
+ ", drive=" << settings.drive() <<
+ ", " << (settings.active_low() ? "active-low" : "active-high") <<
+ ", debounce_period=" << settings.debounce_period().count() <<
+ ", event_clock=" << settings.event_clock() <<
+ ", output_value=" << settings.output_value() <<
+ ")";
+
+ return out;
+}
+
+} /* namespace gpiod */
// SPDX-License-Identifier: LGPL-3.0-or-later
-// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
-#include <gpiod.hpp>
-#include <array>
-#include <map>
-#include <system_error>
+#include <iterator>
+#include <ostream>
#include "internal.hpp"
namespace gpiod {
+namespace line {
namespace {
-const ::std::map<int, int> drive_mapping = {
- { GPIOD_LINE_DRIVE_PUSH_PULL, line::DRIVE_PUSH_PULL, },
- { GPIOD_LINE_DRIVE_OPEN_DRAIN, line::DRIVE_OPEN_DRAIN, },
- { GPIOD_LINE_DRIVE_OPEN_SOURCE, line::DRIVE_OPEN_SOURCE, },
+const ::std::map<line::value, ::std::string> value_names = {
+ { line::value::INACTIVE, "INACTIVE" },
+ { line::value::ACTIVE, "ACTIVE" }
};
-const ::std::map<int, int> bias_mapping = {
- { GPIOD_LINE_BIAS_UNKNOWN, line::BIAS_UNKNOWN, },
- { GPIOD_LINE_BIAS_DISABLED, line::BIAS_DISABLED, },
- { GPIOD_LINE_BIAS_PULL_UP, line::BIAS_PULL_UP, },
- { GPIOD_LINE_BIAS_PULL_DOWN, line::BIAS_PULL_DOWN, },
+const ::std::map<line::direction, ::std::string> direction_names = {
+ { line::direction::AS_IS, "AS_IS" },
+ { line::direction::INPUT, "INPUT" },
+ { line::direction::OUTPUT, "OUTPUT" }
};
-} /* namespace */
-
-GPIOD_CXX_API line::line(void)
- : _m_line(nullptr),
- _m_owner()
-{
-
-}
-
-GPIOD_CXX_API line::line(::gpiod_line* line, const chip& owner)
- : _m_line(line),
- _m_owner(owner._m_chip)
-{
-
-}
-
-GPIOD_CXX_API unsigned int line::offset(void) const
-{
- this->throw_if_null();
- line::chip_guard lock_chip(*this);
-
- return ::gpiod_line_offset(this->_m_line);
-}
-
-GPIOD_CXX_API ::std::string line::name(void) const
-{
- this->throw_if_null();
- line::chip_guard lock_chip(*this);
-
- const char* name = ::gpiod_line_name(this->_m_line);
-
- return name ? ::std::string(name) : ::std::string();
-}
-
-GPIOD_CXX_API ::std::string line::consumer(void) const
-{
- this->throw_if_null();
- line::chip_guard lock_chip(*this);
-
- const char* consumer = ::gpiod_line_consumer(this->_m_line);
-
- return consumer ? ::std::string(consumer) : ::std::string();
-}
-
-GPIOD_CXX_API int line::direction(void) const
-{
- this->throw_if_null();
- line::chip_guard lock_chip(*this);
-
- int dir = ::gpiod_line_direction(this->_m_line);
-
- return dir == GPIOD_LINE_DIRECTION_INPUT ? DIRECTION_INPUT : DIRECTION_OUTPUT;
-}
-
-GPIOD_CXX_API bool line::is_active_low(void) const
-{
- this->throw_if_null();
- line::chip_guard lock_chip(*this);
-
- return ::gpiod_line_is_active_low(this->_m_line);
-}
-
-GPIOD_CXX_API int line::bias(void) const
-{
- this->throw_if_null();
- line::chip_guard lock_chip(*this);
-
- return bias_mapping.at(::gpiod_line_bias(this->_m_line));
-}
-
-GPIOD_CXX_API bool line::is_used(void) const
-{
- this->throw_if_null();
- line::chip_guard lock_chip(*this);
-
- return ::gpiod_line_is_used(this->_m_line);
-}
-
-GPIOD_CXX_API int line::drive(void) const
-{
- this->throw_if_null();
- line::chip_guard lock_chip(*this);
-
- return drive_mapping.at(::gpiod_line_drive(this->_m_line));
-}
-
-GPIOD_CXX_API void line::request(const line_request& config, int default_val) const
-{
- this->throw_if_null();
-
- line_bulk bulk({ *this });
-
- bulk.request(config, { default_val });
-}
-
-GPIOD_CXX_API void line::release(void) const
-{
- this->throw_if_null();
-
- line_bulk bulk({ *this });
-
- bulk.release();
-}
-
-/*
- * REVISIT: Check the performance of get/set_value & event_wait compared to
- * the C API. Creating a line_bulk object involves a memory allocation every
- * time this method if called. If the performance is significantly lower,
- * switch to calling the C functions for setting/getting line values and
- * polling for events on single lines directly.
- */
-
-GPIOD_CXX_API int line::get_value(void) const
-{
- this->throw_if_null();
-
- line_bulk bulk({ *this });
-
- return bulk.get_values()[0];
-}
-
-GPIOD_CXX_API void line::set_value(int val) const
-{
- this->throw_if_null();
-
- line_bulk bulk({ *this });
-
- bulk.set_values({ val });
-}
-
-GPIOD_CXX_API void line::set_config(int direction, ::std::bitset<32> flags,
- int value) const
-{
- this->throw_if_null();
-
- line_bulk bulk({ *this });
-
- bulk.set_config(direction, flags, { value });
-}
-
-GPIOD_CXX_API void line::set_flags(::std::bitset<32> flags) const
-{
- this->throw_if_null();
-
- line_bulk bulk({ *this });
+const ::std::map<line::bias, ::std::string> bias_names = {
+ { line::bias::AS_IS, "AS_IS" },
+ { line::bias::UNKNOWN, "UNKNOWN" },
+ { line::bias::DISABLED, "DISABLED" },
+ { line::bias::PULL_UP, "PULL_UP" },
+ { line::bias::PULL_DOWN, "PULL_DOWN" }
+};
- bulk.set_flags(flags);
-}
+const ::std::map<line::drive, ::std::string> drive_names = {
+ { line::drive::PUSH_PULL, "PUSH_PULL" },
+ { line::drive::OPEN_DRAIN, "OPEN_DRAIN" },
+ { line::drive::OPEN_SOURCE, "OPEN_SOURCE" }
+};
-GPIOD_CXX_API void line::set_direction_input() const
-{
- this->throw_if_null();
+const ::std::map<line::edge, ::std::string> edge_names = {
+ { line::edge::NONE, "NONE" },
+ { line::edge::RISING, "RISING_EDGE" },
+ { line::edge::FALLING, "FALLING_EDGE" },
+ { line::edge::BOTH, "BOTH_EDGES" }
+};
- line_bulk bulk({ *this });
+const ::std::map<line::clock, ::std::string> clock_names = {
+ { line::clock::MONOTONIC, "MONOTONIC" },
+ { line::clock::REALTIME, "REALTIME" },
+ { line::clock::HTE, "HTE" }
+};
- bulk.set_direction_input();
-}
+} /* namespace */
-GPIOD_CXX_API void line::set_direction_output(int value) const
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, line::value val)
{
- this->throw_if_null();
+ out << value_names.at(val);
- line_bulk bulk({ *this });
-
- bulk.set_direction_output({ value });
+ return out;
}
-GPIOD_CXX_API bool line::event_wait(const ::std::chrono::nanoseconds& timeout) const
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, line::direction dir)
{
- this->throw_if_null();
-
- line_bulk bulk({ *this });
-
- line_bulk event_bulk = bulk.event_wait(timeout);
+ out << direction_names.at(dir);
- return !!event_bulk;
+ return out;
}
-GPIOD_CXX_API line_event line::make_line_event(const ::gpiod_line_event& event) const noexcept
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, line::edge edge)
{
- line_event ret;
+ out << edge_names.at(edge);
- if (event.event_type == GPIOD_LINE_EVENT_RISING_EDGE)
- ret.event_type = line_event::RISING_EDGE;
- else if (event.event_type == GPIOD_LINE_EVENT_FALLING_EDGE)
- ret.event_type = line_event::FALLING_EDGE;
-
- ret.timestamp = ::std::chrono::duration_cast<::std::chrono::nanoseconds>(
- ::std::chrono::seconds(event.ts.tv_sec)) +
- ::std::chrono::nanoseconds(event.ts.tv_nsec);
-
- ret.source = *this;
-
- return ret;
+ return out;
}
-GPIOD_CXX_API line_event line::event_read(void) const
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, line::bias bias)
{
- this->throw_if_null();
- line::chip_guard lock_chip(*this);
+ out << bias_names.at(bias);
- ::gpiod_line_event event_buf;
- line_event event;
- int rv;
-
- rv = ::gpiod_line_event_read(this->_m_line, ::std::addressof(event_buf));
- if (rv < 0)
- throw ::std::system_error(errno, ::std::system_category(),
- "error reading line event");
-
- return this->make_line_event(event_buf);
+ return out;
}
-GPIOD_CXX_API ::std::vector<line_event> line::event_read_multiple(void) const
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, line::drive drive)
{
- this->throw_if_null();
- line::chip_guard lock_chip(*this);
-
- /* 16 is the maximum number of events stored in the kernel FIFO. */
- ::std::array<::gpiod_line_event, 16> event_buf;
- ::std::vector<line_event> events;
- int rv;
+ out << drive_names.at(drive);
- rv = ::gpiod_line_event_read_multiple(this->_m_line,
- event_buf.data(), event_buf.size());
- if (rv < 0)
- throw ::std::system_error(errno, ::std::system_category(),
- "error reading multiple line events");
-
- events.reserve(rv);
- for (int i = 0; i < rv; i++)
- events.push_back(this->make_line_event(event_buf[i]));
-
- return events;
+ return out;
}
-GPIOD_CXX_API int line::event_get_fd(void) const
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, line::clock clock)
{
- this->throw_if_null();
- line::chip_guard lock_chip(*this);
-
- int ret = ::gpiod_line_event_get_fd(this->_m_line);
-
- if (ret < 0)
- throw ::std::system_error(errno, ::std::system_category(),
- "unable to get the line event file descriptor");
-
- return ret;
-}
+ out << clock_names.at(clock);
-GPIOD_CXX_API const chip line::get_chip(void) const
-{
- return chip(this->_m_owner);
+ return out;
}
-GPIOD_CXX_API void line::reset(void)
+template<typename T>
+::std::ostream& insert_vector(::std::ostream& out,
+ const ::std::string& name, const ::std::vector<T>& vec)
{
- this->_m_line = nullptr;
- this->_m_owner.reset();
-}
+ out << name << "(";
+ ::std::copy(vec.begin(), ::std::prev(vec.end()),
+ ::std::ostream_iterator<T>(out, ", "));
+ out << vec.back();
+ out << ")";
-GPIOD_CXX_API bool line::operator==(const line& rhs) const noexcept
-{
- return this->_m_line == rhs._m_line;
+ return out;
}
-GPIOD_CXX_API bool line::operator!=(const line& rhs) const noexcept
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const offsets& offs)
{
- return this->_m_line != rhs._m_line;
+ return insert_vector(out, "gpiod::offsets", offs);
}
-GPIOD_CXX_API line::operator bool(void) const noexcept
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const line::values& vals)
{
- return this->_m_line != nullptr;
+ return insert_vector(out, "gpiod::values", vals);
}
-GPIOD_CXX_API bool line::operator!(void) const noexcept
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const line::value_mapping& mapping)
{
- return this->_m_line == nullptr;
-}
+ out << "gpiod::value_mapping(" << mapping.first << ": " << mapping.second << ")";
-GPIOD_CXX_API void line::throw_if_null(void) const
-{
- if (!this->_m_line)
- throw ::std::logic_error("object not holding a GPIO line handle");
+ return out;
}
-GPIOD_CXX_API line::chip_guard::chip_guard(const line& line)
- : _m_chip(line._m_owner)
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const line::value_mappings& mappings)
{
-
+ return insert_vector(out, "gpiod::value_mappings", mappings);
}
+} /* namespace line */
} /* namespace gpiod */
+++ /dev/null
-// SPDX-License-Identifier: LGPL-3.0-or-later
-// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
-
-#include <gpiod.hpp>
-#include <map>
-#include <system_error>
-
-#include "internal.hpp"
-
-namespace gpiod {
-
-GPIOD_CXX_API const ::std::bitset<32> line_request::FLAG_ACTIVE_LOW(GPIOD_BIT(0));
-GPIOD_CXX_API const ::std::bitset<32> line_request::FLAG_OPEN_SOURCE(GPIOD_BIT(1));
-GPIOD_CXX_API const ::std::bitset<32> line_request::FLAG_OPEN_DRAIN(GPIOD_BIT(2));
-GPIOD_CXX_API const ::std::bitset<32> line_request::FLAG_BIAS_DISABLED(GPIOD_BIT(3));
-GPIOD_CXX_API const ::std::bitset<32> line_request::FLAG_BIAS_PULL_DOWN(GPIOD_BIT(4));
-GPIOD_CXX_API const ::std::bitset<32> line_request::FLAG_BIAS_PULL_UP(GPIOD_BIT(5));
-
-namespace {
-
-const ::std::map<int, int> reqtype_mapping = {
- { line_request::DIRECTION_AS_IS, GPIOD_LINE_REQUEST_DIRECTION_AS_IS, },
- { line_request::DIRECTION_INPUT, GPIOD_LINE_REQUEST_DIRECTION_INPUT, },
- { line_request::DIRECTION_OUTPUT, GPIOD_LINE_REQUEST_DIRECTION_OUTPUT, },
- { line_request::EVENT_FALLING_EDGE, GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE, },
- { line_request::EVENT_RISING_EDGE, GPIOD_LINE_REQUEST_EVENT_RISING_EDGE, },
- { line_request::EVENT_BOTH_EDGES, GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES, },
-};
-
-struct bitset_cmp
-{
- bool operator()(const ::std::bitset<32>& lhs, const ::std::bitset<32>& rhs) const
- {
- return lhs.to_ulong() < rhs.to_ulong();
- }
-};
-
-const ::std::map<::std::bitset<32>, int, bitset_cmp> reqflag_mapping = {
- { line_request::FLAG_ACTIVE_LOW, GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW, },
- { line_request::FLAG_OPEN_DRAIN, GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN, },
- { line_request::FLAG_OPEN_SOURCE, GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE, },
- { line_request::FLAG_BIAS_DISABLED, GPIOD_LINE_REQUEST_FLAG_BIAS_DISABLED, },
- { line_request::FLAG_BIAS_PULL_DOWN, GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN, },
- { line_request::FLAG_BIAS_PULL_UP, GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP, },
-};
-
-} /* namespace */
-
-GPIOD_CXX_API const unsigned int line_bulk::MAX_LINES = 64;
-
-GPIOD_CXX_API line_bulk::line_bulk(const ::std::vector<line>& lines)
- : _m_bulk()
-{
- this->_m_bulk.reserve(lines.size());
-
- for (auto& it: lines)
- this->append(it);
-}
-
-GPIOD_CXX_API void line_bulk::append(const line& new_line)
-{
- if (!new_line)
- throw ::std::logic_error("line_bulk cannot hold empty line objects");
-
- if (this->_m_bulk.size() >= MAX_LINES)
- throw ::std::logic_error("maximum number of lines reached");
-
- if (this->_m_bulk.size() >= 1 && this->_m_bulk.begin()->get_chip() != new_line.get_chip())
- throw ::std::logic_error("line_bulk cannot hold GPIO lines from different chips");
-
- this->_m_bulk.push_back(new_line);
-}
-
-GPIOD_CXX_API line& line_bulk::get(unsigned int index)
-{
- return this->_m_bulk.at(index);
-}
-
-GPIOD_CXX_API line& line_bulk::operator[](unsigned int index)
-{
- return this->_m_bulk[index];
-}
-
-GPIOD_CXX_API unsigned int line_bulk::size(void) const noexcept
-{
- return this->_m_bulk.size();
-}
-
-GPIOD_CXX_API bool line_bulk::empty(void) const noexcept
-{
- return this->_m_bulk.empty();
-}
-
-GPIOD_CXX_API void line_bulk::clear(void)
-{
- this->_m_bulk.clear();
-}
-
-GPIOD_CXX_API void line_bulk::request(const line_request& config, const ::std::vector<int> default_vals) const
-{
- this->throw_if_empty();
- line::chip_guard lock_chip(this->_m_bulk.front());
-
- if (!default_vals.empty() && this->size() != default_vals.size())
- throw ::std::invalid_argument("the number of default values must correspond with the number of lines");
-
- ::gpiod_line_request_config conf;
- auto bulk = this->to_line_bulk();
- int rv;
-
- conf.consumer = config.consumer.c_str();
- conf.request_type = reqtype_mapping.at(config.request_type);
- conf.flags = 0;
-
- for (auto& it: reqflag_mapping) {
- if ((it.first & config.flags).to_ulong())
- conf.flags |= it.second;
- }
-
- rv = ::gpiod_line_request_bulk(bulk.get(),
- ::std::addressof(conf),
- default_vals.empty() ? NULL : default_vals.data());
- if (rv)
- throw ::std::system_error(errno, ::std::system_category(),
- "error requesting GPIO lines");
-}
-
-GPIOD_CXX_API void line_bulk::release(void) const
-{
- this->throw_if_empty();
- line::chip_guard lock_chip(this->_m_bulk.front());
-
- auto bulk = this->to_line_bulk();
-
- ::gpiod_line_release_bulk(bulk.get());
-}
-
-GPIOD_CXX_API ::std::vector<int> line_bulk::get_values(void) const
-{
- this->throw_if_empty();
- line::chip_guard lock_chip(this->_m_bulk.front());
-
- auto bulk = this->to_line_bulk();
- ::std::vector<int> values;
- int rv;
-
- values.resize(this->_m_bulk.size());
-
- rv = ::gpiod_line_get_value_bulk(bulk.get(), values.data());
- if (rv)
- throw ::std::system_error(errno, ::std::system_category(),
- "error reading GPIO line values");
-
- return values;
-}
-
-GPIOD_CXX_API void line_bulk::set_values(const ::std::vector<int>& values) const
-{
- this->throw_if_empty();
- line::chip_guard lock_chip(this->_m_bulk.front());
-
- if (values.size() != this->_m_bulk.size())
- throw ::std::invalid_argument("the size of values array must correspond with the number of lines");
-
- auto bulk = this->to_line_bulk();
- int rv;
-
- rv = ::gpiod_line_set_value_bulk(bulk.get(), values.data());
- if (rv)
- throw ::std::system_error(errno, ::std::system_category(),
- "error setting GPIO line values");
-}
-
-GPIOD_CXX_API void line_bulk::set_config(int direction, ::std::bitset<32> flags,
- const ::std::vector<int> values) const
-{
- this->throw_if_empty();
- line::chip_guard lock_chip(this->_m_bulk.front());
-
- if (!values.empty() && this->_m_bulk.size() != values.size())
- throw ::std::invalid_argument("the number of default values must correspond with the number of lines");
-
- auto bulk = this->to_line_bulk();
- int rv, gflags;
-
- gflags = 0;
-
- for (auto& it: reqflag_mapping) {
- if ((it.first & flags).to_ulong())
- gflags |= it.second;
- }
-
- rv = ::gpiod_line_set_config_bulk(bulk.get(), direction,
- gflags, values.data());
- if (rv)
- throw ::std::system_error(errno, ::std::system_category(),
- "error setting GPIO line config");
-}
-
-GPIOD_CXX_API void line_bulk::set_flags(::std::bitset<32> flags) const
-{
- this->throw_if_empty();
- line::chip_guard lock_chip(this->_m_bulk.front());
-
- auto bulk = this->to_line_bulk();
- int rv, gflags;
-
- gflags = 0;
-
- for (auto& it: reqflag_mapping) {
- if ((it.first & flags).to_ulong())
- gflags |= it.second;
- }
-
- rv = ::gpiod_line_set_flags_bulk(bulk.get(), gflags);
- if (rv)
- throw ::std::system_error(errno, ::std::system_category(),
- "error setting GPIO line flags");
-}
-
-GPIOD_CXX_API void line_bulk::set_direction_input() const
-{
- this->throw_if_empty();
- line::chip_guard lock_chip(this->_m_bulk.front());
-
- auto bulk = this->to_line_bulk();
- int rv;
-
- rv = ::gpiod_line_set_direction_input_bulk(bulk.get());
- if (rv)
- throw ::std::system_error(errno, ::std::system_category(),
- "error setting GPIO line direction to input");
-}
-
-GPIOD_CXX_API void line_bulk::set_direction_output(const ::std::vector<int>& values) const
-{
- this->throw_if_empty();
- line::chip_guard lock_chip(this->_m_bulk.front());
-
- if (values.size() != this->_m_bulk.size())
- throw ::std::invalid_argument("the size of values array must correspond with the number of lines");
-
- auto bulk = this->to_line_bulk();
- int rv;
-
- rv = ::gpiod_line_set_direction_output_bulk(bulk.get(), values.data());
- if (rv)
- throw ::std::system_error(errno, ::std::system_category(),
- "error setting GPIO line direction to output");
-}
-
-GPIOD_CXX_API line_bulk line_bulk::event_wait(const ::std::chrono::nanoseconds& timeout) const
-{
- this->throw_if_empty();
- line::chip_guard lock_chip(this->_m_bulk.front());
-
- auto ev_bulk = this->make_line_bulk_ptr();
- auto bulk = this->to_line_bulk();
- ::timespec ts;
- line_bulk ret;
- int rv;
-
- ts.tv_sec = timeout.count() / 1000000000ULL;
- ts.tv_nsec = timeout.count() % 1000000000ULL;
-
- rv = ::gpiod_line_event_wait_bulk(bulk.get(), ::std::addressof(ts), ev_bulk.get());
- if (rv < 0) {
- throw ::std::system_error(errno, ::std::system_category(),
- "error polling for events");
- } else if (rv > 0) {
- auto chip = this->_m_bulk[0].get_chip();
- auto num_lines = ::gpiod_line_bulk_num_lines(ev_bulk.get());
-
- for (unsigned int i = 0; i < num_lines; i++)
- ret.append(line(::gpiod_line_bulk_get_line(ev_bulk.get(), i), chip));
- }
-
- return ret;
-}
-
-GPIOD_CXX_API line_bulk::operator bool(void) const noexcept
-{
- return !this->_m_bulk.empty();
-}
-
-GPIOD_CXX_API bool line_bulk::operator!(void) const noexcept
-{
- return this->_m_bulk.empty();
-}
-
-GPIOD_CXX_API line_bulk::iterator::iterator(const ::std::vector<line>::iterator& it)
- : _m_iter(it)
-{
-
-}
-
-GPIOD_CXX_API line_bulk::iterator& line_bulk::iterator::operator++(void)
-{
- this->_m_iter++;
-
- return *this;
-}
-
-GPIOD_CXX_API const line& line_bulk::iterator::operator*(void) const
-{
- return *this->_m_iter;
-}
-
-GPIOD_CXX_API const line* line_bulk::iterator::operator->(void) const
-{
- return this->_m_iter.operator->();
-}
-
-GPIOD_CXX_API bool line_bulk::iterator::operator==(const iterator& rhs) const noexcept
-{
- return this->_m_iter == rhs._m_iter;
-}
-
-GPIOD_CXX_API bool line_bulk::iterator::operator!=(const iterator& rhs) const noexcept
-{
- return this->_m_iter != rhs._m_iter;
-}
-
-GPIOD_CXX_API line_bulk::iterator line_bulk::begin(void) noexcept
-{
- return line_bulk::iterator(this->_m_bulk.begin());
-}
-
-GPIOD_CXX_API line_bulk::iterator line_bulk::end(void) noexcept
-{
- return line_bulk::iterator(this->_m_bulk.end());
-}
-
-GPIOD_CXX_API void line_bulk::throw_if_empty(void) const
-{
- if (this->_m_bulk.empty())
- throw ::std::logic_error("line_bulk not holding any GPIO lines");
-}
-
-GPIOD_CXX_API line_bulk::line_bulk_ptr line_bulk::make_line_bulk_ptr(void) const
-{
- line_bulk_ptr bulk(::gpiod_line_bulk_new(this->size()));
-
- if (!bulk)
- throw ::std::system_error(errno, ::std::system_category(),
- "unable to allocate new bulk object");
-
- return bulk;
-}
-
-GPIOD_CXX_API line_bulk::line_bulk_ptr line_bulk::to_line_bulk(void) const
-{
- line_bulk_ptr bulk = this->make_line_bulk_ptr();
-
- for (auto& it: this->_m_bulk)
- ::gpiod_line_bulk_add_line(bulk.get(), it._m_line);
-
- return bulk;
-}
-
-GPIOD_CXX_API void line_bulk::line_bulk_deleter::operator()(::gpiod_line_bulk *bulk)
-{
- ::gpiod_line_bulk_free(bulk);
-}
-
-} /* namespace gpiod */
--- /dev/null
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include "internal.hpp"
+
+namespace gpiod {
+
+GPIOD_CXX_API bool is_gpiochip_device(const ::std::filesystem::path& path)
+{
+ return ::gpiod_is_gpiochip_device(path.c_str());
+}
+
+GPIOD_CXX_API const ::std::string& version_string()
+{
+ static const ::std::string version(::gpiod_version_string());
+
+ return version;
+}
+
+} /* namespace gpiod */
--- /dev/null
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <ostream>
+#include <utility>
+
+#include "internal.hpp"
+
+namespace gpiod {
+
+struct request_builder::impl
+{
+ impl(chip& parent)
+ : line_cfg(),
+ req_cfg(),
+ parent(parent)
+ {
+
+ }
+
+ impl(const impl& other) = delete;
+ impl(impl&& other) = delete;
+ impl& operator=(const impl& other) = delete;
+ impl& operator=(impl&& other) = delete;
+
+ line_config line_cfg;
+ request_config req_cfg;
+ chip parent;
+};
+
+GPIOD_CXX_API request_builder::request_builder(chip& chip)
+ : _m_priv(new impl(chip))
+{
+
+}
+
+GPIOD_CXX_API request_builder::request_builder(request_builder&& other) noexcept
+ : _m_priv(::std::move(other._m_priv))
+{
+
+}
+
+GPIOD_CXX_API request_builder::~request_builder()
+{
+
+}
+
+GPIOD_CXX_API request_builder& request_builder::operator=(request_builder&& other) noexcept
+{
+ this->_m_priv = ::std::move(other._m_priv);
+
+ return *this;
+}
+
+GPIOD_CXX_API request_builder& request_builder::set_request_config(request_config& req_cfg)
+{
+ this->_m_priv->req_cfg = req_cfg;
+
+ return *this;
+}
+
+GPIOD_CXX_API const request_config& request_builder::get_request_config() const noexcept
+{
+ return this->_m_priv->req_cfg;
+}
+
+GPIOD_CXX_API request_builder&
+request_builder::set_consumer(const ::std::string& consumer) noexcept
+{
+ this->_m_priv->req_cfg.set_consumer(consumer);
+
+ return *this;
+}
+
+GPIOD_CXX_API request_builder&
+request_builder::set_event_buffer_size(::std::size_t event_buffer_size) noexcept
+{
+ this->_m_priv->req_cfg.set_event_buffer_size(event_buffer_size);
+
+ return *this;
+}
+
+GPIOD_CXX_API request_builder& request_builder::set_line_config(line_config &line_cfg)
+{
+ this->_m_priv->line_cfg = line_cfg;
+
+ return *this;
+}
+
+GPIOD_CXX_API const line_config& request_builder::get_line_config() const noexcept
+{
+ return this->_m_priv->line_cfg;
+}
+
+GPIOD_CXX_API request_builder&
+request_builder::add_line_settings(line::offset offset, const line_settings& settings)
+{
+ return this->add_line_settings(line::offsets({offset}), settings);
+}
+
+GPIOD_CXX_API request_builder&
+request_builder::add_line_settings(const line::offsets& offsets, const line_settings& settings)
+{
+ this->_m_priv->line_cfg.add_line_settings(offsets, settings);
+
+ return *this;
+}
+
+GPIOD_CXX_API line_request request_builder::do_request()
+{
+ line_request_ptr request(::gpiod_chip_request_lines(
+ this->_m_priv->parent._m_priv->chip.get(),
+ this->_m_priv->req_cfg._m_priv->config.get(),
+ this->_m_priv->line_cfg._m_priv->config.get()));
+ if (!request)
+ throw_from_errno("error requesting GPIO lines");
+
+ line_request ret;
+ ret._m_priv.get()->set_request_ptr(request);
+
+ return ret;
+}
+
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const request_builder& builder)
+{
+ out << "gpiod::request_builder(request_config=" << builder._m_priv->req_cfg <<
+ ", line_config=" << builder._m_priv->line_cfg <<
+ ", parent=" << builder._m_priv->parent <<
+ ")";
+
+ return out;
+}
+
+} /* namespace gpiod */
--- /dev/null
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <ostream>
+#include <utility>
+
+#include "internal.hpp"
+
+namespace gpiod {
+
+namespace {
+
+request_config_ptr make_request_config()
+{
+ request_config_ptr config(::gpiod_request_config_new());
+ if (!config)
+ throw_from_errno("Unable to allocate the request config object");
+
+ return config;
+}
+
+} /* namespace */
+
+request_config::impl::impl()
+ : config(make_request_config())
+{
+
+}
+
+GPIOD_CXX_API request_config::request_config()
+ : _m_priv(new impl)
+{
+
+}
+
+GPIOD_CXX_API request_config::request_config(request_config&& other) noexcept
+ : _m_priv(::std::move(other._m_priv))
+{
+
+}
+
+GPIOD_CXX_API request_config::~request_config()
+{
+
+}
+
+request_config& request_config::operator=(const request_config& other)
+{
+ this->_m_priv = other._m_priv;
+
+ return *this;
+}
+
+GPIOD_CXX_API request_config& request_config::operator=(request_config&& other) noexcept
+{
+ this->_m_priv = ::std::move(other._m_priv);
+
+ return *this;
+}
+
+GPIOD_CXX_API request_config&
+request_config::set_consumer(const ::std::string& consumer) noexcept
+{
+ ::gpiod_request_config_set_consumer(this->_m_priv->config.get(), consumer.c_str());
+
+ return *this;
+}
+
+GPIOD_CXX_API ::std::string request_config::consumer() const noexcept
+{
+ const char* consumer = ::gpiod_request_config_get_consumer(this->_m_priv->config.get());
+
+ return consumer ?: "";
+}
+
+GPIOD_CXX_API request_config&
+request_config::set_event_buffer_size(::std::size_t event_buffer_size) noexcept
+{
+ ::gpiod_request_config_set_event_buffer_size(this->_m_priv->config.get(),
+ event_buffer_size);
+
+ return *this;
+}
+
+GPIOD_CXX_API ::std::size_t request_config::event_buffer_size() const noexcept
+{
+ return ::gpiod_request_config_get_event_buffer_size(this->_m_priv->config.get());
+}
+
+GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const request_config& config)
+{
+ ::std::string consumer;
+
+ consumer = config.consumer().empty() ? "N/A" : ::std::string("'") + config.consumer() + "'";
+
+ out << "gpiod::request_config(consumer=" << consumer <<
+ ", event_buffer_size=" << config.event_buffer_size() <<
+ ")";
+
+ return out;
+}
+
+} /* namespace gpiod */
# SPDX-License-Identifier: GPL-2.0-or-later
# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
-AM_CPPFLAGS = -I$(top_srcdir)/bindings/cxx/ -I$(top_srcdir)/include
-AM_CPPFLAGS += -I$(top_srcdir)/tests/mockup/
-AM_CPPFLAGS += -Wall -Wextra -g -std=gnu++11 $(CATCH2_CFLAGS)
+AM_CXXFLAGS = -I$(top_srcdir)/bindings/cxx/ -I$(top_srcdir)/include
+AM_CXXFLAGS += -I$(top_srcdir)/tests/gpiosim/
+AM_CXXFLAGS += -Wall -Wextra -g -std=gnu++17 $(CATCH2_CFLAGS)
+AM_CXXFLAGS += $(PROFILING_CFLAGS)
AM_LDFLAGS = -lgpiodcxx -L$(top_builddir)/bindings/cxx/
-AM_LDFLAGS += -lgpiomockup -L$(top_builddir)/tests/mockup/
+AM_LDFLAGS += -lgpiosim -L$(top_builddir)/tests/gpiosim/
+AM_LDFLAGS += $(PROFILING_LDFLAGS)
AM_LDFLAGS += -pthread
bin_PROGRAMS = gpiod-cxx-test
gpiod_cxx_test_SOURCES = \
+ check-kernel.cpp \
gpiod-cxx-test-main.cpp \
- gpiod-cxx-test.cpp \
- gpio-mockup.cpp \
- gpio-mockup.hpp \
+ gpiosim.cpp \
+ gpiosim.hpp \
+ helpers.cpp \
+ helpers.hpp \
tests-chip.cpp \
- tests-event.cpp \
- tests-iter.cpp \
- tests-line.cpp
+ tests-chip-info.cpp \
+ tests-edge-event.cpp \
+ tests-info-event.cpp \
+ tests-line.cpp \
+ tests-line-config.cpp \
+ tests-line-info.cpp \
+ tests-line-request.cpp \
+ tests-line-settings.cpp \
+ tests-misc.cpp \
+ tests-request-config.cpp
\ No newline at end of file
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2017-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <linux/version.h>
+#include <sys/utsname.h>
+#include <system_error>
+#include <sstream>
+
+namespace {
+
+class kernel_checker
+{
+public:
+ kernel_checker(int major, int minor, int release)
+ {
+ int curr_major, curr_minor, curr_release, curr_ver, req_ver;
+ ::std::string major_str, minor_str, release_str;
+ ::utsname un;
+ int ret;
+
+ ret = ::uname(::std::addressof(un));
+ if (ret)
+ throw ::std::system_error(errno, ::std::system_category(),
+ "unable to read the kernel version");
+
+ ::std::stringstream ver_stream(::std::string(un.release));
+ ::std::getline(ver_stream, major_str, '.');
+ ::std::getline(ver_stream, minor_str, '.');
+ ::std::getline(ver_stream, release_str, '-');
+
+ curr_major = ::std::stoi(major_str, nullptr, 0);
+ curr_minor = ::std::stoi(minor_str, nullptr, 0);
+ curr_release = ::std::stoi(release_str, nullptr, 0);
+
+ curr_ver = KERNEL_VERSION(curr_major, curr_minor, curr_release);
+ req_ver = KERNEL_VERSION(major, minor, release);
+
+ if (curr_ver < req_ver)
+ throw ::std::runtime_error("kernel release must be at least: " +
+ ::std::to_string(major) + "." +
+ ::std::to_string(minor) + "." +
+ ::std::to_string(release));
+ }
+};
+
+kernel_checker require_kernel(5, 19, 0);
+
+} /* namespace */
+++ /dev/null
-// SPDX-License-Identifier: LGPL-3.0-or-later
-// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
-
-#include <system_error>
-
-#include "gpio-mockup.hpp"
-
-namespace gpiod {
-namespace test {
-
-const ::std::bitset<32> mockup::FLAG_NAMED_LINES("1");
-
-mockup& mockup::instance(void)
-{
- static mockup mockup;
-
- return mockup;
-}
-
-mockup::mockup(void) : _m_mockup(::gpio_mockup_new())
-{
- if (!this->_m_mockup)
- throw ::std::system_error(errno, ::std::system_category(),
- "unable to create the gpio-mockup context");
-}
-
-mockup::~mockup(void)
-{
- ::gpio_mockup_unref(this->_m_mockup);
-}
-
-void mockup::probe(const ::std::vector<unsigned int>& chip_sizes,
- const ::std::bitset<32>& flags)
-{
- int ret, probe_flags = 0;
-
- if (flags.to_ulong() & FLAG_NAMED_LINES.to_ulong())
- probe_flags |= GPIO_MOCKUP_FLAG_NAMED_LINES;
-
- ret = ::gpio_mockup_probe(this->_m_mockup, chip_sizes.size(),
- chip_sizes.data(), probe_flags);
- if (ret)
- throw ::std::system_error(errno, ::std::system_category(),
- "unable to probe gpio-mockup module");
-}
-
-void mockup::remove(void)
-{
- int ret = ::gpio_mockup_remove(this->_m_mockup);
- if (ret)
- throw ::std::system_error(errno, ::std::system_category(),
- "unable to remove gpio-mockup module");
-}
-
-::std::string mockup::chip_name(unsigned int idx) const
-{
- const char *name = ::gpio_mockup_chip_name(this->_m_mockup, idx);
- if (!name)
- throw ::std::system_error(errno, ::std::system_category(),
- "unable to retrieve the chip name");
-
- return ::std::string(name);
-}
-
-::std::string mockup::chip_path(unsigned int idx) const
-{
- const char *path = ::gpio_mockup_chip_path(this->_m_mockup, idx);
- if (!path)
- throw ::std::system_error(errno, ::std::system_category(),
- "unable to retrieve the chip path");
-
- return ::std::string(path);
-}
-
-unsigned int mockup::chip_num(unsigned int idx) const
-{
- int num = ::gpio_mockup_chip_num(this->_m_mockup, idx);
- if (num < 0)
- throw ::std::system_error(errno, ::std::system_category(),
- "unable to retrieve the chip number");
-
- return num;
-}
-
-int mockup::chip_get_value(unsigned int chip_idx, unsigned int line_offset)
-{
- int val = ::gpio_mockup_get_value(this->_m_mockup, chip_idx, line_offset);
- if (val < 0)
- throw ::std::system_error(errno, ::std::system_category(),
- "error reading the line value");
-
- return val;
-}
-
-void mockup::chip_set_pull(unsigned int chip_idx, unsigned int line_offset, int pull)
-{
- int ret = ::gpio_mockup_set_pull(this->_m_mockup, chip_idx, line_offset, pull);
- if (ret)
- throw ::std::system_error(errno, ::std::system_category(),
- "error setting line pull");
-}
-
-mockup::probe_guard::probe_guard(const ::std::vector<unsigned int>& chip_sizes,
- const ::std::bitset<32>& flags)
-{
- mockup::instance().probe(chip_sizes, flags);
-}
-
-mockup::probe_guard::~probe_guard(void)
-{
- mockup::instance().remove();
-}
-
-mockup::event_thread::event_thread(unsigned int chip_index,
- unsigned int line_offset, unsigned int period_ms)
- : _m_chip_index(chip_index),
- _m_line_offset(line_offset),
- _m_period_ms(period_ms),
- _m_stop(false),
- _m_mutex(),
- _m_cond(),
- _m_thread(&event_thread::event_worker, this)
-{
-
-}
-
-mockup::event_thread::~event_thread(void)
-{
- ::std::unique_lock<::std::mutex> lock(this->_m_mutex);
- this->_m_stop = true;
- this->_m_cond.notify_all();
- lock.unlock();
- this->_m_thread.join();
-}
-
-void mockup::event_thread::event_worker(void)
-{
- for (unsigned int i = 0;; i++) {
- ::std::unique_lock<::std::mutex> lock(this->_m_mutex);
-
- if (this->_m_stop)
- break;
-
- ::std::cv_status status = this->_m_cond.wait_for(lock,
- std::chrono::milliseconds(this->_m_period_ms));
- if (status == ::std::cv_status::timeout)
- mockup::instance().chip_set_pull(this->_m_chip_index,
- this->_m_line_offset, i % 2);
- }
-}
-
-} /* namespace test */
-} /* namespace gpiod */
+++ /dev/null
-/* SPDX-License-Identifier: LGPL-3.0-or-later */
-/* SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com> */
-
-#ifndef __GPIOD_CXX_TEST_HPP__
-#define __GPIOD_CXX_TEST_HPP__
-
-#include <bitset>
-#include <condition_variable>
-#include <gpio-mockup.h>
-#include <mutex>
-#include <string>
-#include <vector>
-#include <thread>
-
-namespace gpiod {
-namespace test {
-
-class mockup
-{
-public:
-
- static mockup& instance(void);
-
- mockup(const mockup& other) = delete;
- mockup(mockup&& other) = delete;
- mockup& operator=(const mockup& other) = delete;
- mockup& operator=(mockup&& other) = delete;
-
- void probe(const ::std::vector<unsigned int>& chip_sizes,
- const ::std::bitset<32>& flags = 0);
- void remove(void);
-
- std::string chip_name(unsigned int idx) const;
- std::string chip_path(unsigned int idx) const;
- unsigned int chip_num(unsigned int idx) const;
-
- int chip_get_value(unsigned int chip_idx, unsigned int line_offset);
- void chip_set_pull(unsigned int chip_idx, unsigned int line_offset, int pull);
-
- static const ::std::bitset<32> FLAG_NAMED_LINES;
-
- class probe_guard
- {
- public:
-
- probe_guard(const ::std::vector<unsigned int>& chip_sizes,
- const ::std::bitset<32>& flags = 0);
- ~probe_guard(void);
-
- probe_guard(const probe_guard& other) = delete;
- probe_guard(probe_guard&& other) = delete;
- probe_guard& operator=(const probe_guard& other) = delete;
- probe_guard& operator=(probe_guard&& other) = delete;
- };
-
- class event_thread
- {
- public:
-
- event_thread(unsigned int chip_index, unsigned int line_offset, unsigned int period_ms);
- ~event_thread(void);
-
- event_thread(const event_thread& other) = delete;
- event_thread(event_thread&& other) = delete;
- event_thread& operator=(const event_thread& other) = delete;
- event_thread& operator=(event_thread&& other) = delete;
-
- private:
-
- void event_worker(void);
-
- unsigned int _m_chip_index;
- unsigned int _m_line_offset;
- unsigned int _m_period_ms;
-
- bool _m_stop;
-
- ::std::mutex _m_mutex;
- ::std::condition_variable _m_cond;
- ::std::thread _m_thread;
- };
-
-private:
-
- mockup(void);
- ~mockup(void);
-
- ::gpio_mockup *_m_mockup;
-};
-
-} /* namespace test */
-} /* namespace gpiod */
-
-#endif /* __GPIOD_CXX_TEST_HPP__ */
+++ /dev/null
-// SPDX-License-Identifier: GPL-2.0-or-later
-// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
-
-#include <linux/version.h>
-#include <sys/utsname.h>
-#include <system_error>
-#include <sstream>
-
-namespace {
-
-class kernel_checker
-{
-public:
-
- kernel_checker(unsigned int major, unsigned int minor, unsigned int release)
- {
- unsigned long curr_major, curr_minor, curr_release, curr_ver, req_ver;
- ::std::string major_str, minor_str, release_str;
- ::utsname un;
- int ret;
-
- ret = ::uname(::std::addressof(un));
- if (ret)
- throw ::std::system_error(errno, ::std::system_category(),
- "unable to read the kernel version");
-
- ::std::stringstream ver_stream(::std::string(un.release));
- ::std::getline(ver_stream, major_str, '.');
- ::std::getline(ver_stream, minor_str, '.');
- ::std::getline(ver_stream, release_str, '.');
-
- curr_major = ::std::stoul(major_str, nullptr, 0);
- curr_minor = ::std::stoul(minor_str, nullptr, 0);
- curr_release = ::std::stoul(release_str, nullptr, 0);
-
- curr_ver = KERNEL_VERSION(curr_major, curr_minor, curr_release);
- req_ver = KERNEL_VERSION(major, minor, release);
-
- if (curr_ver < req_ver)
- throw ::std::system_error(EOPNOTSUPP, ::std::system_category(),
- "kernel release must be at least: " +
- ::std::to_string(major) + "." +
- ::std::to_string(minor) + "." +
- ::std::to_string(release));
- }
-
- kernel_checker(const kernel_checker& other) = delete;
- kernel_checker(kernel_checker&& other) = delete;
- kernel_checker& operator=(const kernel_checker& other) = delete;
- kernel_checker& operator=(kernel_checker&& other) = delete;
-};
-
-kernel_checker require_kernel(5, 10, 0);
-
-} /* namespace */
--- /dev/null
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/* SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+#include <map>
+#include <system_error>
+#include <utility>
+
+#include "gpiosim.h"
+#include "gpiosim.hpp"
+
+namespace gpiosim {
+
+namespace {
+
+const ::std::map<chip::pull, int> pull_mapping = {
+ { chip::pull::PULL_UP, GPIOSIM_PULL_UP },
+ { chip::pull::PULL_DOWN, GPIOSIM_PULL_DOWN }
+};
+
+const ::std::map<chip_builder::hog_direction, int> hog_dir_mapping = {
+ { chip_builder::hog_direction::INPUT, GPIOSIM_HOG_DIR_INPUT },
+ { chip_builder::hog_direction::OUTPUT_HIGH, GPIOSIM_HOG_DIR_OUTPUT_HIGH },
+ { chip_builder::hog_direction::OUTPUT_LOW, GPIOSIM_HOG_DIR_OUTPUT_LOW }
+};
+
+const ::std::map<int, chip::value> value_mapping = {
+ { GPIOSIM_VALUE_INACTIVE, chip::value::INACTIVE },
+ { GPIOSIM_VALUE_ACTIVE, chip::value::ACTIVE }
+};
+
+template<class gpiosim_type, void free_func(gpiosim_type*)> struct deleter
+{
+ void operator()(gpiosim_type* ptr)
+ {
+ free_func(ptr);
+ }
+};
+
+using ctx_deleter = deleter<::gpiosim_ctx, ::gpiosim_ctx_unref>;
+using dev_deleter = deleter<::gpiosim_dev, ::gpiosim_dev_unref>;
+using bank_deleter = deleter<::gpiosim_bank, ::gpiosim_bank_unref>;
+
+using ctx_ptr = ::std::unique_ptr<::gpiosim_ctx, ctx_deleter>;
+using dev_ptr = ::std::unique_ptr<::gpiosim_dev, dev_deleter>;
+using bank_ptr = ::std::unique_ptr<::gpiosim_bank, bank_deleter>;
+
+ctx_ptr sim_ctx;
+
+class sim_ctx_initializer
+{
+public:
+ sim_ctx_initializer()
+ {
+ sim_ctx.reset(gpiosim_ctx_new());
+ if (!sim_ctx)
+ throw ::std::system_error(errno, ::std::system_category(),
+ "unable to create the GPIO simulator context");
+ }
+};
+
+dev_ptr make_sim_dev()
+{
+ static sim_ctx_initializer ctx_initializer;
+
+ dev_ptr dev(::gpiosim_dev_new(sim_ctx.get()));
+ if (!dev)
+ throw ::std::system_error(errno, ::std::system_category(),
+ "failed to create a new GPIO simulator device");
+
+ return dev;
+}
+
+bank_ptr make_sim_bank(const dev_ptr& dev)
+{
+ bank_ptr bank(::gpiosim_bank_new(dev.get()));
+ if (!bank)
+ throw ::std::system_error(errno, ::std::system_category(),
+ "failed to create a new GPIO simulator bank");
+
+ return bank;
+}
+
+} /* namespace */
+
+struct chip::impl
+{
+ impl()
+ : dev(make_sim_dev()),
+ bank(make_sim_bank(this->dev))
+ {
+
+ }
+
+ impl(const impl& other) = delete;
+ impl(impl&& other) = delete;
+ ~impl() = default;
+ impl& operator=(const impl& other) = delete;
+ impl& operator=(impl&& other) = delete;
+
+ dev_ptr dev;
+ bank_ptr bank;
+};
+
+chip::chip()
+ : _m_priv(new impl)
+{
+
+}
+
+chip::chip(chip&& other)
+ : _m_priv(::std::move(other._m_priv))
+{
+
+}
+
+chip::~chip()
+{
+
+}
+
+chip& chip::operator=(chip&& other)
+{
+ this->_m_priv = ::std::move(other._m_priv);
+
+ return *this;
+}
+
+::std::filesystem::path chip::dev_path() const
+{
+ return ::gpiosim_bank_get_dev_path(this->_m_priv->bank.get());
+}
+
+::std::string chip::name() const
+{
+ return ::gpiosim_bank_get_chip_name(this->_m_priv->bank.get());
+}
+
+chip::value chip::get_value(unsigned int offset)
+{
+ int val = ::gpiosim_bank_get_value(this->_m_priv->bank.get(), offset);
+ if (val < 0)
+ throw ::std::system_error(errno, ::std::system_category(),
+ "failed to read the simulated GPIO line value");
+
+ return value_mapping.at(val);
+}
+
+void chip::set_pull(unsigned int offset, pull pull)
+{
+ int ret = ::gpiosim_bank_set_pull(this->_m_priv->bank.get(),
+ offset, pull_mapping.at(pull));
+ if (ret)
+ throw ::std::system_error(errno, ::std::system_category(),
+ "failed to set the pull of simulated GPIO line");
+}
+
+struct chip_builder::impl
+{
+ impl()
+ : num_lines(0),
+ label(),
+ line_names(),
+ hogs()
+ {
+
+ }
+
+ ::std::size_t num_lines;
+ ::std::string label;
+ ::std::map<unsigned int, ::std::string> line_names;
+ ::std::map<unsigned int, ::std::pair<::std::string, hog_direction>> hogs;
+};
+
+chip_builder::chip_builder()
+ : _m_priv(new impl)
+{
+
+}
+
+chip_builder::chip_builder(chip_builder&& other)
+ : _m_priv(::std::move(other._m_priv))
+{
+
+}
+
+chip_builder::~chip_builder()
+{
+
+}
+
+chip_builder& chip_builder::operator=(chip_builder&& other)
+{
+ this->_m_priv = ::std::move(other._m_priv);
+
+ return *this;
+}
+
+chip_builder& chip_builder::set_num_lines(::std::size_t num_lines)
+{
+ this->_m_priv->num_lines = num_lines;
+
+ return *this;
+}
+
+chip_builder& chip_builder::set_label(const ::std::string& label)
+{
+ this->_m_priv->label = label;
+
+ return *this;
+}
+
+chip_builder& chip_builder::set_line_name(unsigned int offset, const ::std::string& name)
+{
+ this->_m_priv->line_names[offset] = name;
+
+ return *this;
+}
+
+chip_builder& chip_builder::set_hog(unsigned int offset, const ::std::string& name, hog_direction direction)
+{
+ this->_m_priv->hogs[offset] = { name, direction };
+
+ return *this;
+}
+
+chip chip_builder::build()
+{
+ chip sim;
+ int ret;
+
+ if (this->_m_priv->num_lines) {
+ ret = ::gpiosim_bank_set_num_lines(sim._m_priv->bank.get(),
+ this->_m_priv->num_lines);
+ if (ret)
+ throw ::std::system_error(errno, ::std::system_category(),
+ "failed to set number of lines");
+ }
+
+ if (!this->_m_priv->label.empty()) {
+ ret = ::gpiosim_bank_set_label(sim._m_priv->bank.get(),
+ this->_m_priv->label.c_str());
+ if (ret)
+ throw ::std::system_error(errno, ::std::system_category(),
+ "failed to set the chip label");
+ }
+
+ for (const auto& name: this->_m_priv->line_names) {
+ ret = ::gpiosim_bank_set_line_name(sim._m_priv->bank.get(),
+ name.first, name.second.c_str());
+ if (ret)
+ throw ::std::system_error(errno, ::std::system_category(),
+ "failed to set the line name");
+ }
+
+ for (const auto& hog: this->_m_priv->hogs) {
+ ret = ::gpiosim_bank_hog_line(sim._m_priv->bank.get(), hog.first,
+ hog.second.first.c_str(),
+ hog_dir_mapping.at(hog.second.second));
+ if (ret)
+ throw ::std::system_error(errno, ::std::system_category(),
+ "failed to hog the line");
+ }
+
+ ret = ::gpiosim_dev_enable(sim._m_priv->dev.get());
+ if (ret)
+ throw ::std::system_error(errno, ::std::system_category(),
+ "failed to enable the simulated GPIO device");
+
+ return sim;
+}
+
+chip_builder make_sim()
+{
+ return chip_builder();
+}
+
+} /* namespace gpiosim */
--- /dev/null
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/* SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+#ifndef __GPIOD_CXX_GPIOSIM_HPP__
+#define __GPIOD_CXX_GPIOSIM_HPP__
+
+#include <filesystem>
+#include <memory>
+
+namespace gpiosim {
+
+class chip_builder;
+
+class chip
+{
+public:
+ enum class pull {
+ PULL_UP = 1,
+ PULL_DOWN
+ };
+
+ enum class value {
+ INACTIVE = 0,
+ ACTIVE = 1
+ };
+
+ chip(const chip& other) = delete;
+ chip(chip&& other);
+ ~chip();
+
+ chip& operator=(const chip& other) = delete;
+ chip& operator=(chip&& other);
+
+ ::std::filesystem::path dev_path() const;
+ ::std::string name() const;
+
+ value get_value(unsigned int offset);
+ void set_pull(unsigned int offset, pull pull);
+
+private:
+
+ chip();
+
+ struct impl;
+
+ ::std::unique_ptr<impl> _m_priv;
+
+ friend chip_builder;
+};
+
+class chip_builder
+{
+public:
+ enum class hog_direction {
+ INPUT = 1,
+ OUTPUT_HIGH,
+ OUTPUT_LOW
+ };
+
+ chip_builder();
+ chip_builder(const chip_builder& other) = delete;
+ chip_builder(chip_builder&& other);
+ ~chip_builder();
+
+ chip_builder& operator=(const chip_builder& other) = delete;
+ chip_builder& operator=(chip_builder&& other);
+
+ chip_builder& set_num_lines(::std::size_t num_lines);
+ chip_builder& set_label(const ::std::string& label);
+ chip_builder& set_line_name(unsigned int offset, const ::std::string& name);
+ chip_builder& set_hog(unsigned int offset, const ::std::string& name, hog_direction direction);
+
+ chip build();
+
+private:
+
+ struct impl;
+
+ ::std::unique_ptr<impl> _m_priv;
+};
+
+chip_builder make_sim();
+
+} /* namespace gpiosim */
+
+#endif /* __GPIOD_CXX_GPIOSIM_HPP__ */
--- /dev/null
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include "helpers.hpp"
+
+system_error_matcher::system_error_matcher(int expected_errno)
+ : _m_cond(::std::system_category().default_error_condition(expected_errno))
+{
+
+}
+
+::std::string system_error_matcher::describe() const
+{
+ return "matches: errno " + ::std::to_string(this->_m_cond.value());
+}
+
+bool system_error_matcher::match(const ::std::system_error& error) const
+{
+ return error.code().value() == this->_m_cond.value();
+}
+
+regex_matcher::regex_matcher(const ::std::string& pattern)
+ : _m_pattern(pattern),
+ _m_repr("matches: regex \"" + pattern + "\"")
+{
+
+}
+
+::std::string regex_matcher::describe() const
+{
+ return this->_m_repr;
+}
+
+bool regex_matcher::match(const ::std::string& str) const
+{
+ return ::std::regex_match(str, this->_m_pattern);
+}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+#ifndef __GPIOD_CXX_TEST_HELPERS_HPP__
+#define __GPIOD_CXX_TEST_HELPERS_HPP__
+
+#include <catch2/catch.hpp>
+#include <regex>
+#include <string>
+#include <sstream>
+#include <system_error>
+
+class system_error_matcher : public Catch::MatcherBase<::std::system_error>
+{
+public:
+ explicit system_error_matcher(int expected_errno);
+ ::std::string describe() const override;
+ bool match(const ::std::system_error& error) const override;
+
+private:
+ ::std::error_condition _m_cond;
+};
+
+class regex_matcher : public Catch::MatcherBase<::std::string>
+{
+public:
+ explicit regex_matcher(const ::std::string& pattern);
+ ::std::string describe() const override;
+ bool match(const ::std::string& str) const override;
+
+private:
+ ::std::regex _m_pattern;
+ ::std::string _m_repr;
+};
+
+template<class T> class stringify_matcher : public Catch::MatcherBase<T>
+{
+public:
+ explicit stringify_matcher(const ::std::string& expected) : _m_expected(expected)
+ {
+
+ }
+
+ ::std::string describe() const override
+ {
+ return "equals " + this->_m_expected;
+ }
+
+ bool match(const T& obj) const override
+ {
+ ::std::stringstream buf;
+
+ buf << obj;
+
+ return buf.str() == this->_m_expected;
+ }
+
+private:
+ ::std::string _m_expected;
+};
+
+#endif /* __GPIOD_CXX_TEST_HELPERS_HPP__ */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <catch2/catch.hpp>
+#include <gpiod.hpp>
+#include <sstream>
+
+#include "gpiosim.hpp"
+#include "helpers.hpp"
+
+using ::gpiosim::make_sim;
+
+namespace {
+
+TEST_CASE("chip_info properties can be read", "[chip-info][chip]")
+{
+ auto sim = make_sim()
+ .set_num_lines(8)
+ .set_label("foobar")
+ .build();
+
+ ::gpiod::chip chip(sim.dev_path());
+ auto info = chip.get_info();
+
+ SECTION("get chip name")
+ {
+ REQUIRE_THAT(info.name(), Catch::Equals(sim.name()));
+ }
+
+ SECTION("get chip label")
+ {
+ REQUIRE_THAT(info.label(), Catch::Equals("foobar"));
+ }
+
+ SECTION("get num_lines")
+ {
+ REQUIRE(info.num_lines() == 8);
+ }
+}
+
+TEST_CASE("chip_info can be copied and moved", "[chip-info]")
+{
+ auto sim = make_sim()
+ .set_num_lines(4)
+ .set_label("foobar")
+ .build();
+
+ ::gpiod::chip chip(sim.dev_path());
+ auto info = chip.get_info();
+
+ SECTION("copy constructor works")
+ {
+ auto copy(info);
+
+ REQUIRE_THAT(copy.name(), Catch::Equals(sim.name()));
+ REQUIRE_THAT(copy.label(), Catch::Equals("foobar"));
+ REQUIRE(copy.num_lines() == 4);
+
+ REQUIRE_THAT(info.name(), Catch::Equals(sim.name()));
+ REQUIRE_THAT(info.label(), Catch::Equals("foobar"));
+ REQUIRE(info.num_lines() == 4);
+ }
+
+ SECTION("assignment operator works")
+ {
+ auto copy = chip.get_info();
+
+ copy = info;
+
+ REQUIRE_THAT(copy.name(), Catch::Equals(sim.name()));
+ REQUIRE_THAT(copy.label(), Catch::Equals("foobar"));
+ REQUIRE(copy.num_lines() == 4);
+
+ REQUIRE_THAT(info.name(), Catch::Equals(sim.name()));
+ REQUIRE_THAT(info.label(), Catch::Equals("foobar"));
+ REQUIRE(info.num_lines() == 4);
+ }
+
+ SECTION("move constructor works")
+ {
+ auto moved(std::move(info));
+
+ REQUIRE_THAT(moved.name(), Catch::Equals(sim.name()));
+ REQUIRE_THAT(moved.label(), Catch::Equals("foobar"));
+ REQUIRE(moved.num_lines() == 4);
+ }
+
+ SECTION("move assignment operator works")
+ {
+ auto moved = chip.get_info();
+
+ moved = ::std::move(info);
+
+ REQUIRE_THAT(moved.name(), Catch::Equals(sim.name()));
+ REQUIRE_THAT(moved.label(), Catch::Equals("foobar"));
+ REQUIRE(moved.num_lines() == 4);
+ }
+}
+
+TEST_CASE("stream insertion operator works for chip_info", "[chip-info]")
+{
+ auto sim = make_sim()
+ .set_num_lines(4)
+ .set_label("foobar")
+ .build();
+
+ ::gpiod::chip chip(sim.dev_path());
+ auto info = chip.get_info();
+ ::std::stringstream expected;
+
+ expected << "gpiod::chip_info(name=\"" << sim.name() <<
+ "\", label=\"foobar\", num_lines=4)";
+
+ REQUIRE_THAT(info, stringify_matcher<::gpiod::chip_info>(expected.str()));
+}
+
+} /* namespace */
// SPDX-License-Identifier: GPL-2.0-or-later
-// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
#include <catch2/catch.hpp>
#include <gpiod.hpp>
+#include <sstream>
+#include <system_error>
+#include <utility>
-#include "gpio-mockup.hpp"
+#include "gpiosim.hpp"
+#include "helpers.hpp"
-using ::gpiod::test::mockup;
+using ::gpiosim::make_sim;
-TEST_CASE("GPIO chip device can be verified with is_gpiochip_device()", "[chip]")
-{
- mockup::probe_guard mockup_chips({ 8 });
+namespace {
- SECTION("good chip")
+TEST_CASE("chip constructor works", "[chip]")
+{
+ SECTION("open an existing GPIO chip")
{
- REQUIRE(::gpiod::is_gpiochip_device(mockup::instance().chip_path(0)));
+ auto sim = make_sim().build();
+
+ REQUIRE_NOTHROW(::gpiod::chip(sim.dev_path()));
}
- SECTION("not a chip")
+ SECTION("opening a nonexistent file fails with ENOENT")
{
- REQUIRE_FALSE(::gpiod::is_gpiochip_device("/dev/null"));
+ REQUIRE_THROWS_MATCHES(::gpiod::chip("/dev/nonexistent"),
+ ::std::system_error, system_error_matcher(ENOENT));
}
- SECTION("nonexistent file")
+ SECTION("opening a file that is not a device fails with ENOTTY")
{
- REQUIRE_FALSE(::gpiod::is_gpiochip_device("/dev/nonexistent_device"));
+ REQUIRE_THROWS_MATCHES(::gpiod::chip("/tmp"),
+ ::std::system_error, system_error_matcher(ENOTTY));
}
-}
-TEST_CASE("GPIO chip device can be opened", "[chip]")
-{
- mockup::probe_guard mockup_chips({ 8, 8, 8 });
-
- SECTION("open from constructor")
+ SECTION("opening a non-GPIO character device fails with ENODEV")
{
- REQUIRE_NOTHROW(::gpiod::chip(mockup::instance().chip_path(1)));
+ REQUIRE_THROWS_MATCHES(::gpiod::chip("/dev/null"),
+ ::std::system_error, system_error_matcher(ENODEV));
}
- SECTION("open from open() method")
+ SECTION("move constructor")
{
- ::gpiod::chip chip;
-
- REQUIRE_FALSE(!!chip);
-
- REQUIRE_NOTHROW(chip.open(mockup::instance().chip_path(1)));
+ auto sim = make_sim()
+ .set_label("foobar")
+ .build();
+
+ ::gpiod::chip first(sim.dev_path());
+ REQUIRE_THAT(first.get_info().label(), Catch::Equals("foobar"));
+ ::gpiod::chip second(::std::move(first));
+ REQUIRE_THAT(second.get_info().label(), Catch::Equals("foobar"));
}
}
-TEST_CASE("Uninitialized GPIO chip behaves correctly", "[chip]")
+TEST_CASE("chip operators work", "[chip]")
{
- ::gpiod::chip chip;
+ auto sim = make_sim()
+ .set_label("foobar")
+ .build();
+
+ ::gpiod::chip chip(sim.dev_path());
- SECTION("uninitialized chip is 'false'")
+ SECTION("assignment operator")
{
- REQUIRE_FALSE(!!chip);
+ auto moved_sim = make_sim()
+ .set_label("moved")
+ .build();
+
+ ::gpiod::chip moved_chip(moved_sim.dev_path());
+
+ REQUIRE_THAT(chip.get_info().label(), Catch::Equals("foobar"));
+ chip = ::std::move(moved_chip);
+ REQUIRE_THAT(chip.get_info().label(), Catch::Equals("moved"));
}
- SECTION("using uninitialized chip throws logic_error")
+ SECTION("boolean operator")
{
- REQUIRE_THROWS_AS(chip.name(), ::std::logic_error);
+ REQUIRE(chip);
+ chip.close();
+ REQUIRE_FALSE(chip);
}
}
-TEST_CASE("Trying to open a nonexistent chip throws system_error", "[chip]")
+TEST_CASE("chip properties can be read", "[chip]")
{
- REQUIRE_THROWS_AS(::gpiod::chip("nonexistent-chip"), ::std::system_error);
-}
+ auto sim = make_sim()
+ .set_num_lines(8)
+ .set_label("foobar")
+ .build();
-TEST_CASE("Chip object can be reset", "[chip]")
-{
- mockup::probe_guard mockup_chips({ 8 });
+ ::gpiod::chip chip(sim.dev_path());
- ::gpiod::chip chip(mockup::instance().chip_path(0));
- REQUIRE(chip);
- chip.reset();
- REQUIRE_FALSE(!!chip);
-}
-
-TEST_CASE("Chip info can be correctly retrieved", "[chip]")
-{
- mockup::probe_guard mockup_chips({ 8, 16, 8 });
+ SECTION("get device path")
+ {
+ REQUIRE_THAT(chip.path(), Catch::Equals(sim.dev_path()));
+ }
- ::gpiod::chip chip(mockup::instance().chip_path(1));
- REQUIRE(chip.name() == mockup::instance().chip_name(1));
- REQUIRE(chip.label() == "gpio-mockup-B");
- REQUIRE(chip.num_lines() == 16);
+ SECTION("get file descriptor")
+ {
+ REQUIRE(chip.fd() >= 0);
+ }
}
-TEST_CASE("Chip object can be copied and compared", "[chip]")
+TEST_CASE("line lookup by name works", "[chip]")
{
- mockup::probe_guard mockup_chips({ 8, 8 });
-
- ::gpiod::chip chip1(mockup::instance().chip_path(0));
- auto chip2 = chip1;
- REQUIRE(chip1 == chip2);
- REQUIRE_FALSE(chip1 != chip2);
- ::gpiod::chip chip3(mockup::instance().chip_path(1));
- REQUIRE(chip1 != chip3);
- REQUIRE_FALSE(chip2 == chip3);
-}
+ auto sim = make_sim()
+ .set_num_lines(8)
+ .set_line_name(0, "foo")
+ .set_line_name(2, "bar")
+ .set_line_name(3, "baz")
+ .set_line_name(5, "xyz")
+ .build();
-TEST_CASE("Lines can be retrieved from chip objects", "[chip]")
-{
- mockup::probe_guard mockup_chips({ 8, 8, 8 }, mockup::FLAG_NAMED_LINES);
- ::gpiod::chip chip(mockup::instance().chip_path(1));
+ ::gpiod::chip chip(sim.dev_path());
- SECTION("get single line by offset")
+ SECTION("lookup successful")
{
- auto line = chip.get_line(3);
- REQUIRE(line.name() == "gpio-mockup-B-3");
+ REQUIRE(chip.get_line_offset_from_name("baz") == 3);
}
- SECTION("find single line by name")
+ SECTION("lookup failed")
{
- auto offset = chip.find_line("gpio-mockup-B-3");
- REQUIRE(offset == 3);
+ REQUIRE(chip.get_line_offset_from_name("nonexistent") < 0);
}
+}
- SECTION("get multiple lines by offsets")
- {
- auto lines = chip.get_lines({ 1, 3, 4, 7});
- REQUIRE(lines.size() == 4);
- REQUIRE(lines.get(0).name() == "gpio-mockup-B-1");
- REQUIRE(lines.get(1).name() == "gpio-mockup-B-3");
- REQUIRE(lines.get(2).name() == "gpio-mockup-B-4");
- REQUIRE(lines.get(3).name() == "gpio-mockup-B-7");
- }
+TEST_CASE("line lookup: behavior for duplicate names", "[chip]")
+{
+ auto sim = make_sim()
+ .set_num_lines(8)
+ .set_line_name(0, "foo")
+ .set_line_name(2, "bar")
+ .set_line_name(3, "baz")
+ .set_line_name(5, "bar")
+ .build();
- SECTION("get multiple lines by offsets in mixed order")
- {
- auto lines = chip.get_lines({ 5, 1, 3, 2});
- REQUIRE(lines.size() == 4);
- REQUIRE(lines.get(0).name() == "gpio-mockup-B-5");
- REQUIRE(lines.get(1).name() == "gpio-mockup-B-1");
- REQUIRE(lines.get(2).name() == "gpio-mockup-B-3");
- REQUIRE(lines.get(3).name() == "gpio-mockup-B-2");
- }
+ ::gpiod::chip chip(sim.dev_path());
+
+ REQUIRE(chip.get_line_offset_from_name("bar") == 2);
}
-TEST_CASE("All lines can be retrieved from a chip at once", "[chip]")
+TEST_CASE("closed chip can no longer be used", "[chip]")
{
- mockup::probe_guard mockup_chips({ 4 });
- ::gpiod::chip chip(mockup::instance().chip_path(0));
-
- auto lines = chip.get_all_lines();
- REQUIRE(lines.size() == 4);
- REQUIRE(lines.get(0).offset() == 0);
- REQUIRE(lines.get(1).offset() == 1);
- REQUIRE(lines.get(2).offset() == 2);
- REQUIRE(lines.get(3).offset() == 3);
+ auto sim = make_sim().build();
+
+ ::gpiod::chip chip(sim.dev_path());
+ chip.close();
+ REQUIRE_THROWS_AS(chip.path(), ::gpiod::chip_closed);
}
-TEST_CASE("Errors occurring when retrieving lines are correctly reported", "[chip]")
+TEST_CASE("stream insertion operator works for chip", "[chip]")
{
- mockup::probe_guard mockup_chips({ 8 }, mockup::FLAG_NAMED_LINES);
- ::gpiod::chip chip(mockup::instance().chip_path(0));
+ auto sim = make_sim()
+ .set_num_lines(4)
+ .set_label("foobar")
+ .build();
- SECTION("invalid offset (single line)")
- {
- REQUIRE_THROWS_AS(chip.get_line(9), ::std::out_of_range);
- }
+ ::gpiod::chip chip(sim.dev_path());
+ ::std::stringstream buf;
- SECTION("invalid offset (multiple lines)")
+ SECTION("open chip")
{
- REQUIRE_THROWS_AS(chip.get_lines({ 1, 19, 4, 7 }), ::std::out_of_range);
+ ::std::stringstream expected;
+
+ expected << "gpiod::chip(path=" << sim.dev_path() <<
+ ", info=gpiod::chip_info(name=\"" << sim.name() <<
+ "\", label=\"foobar\", num_lines=4))";
+
+ buf << chip;
+ REQUIRE_THAT(buf.str(), Catch::Equals(expected.str()));
}
- SECTION("line not found by name")
+ SECTION("closed chip")
{
- REQUIRE(chip.find_line("nonexistent-line") == -1);
+ chip.close();
+ REQUIRE_THAT(chip, stringify_matcher<::gpiod::chip>("gpiod::chip(closed)"));
}
}
+
+} /* namespace */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <catch2/catch.hpp>
+#include <chrono>
+#include <gpiod.hpp>
+#include <sstream>
+#include <thread>
+#include <utility>
+
+#include "gpiosim.hpp"
+#include "helpers.hpp"
+
+using ::gpiosim::make_sim;
+using direction = ::gpiod::line::direction;
+using edge = ::gpiod::line::edge;
+using offsets = ::gpiod::line::offsets;
+using pull = ::gpiosim::chip::pull;
+using event_type = ::gpiod::edge_event::event_type;
+
+namespace {
+
+TEST_CASE("edge_event_buffer capacity settings work", "[edge-event]")
+{
+ SECTION("default capacity")
+ {
+ REQUIRE(::gpiod::edge_event_buffer().capacity() == 64);
+ }
+
+ SECTION("user-defined capacity")
+ {
+ REQUIRE(::gpiod::edge_event_buffer(123).capacity() == 123);
+ }
+
+ SECTION("max capacity")
+ {
+ REQUIRE(::gpiod::edge_event_buffer(16 * 64 * 2).capacity() == 1024);
+ }
+}
+
+TEST_CASE("edge_event wait timeout", "[edge-event]")
+{
+ auto sim = make_sim().build();
+ ::gpiod::chip chip(sim.dev_path());
+
+ auto request = chip.prepare_request()
+ .add_line_settings(
+ 0,
+ ::gpiod::line_settings()
+ .set_edge_detection(edge::BOTH)
+ )
+ .do_request();
+
+ REQUIRE_FALSE(request.wait_edge_event(::std::chrono::milliseconds(100)));
+}
+
+TEST_CASE("output mode and edge detection don't work together", "[edge-event]")
+{
+ auto sim = make_sim().build();
+
+ REQUIRE_THROWS_AS(
+ ::gpiod::chip(sim.dev_path())
+ .prepare_request()
+ .add_line_settings(
+ 0,
+ ::gpiod::line_settings()
+ .set_direction(direction::OUTPUT)
+ .set_edge_detection(edge::BOTH)
+ )
+ .do_request(),
+ ::std::invalid_argument
+ );
+}
+
+void trigger_falling_and_rising_edge(::gpiosim::chip& sim, unsigned int offset)
+{
+ ::std::this_thread::sleep_for(::std::chrono::milliseconds(30));
+ sim.set_pull(offset, pull::PULL_UP);
+ ::std::this_thread::sleep_for(::std::chrono::milliseconds(30));
+ sim.set_pull(offset, pull::PULL_DOWN);
+}
+
+void trigger_rising_edge_events_on_two_offsets(::gpiosim::chip& sim,
+ unsigned int off0, unsigned int off1)
+{
+ ::std::this_thread::sleep_for(::std::chrono::milliseconds(30));
+ sim.set_pull(off0, pull::PULL_UP);
+ ::std::this_thread::sleep_for(::std::chrono::milliseconds(30));
+ sim.set_pull(off1, pull::PULL_UP);
+}
+
+TEST_CASE("waiting for and reading edge events works", "[edge-event]")
+{
+ auto sim = make_sim()
+ .set_num_lines(8)
+ .build();
+
+ ::gpiod::chip chip(sim.dev_path());
+ ::gpiod::edge_event_buffer buffer;
+
+ SECTION("both edge events")
+ {
+ auto request = chip
+ .prepare_request()
+ .add_line_settings(
+ 2,
+ ::gpiod::line_settings()
+ .set_edge_detection(edge::BOTH)
+ )
+ .do_request();
+
+ ::std::uint64_t ts_rising, ts_falling;
+
+ ::std::thread thread(trigger_falling_and_rising_edge, ::std::ref(sim), 2);
+
+ REQUIRE(request.wait_edge_event(::std::chrono::seconds(1)));
+ REQUIRE(request.read_edge_event(buffer, 1) == 1);
+ REQUIRE(buffer.num_events() == 1);
+ auto event = buffer.get_event(0);
+ REQUIRE(event.type() == event_type::RISING_EDGE);
+ REQUIRE(event.line_offset() == 2);
+ ts_rising = event.timestamp_ns();
+
+ REQUIRE(request.wait_edge_event(::std::chrono::seconds(1)));
+ REQUIRE(request.read_edge_event(buffer, 1) == 1);
+ REQUIRE(buffer.num_events() == 1);
+ event = buffer.get_event(0);
+ REQUIRE(event.type() == event_type::FALLING_EDGE);
+ REQUIRE(event.line_offset() == 2);
+ ts_falling = event.timestamp_ns();
+
+ REQUIRE_FALSE(request.wait_edge_event(::std::chrono::milliseconds(100)));
+
+ thread.join();
+
+ REQUIRE(ts_falling > ts_rising);
+ }
+
+ SECTION("rising edge event")
+ {
+ auto request = chip
+ .prepare_request()
+ .add_line_settings(
+ 6,
+ ::gpiod::line_settings()
+ .set_edge_detection(edge::RISING)
+ )
+ .do_request();
+
+ ::std::thread thread(trigger_falling_and_rising_edge, ::std::ref(sim), 6);
+
+ REQUIRE(request.wait_edge_event(::std::chrono::seconds(1)));
+ REQUIRE(request.read_edge_event(buffer, 1) == 1);
+ REQUIRE(buffer.num_events() == 1);
+ auto event = buffer.get_event(0);
+ REQUIRE(event.type() == event_type::RISING_EDGE);
+ REQUIRE(event.line_offset() == 6);
+
+ REQUIRE_FALSE(request.wait_edge_event(::std::chrono::milliseconds(100)));
+
+ thread.join();
+ }
+
+ SECTION("falling edge event")
+ {
+ auto request = chip
+ .prepare_request()
+ .add_line_settings(
+ 7,
+ ::gpiod::line_settings()
+ .set_edge_detection(edge::FALLING)
+ )
+ .do_request();
+
+ ::std::thread thread(trigger_falling_and_rising_edge, ::std::ref(sim), 7);
+
+ REQUIRE(request.wait_edge_event(::std::chrono::seconds(1)));
+ REQUIRE(request.read_edge_event(buffer, 1) == 1);
+ REQUIRE(buffer.num_events() == 1);
+ auto event = buffer.get_event(0);
+ REQUIRE(event.type() == event_type::FALLING_EDGE);
+ REQUIRE(event.line_offset() == 7);
+
+ REQUIRE_FALSE(request.wait_edge_event(::std::chrono::milliseconds(100)));
+
+ thread.join();
+ }
+
+ SECTION("sequence numbers")
+ {
+ auto request = chip
+ .prepare_request()
+ .add_line_settings(
+ { 0, 1 },
+ ::gpiod::line_settings()
+ .set_edge_detection(edge::BOTH)
+ )
+ .do_request();
+
+ ::std::thread thread(trigger_rising_edge_events_on_two_offsets, ::std::ref(sim), 0, 1);
+
+ REQUIRE(request.wait_edge_event(::std::chrono::seconds(1)));
+ REQUIRE(request.read_edge_event(buffer, 1) == 1);
+ REQUIRE(buffer.num_events() == 1);
+ auto event = buffer.get_event(0);
+ REQUIRE(event.type() == event_type::RISING_EDGE);
+ REQUIRE(event.line_offset() == 0);
+ REQUIRE(event.global_seqno() == 1);
+ REQUIRE(event.line_seqno() == 1);
+
+ REQUIRE(request.wait_edge_event(::std::chrono::seconds(1)));
+ REQUIRE(request.read_edge_event(buffer, 1) == 1);
+ REQUIRE(buffer.num_events() == 1);
+ event = buffer.get_event(0);
+ REQUIRE(event.type() == event_type::RISING_EDGE);
+ REQUIRE(event.line_offset() == 1);
+ REQUIRE(event.global_seqno() == 2);
+ REQUIRE(event.line_seqno() == 1);
+
+ thread.join();
+ }
+}
+
+TEST_CASE("reading multiple events", "[edge-event]")
+{
+ auto sim = make_sim()
+ .set_num_lines(8)
+ .build();
+
+ ::gpiod::chip chip(sim.dev_path());
+
+ auto request = chip
+ .prepare_request()
+ .add_line_settings(
+ 1,
+ ::gpiod::line_settings()
+ .set_edge_detection(edge::BOTH)
+ )
+ .do_request();
+
+ unsigned long line_seqno = 1, global_seqno = 1;
+
+ sim.set_pull(1, pull::PULL_UP);
+ ::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
+ sim.set_pull(1, pull::PULL_DOWN);
+ ::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
+ sim.set_pull(1, pull::PULL_UP);
+ ::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
+
+ SECTION("read multiple events")
+ {
+ ::gpiod::edge_event_buffer buffer;
+
+ REQUIRE(request.wait_edge_event(::std::chrono::seconds(1)));
+ REQUIRE(request.read_edge_event(buffer) == 3);
+ REQUIRE(buffer.num_events() == 3);
+
+ for (const auto& event: buffer) {
+ REQUIRE(event.line_offset() == 1);
+ REQUIRE(event.line_seqno() == line_seqno++);
+ REQUIRE(event.global_seqno() == global_seqno++);
+ }
+ }
+
+ SECTION("read over capacity")
+ {
+ ::gpiod::edge_event_buffer buffer(2);
+
+ REQUIRE(request.wait_edge_event(::std::chrono::seconds(1)));
+ REQUIRE(request.read_edge_event(buffer) == 2);
+ REQUIRE(buffer.num_events() == 2);
+ }
+}
+
+TEST_CASE("edge_event_buffer can be moved", "[edge-event]")
+{
+ auto sim = make_sim()
+ .set_num_lines(2)
+ .build();
+
+ ::gpiod::chip chip(sim.dev_path());
+ ::gpiod::edge_event_buffer buffer(13);
+
+ /* Get some events into the buffer. */
+ auto request = chip
+ .prepare_request()
+ .add_line_settings(
+ 1,
+ ::gpiod::line_settings()
+ .set_edge_detection(edge::BOTH)
+ )
+ .do_request();
+
+ sim.set_pull(1, pull::PULL_UP);
+ ::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
+ sim.set_pull(1, pull::PULL_DOWN);
+ ::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
+ sim.set_pull(1, pull::PULL_UP);
+ ::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
+
+ ::std::this_thread::sleep_for(::std::chrono::milliseconds(500));
+
+ REQUIRE(request.wait_edge_event(::std::chrono::seconds(1)));
+ REQUIRE(request.read_edge_event(buffer) == 3);
+
+ SECTION("move constructor works")
+ {
+ auto moved(::std::move(buffer));
+ REQUIRE(moved.capacity() == 13);
+ REQUIRE(moved.num_events() == 3);
+ }
+
+ SECTION("move assignment operator works")
+ {
+ ::gpiod::edge_event_buffer moved;
+
+ moved = ::std::move(buffer);
+ REQUIRE(moved.capacity() == 13);
+ REQUIRE(moved.num_events() == 3);
+ }
+}
+
+TEST_CASE("edge_event can be copied and moved", "[edge-event]")
+{
+ auto sim = make_sim().build();
+ ::gpiod::chip chip(sim.dev_path());
+ ::gpiod::edge_event_buffer buffer;
+
+ auto request = chip
+ .prepare_request()
+ .add_line_settings(
+ 0,
+ ::gpiod::line_settings()
+ .set_edge_detection(edge::BOTH)
+ )
+ .do_request();
+
+ sim.set_pull(0, pull::PULL_UP);
+ ::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
+ REQUIRE(request.wait_edge_event(::std::chrono::seconds(1)));
+ REQUIRE(request.read_edge_event(buffer) == 1);
+ auto event = buffer.get_event(0);
+
+ sim.set_pull(0, pull::PULL_DOWN);
+ ::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
+ REQUIRE(request.wait_edge_event(::std::chrono::seconds(1)));
+ REQUIRE(request.read_edge_event(buffer) == 1);
+ auto copy = buffer.get_event(0);
+
+ SECTION("copy constructor works")
+ {
+ auto copy(event);
+ REQUIRE(copy.line_offset() == 0);
+ REQUIRE(copy.type() == event_type::RISING_EDGE);
+ REQUIRE(event.line_offset() == 0);
+ REQUIRE(event.type() == event_type::RISING_EDGE);
+ }
+
+ SECTION("move constructor works")
+ {
+ auto copy(::std::move(event));
+ REQUIRE(copy.line_offset() == 0);
+ REQUIRE(copy.type() == event_type::RISING_EDGE);
+ }
+
+ SECTION("assignment operator works")
+ {
+ copy = event;
+ REQUIRE(copy.line_offset() == 0);
+ REQUIRE(copy.type() == event_type::RISING_EDGE);
+ REQUIRE(event.line_offset() == 0);
+ REQUIRE(event.type() == event_type::RISING_EDGE);
+ }
+
+ SECTION("move assignment operator works")
+ {
+ copy = ::std::move(event);
+ REQUIRE(copy.line_offset() == 0);
+ REQUIRE(copy.type() == event_type::RISING_EDGE);
+ }
+}
+
+TEST_CASE("stream insertion operators work for edge_event and edge_event_buffer", "[edge-event]")
+{
+ /*
+ * This tests the stream insertion operators for both edge_event and
+ * edge_event_buffer classes.
+ */
+
+ auto sim = make_sim().build();
+ ::gpiod::chip chip(sim.dev_path());
+ ::gpiod::edge_event_buffer buffer;
+ ::std::stringstream sbuf, expected;
+
+ auto request = chip
+ .prepare_request()
+ .add_line_settings(
+ 0,
+ ::gpiod::line_settings()
+ .set_edge_detection(edge::BOTH)
+ )
+ .do_request();
+
+ sim.set_pull(0, pull::PULL_UP);
+ ::std::this_thread::sleep_for(::std::chrono::milliseconds(30));
+ sim.set_pull(0, pull::PULL_DOWN);
+ ::std::this_thread::sleep_for(::std::chrono::milliseconds(30));
+
+ REQUIRE(request.wait_edge_event(::std::chrono::seconds(1)));
+ REQUIRE(request.read_edge_event(buffer) == 2);
+
+ sbuf << buffer;
+
+ expected << "gpiod::edge_event_buffer\\(num_events=2, capacity=64, events=\\[gpiod::edge_event\\" <<
+ "(type='RISING_EDGE', timestamp=[1-9][0-9]+, line_offset=0, global_seqno=1, " <<
+ "line_seqno=1\\), gpiod::edge_event\\(type='FALLING_EDGE', timestamp=[1-9][0-9]+, " <<
+ "line_offset=0, global_seqno=2, line_seqno=2\\)\\]\\)";
+
+ REQUIRE_THAT(sbuf.str(), regex_matcher(expected.str()));
+}
+
+} /* namespace */
+++ /dev/null
-// SPDX-License-Identifier: GPL-2.0-or-later
-// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
-
-#include <catch2/catch.hpp>
-#include <gpiod.hpp>
-#include <poll.h>
-#include <thread>
-
-#include "gpio-mockup.hpp"
-
-using ::gpiod::test::mockup;
-
-namespace {
-
-const ::std::string consumer = "event-test";
-
-} /* namespace */
-
-TEST_CASE("Line events can be detected", "[event][line]")
-{
- mockup::probe_guard mockup_chips({ 8 });
- mockup::event_thread events(0, 4, 200);
- ::gpiod::chip chip(mockup::instance().chip_path(0));
- auto line = chip.get_line(4);
- ::gpiod::line_request config;
-
- config.consumer = consumer.c_str();
-
- SECTION("rising edge")
- {
- config.request_type = ::gpiod::line_request::EVENT_RISING_EDGE;
- line.request(config);
-
- auto got_event = line.event_wait(::std::chrono::seconds(1));
- REQUIRE(got_event);
-
- auto event = line.event_read();
- REQUIRE(event.source == line);
- REQUIRE(event.event_type == ::gpiod::line_event::RISING_EDGE);
- }
-
- SECTION("falling edge")
- {
- config.request_type = ::gpiod::line_request::EVENT_FALLING_EDGE;
-
- line.request(config);
-
- auto got_event = line.event_wait(::std::chrono::seconds(1));
- REQUIRE(got_event);
-
- auto event = line.event_read();
- REQUIRE(event.source == line);
- REQUIRE(event.event_type == ::gpiod::line_event::FALLING_EDGE);
- }
-
- SECTION("both edges")
- {
- config.request_type = ::gpiod::line_request::EVENT_BOTH_EDGES;
-
- line.request(config);
-
- auto got_event = line.event_wait(::std::chrono::seconds(1));
- REQUIRE(got_event);
-
- auto event = line.event_read();
- REQUIRE(event.source == line);
- REQUIRE(event.event_type == ::gpiod::line_event::RISING_EDGE);
-
- got_event = line.event_wait(::std::chrono::seconds(1));
- REQUIRE(got_event);
-
- event = line.event_read();
- REQUIRE(event.source == line);
- REQUIRE(event.event_type == ::gpiod::line_event::FALLING_EDGE);
- }
-
- SECTION("active-low")
- {
- config.request_type = ::gpiod::line_request::EVENT_BOTH_EDGES;
- config.flags = ::gpiod::line_request::FLAG_ACTIVE_LOW;
-
- line.request(config);
-
- auto got_event = line.event_wait(::std::chrono::seconds(1));
- REQUIRE(got_event);
-
- auto event = line.event_read();
- REQUIRE(event.source == line);
- REQUIRE(event.event_type == ::gpiod::line_event::FALLING_EDGE);
-
- got_event = line.event_wait(::std::chrono::seconds(1));
- REQUIRE(got_event);
-
- event = line.event_read();
- REQUIRE(event.source == line);
- REQUIRE(event.event_type == ::gpiod::line_event::RISING_EDGE);
- }
-}
-
-TEST_CASE("Watching line_bulk objects for events works", "[event][bulk]")
-{
- mockup::probe_guard mockup_chips({ 8 });
- mockup::event_thread events(0, 2, 200);
- ::gpiod::chip chip(mockup::instance().chip_path(0));
- auto lines = chip.get_lines({ 0, 1, 2, 3 });
- ::gpiod::line_request config;
-
- config.consumer = consumer.c_str();
- config.request_type = ::gpiod::line_request::EVENT_BOTH_EDGES;
- lines.request(config);
-
- auto event_lines = lines.event_wait(::std::chrono::seconds(1));
- REQUIRE(event_lines);
- REQUIRE(event_lines.size() == 1);
-
- auto event = event_lines.get(0).event_read();
- REQUIRE(event.source == event_lines.get(0));
- REQUIRE(event.event_type == ::gpiod::line_event::RISING_EDGE);
-
- event_lines = lines.event_wait(::std::chrono::seconds(1));
- REQUIRE(event_lines);
- REQUIRE(event_lines.size() == 1);
-
- event = event_lines.get(0).event_read();
- REQUIRE(event.source == event_lines.get(0));
- REQUIRE(event.event_type == ::gpiod::line_event::FALLING_EDGE);
-}
-
-TEST_CASE("It's possible to retrieve the event file descriptor", "[event][line]")
-{
- mockup::probe_guard mockup_chips({ 8 });
- ::gpiod::chip chip(mockup::instance().chip_path(0));
- auto line = chip.get_line(4);
- ::gpiod::line_request config;
-
- config.consumer = consumer.c_str();
-
- SECTION("get the fd")
- {
- config.request_type = ::gpiod::line_request::EVENT_BOTH_EDGES;
-
- line.request(config);
- REQUIRE(line.event_get_fd() >= 0);
- }
-
- SECTION("error if not requested")
- {
- REQUIRE_THROWS_AS(line.event_get_fd(), ::std::system_error);
- }
-
- SECTION("error if requested for values")
- {
- config.request_type = ::gpiod::line_request::DIRECTION_INPUT;
-
- line.request(config);
- REQUIRE_THROWS_AS(line.event_get_fd(), ::std::system_error);
- }
-}
-
-TEST_CASE("Event file descriptors can be used for polling", "[event]")
-{
- mockup::probe_guard mockup_chips({ 8 });
- mockup::event_thread events(0, 3, 200);
- ::gpiod::chip chip(mockup::instance().chip_path(0));
- auto lines = chip.get_lines({ 0, 1, 2, 3, 4, 5 });
-
- ::gpiod::line_request config;
- config.consumer = consumer.c_str();
- config.request_type = ::gpiod::line_request::EVENT_BOTH_EDGES;
-
- lines.request(config);
-
- ::std::vector<::pollfd> fds(3);
- fds[0].fd = lines[1].event_get_fd();
- fds[1].fd = lines[3].event_get_fd();
- fds[2].fd = lines[5].event_get_fd();
-
- fds[0].events = fds[1].events = fds[2].events = POLLIN | POLLPRI;
-
- int ret = ::poll(fds.data(), 3, 1000);
- REQUIRE(ret == 1);
-
- for (int i = 0; i < 3; i++) {
- if (fds[i].revents) {
- auto event = lines[3].event_read();
- REQUIRE(event.source == lines[3]);
- REQUIRE(event.event_type == ::gpiod::line_event::RISING_EDGE);
- }
- }
-}
-
-TEST_CASE("It's possible to read a value from a line requested for events", "[event][line]")
-{
- mockup::probe_guard mockup_chips({ 8 });
- ::gpiod::chip chip(mockup::instance().chip_path(0));
- auto line = chip.get_line(4);
- ::gpiod::line_request config;
-
- config.consumer = consumer.c_str();
- config.request_type = ::gpiod::line_request::EVENT_BOTH_EDGES;
-
- mockup::instance().chip_set_pull(0, 4, 1);
-
- SECTION("active-high (default)")
- {
- line.request(config);
- REQUIRE(line.get_value() == 1);
- }
-
- SECTION("active-low")
- {
- config.flags = ::gpiod::line_request::FLAG_ACTIVE_LOW;
- line.request(config);
- REQUIRE(line.get_value() == 0);
- }
-}
-
-TEST_CASE("It's possible to read values from lines requested for events", "[event][bulk]")
-{
- mockup::probe_guard mockup_chips({ 8 });
- ::gpiod::chip chip(mockup::instance().chip_path(0));
- auto lines = chip.get_lines({ 0, 1, 2, 3, 4 });
- ::gpiod::line_request config;
-
- config.consumer = consumer.c_str();
- config.request_type = ::gpiod::line_request::EVENT_BOTH_EDGES;
-
- mockup::instance().chip_set_pull(0, 5, 1);
-
- SECTION("active-high (default)")
- {
- lines.request(config);
- REQUIRE(lines.get_values() == ::std::vector<int>({ 0, 0, 0, 0, 0 }));
- mockup::instance().chip_set_pull(0, 1, 1);
- mockup::instance().chip_set_pull(0, 3, 1);
- mockup::instance().chip_set_pull(0, 4, 1);
- REQUIRE(lines.get_values() == ::std::vector<int>({ 0, 1, 0, 1, 1 }));
- }
-
- SECTION("active-low")
- {
- config.flags = ::gpiod::line_request::FLAG_ACTIVE_LOW;
- lines.request(config);
- REQUIRE(lines.get_values() == ::std::vector<int>({ 1, 1, 1, 1, 1 }));
- mockup::instance().chip_set_pull(0, 1, 1);
- mockup::instance().chip_set_pull(0, 3, 1);
- mockup::instance().chip_set_pull(0, 4, 1);
- REQUIRE(lines.get_values() == ::std::vector<int>({ 1, 0, 1, 0, 0 }));
- }
-}
-
-TEST_CASE("It's possible to read more than one line event", "[event][line]")
-{
- mockup::probe_guard mockup_chips({ 8 });
- ::gpiod::chip chip(mockup::instance().chip_path(0));
- auto line = chip.get_line(4);
- ::gpiod::line_request config;
-
- config.consumer = consumer.c_str();
- config.request_type = ::gpiod::line_request::EVENT_BOTH_EDGES;
-
- line.request(config);
-
- mockup::instance().chip_set_pull(0, 4, 1);
- ::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
- mockup::instance().chip_set_pull(0, 4, 0);
- ::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
- mockup::instance().chip_set_pull(0, 4, 1);
- ::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
-
- auto events = line.event_read_multiple();
-
- REQUIRE(events.size() == 3);
- REQUIRE(events.at(0).event_type == ::gpiod::line_event::RISING_EDGE);
- REQUIRE(events.at(1).event_type == ::gpiod::line_event::FALLING_EDGE);
- REQUIRE(events.at(2).event_type == ::gpiod::line_event::RISING_EDGE);
- REQUIRE(events.at(0).source == line);
- REQUIRE(events.at(1).source == line);
- REQUIRE(events.at(2).source == line);
-}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <catch2/catch.hpp>
+#include <chrono>
+#include <gpiod.hpp>
+#include <sstream>
+#include <thread>
+#include <utility>
+
+#include "gpiosim.hpp"
+#include "helpers.hpp"
+
+using ::gpiosim::make_sim;
+using direction = ::gpiod::line::direction;
+using event_type = ::gpiod::info_event::event_type;
+
+namespace {
+
+void request_reconfigure_release_line(::gpiod::chip& chip)
+{
+ ::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
+
+ auto request = chip
+ .prepare_request()
+ .add_line_settings(7, ::gpiod::line_settings())
+ .do_request();
+
+ ::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
+
+ request.reconfigure_lines(
+ ::gpiod::line_config()
+ .add_line_settings(
+ 7,
+ ::gpiod::line_settings()
+ .set_direction(direction::OUTPUT)
+ )
+ );
+
+ ::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
+
+ request.release();
+}
+
+TEST_CASE("Lines can be watched", "[info-event][chip]")
+{
+ auto sim = make_sim()
+ .set_num_lines(8)
+ .build();
+
+ ::gpiod::chip chip(sim.dev_path());
+
+ SECTION("watch_line_info() returns line info")
+ {
+ auto info = chip.watch_line_info(7);
+ REQUIRE(info.offset() == 7);
+ }
+
+ SECTION("watch_line_info() fails for offset out of range")
+ {
+ REQUIRE_THROWS_AS(chip.watch_line_info(8), ::std::invalid_argument);
+ }
+
+ SECTION("waiting for event timeout")
+ {
+ chip.watch_line_info(3);
+ REQUIRE_FALSE(chip.wait_info_event(::std::chrono::milliseconds(100)));
+ }
+
+ SECTION("request-reconfigure-release events")
+ {
+ auto info = chip.watch_line_info(7);
+ ::std::uint64_t ts_req, ts_rec, ts_rel;
+
+ REQUIRE(info.direction() == direction::INPUT);
+
+ ::std::thread thread(request_reconfigure_release_line, ::std::ref(chip));
+
+ REQUIRE(chip.wait_info_event(::std::chrono::seconds(1)));
+ auto event = chip.read_info_event();
+ REQUIRE(event.type() == event_type::LINE_REQUESTED);
+ REQUIRE(event.get_line_info().direction() == direction::INPUT);
+ ts_req = event.timestamp_ns();
+
+ REQUIRE(chip.wait_info_event(::std::chrono::seconds(1)));
+ event = chip.read_info_event();
+ REQUIRE(event.type() == event_type::LINE_CONFIG_CHANGED);
+ REQUIRE(event.get_line_info().direction() == direction::OUTPUT);
+ ts_rec = event.timestamp_ns();
+
+ REQUIRE(chip.wait_info_event(::std::chrono::seconds(1)));
+ event = chip.read_info_event();
+ REQUIRE(event.type() == event_type::LINE_RELEASED);
+ ts_rel = event.timestamp_ns();
+
+ /* No more events. */
+ REQUIRE_FALSE(chip.wait_info_event(::std::chrono::milliseconds(100)));
+ thread.join();
+
+ /* Check timestamps are really monotonic. */
+ REQUIRE(ts_rel > ts_rec);
+ REQUIRE(ts_rec > ts_req);
+ }
+}
+
+TEST_CASE("info_event can be copied and moved", "[info-event]")
+{
+ auto sim = make_sim().build();
+ ::gpiod::chip chip(sim.dev_path());
+ ::std::stringstream buf, expected;
+
+ chip.watch_line_info(0);
+
+ auto request = chip
+ .prepare_request()
+ .add_line_settings(0, ::gpiod::line_settings())
+ .do_request();
+
+ REQUIRE(chip.wait_info_event(::std::chrono::seconds(1)));
+ auto event = chip.read_info_event();
+
+ request.release();
+
+ REQUIRE(chip.wait_info_event(::std::chrono::seconds(1)));
+ auto copy = chip.read_info_event();
+
+ SECTION("copy constructor works")
+ {
+ auto copy(event);
+
+ REQUIRE(copy.type() == event_type::LINE_REQUESTED);
+ REQUIRE(copy.get_line_info().offset() == 0);
+
+ REQUIRE(event.type() == event_type::LINE_REQUESTED);
+ REQUIRE(event.get_line_info().offset() == 0);
+ }
+
+ SECTION("assignment operator works")
+ {
+ copy = event;
+
+ REQUIRE(copy.type() == event_type::LINE_REQUESTED);
+ REQUIRE(copy.get_line_info().offset() == 0);
+
+ REQUIRE(event.type() == event_type::LINE_REQUESTED);
+ REQUIRE(event.get_line_info().offset() == 0);
+ }
+
+ SECTION("move constructor works")
+ {
+ auto copy(::std::move(event));
+
+ REQUIRE(copy.type() == event_type::LINE_REQUESTED);
+ REQUIRE(copy.get_line_info().offset() == 0);
+ }
+
+ SECTION("move assignment operator works")
+ {
+ copy = ::std::move(event);
+
+ REQUIRE(copy.type() == event_type::LINE_REQUESTED);
+ REQUIRE(copy.get_line_info().offset() == 0);
+ }
+}
+
+TEST_CASE("info_event stream insertion operator works", "[info-event][line-info]")
+{
+ /*
+ * This tests the stream insertion operator for both the info_event
+ * and line_info classes.
+ */
+
+ auto sim = make_sim().build();
+ ::gpiod::chip chip(sim.dev_path());
+ ::std::stringstream buf, expected;
+
+ chip.watch_line_info(0);
+
+ auto request = chip
+ .prepare_request()
+ .add_line_settings(0, ::gpiod::line_settings())
+ .do_request();
+
+ auto event = chip.read_info_event();
+
+ buf << event;
+
+ expected << "gpiod::info_event\\(event_type='LINE_REQUESTED', timestamp=[1-9][0-9]+, " <<
+ "line_info=gpiod::line_info\\(offset=0, name=unnamed, used=true, consumer='', " <<
+ "direction=INPUT, active_low=false, bias=UNKNOWN, drive=PUSH_PULL, " <<
+ "edge_detection=NONE, event_clock=MONOTONIC, debounced=false\\)\\)";
+
+ REQUIRE_THAT(buf.str(), regex_matcher(expected.str()));
+}
+
+} /* namespace */
+++ /dev/null
-// SPDX-License-Identifier: GPL-2.0-or-later
-// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
-
-#include <catch2/catch.hpp>
-#include <gpiod.hpp>
-
-#include "gpio-mockup.hpp"
-
-using ::gpiod::test::mockup;
-
-TEST_CASE("Line iterator works", "[iter][line]")
-{
- mockup::probe_guard mockup_chips({ 4 });
- ::gpiod::chip chip(mockup::instance().chip_path(0));
- unsigned int count = 0;
-
- for (auto& it: ::gpiod::line_iter(chip))
- REQUIRE(it.offset() == count++);
-
- REQUIRE(count == chip.num_lines());
-}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <catch2/catch.hpp>
+#include <gpiod.hpp>
+
+#include "helpers.hpp"
+
+using namespace ::std::chrono_literals;
+using direction = ::gpiod::line::direction;
+using drive = ::gpiod::line::drive;
+using edge = ::gpiod::line::edge;
+
+namespace {
+
+TEST_CASE("line_config constructor works", "[line-config]")
+{
+ SECTION("no arguments - default values")
+ {
+ ::gpiod::line_config cfg;
+
+ REQUIRE(cfg.get_line_settings().size() == 0);
+ }
+}
+
+TEST_CASE("adding line_settings to line_config works", "[line-config][line-settings]")
+{
+ ::gpiod::line_config cfg;
+
+ cfg.add_line_settings(4,
+ ::gpiod::line_settings()
+ .set_direction(direction::INPUT)
+ .set_edge_detection(edge::RISING));
+
+ cfg.add_line_settings({7, 2},
+ ::gpiod::line_settings()
+ .set_direction(direction::OUTPUT)
+ .set_drive(drive::OPEN_DRAIN));
+
+ auto settings = cfg.get_line_settings();
+
+ REQUIRE(settings.size() == 3);
+ REQUIRE(settings.at(2).direction() == direction::OUTPUT);
+ REQUIRE(settings.at(2).drive() == drive::OPEN_DRAIN);
+ REQUIRE(settings.at(4).direction() == direction::INPUT);
+ REQUIRE(settings.at(4).edge_detection() == edge::RISING);
+ REQUIRE(settings.at(7).direction() == direction::OUTPUT);
+ REQUIRE(settings.at(7).drive() == drive::OPEN_DRAIN);
+}
+
+TEST_CASE("line_config can be reset", "[line-config]")
+{
+ ::gpiod::line_config cfg;
+
+ cfg.add_line_settings({3, 4, 7},
+ ::gpiod::line_settings()
+ .set_direction(direction::INPUT)
+ .set_edge_detection(edge::BOTH));
+
+ auto settings = cfg.get_line_settings();
+
+ REQUIRE(settings.size() == 3);
+ REQUIRE(settings.at(3).direction() == direction::INPUT);
+ REQUIRE(settings.at(3).edge_detection() == edge::BOTH);
+ REQUIRE(settings.at(4).direction() == direction::INPUT);
+ REQUIRE(settings.at(4).edge_detection() == edge::BOTH);
+ REQUIRE(settings.at(7).direction() == direction::INPUT);
+ REQUIRE(settings.at(7).edge_detection() == edge::BOTH);
+
+ cfg.reset();
+
+ REQUIRE(cfg.get_line_settings().size() == 0);
+}
+
+TEST_CASE("line_config stream insertion operator works", "[line-config]")
+{
+ ::gpiod::line_config cfg;
+
+ SECTION("empty config")
+ {
+ REQUIRE_THAT(cfg, stringify_matcher<::gpiod::line_config>(
+ "gpiod::line_config(num_settings=0)"));
+ }
+
+ SECTION("config with settings")
+ {
+ cfg.add_line_settings({0, 2},
+ ::gpiod::line_settings()
+ .set_direction(direction::OUTPUT)
+ .set_drive(drive::OPEN_SOURCE)
+ );
+
+ REQUIRE_THAT(cfg, stringify_matcher<::gpiod::line_config>(
+ "gpiod::line_config(num_settings=2, "
+ "settings=[0: gpiod::line_settings(direction=OUTPUT, edge_detection=NONE, "
+ "bias=AS_IS, drive=OPEN_SOURCE, active-high, debounce_period=0, "
+ "event_clock=MONOTONIC, output_value=INACTIVE), "
+ "2: gpiod::line_settings(direction=OUTPUT, edge_detection=NONE, bias=AS_IS, "
+ "drive=OPEN_SOURCE, active-high, debounce_period=0, event_clock=MONOTONIC, "
+ "output_value=INACTIVE)])"));
+ }
+}
+
+} /* namespace */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <catch2/catch.hpp>
+#include <gpiod.hpp>
+#include <string>
+
+#include "helpers.hpp"
+#include "gpiosim.hpp"
+
+using ::gpiosim::make_sim;
+using hog_dir = ::gpiosim::chip_builder::hog_direction;
+using direction = ::gpiod::line::direction;
+using edge = ::gpiod::line::edge;
+using bias = ::gpiod::line::bias;
+using drive = ::gpiod::line::drive;
+using event_clock = ::gpiod::line::clock;
+
+using namespace ::std::chrono_literals;
+
+namespace {
+
+TEST_CASE("get_line_info() works", "[chip][line-info]")
+{
+ auto sim = make_sim()
+ .set_num_lines(8)
+ .set_line_name(0, "foobar")
+ .set_hog(0, "hog", hog_dir::OUTPUT_HIGH)
+ .build();
+
+ ::gpiod::chip chip(sim.dev_path());
+
+ SECTION("line_info can be retrieved from chip")
+ {
+ auto info = chip.get_line_info(0);
+
+ REQUIRE(info.offset() == 0);
+ REQUIRE_THAT(info.name(), Catch::Equals("foobar"));
+ REQUIRE(info.used());
+ REQUIRE_THAT(info.consumer(), Catch::Equals("hog"));
+ REQUIRE(info.direction() == ::gpiod::line::direction::OUTPUT);
+ REQUIRE_FALSE(info.active_low());
+ REQUIRE(info.bias() == ::gpiod::line::bias::UNKNOWN);
+ REQUIRE(info.drive() == ::gpiod::line::drive::PUSH_PULL);
+ REQUIRE(info.edge_detection() == ::gpiod::line::edge::NONE);
+ REQUIRE(info.event_clock() == ::gpiod::line::clock::MONOTONIC);
+ REQUIRE_FALSE(info.debounced());
+ REQUIRE(info.debounce_period() == 0us);
+ }
+
+ SECTION("offset out of range")
+ {
+ REQUIRE_THROWS_AS(chip.get_line_info(8), ::std::invalid_argument);
+ }
+}
+
+TEST_CASE("line properties can be retrieved", "[line-info]")
+{
+ auto sim = make_sim()
+ .set_num_lines(8)
+ .set_line_name(1, "foo")
+ .set_line_name(2, "bar")
+ .set_line_name(4, "baz")
+ .set_line_name(5, "xyz")
+ .set_hog(3, "hog3", hog_dir::OUTPUT_HIGH)
+ .set_hog(4, "hog4", hog_dir::OUTPUT_LOW)
+ .build();
+
+ ::gpiod::chip chip(sim.dev_path());
+
+ SECTION("basic properties")
+ {
+ auto info4 = chip.get_line_info(4);
+ auto info6 = chip.get_line_info(6);
+
+ REQUIRE(info4.offset() == 4);
+ REQUIRE_THAT(info4.name(), Catch::Equals("baz"));
+ REQUIRE(info4.used());
+ REQUIRE_THAT(info4.consumer(), Catch::Equals("hog4"));
+ REQUIRE(info4.direction() == direction::OUTPUT);
+ REQUIRE(info4.edge_detection() == edge::NONE);
+ REQUIRE_FALSE(info4.active_low());
+ REQUIRE(info4.bias() == bias::UNKNOWN);
+ REQUIRE(info4.drive() == drive::PUSH_PULL);
+ REQUIRE(info4.event_clock() == event_clock::MONOTONIC);
+ REQUIRE_FALSE(info4.debounced());
+ REQUIRE(info4.debounce_period() == 0us);
+ }
+}
+
+TEST_CASE("line_info can be copied and moved")
+{
+ auto sim = make_sim()
+ .set_num_lines(4)
+ .set_line_name(2, "foobar")
+ .build();
+
+ ::gpiod::chip chip(sim.dev_path());
+ auto info = chip.get_line_info(2);
+
+ SECTION("copy constructor works")
+ {
+ auto copy(info);
+ REQUIRE(copy.offset() == 2);
+ REQUIRE_THAT(copy.name(), Catch::Equals("foobar"));
+ /* info can still be used */
+ REQUIRE(info.offset() == 2);
+ REQUIRE_THAT(info.name(), Catch::Equals("foobar"));
+ }
+
+ SECTION("assignment operator works")
+ {
+ auto copy = chip.get_line_info(0);
+ copy = info;
+ REQUIRE(copy.offset() == 2);
+ REQUIRE_THAT(copy.name(), Catch::Equals("foobar"));
+ /* info can still be used */
+ REQUIRE(info.offset() == 2);
+ REQUIRE_THAT(info.name(), Catch::Equals("foobar"));
+ }
+
+ SECTION("move constructor works")
+ {
+ auto copy(::std::move(info));
+ REQUIRE(copy.offset() == 2);
+ REQUIRE_THAT(copy.name(), Catch::Equals("foobar"));
+ }
+
+ SECTION("move assignment operator works")
+ {
+ auto copy = chip.get_line_info(0);
+ copy = ::std::move(info);
+ REQUIRE(copy.offset() == 2);
+ REQUIRE_THAT(copy.name(), Catch::Equals("foobar"));
+ }
+}
+
+TEST_CASE("line_info stream insertion operator works")
+{
+ auto sim = make_sim()
+ .set_line_name(0, "foo")
+ .set_hog(0, "hogger", hog_dir::OUTPUT_HIGH)
+ .build();
+
+ ::gpiod::chip chip(sim.dev_path());
+
+ auto info = chip.get_line_info(0);
+
+ REQUIRE_THAT(info, stringify_matcher<::gpiod::line_info>(
+ "gpiod::line_info(offset=0, name='foo', used=true, consumer='foo', direction=OUTPUT, "
+ "active_low=false, bias=UNKNOWN, drive=PUSH_PULL, edge_detection=NONE, event_clock=MONOTONIC, debounced=false)"));
+}
+
+} /* namespace */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <catch2/catch.hpp>
+#include <gpiod.hpp>
+#include <sstream>
+#include <stdexcept>
+#include <vector>
+
+#include "gpiosim.hpp"
+#include "helpers.hpp"
+
+using ::gpiosim::make_sim;
+using offsets = ::gpiod::line::offsets;
+using values = ::gpiod::line::values;
+using direction = ::gpiod::line::direction;
+using value = ::gpiod::line::value;
+using simval = ::gpiosim::chip::value;
+using pull = ::gpiosim::chip::pull;
+
+namespace {
+
+class value_matcher : public Catch::MatcherBase<value>
+{
+public:
+ value_matcher(pull pull, bool active_low = false)
+ : _m_pull(pull),
+ _m_active_low(active_low)
+ {
+
+ }
+
+ ::std::string describe() const override
+ {
+ ::std::string repr(this->_m_pull == pull::PULL_UP ? "PULL_UP" : "PULL_DOWN");
+ ::std::string active_low = this->_m_active_low ? "(active-low) " : "";
+
+ return active_low + "corresponds with " + repr;
+ }
+
+ bool match(const value& val) const override
+ {
+ if (this->_m_active_low) {
+ if ((val == value::ACTIVE && this->_m_pull == pull::PULL_DOWN) ||
+ (val == value::INACTIVE && this->_m_pull == pull::PULL_UP))
+ return true;
+ } else {
+ if ((val == value::ACTIVE && this->_m_pull == pull::PULL_UP) ||
+ (val == value::INACTIVE && this->_m_pull == pull::PULL_DOWN))
+ return true;
+ }
+
+ return false;
+ }
+
+private:
+ pull _m_pull;
+ bool _m_active_low;
+};
+
+TEST_CASE("requesting lines behaves correctly with invalid arguments", "[line-request][chip]")
+{
+ auto sim = make_sim()
+ .set_num_lines(8)
+ .build();
+
+ ::gpiod::chip chip(sim.dev_path());
+
+ SECTION("no offsets")
+ {
+ REQUIRE_THROWS_AS(chip.prepare_request().do_request(), ::std::invalid_argument);
+ }
+
+ SECTION("duplicate offsets")
+ {
+ auto request = chip
+ .prepare_request()
+ .add_line_settings({ 2, 0, 0, 4 }, ::gpiod::line_settings())
+ .do_request();
+
+ auto offsets = request.offsets();
+
+ REQUIRE(offsets.size() == 3);
+ REQUIRE(offsets[0] == 2);
+ REQUIRE(offsets[1] == 0);
+ REQUIRE(offsets[2] == 4);
+ }
+
+ SECTION("offset out of bounds")
+ {
+ REQUIRE_THROWS_AS(chip
+ .prepare_request()
+ .add_line_settings({ 2, 0, 8, 4 }, ::gpiod::line_settings())
+ .do_request(),
+ ::std::invalid_argument
+ );
+ }
+}
+
+TEST_CASE("consumer string is set correctly", "[line-request]")
+{
+ auto sim = make_sim()
+ .set_num_lines(4)
+ .build();
+
+ ::gpiod::chip chip(sim.dev_path());
+ offsets offs({ 3, 0, 2 });
+
+ SECTION("set custom consumer")
+ {
+ auto request = chip
+ .prepare_request()
+ .add_line_settings(offs, ::gpiod::line_settings())
+ .set_consumer("foobar")
+ .do_request();
+
+ auto info = chip.get_line_info(2);
+
+ REQUIRE(info.used());
+ REQUIRE_THAT(info.consumer(), Catch::Equals("foobar"));
+ }
+
+ SECTION("empty consumer")
+ {
+ auto request = chip
+ .prepare_request()
+ .add_line_settings(2, ::gpiod::line_settings())
+ .do_request();
+
+ auto info = chip.get_line_info(2);
+
+ REQUIRE(info.used());
+ REQUIRE_THAT(info.consumer(), Catch::Equals("?"));
+ }
+}
+
+TEST_CASE("values can be read", "[line-request]")
+{
+ auto sim = make_sim()
+ .set_num_lines(8)
+ .build();
+
+ const offsets offs({ 7, 1, 0, 6, 2 });
+
+ const ::std::vector<pull> pulls({
+ pull::PULL_UP,
+ pull::PULL_UP,
+ pull::PULL_DOWN,
+ pull::PULL_UP,
+ pull::PULL_DOWN
+ });
+
+ for (unsigned int i = 0; i < offs.size(); i++)
+ sim.set_pull(offs[i], pulls[i]);
+
+ auto request = ::gpiod::chip(sim.dev_path())
+ .prepare_request()
+ .add_line_settings(
+ offs,
+ ::gpiod::line_settings()
+ .set_direction(direction::INPUT)
+ )
+ .do_request();
+
+ SECTION("get all values (returning variant)")
+ {
+ auto vals = request.get_values();
+
+ REQUIRE_THAT(vals[0], value_matcher(pull::PULL_UP));
+ REQUIRE_THAT(vals[1], value_matcher(pull::PULL_UP));
+ REQUIRE_THAT(vals[2], value_matcher(pull::PULL_DOWN));
+ REQUIRE_THAT(vals[3], value_matcher(pull::PULL_UP));
+ REQUIRE_THAT(vals[4], value_matcher(pull::PULL_DOWN));
+ }
+
+ SECTION("get all values (passed buffer variant)")
+ {
+ values vals(5);
+
+ request.get_values(vals);
+
+ REQUIRE_THAT(vals[0], value_matcher(pull::PULL_UP));
+ REQUIRE_THAT(vals[1], value_matcher(pull::PULL_UP));
+ REQUIRE_THAT(vals[2], value_matcher(pull::PULL_DOWN));
+ REQUIRE_THAT(vals[3], value_matcher(pull::PULL_UP));
+ REQUIRE_THAT(vals[4], value_matcher(pull::PULL_DOWN));
+ }
+
+ SECTION("get_values(buffer) throws for invalid buffer size")
+ {
+ values vals(4);
+ REQUIRE_THROWS_AS(request.get_values(vals), ::std::invalid_argument);
+ vals.resize(6);
+ REQUIRE_THROWS_AS(request.get_values(vals), ::std::invalid_argument);
+ }
+
+ SECTION("get a single value")
+ {
+ auto val = request.get_value(7);
+
+ REQUIRE_THAT(val, value_matcher(pull::PULL_UP));
+ }
+
+ SECTION("get a single value (active-low)")
+ {
+ request.reconfigure_lines(
+ ::gpiod::line_config()
+ .add_line_settings(
+ offs,
+ ::gpiod::line_settings()
+ .set_active_low(true))
+ );
+
+ auto val = request.get_value(7);
+
+ REQUIRE_THAT(val, value_matcher(pull::PULL_UP, true));
+ }
+
+ SECTION("get a subset of values (returning variant)")
+ {
+ auto vals = request.get_values(offsets({ 2, 0, 6 }));
+
+ REQUIRE_THAT(vals[0], value_matcher(pull::PULL_DOWN));
+ REQUIRE_THAT(vals[1], value_matcher(pull::PULL_DOWN));
+ REQUIRE_THAT(vals[2], value_matcher(pull::PULL_UP));
+ }
+
+ SECTION("get a subset of values (passed buffer variant)")
+ {
+ values vals(3);
+
+ request.get_values(offsets({ 2, 0, 6 }), vals);
+
+ REQUIRE_THAT(vals[0], value_matcher(pull::PULL_DOWN));
+ REQUIRE_THAT(vals[1], value_matcher(pull::PULL_DOWN));
+ REQUIRE_THAT(vals[2], value_matcher(pull::PULL_UP));
+ }
+}
+
+TEST_CASE("output values can be set at request time", "[line-request]")
+{
+ auto sim = make_sim()
+ .set_num_lines(8)
+ .build();
+
+ ::gpiod::chip chip(sim.dev_path());
+ const offsets offs({ 0, 1, 3, 4 });
+
+ ::gpiod::line_settings settings;
+ settings.set_direction(direction::OUTPUT);
+ settings.set_output_value(value::ACTIVE);
+
+ ::gpiod::line_config line_cfg;
+ line_cfg.add_line_settings(offs, settings);
+
+ SECTION("default output value")
+ {
+ auto request = chip
+ .prepare_request()
+ .set_line_config(line_cfg)
+ .do_request();
+
+ for (const auto& off: offs)
+ REQUIRE(sim.get_value(off) == simval::ACTIVE);
+
+ REQUIRE(sim.get_value(2) == simval::INACTIVE);
+ }
+
+ SECTION("overridden output value")
+ {
+ settings.set_output_value(value::INACTIVE);
+ line_cfg.add_line_settings(1, settings);
+
+ auto request = chip
+ .prepare_request()
+ .set_line_config(line_cfg)
+ .do_request();
+
+ REQUIRE(sim.get_value(0) == simval::ACTIVE);
+ REQUIRE(sim.get_value(1) == simval::INACTIVE);
+ REQUIRE(sim.get_value(2) == simval::INACTIVE);
+ REQUIRE(sim.get_value(3) == simval::ACTIVE);
+ REQUIRE(sim.get_value(4) == simval::ACTIVE);
+ }
+}
+
+TEST_CASE("values can be set after requesting lines", "[line-request]")
+{
+ auto sim = make_sim()
+ .set_num_lines(8)
+ .build();
+
+ const offsets offs({ 0, 1, 3, 4 });
+
+ auto request = ::gpiod::chip(sim.dev_path())
+ .prepare_request()
+ .add_line_settings(
+ offs,
+ ::gpiod::line_settings()
+ .set_direction(direction::OUTPUT)
+ )
+ .do_request();
+
+ SECTION("set single value")
+ {
+ request.set_value(1, value::ACTIVE);
+
+ REQUIRE(sim.get_value(0) == simval::INACTIVE);
+ REQUIRE(sim.get_value(1) == simval::ACTIVE);
+ REQUIRE(sim.get_value(3) == simval::INACTIVE);
+ REQUIRE(sim.get_value(4) == simval::INACTIVE);
+ }
+
+ SECTION("set all values")
+ {
+ request.set_values({
+ value::ACTIVE,
+ value::INACTIVE,
+ value::ACTIVE,
+ value::INACTIVE
+ });
+
+ REQUIRE(sim.get_value(0) == simval::ACTIVE);
+ REQUIRE(sim.get_value(1) == simval::INACTIVE);
+ REQUIRE(sim.get_value(3) == simval::ACTIVE);
+ REQUIRE(sim.get_value(4) == simval::INACTIVE);
+ }
+
+ SECTION("set a subset of values")
+ {
+ request.set_values({ 4, 3 }, { value::ACTIVE, value::INACTIVE });
+
+ REQUIRE(sim.get_value(0) == simval::INACTIVE);
+ REQUIRE(sim.get_value(1) == simval::INACTIVE);
+ REQUIRE(sim.get_value(3) == simval::INACTIVE);
+ REQUIRE(sim.get_value(4) == simval::ACTIVE);
+ }
+
+ SECTION("set a subset of values with mappings")
+ {
+ request.set_values({
+ { 0, value::ACTIVE },
+ { 4, value::INACTIVE },
+ { 1, value::ACTIVE}
+ });
+
+ REQUIRE(sim.get_value(0) == simval::ACTIVE);
+ REQUIRE(sim.get_value(1) == simval::ACTIVE);
+ REQUIRE(sim.get_value(3) == simval::INACTIVE);
+ REQUIRE(sim.get_value(4) == simval::INACTIVE);
+ }
+}
+
+TEST_CASE("line_request can be moved", "[line-request]")
+{
+ auto sim = make_sim()
+ .set_num_lines(8)
+ .build();
+
+ ::gpiod::chip chip(sim.dev_path());
+ const offsets offs({ 3, 1, 0, 2 });
+
+ auto request = chip
+ .prepare_request()
+ .add_line_settings(
+ offs,
+ ::gpiod::line_settings()
+ )
+ .do_request();
+
+ auto fd = request.fd();
+
+ auto another = chip
+ .prepare_request()
+ .add_line_settings(6, ::gpiod::line_settings())
+ .do_request();
+
+ SECTION("move constructor works")
+ {
+ auto moved(::std::move(request));
+
+ REQUIRE(moved.fd() == fd);
+ REQUIRE_THAT(moved.offsets(), Catch::Equals(offs));
+ }
+
+ SECTION("move assignment operator works")
+ {
+ another = ::std::move(request);
+
+ REQUIRE(another.fd() == fd);
+ REQUIRE_THAT(another.offsets(), Catch::Equals(offs));
+ }
+}
+
+TEST_CASE("released request can no longer be used", "[line-request]")
+{
+ auto sim = make_sim().build();
+
+ auto request = ::gpiod::chip(sim.dev_path())
+ .prepare_request()
+ .add_line_settings(0, ::gpiod::line_settings())
+ .do_request();
+
+ request.release();
+
+ REQUIRE_THROWS_AS(request.offsets(), ::gpiod::request_released);
+}
+
+TEST_CASE("line_request survives parent chip", "[line-request][chip]")
+{
+ auto sim = make_sim().build();
+
+ sim.set_pull(0, pull::PULL_UP);
+
+ SECTION("chip is released")
+ {
+ ::gpiod::chip chip(sim.dev_path());
+
+ auto request = chip
+ .prepare_request()
+ .add_line_settings(
+ 0,
+ ::gpiod::line_settings()
+ .set_direction(direction::INPUT)
+ )
+ .do_request();
+
+ REQUIRE_THAT(request.get_value(0), value_matcher(pull::PULL_UP));
+
+ chip.close();
+
+ REQUIRE_THAT(request.get_value(0), value_matcher(pull::PULL_UP));
+ }
+
+ SECTION("chip goes out of scope")
+ {
+ /* Need to get the request object somehow. */
+ ::gpiod::chip dummy(sim.dev_path());
+ ::gpiod::line_config cfg;
+ cfg.add_line_settings(0, ::gpiod::line_settings().set_direction(direction::INPUT));
+
+ auto request = dummy
+ .prepare_request()
+ .set_line_config(cfg)
+ .do_request();
+
+ request.release();
+ dummy.close();
+
+ {
+ ::gpiod::chip chip(sim.dev_path());
+
+ request = chip
+ .prepare_request()
+ .set_line_config(cfg)
+ .do_request();
+
+ REQUIRE_THAT(request.get_value(0), value_matcher(pull::PULL_UP));
+ }
+
+ REQUIRE_THAT(request.get_value(0), value_matcher(pull::PULL_UP));
+ }
+}
+
+TEST_CASE("line_request stream insertion operator works", "[line-request]")
+{
+ auto sim = make_sim()
+ .set_num_lines(4)
+ .build();
+
+ auto request = ::gpiod::chip(sim.dev_path())
+ .prepare_request()
+ .add_line_settings({ 3, 1, 0, 2}, ::gpiod::line_settings())
+ .do_request();
+
+ ::std::stringstream buf, expected;
+
+ expected << "gpiod::line_request(num_lines=4, line_offsets=gpiod::offsets(3, 1, 0, 2), fd=" <<
+ request.fd() << ")";
+
+ SECTION("active request")
+ {
+ buf << request;
+
+ REQUIRE_THAT(buf.str(), Catch::Equals(expected.str()));
+ }
+
+ SECTION("request released")
+ {
+ request.release();
+
+ buf << request;
+
+ REQUIRE_THAT(buf.str(), Catch::Equals("gpiod::line_request(released)"));
+ }
+}
+
+} /* namespace */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <catch2/catch.hpp>
+#include <gpiod.hpp>
+
+#include "helpers.hpp"
+
+using value = ::gpiod::line::value;
+using direction = ::gpiod::line::direction;
+using edge = ::gpiod::line::edge;
+using bias = ::gpiod::line::bias;
+using drive = ::gpiod::line::drive;
+using clock_type = ::gpiod::line::clock;
+using value = ::gpiod::line::value;
+
+using namespace ::std::chrono_literals;
+
+namespace {
+
+TEST_CASE("line_settings constructor works", "[line-settings]")
+{
+ ::gpiod::line_settings settings;
+
+ REQUIRE(settings.direction() == direction::AS_IS);
+ REQUIRE(settings.edge_detection() == edge::NONE);
+ REQUIRE(settings.bias() == bias::AS_IS);
+ REQUIRE(settings.drive() == drive::PUSH_PULL);
+ REQUIRE_FALSE(settings.active_low());
+ REQUIRE(settings.debounce_period() == 0us);
+ REQUIRE(settings.event_clock() == clock_type::MONOTONIC);
+ REQUIRE(settings.output_value() == value::INACTIVE);
+}
+
+TEST_CASE("line_settings mutators work", "[line-settings]")
+{
+ ::gpiod::line_settings settings;
+
+ SECTION("direction")
+ {
+ settings.set_direction(direction::INPUT);
+ REQUIRE(settings.direction() == direction::INPUT);
+ settings.set_direction(direction::AS_IS);
+ REQUIRE(settings.direction() == direction::AS_IS);
+ settings.set_direction(direction::OUTPUT);
+ REQUIRE(settings.direction() == direction::OUTPUT);
+ REQUIRE_THROWS_AS(settings.set_direction(static_cast<direction>(999)),
+ ::std::invalid_argument);
+ }
+
+ SECTION("edge detection")
+ {
+ settings.set_edge_detection(edge::BOTH);
+ REQUIRE(settings.edge_detection() == edge::BOTH);
+ settings.set_edge_detection(edge::NONE);
+ REQUIRE(settings.edge_detection() == edge::NONE);
+ settings.set_edge_detection(edge::FALLING);
+ REQUIRE(settings.edge_detection() == edge::FALLING);
+ settings.set_edge_detection(edge::RISING);
+ REQUIRE(settings.edge_detection() == edge::RISING);
+ REQUIRE_THROWS_AS(settings.set_edge_detection(static_cast<edge>(999)),
+ ::std::invalid_argument);
+ }
+
+ SECTION("bias")
+ {
+ settings.set_bias(bias::DISABLED);
+ REQUIRE(settings.bias() == bias::DISABLED);
+ settings.set_bias(bias::AS_IS);
+ REQUIRE(settings.bias() == bias::AS_IS);
+ settings.set_bias(bias::PULL_DOWN);
+ REQUIRE(settings.bias() == bias::PULL_DOWN);
+ settings.set_bias(bias::PULL_UP);
+ REQUIRE(settings.bias() == bias::PULL_UP);
+ REQUIRE_THROWS_AS(settings.set_bias(static_cast<bias>(999)), ::std::invalid_argument);
+ REQUIRE_THROWS_AS(settings.set_bias(bias::UNKNOWN), ::std::invalid_argument);
+ }
+
+ SECTION("drive")
+ {
+ settings.set_drive(drive::OPEN_DRAIN);
+ REQUIRE(settings.drive() == drive::OPEN_DRAIN);
+ settings.set_drive(drive::PUSH_PULL);
+ REQUIRE(settings.drive() == drive::PUSH_PULL);
+ settings.set_drive(drive::OPEN_SOURCE);
+ REQUIRE(settings.drive() == drive::OPEN_SOURCE);
+ REQUIRE_THROWS_AS(settings.set_drive(static_cast<drive>(999)), ::std::invalid_argument);
+ }
+
+ SECTION("active-low")
+ {
+ settings.set_active_low(true);
+ REQUIRE(settings.active_low());
+ settings.set_active_low(false);
+ REQUIRE_FALSE(settings.active_low());
+ }
+
+ SECTION("debounce period")
+ {
+ settings.set_debounce_period(2000us);
+ REQUIRE(settings.debounce_period() == 2000us);
+ }
+
+ SECTION("event clock")
+ {
+ settings.set_event_clock(clock_type::REALTIME);
+ REQUIRE(settings.event_clock() == clock_type::REALTIME);
+ settings.set_event_clock(clock_type::MONOTONIC);
+ REQUIRE(settings.event_clock() == clock_type::MONOTONIC);
+ settings.set_event_clock(clock_type::HTE);
+ REQUIRE(settings.event_clock() == clock_type::HTE);
+ REQUIRE_THROWS_AS(settings.set_event_clock(static_cast<clock_type>(999)),
+ ::std::invalid_argument);
+ }
+
+ SECTION("output value")
+ {
+ settings.set_output_value(value::ACTIVE);
+ REQUIRE(settings.output_value() == value::ACTIVE);
+ settings.set_output_value(value::INACTIVE);
+ REQUIRE(settings.output_value() == value::INACTIVE);
+ REQUIRE_THROWS_AS(settings.set_output_value(static_cast<value>(999)),
+ ::std::invalid_argument);
+ }
+}
+
+TEST_CASE("line_settings stream insertion operator works", "[line-settings]")
+{
+ ::gpiod::line_settings settings;
+
+ REQUIRE_THAT(settings
+ .set_active_low(true)
+ .set_direction(direction::INPUT)
+ .set_edge_detection(edge::BOTH)
+ .set_bias(bias::PULL_DOWN)
+ .set_event_clock(clock_type::REALTIME),
+ stringify_matcher<::gpiod::line_settings>(
+ "gpiod::line_settings(direction=INPUT, edge_detection=BOTH_EDGES, "
+ "bias=PULL_DOWN, drive=PUSH_PULL, active-low, debounce_period=0, "
+ "event_clock=REALTIME, output_value=INACTIVE)"
+ )
+ );
+}
+
+} /* namespace */
// SPDX-License-Identifier: GPL-2.0-or-later
-// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
#include <catch2/catch.hpp>
#include <gpiod.hpp>
-#include "gpio-mockup.hpp"
+#include "helpers.hpp"
-using ::gpiod::test::mockup;
+using offset = ::gpiod::line::offset;
+using value = ::gpiod::line::value;
+using direction = ::gpiod::line::direction;
+using edge = ::gpiod::line::edge;
+using bias = ::gpiod::line::bias;
+using drive = ::gpiod::line::drive;
+using clock_type = ::gpiod::line::clock;
+using offsets = ::gpiod::line::offsets;
+using values = ::gpiod::line::values;
+using value_mapping = ::gpiod::line::value_mapping;
+using value_mappings = ::gpiod::line::value_mappings;
namespace {
-const ::std::string consumer = "line-test";
-
-} /* namespace */
-
-TEST_CASE("Line information can be correctly retrieved", "[line]")
-{
- mockup::probe_guard mockup_chips({ 8 }, mockup::FLAG_NAMED_LINES);
- ::gpiod::chip chip(mockup::instance().chip_path(0));
- auto line = chip.get_line(4);
-
- SECTION("unexported line")
- {
- REQUIRE(line.offset() == 4);
- REQUIRE(line.name() == "gpio-mockup-A-4");
- REQUIRE(line.direction() == ::gpiod::line::DIRECTION_INPUT);
- REQUIRE_FALSE(line.is_active_low());
- REQUIRE(line.consumer().empty());
- REQUIRE_FALSE(line.is_used());
- REQUIRE(line.drive() == ::gpiod::line::DRIVE_PUSH_PULL);
- REQUIRE(line.bias() == ::gpiod::line::BIAS_UNKNOWN);
- }
-
- SECTION("exported line")
- {
- ::gpiod::line_request config;
-
- config.consumer = consumer.c_str();
- config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT;
- line.request(config);
-
- REQUIRE(line.offset() == 4);
- REQUIRE(line.name() == "gpio-mockup-A-4");
- REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT);
- REQUIRE_FALSE(line.is_active_low());
- REQUIRE(line.is_used());
- REQUIRE(line.drive() == ::gpiod::line::DRIVE_PUSH_PULL);
- REQUIRE(line.bias() == ::gpiod::line::BIAS_UNKNOWN);
- }
-
- SECTION("exported line with flags")
- {
- ::gpiod::line_request config;
-
- config.consumer = consumer.c_str();
- config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT;
- config.flags = ::gpiod::line_request::FLAG_ACTIVE_LOW |
- ::gpiod::line_request::FLAG_OPEN_DRAIN;
- line.request(config);
-
- REQUIRE(line.offset() == 4);
- REQUIRE(line.name() == "gpio-mockup-A-4");
- REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT);
- REQUIRE(line.is_active_low());
- REQUIRE(line.is_used());
- REQUIRE(line.drive() == ::gpiod::line::DRIVE_OPEN_DRAIN);
- REQUIRE(line.bias() == ::gpiod::line::BIAS_UNKNOWN);
- }
-
- SECTION("exported open source line")
- {
- ::gpiod::line_request config;
-
- config.consumer = consumer.c_str();
- config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT;
- config.flags = ::gpiod::line_request::FLAG_OPEN_SOURCE;
- line.request(config);
-
- REQUIRE(line.offset() == 4);
- REQUIRE(line.name() == "gpio-mockup-A-4");
- REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT);
- REQUIRE_FALSE(line.is_active_low());
- REQUIRE(line.is_used());
- REQUIRE(line.drive() == ::gpiod::line::DRIVE_OPEN_SOURCE);
- REQUIRE(line.bias() == ::gpiod::line::BIAS_UNKNOWN);
- }
-
- SECTION("exported bias disable line")
- {
- ::gpiod::line_request config;
-
- config.consumer = consumer.c_str();
- config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT;
- config.flags = ::gpiod::line_request::FLAG_BIAS_DISABLED;
- line.request(config);
-
- REQUIRE(line.offset() == 4);
- REQUIRE(line.name() == "gpio-mockup-A-4");
- REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT);
- REQUIRE_FALSE(line.is_active_low());
- REQUIRE(line.is_used());
- REQUIRE(line.drive() == ::gpiod::line::DRIVE_PUSH_PULL);
- REQUIRE(line.bias() == ::gpiod::line::BIAS_DISABLED);
- }
-
- SECTION("exported pull-down line")
- {
- ::gpiod::line_request config;
-
- config.consumer = consumer.c_str();
- config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT;
- config.flags = ::gpiod::line_request::FLAG_BIAS_PULL_DOWN;
- line.request(config);
-
- REQUIRE(line.offset() == 4);
- REQUIRE(line.name() == "gpio-mockup-A-4");
- REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT);
- REQUIRE_FALSE(line.is_active_low());;
- REQUIRE(line.is_used());
- REQUIRE(line.drive() == ::gpiod::line::DRIVE_PUSH_PULL);
- REQUIRE(line.bias() == ::gpiod::line::BIAS_PULL_DOWN);
- }
-
- SECTION("exported pull-up line")
- {
- ::gpiod::line_request config;
-
- config.consumer = consumer.c_str();
- config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT;
- config.flags = ::gpiod::line_request::FLAG_BIAS_PULL_UP;
- line.request(config);
-
- REQUIRE(line.offset() == 4);
- REQUIRE(line.name() == "gpio-mockup-A-4");
- REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT);
- REQUIRE_FALSE(line.is_active_low());
- REQUIRE(line.is_used());
- REQUIRE(line.drive() == ::gpiod::line::DRIVE_PUSH_PULL);
- REQUIRE(line.bias() == ::gpiod::line::BIAS_PULL_UP);
- }
-}
-
-TEST_CASE("Line values can be set and read", "[line]")
+TEST_CASE("stream insertion operators for types in gpiod::line work", "[line]")
{
- mockup::probe_guard mockup_chips({ 8 });
- ::gpiod::chip chip(mockup::instance().chip_path(0));
- ::gpiod::line_request config;
-
- config.consumer = consumer.c_str();
-
- SECTION("get value (single line)")
- {
- auto line = chip.get_line(3);
- config.request_type = ::gpiod::line_request::DIRECTION_INPUT;
- line.request(config);
- REQUIRE(line.get_value() == 0);
- mockup::instance().chip_set_pull(0, 3, 1);
- REQUIRE(line.get_value() == 1);
- }
-
- SECTION("set value (single line)")
- {
- auto line = chip.get_line(3);
- config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT;
- line.request(config);
- line.set_value(1);
- REQUIRE(mockup::instance().chip_get_value(0, 3) == 1);
- line.set_value(0);
- REQUIRE(mockup::instance().chip_get_value(0, 3) == 0);
- }
-
- SECTION("set value with default value parameter")
- {
- auto line = chip.get_line(3);
- config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT;
- line.request(config, 1);
- REQUIRE(mockup::instance().chip_get_value(0, 3) == 1);
- }
-
- SECTION("get multiple values at once")
- {
- auto lines = chip.get_lines({ 0, 1, 2, 3, 4 });
- config.request_type = ::gpiod::line_request::DIRECTION_INPUT;
- lines.request(config);
- REQUIRE(lines.get_values() == ::std::vector<int>({ 0, 0, 0, 0, 0 }));
- mockup::instance().chip_set_pull(0, 1, 1);
- mockup::instance().chip_set_pull(0, 3, 1);
- mockup::instance().chip_set_pull(0, 4, 1);
- REQUIRE(lines.get_values() == ::std::vector<int>({ 0, 1, 0, 1, 1 }));
- }
-
- SECTION("set multiple values at once")
+ SECTION("offset")
{
- auto lines = chip.get_lines({ 0, 1, 2, 6, 7 });
- config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT;
- lines.request(config);
- lines.set_values({ 1, 1, 0, 1, 0 });
- REQUIRE(mockup::instance().chip_get_value(0, 0) == 1);
- REQUIRE(mockup::instance().chip_get_value(0, 1) == 1);
- REQUIRE(mockup::instance().chip_get_value(0, 2) == 0);
- REQUIRE(mockup::instance().chip_get_value(0, 6) == 1);
- REQUIRE(mockup::instance().chip_get_value(0, 7) == 0);
- }
+ offset off = 4;
- SECTION("set multiple values with default values parameter")
- {
- auto lines = chip.get_lines({ 1, 2, 4, 6, 7 });
- config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT;
- lines.request(config, { 1, 1, 0, 1, 0 });
- REQUIRE(mockup::instance().chip_get_value(0, 1) == 1);
- REQUIRE(mockup::instance().chip_get_value(0, 2) == 1);
- REQUIRE(mockup::instance().chip_get_value(0, 4) == 0);
- REQUIRE(mockup::instance().chip_get_value(0, 6) == 1);
- REQUIRE(mockup::instance().chip_get_value(0, 7) == 0);
+ REQUIRE_THAT(off, stringify_matcher<offset>("4"));
}
- SECTION("get value (single line, active-low")
- {
- auto line = chip.get_line(4);
- config.request_type = ::gpiod::line_request::DIRECTION_INPUT;
- config.flags = ::gpiod::line_request::FLAG_ACTIVE_LOW;
- line.request(config);
- REQUIRE(line.get_value() == 1);
- mockup::instance().chip_set_pull(0, 4, 1);
- REQUIRE(line.get_value() == 0);
- }
-
- SECTION("set value (single line, active-low)")
- {
- auto line = chip.get_line(3);
- config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT;
- config.flags = ::gpiod::line_request::FLAG_ACTIVE_LOW;
- line.request(config);
- line.set_value(1);
- REQUIRE(mockup::instance().chip_get_value(0, 3) == 0);
- line.set_value(0);
- REQUIRE(mockup::instance().chip_get_value(0, 3) == 1);
- }
-}
-
-TEST_CASE("Line can be reconfigured", "[line]")
-{
- mockup::probe_guard mockup_chips({ 8 });
- ::gpiod::chip chip(mockup::instance().chip_path(0));
- ::gpiod::line_request config;
-
- config.consumer = consumer.c_str();
-
- SECTION("set config (single line, active-state)")
+ SECTION("value")
{
- auto line = chip.get_line(3);
- config.request_type = ::gpiod::line_request::DIRECTION_INPUT;
- config.flags = 0;
- line.request(config);
- REQUIRE(line.direction() == ::gpiod::line::DIRECTION_INPUT);
- REQUIRE_FALSE(line.is_active_low());
+ auto active = value::ACTIVE;
+ auto inactive = value::INACTIVE;
- line.set_config(::gpiod::line_request::DIRECTION_OUTPUT,
- ::gpiod::line_request::FLAG_ACTIVE_LOW,1);
- REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT);
- REQUIRE(line.is_active_low());
- REQUIRE(mockup::instance().chip_get_value(0, 3) == 0);
- line.set_value(0);
- REQUIRE(mockup::instance().chip_get_value(0, 3) == 1);
-
- line.set_config(::gpiod::line_request::DIRECTION_OUTPUT, 0);
- REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT);
- REQUIRE_FALSE(line.is_active_low());
- REQUIRE(mockup::instance().chip_get_value(0, 3) == 0);
- line.set_value(1);
- REQUIRE(mockup::instance().chip_get_value(0, 3) == 1);
+ REQUIRE_THAT(active, stringify_matcher<value>("ACTIVE"));
+ REQUIRE_THAT(inactive, stringify_matcher<value>("INACTIVE"));
}
- SECTION("set flags (single line, active-state)")
+ SECTION("direction")
{
- auto line = chip.get_line(3);
- config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT;
- config.flags = 0;
- line.request(config,1);
- REQUIRE(mockup::instance().chip_get_value(0, 3) == 1);
-
- line.set_flags(::gpiod::line_request::FLAG_ACTIVE_LOW);
- REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT);
- REQUIRE(line.is_active_low());
- REQUIRE(mockup::instance().chip_get_value(0, 3) == 0);
+ auto input = direction::INPUT;
+ auto output = direction::OUTPUT;
+ auto as_is = direction::AS_IS;
- line.set_flags(0);
- REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT);
- REQUIRE_FALSE(line.is_active_low());
- REQUIRE(mockup::instance().chip_get_value(0, 3) == 1);
+ REQUIRE_THAT(input, stringify_matcher<direction>("INPUT"));
+ REQUIRE_THAT(output, stringify_matcher<direction>("OUTPUT"));
+ REQUIRE_THAT(as_is, stringify_matcher<direction>("AS_IS"));
}
- SECTION("set flags (single line, drive)")
+ SECTION("edge")
{
- auto line = chip.get_line(3);
- config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT;
- config.flags = 0;
- line.request(config);
- REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT);
- REQUIRE(line.drive() == ::gpiod::line::DRIVE_PUSH_PULL);
-
- line.set_flags(::gpiod::line_request::FLAG_OPEN_DRAIN);
- REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT);
- REQUIRE(line.drive() == ::gpiod::line::DRIVE_OPEN_DRAIN);
-
- line.set_flags(::gpiod::line_request::FLAG_OPEN_SOURCE);
- REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT);
- REQUIRE(line.drive() == ::gpiod::line::DRIVE_OPEN_SOURCE);
+ auto rising = edge::RISING;
+ auto falling = edge::FALLING;
+ auto both = edge::BOTH;
+ auto none = edge::NONE;
- line.set_flags(0);
- REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT);
- REQUIRE(line.drive() == ::gpiod::line::DRIVE_PUSH_PULL);
+ REQUIRE_THAT(rising, stringify_matcher<edge>("RISING_EDGE"));
+ REQUIRE_THAT(falling, stringify_matcher<edge>("FALLING_EDGE"));
+ REQUIRE_THAT(both, stringify_matcher<edge>("BOTH_EDGES"));
+ REQUIRE_THAT(none, stringify_matcher<edge>("NONE"));
}
- SECTION("set flags (single line, bias)")
+ SECTION("bias")
{
- auto line = chip.get_line(3);
- config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT;
- config.flags = 0;
- line.request(config);
- REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT);
- REQUIRE(line.drive() == ::gpiod::line::DRIVE_PUSH_PULL);
+ auto pull_up = bias::PULL_UP;
+ auto pull_down = bias::PULL_DOWN;
+ auto disabled = bias::DISABLED;
+ auto as_is = bias::AS_IS;
+ auto unknown = bias::UNKNOWN;
- line.set_flags(::gpiod::line_request::FLAG_OPEN_DRAIN);
- REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT);
- REQUIRE(line.drive() == ::gpiod::line::DRIVE_OPEN_DRAIN);
-
- line.set_flags(::gpiod::line_request::FLAG_OPEN_SOURCE);
- REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT);
- REQUIRE(line.drive() == ::gpiod::line::DRIVE_OPEN_SOURCE);
-
- line.set_flags(0);
- REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT);
- REQUIRE(line.drive() == ::gpiod::line::DRIVE_PUSH_PULL);
+ REQUIRE_THAT(pull_up, stringify_matcher<bias>("PULL_UP"));
+ REQUIRE_THAT(pull_down, stringify_matcher<bias>("PULL_DOWN"));
+ REQUIRE_THAT(disabled, stringify_matcher<bias>("DISABLED"));
+ REQUIRE_THAT(as_is, stringify_matcher<bias>("AS_IS"));
+ REQUIRE_THAT(unknown, stringify_matcher<bias>("UNKNOWN"));
}
- SECTION("set direction input (single line)")
+ SECTION("drive")
{
- auto line = chip.get_line(3);
- config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT;
- config.flags = 0;
- line.request(config);
- REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT);
- line.set_direction_input();
- REQUIRE(line.direction() == ::gpiod::line::DIRECTION_INPUT);
- }
+ auto push_pull = drive::PUSH_PULL;
+ auto open_drain = drive::OPEN_DRAIN;
+ auto open_source = drive::OPEN_SOURCE;
- SECTION("set direction output (single line)")
- {
- auto line = chip.get_line(3);
- config.request_type = ::gpiod::line_request::DIRECTION_INPUT;
- config.flags = 0;
- line.request(config);
- REQUIRE(line.direction() == ::gpiod::line::DIRECTION_INPUT);
- line.set_direction_output(1);
- REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT);
- REQUIRE(mockup::instance().chip_get_value(0, 3) == 1);
+ REQUIRE_THAT(push_pull, stringify_matcher<drive>("PUSH_PULL"));
+ REQUIRE_THAT(open_drain, stringify_matcher<drive>("OPEN_DRAIN"));
+ REQUIRE_THAT(open_source, stringify_matcher<drive>("OPEN_SOURCE"));
}
-}
-
-TEST_CASE("Exported line can be released", "[line]")
-{
- mockup::probe_guard mockup_chips({ 8 });
- ::gpiod::chip chip(mockup::instance().chip_path(0));
- auto line = chip.get_line(4);
- ::gpiod::line_request config;
-
- config.consumer = consumer.c_str();
- config.request_type = ::gpiod::line_request::DIRECTION_INPUT;
-
- line.request(config);
- REQUIRE(line.get_value() == 0);
-
- line.release();
-
- REQUIRE_THROWS_AS(line.get_value(), ::std::system_error);
-}
-
-TEST_CASE("Uninitialized GPIO line behaves correctly", "[line]")
-{
- ::gpiod::line line;
-
- SECTION("uninitialized line is 'false'")
+ SECTION("clock")
{
- REQUIRE_FALSE(line);
- }
+ auto monotonic = clock_type::MONOTONIC;
+ auto realtime = clock_type::REALTIME;
+ auto hte = clock_type::HTE;
- SECTION("using uninitialized line throws logic_error")
- {
- REQUIRE_THROWS_AS(line.name(), ::std::logic_error);
+ REQUIRE_THAT(monotonic, stringify_matcher<clock_type>("MONOTONIC"));
+ REQUIRE_THAT(realtime, stringify_matcher<clock_type>("REALTIME"));
+ REQUIRE_THAT(hte, stringify_matcher<clock_type>("HTE"));
}
-}
-
-TEST_CASE("Uninitialized GPIO line_bulk behaves correctly", "[line][bulk]")
-{
- ::gpiod::line_bulk bulk;
- SECTION("uninitialized line_bulk is 'false'")
+ SECTION("offsets")
{
- REQUIRE_FALSE(bulk);
- }
+ offsets offs = { 2, 5, 3, 9, 8, 7 };
- SECTION("using uninitialized line_bulk throws logic_error")
- {
- REQUIRE_THROWS_AS(bulk.get(0), ::std::logic_error);
+ REQUIRE_THAT(offs, stringify_matcher<offsets>("gpiod::offsets(2, 5, 3, 9, 8, 7)"));
}
-}
-
-TEST_CASE("Cannot request the same line twice", "[line]")
-{
- mockup::probe_guard mockup_chips({ 8 });
- ::gpiod::chip chip(mockup::instance().chip_path(0));
- ::gpiod::line_request config;
-
- config.consumer = consumer.c_str();
- config.request_type = ::gpiod::line_request::DIRECTION_INPUT;
- SECTION("two separate calls to request()")
+ SECTION("values")
{
- auto line = chip.get_line(3);
+ values vals = {
+ value::ACTIVE,
+ value::INACTIVE,
+ value::ACTIVE,
+ value::ACTIVE,
+ value::INACTIVE
+ };
- REQUIRE_NOTHROW(line.request(config));
- REQUIRE_THROWS_AS(line.request(config), ::std::system_error);
+ REQUIRE_THAT(vals,
+ stringify_matcher<values>("gpiod::values(ACTIVE, INACTIVE, ACTIVE, ACTIVE, INACTIVE)"));
}
- SECTION("request the same line twice in line_bulk")
+ SECTION("value_mapping")
{
- /*
- * While a line_bulk object can hold two or more line objects
- * representing the same line - requesting it will fail.
- */
- auto lines = chip.get_lines({ 2, 3, 4, 4 });
+ value_mapping val = { 4, value::ACTIVE };
- REQUIRE_THROWS_AS(lines.request(config), ::std::system_error);
+ REQUIRE_THAT(val, stringify_matcher<value_mapping>("gpiod::value_mapping(4: ACTIVE)"));
}
-}
-
-TEST_CASE("Cannot get/set values of unrequested lines", "[line]")
-{
- mockup::probe_guard mockup_chips({ 8 });
- ::gpiod::chip chip(mockup::instance().chip_path(0));
- auto line = chip.get_line(3);
- SECTION("get value")
+ SECTION("value_mappings")
{
- REQUIRE_THROWS_AS(line.get_value(), ::std::system_error);
- }
+ value_mappings vals = { { 0, value::ACTIVE }, { 4, value::INACTIVE }, { 8, value::ACTIVE } };
- SECTION("set value")
- {
- REQUIRE_THROWS_AS(line.set_value(1), ::std::system_error);
+ REQUIRE_THAT(vals, stringify_matcher<value_mappings>(
+ "gpiod::value_mappings(gpiod::value_mapping(0: ACTIVE), gpiod::value_mapping(4: INACTIVE), gpiod::value_mapping(8: ACTIVE))"));
}
}
-TEST_CASE("Line objects can be compared")
-{
- mockup::probe_guard mockup_chips({ 8 });
- ::gpiod::chip chip(mockup::instance().chip_path(0));
- auto line1 = chip.get_line(3);
- auto line2 = chip.get_line(3);
- auto line3 = chip.get_line(4);
-
- REQUIRE(line1 == line2);
- REQUIRE(line2 != line3);
-}
+} /* namespace */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <catch2/catch.hpp>
+#include <filesystem>
+#include <gpiod.hpp>
+#include <string>
+#include <regex>
+#include <unistd.h>
+
+#include "gpiosim.hpp"
+#include "helpers.hpp"
+
+using ::gpiosim::make_sim;
+
+namespace {
+
+class symlink_guard
+{
+public:
+ symlink_guard(const ::std::filesystem::path& target,
+ const ::std::filesystem::path& link)
+ : _m_link(link)
+ {
+ ::std::filesystem::create_symlink(target, this->_m_link);
+ }
+
+ ~symlink_guard()
+ {
+ ::std::filesystem::remove(this->_m_link);
+ }
+
+private:
+ ::std::filesystem::path _m_link;
+};
+
+TEST_CASE("is_gpiochip_device() works", "[misc][chip]")
+{
+ SECTION("is_gpiochip_device() returns false for /dev/null")
+ {
+ REQUIRE_FALSE(::gpiod::is_gpiochip_device("/dev/null"));
+ }
+
+ SECTION("is_gpiochip_device() returns false for nonexistent file")
+ {
+ REQUIRE_FALSE(::gpiod::is_gpiochip_device("/dev/nonexistent"));
+ }
+
+ SECTION("is_gpiochip_device() returns true for a GPIO chip")
+ {
+ auto sim = make_sim().build();
+
+ REQUIRE(::gpiod::is_gpiochip_device(sim.dev_path()));
+ }
+
+ SECTION("is_gpiochip_device() can resolve a symlink")
+ {
+ auto sim = make_sim().build();
+ ::std::string link("/tmp/gpiod-cxx-tmp-link.");
+
+ link += ::std::to_string(::getpid());
+
+ symlink_guard link_guard(sim.dev_path(), link);
+
+ REQUIRE(::gpiod::is_gpiochip_device(link));
+ }
+}
+
+TEST_CASE("version_string() returns a valid API version", "[misc]")
+{
+ SECTION("check version_string() format")
+ {
+ REQUIRE_THAT(::gpiod::version_string(),
+ regex_matcher("^[0-9][1-9]?\\.[0-9][1-9]?([\\.0-9]?|\\-devel)$"));
+ }
+}
+
+} /* namespace */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <catch2/catch.hpp>
+#include <cstddef>
+#include <gpiod.hpp>
+#include <string>
+#include <sstream>
+
+#include "helpers.hpp"
+
+using offsets = ::gpiod::line::offsets;
+
+namespace {
+
+TEST_CASE("request_config constructor works", "[request-config]")
+{
+ SECTION("no arguments")
+ {
+ ::gpiod::request_config cfg;
+
+ REQUIRE(cfg.consumer().empty());
+ REQUIRE(cfg.event_buffer_size() == 0);
+ }
+}
+
+TEST_CASE("request_config can be moved", "[request-config]")
+{
+ ::gpiod::request_config cfg;
+
+ cfg.set_consumer("foobar").set_event_buffer_size(64);
+
+ SECTION("move constructor works")
+ {
+ auto moved(::std::move(cfg));
+ REQUIRE_THAT(moved.consumer(), Catch::Equals("foobar"));
+ REQUIRE(moved.event_buffer_size() == 64);
+ }
+
+ SECTION("move assignment operator works")
+ {
+ ::gpiod::request_config moved;
+
+ moved = ::std::move(cfg);
+
+ REQUIRE_THAT(moved.consumer(), Catch::Equals("foobar"));
+ REQUIRE(moved.event_buffer_size() == 64);
+ }
+}
+
+TEST_CASE("request_config mutators work", "[request-config]")
+{
+ ::gpiod::request_config cfg;
+
+ SECTION("set consumer")
+ {
+ cfg.set_consumer("foobar");
+ REQUIRE_THAT(cfg.consumer(), Catch::Equals("foobar"));
+ }
+
+ SECTION("set event_buffer_size")
+ {
+ cfg.set_event_buffer_size(128);
+ REQUIRE(cfg.event_buffer_size() == 128);
+ }
+}
+
+TEST_CASE("request_config stream insertion operator works", "[request-config]")
+{
+ ::gpiod::request_config cfg;
+
+ cfg.set_consumer("foobar").set_event_buffer_size(32);
+
+ ::std::stringstream buf;
+
+ buf << cfg;
+
+ ::std::string expected("gpiod::request_config(consumer='foobar', event_buffer_size=32)");
+
+ REQUIRE_THAT(buf.str(), Catch::Equals(expected));
+}
+
+} /* namespace */
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2017-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+build/
+__pycache__/
+dist/
+gpiod.egg-info/
+*.so
# SPDX-License-Identifier: GPL-2.0-or-later
-# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
-pyexec_LTLIBRARIES = gpiod.la
+EXTRA_DIST = setup.py
-gpiod_la_SOURCES = gpiodmodule.c
+if WITH_TESTS
+
+BUILD_TESTS = 1
+
+endif
+
+all-local:
+ GPIOD_VERSION_STRING=$(VERSION_STR) \
+ GPIOD_WITH_TESTS=$(BUILD_TESTS) \
+ $(PYTHON) setup.py build_ext --inplace \
+ --include-dirs=$(top_srcdir)/include/:$(top_srcdir)/tests/gpiosim/ \
+ --library-dirs=$(top_builddir)/lib/.libs/:$(top_srcdir)/tests/gpiosim/.libs/
-gpiod_la_CFLAGS = -I$(top_srcdir)/include/
-gpiod_la_CFLAGS += -Wall -Wextra -g -std=gnu89 $(PYTHON_CPPFLAGS)
-gpiod_la_LDFLAGS = -module -avoid-version
-gpiod_la_LIBADD = $(top_builddir)/lib/libgpiod.la $(PYTHON_LIBS)
+install-exec-local:
+ GPIOD_WITH_TESTS= \
+ $(PYTHON) setup.py install --prefix=$(prefix)
-SUBDIRS = .
+SUBDIRS = gpiod
if WITH_TESTS
# SPDX-License-Identifier: GPL-2.0-or-later
-# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
-EXTRA_DIST = \
- gpiodetect.py \
- gpiofind.py \
- gpioget.py \
- gpioinfo.py \
- gpiomon.py \
- gpioset.py
+EXTRA_DIST = \
+ gpiodetect.py \
+ gpiofind.py \
+ gpioget.py \
+ gpioinfo.py \
+ gpiomon.py \
+ gpioset.py
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-or-later
-# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
-'''Reimplementation of the gpiodetect tool in Python.'''
+"""Reimplementation of the gpiodetect tool in Python."""
import gpiod
import os
-if __name__ == '__main__':
- for entry in os.scandir('/dev/'):
- if gpiod.is_gpiochip_device(entry.path):
- with gpiod.Chip(entry.path) as chip:
- print('{} [{}] ({} lines)'.format(chip.name(),
- chip.label(),
- chip.num_lines()))
+from helpers import gpio_chips
+
+if __name__ == "__main__":
+ for chip in gpio_chips():
+ info = chip.get_info()
+ print("{} [{}] ({} lines)".format(info.name, info.label, info.num_lines))
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-or-later
-# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
-'''Reimplementation of the gpiofind tool in Python.'''
+"""Reimplementation of the gpiofind tool in Python."""
import gpiod
import os
import sys
-if __name__ == '__main__':
- for entry in os.scandir('/dev/'):
+if __name__ == "__main__":
+ for entry in os.scandir("/dev/"):
if gpiod.is_gpiochip_device(entry.path):
with gpiod.Chip(entry.path) as chip:
- offset = chip.find_line(sys.argv[1], unique=True)
+ offset = chip.line_offset_from_id(sys.argv[1])
if offset is not None:
- print('{} {}'.format(line.owner().name(), offset))
- sys.exit(0)
+ print("{} {}".format(chip.get_info().name, offset))
+ sys.exit(0)
sys.exit(1)
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-or-later
-# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
-'''Simplified reimplementation of the gpioget tool in Python.'''
+"""Simplified reimplementation of the gpioget tool in Python."""
import gpiod
import sys
-if __name__ == '__main__':
+from gpiod.line import Direction
+
+if __name__ == "__main__":
if len(sys.argv) < 3:
- raise TypeError('usage: gpioget.py <gpiochip> <offset1> <offset2> ...')
+ raise TypeError("usage: gpioget.py <gpiochip> <offset1> <offset2> ...")
+
+ path = sys.argv[1]
+ lines = [int(line) if line.isdigit() else line for line in sys.argv[2:]]
- with gpiod.Chip(sys.argv[1]) as chip:
- offsets = []
- for off in sys.argv[2:]:
- offsets.append(int(off))
+ request = gpiod.request_lines(
+ path,
+ consumer="gpioget.py",
+ config={tuple(lines): gpiod.LineSettings(direction=Direction.INPUT)},
+ )
- lines = chip.get_lines(offsets)
- lines.request(consumer=sys.argv[0], type=gpiod.LINE_REQ_DIR_IN)
- vals = lines.get_values()
+ vals = request.get_values()
- for val in vals:
- print(val, end=' ')
- print()
+ for val in vals:
+ print("{} ".format(val.value), end="")
+ print()
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-or-later
-# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
-'''Simplified reimplementation of the gpioinfo tool in Python.'''
+"""Simplified reimplementation of the gpioinfo tool in Python."""
import gpiod
import os
-if __name__ == '__main__':
- for entry in os.scandir('/dev/'):
- if gpiod.is_gpiochip_device(entry.path):
- with gpiod.Chip(entry.path) as chip:
- print('{} - {} lines:'.format(chip.name(), chip.num_lines()))
+from helpers import gpio_chips
- for line in gpiod.LineIter(chip):
- offset = line.offset()
- name = line.name()
- consumer = line.consumer()
- direction = line.direction()
- active_low = line.is_active_low()
+if __name__ == "__main__":
+ for chip in gpio_chips():
+ cinfo = chip.get_info()
+ print("{} - {} lines:".format(cinfo.name, cinfo.num_lines))
- print('\tline {:>3}: {:>18} {:>12} {:>8} {:>10}'.format(
- offset,
- 'unnamed' if name is None else name,
- 'unused' if consumer is None else consumer,
- 'input' if direction == gpiod.Line.DIRECTION_INPUT else 'output',
- 'active-low' if active_low else 'active-high'))
+ for offset in range(0, cinfo.num_lines):
+ linfo = chip.get_line_info(offset)
+ is_input = linfo.direction == gpiod.line.Direction.INPUT
+ print(
+ "\tline {:>3}: {:>18} {:>12} {:>8} {:>10}".format(
+ linfo.offset,
+ linfo.name or "unnamed",
+ linfo.consumer or "unused",
+ "input" if is_input else "output",
+ "active-low" if linfo.active_low else "active-high",
+ )
+ )
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-or-later
-# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
-'''Simplified reimplementation of the gpiomon tool in Python.'''
+"""Simplified reimplementation of the gpiomon tool in Python."""
import gpiod
import sys
-if __name__ == '__main__':
- def print_event(event):
- if event.type == gpiod.LineEvent.RISING_EDGE:
- evstr = ' RISING EDGE'
- elif event.type == gpiod.LineEvent.FALLING_EDGE:
- evstr = 'FALLING EDGE'
- else:
- raise TypeError('Invalid event type')
-
- print('event: {} offset: {} timestamp: [{}.{}]'.format(evstr,
- event.source.offset(),
- event.sec, event.nsec))
+from gpiod.line import Edge
+if __name__ == "__main__":
if len(sys.argv) < 3:
- raise TypeError('usage: gpiomon.py <gpiochip> <offset1> <offset2> ...')
-
- with gpiod.Chip(sys.argv[1]) as chip:
- offsets = []
- for off in sys.argv[2:]:
- offsets.append(int(off))
-
- lines = chip.get_lines(offsets)
- lines.request(consumer=sys.argv[0], type=gpiod.LINE_REQ_EV_BOTH_EDGES)
-
- try:
- while True:
- ev_lines = lines.event_wait(sec=1)
- if ev_lines:
- for line in ev_lines:
- event = line.event_read()
- print_event(event)
- except KeyboardInterrupt:
- sys.exit(130)
+ raise TypeError("usage: gpiomon.py <gpiochip> <offset1> <offset2> ...")
+
+ path = sys.argv[1]
+ lines = [int(line) if line.isdigit() else line for line in sys.argv[2:]]
+
+ with gpiod.request_lines(
+ path,
+ consumer="gpiomon.py",
+ config={tuple(lines): gpiod.LineSettings(edge_detection=Edge.BOTH)},
+ ) as request:
+ while True:
+ for event in request.read_edge_event():
+ print(event)
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-or-later
-# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
-'''Simplified reimplementation of the gpioset tool in Python.'''
+"""Simplified reimplementation of the gpioset tool in Python."""
import gpiod
import sys
-if __name__ == '__main__':
+from gpiod.line import Direction, Value
+
+if __name__ == "__main__":
if len(sys.argv) < 3:
- raise TypeError('usage: gpioset.py <gpiochip> <offset1>=<value1> ...')
-
- with gpiod.Chip(sys.argv[1]) as chip:
- offsets = []
- values = []
- for arg in sys.argv[2:]:
- arg = arg.split('=')
- offsets.append(int(arg[0]))
- values.append(int(arg[1]))
-
- lines = chip.get_lines(offsets)
- lines.request(consumer=sys.argv[0], type=gpiod.LINE_REQ_DIR_OUT)
- lines.set_values(values)
- input()
+ raise TypeError(
+ "usage: gpioset.py <gpiochip> <offset1>=<value1> <offset2>=<value2> ..."
+ )
+
+ path = sys.argv[1]
+
+ def parse_value(arg):
+ x, y = arg.split("=")
+ return (x, Value(int(y)))
+
+ lvs = [parse_value(arg) for arg in sys.argv[2:]]
+ lines = [x[0] for x in lvs]
+ values = dict(lvs)
+
+ request = gpiod.request_lines(
+ path,
+ consumer="gpioset.py",
+ config={tuple(lines): gpiod.LineSettings(direction=Direction.OUTPUT)},
+ )
+
+ vals = request.set_values(values)
+
+ input()
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+import gpiod
+import os
+
+
+def gpio_chips():
+ for path in [
+ entry.path
+ for entry in os.scandir("/dev/")
+ if gpiod.is_gpiochip_device(entry.path)
+ ]:
+ with gpiod.Chip(path) as chip:
+ yield chip
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+SUBDIRS = ext
+
+EXTRA_DIST = \
+ chip_info.py \
+ chip.py \
+ edge_event.py \
+ exception.py \
+ info_event.py \
+ __init__.py \
+ internal.py \
+ line_info.py \
+ line.py \
+ line_request.py \
+ line_settings.py
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+"""
+Python bindings for libgpiod.
+
+This module wraps the native C API of libgpiod in a set of python classes.
+"""
+
+from . import _ext
+from . import line
+from .chip import Chip
+from .chip_info import ChipInfo
+from .edge_event import EdgeEvent
+from .exception import ChipClosedError, RequestReleasedError
+from .info_event import InfoEvent
+from .line_request import LineRequest
+from .line_settings import LineSettings
+
+__version__ = _ext.__version__
+
+
+def is_gpiochip_device(path: str) -> bool:
+ """
+ Check if the file pointed to by path is a GPIO chip character device.
+
+ Args:
+ path
+ Path to the file that should be checked.
+
+ Returns:
+ Returns true if so, False otherwise.
+ """
+ return _ext.is_gpiochip_device(path)
+
+
+def request_lines(path: str, *args, **kwargs) -> LineRequest:
+ """
+ Open a GPIO chip pointed to by 'path', request lines according to the
+ configuration arguments, close the chip and return the request object.
+
+ Args:
+ path
+ Path to the GPIO character device file.
+ *args
+ **kwargs
+ See Chip.request_lines() for configuration arguments.
+
+ Returns:
+ Returns a new LineRequest object.
+ """
+ with Chip(path) as chip:
+ return chip.request_lines(*args, **kwargs)
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+from . import _ext
+from .chip_info import ChipInfo
+from .exception import ChipClosedError
+from .info_event import InfoEvent
+from .internal import poll_fd
+from .line_info import LineInfo
+from .line_settings import LineSettings, _line_settings_to_ext
+from .line_request import LineRequest
+from collections import Counter
+from datetime import timedelta
+from errno import ENOENT
+from select import select
+from typing import Union, Optional
+
+
+class Chip:
+ """
+ Represents a GPIO chip.
+
+ Chip object manages all resources associated with the GPIO chip it represents.
+
+ The gpiochip device file is opened during the object's construction. The Chip
+ object's constructor takes the path to the GPIO chip device file
+ as the only argument.
+
+ Callers must close the chip by calling the close() method when it's no longer
+ used.
+
+ Example:
+
+ chip = gpiod.Chip(\"/dev/gpiochip0\")
+ do_something(chip)
+ chip.close()
+
+ The gpiod.Chip class also supports controlled execution ('with' statement).
+
+ Example:
+
+ with gpiod.Chip(path="/dev/gpiochip0") as chip:
+ do_something(chip)
+ """
+
+ def __init__(self, path: str):
+ """
+ Open a GPIO device.
+
+ Args:
+ path:
+ Path to the GPIO character device file.
+ """
+ self._chip = _ext.Chip(path)
+ self._info = None
+
+ def __bool__(self) -> bool:
+ """
+ Boolean conversion for GPIO chips.
+
+ Returns:
+ True if the chip is open and False if it's closed.
+ """
+ return True if self._chip else False
+
+ def __enter__(self):
+ """
+ Controlled execution enter callback.
+ """
+ self._check_closed()
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback) -> None:
+ """
+ Controlled execution exit callback.
+ """
+ self.close()
+
+ def _check_closed(self) -> None:
+ if not self._chip:
+ raise ChipClosedError()
+
+ def close(self) -> None:
+ """
+ Close the associated GPIO chip descriptor. The chip object must no
+ longer be used after this method is called.
+ """
+ self._check_closed()
+ self._chip.close()
+ self._chip = None
+
+ def get_info(self) -> ChipInfo:
+ """
+ Get the information about the chip.
+
+ Returns:
+ New gpiod.ChipInfo object.
+ """
+ self._check_closed()
+
+ if not self._info:
+ self._info = self._chip.get_info()
+
+ return self._info
+
+ def line_offset_from_id(self, id: Union[str, int]) -> int:
+ """
+ Map a line's identifier to its offset within the chip.
+
+ Args:
+ id:
+ Name of the GPIO line, its offset as a string or its offset as an
+ integer.
+
+ Returns:
+ If id is an integer - it's returned as is (unless it's out of range
+ for this chip). If it's a string, the method tries to interpret it as
+ the name of the line first and tries too perform a name lookup within
+ the chip. If it fails, it tries to convert the string to an integer
+ and check if it represents a valid offset within the chip and if
+ so - returns it.
+ """
+ self._check_closed()
+
+ if not isinstance(id, int):
+ try:
+ return self._chip.line_offset_from_id(id)
+ except OSError as ex:
+ if ex.errno == ENOENT:
+ try:
+ offset = int(id)
+ except ValueError:
+ raise ex
+ else:
+ raise ex
+ else:
+ offset = id
+
+ if offset >= self.get_info().num_lines:
+ raise ValueError("line offset of out range")
+
+ return offset
+
+ def _get_line_info(self, line: Union[int, str], watch: bool) -> LineInfo:
+ self._check_closed()
+ return self._chip.get_line_info(self.line_offset_from_id(line), watch)
+
+ def get_line_info(self, line: Union[int, str]) -> LineInfo:
+ """
+ Get the snapshot of information about the line at given offset.
+
+ Args:
+ line:
+ Offset or name of the GPIO line to get information for.
+
+ Returns:
+ New LineInfo object.
+ """
+ return self._get_line_info(line, watch=False)
+
+ def watch_line_info(self, line: Union[int, str]) -> LineInfo:
+ """
+ Get the snapshot of information about the line at given offset and
+ start watching it for future changes.
+
+ Args:
+ line:
+ Offset or name of the GPIO line to get information for.
+
+ Returns:
+ New gpiod.LineInfo object.
+ """
+ return self._get_line_info(line, watch=True)
+
+ def unwatch_line_info(self, line: Union[int, str]) -> None:
+ """
+ Stop watching a line for status changes.
+
+ Args:
+ line:
+ Offset or name of the line to stop watching.
+ """
+ self._check_closed()
+ return self._chip.unwatch_line_info(self.line_offset_from_id(line))
+
+ def wait_info_event(
+ self, timeout: Optional[Union[timedelta, float]] = None
+ ) -> bool:
+ """
+ Wait for line status change events on any of the watched lines on the
+ chip.
+
+ Args:
+ timeout:
+ Wait time limit represented as either a datetime.timedelta object
+ or the number of seconds stored in a float.
+
+ Returns:
+ True if an info event is ready to be read from the chip, False if the
+ wait timed out without any events.
+ """
+ self._check_closed()
+
+ return poll_fd(self.fd, timeout)
+
+ def read_info_event(self) -> InfoEvent:
+ """
+ Read a single line status change event from the chip.
+
+ Returns:
+ New gpiod.InfoEvent object.
+
+ Note:
+ This function may block if there are no available events in the queue.
+ """
+ self._check_closed()
+ return self._chip.read_info_event()
+
+ def request_lines(
+ self,
+ config: dict[tuple[Union[int, str]], Optional[LineSettings]],
+ consumer: Optional[str] = None,
+ event_buffer_size: Optional[int] = None,
+ ) -> LineRequest:
+ """
+ Request a set of lines for exclusive usage.
+
+ Args:
+ config:
+ Dictionary mapping offsets or names (or tuples thereof) to
+ LineSettings. If None is passed as the value of the mapping,
+ default settings are used.
+ consumer:
+ Consumer string to use for this request.
+ event_buffer_size:
+ Size of the kernel edge event buffer to configure for this request.
+
+ Returns:
+ New LineRequest object.
+ """
+ self._check_closed()
+
+ line_cfg = _ext.LineConfig()
+
+ # Sanitize lines - don't allow offset repeatitions or offset-name conflicts.
+ for offset, count in Counter(
+ [
+ self.line_offset_from_id(line)
+ for line in (
+ lambda t: [
+ j for i in (t) for j in (i if isinstance(i, tuple) else (i,))
+ ]
+ )(tuple(config.keys()))
+ ]
+ ).items():
+ if count != 1:
+ raise ValueError(
+ "line must be configured exactly once - offset {} repeats".format(
+ offset
+ )
+ )
+
+ for lines, settings in config.items():
+ offsets = list()
+ name_map = dict()
+ offset_map = dict()
+
+ if isinstance(lines, int) or isinstance(lines, str):
+ lines = (lines,)
+
+ for line in lines:
+ offset = self.line_offset_from_id(line)
+ offsets.append(offset)
+ if isinstance(line, str):
+ name_map[line] = offset
+ offset_map[offset] = line
+
+ line_cfg.add_line_settings(
+ offsets, _line_settings_to_ext(settings or LineSettings())
+ )
+
+ req_internal = self._chip.request_lines(line_cfg, consumer, event_buffer_size)
+ request = LineRequest(req_internal)
+
+ request._offsets = req_internal.offsets
+ request._name_map = name_map
+ request._offset_map = offset_map
+
+ request._lines = [
+ offset_map[off] if off in offset_map else off for off in request.offsets
+ ]
+
+ return request
+
+ def __repr__(self) -> str:
+ """
+ Return a string that can be used to re-create this chip object.
+ """
+ if not self._chip:
+ return "<Chip CLOSED>"
+
+ return 'Chip("{}")'.format(self.path)
+
+ def __str__(self) -> str:
+ """
+ Return a user-friendly, human-readable description of this chip.
+ """
+ if not self._chip:
+ return "<Chip CLOSED>"
+
+ return '<Chip path="{}" fd={} info={}>'.format(
+ self.path, self.fd, self.get_info()
+ )
+
+ @property
+ def path(self) -> str:
+ """
+ Filesystem path used to open this chip.
+ """
+ self._check_closed()
+ return self._chip.path
+
+ @property
+ def fd(self) -> int:
+ """
+ File descriptor associated with this chip.
+ """
+ self._check_closed()
+ return self._chip.fd
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+
+from dataclasses import dataclass
+
+
+@dataclass(frozen=True, repr=False)
+class ChipInfo:
+ """
+ Snapshot of a chip's status.
+ """
+
+ name: str
+ label: str
+ num_lines: int
+
+ def __str__(self):
+ return '<ChipInfo name="{}" label="{}" num_lines={}>'.format(
+ self.name, self.label, self.num_lines
+ )
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+from . import _ext
+from dataclasses import dataclass
+from enum import Enum
+
+
+@dataclass(frozen=True, init=False, repr=False)
+class EdgeEvent:
+ """
+ Immutable object containing data about a single edge event.
+ """
+
+ class Type(Enum):
+ RISING_EDGE = _ext.EDGE_EVENT_TYPE_RISING
+ FALLING_EDGE = _ext.EDGE_EVENT_TYPE_FALLING
+
+ event_type: Type
+ timestamp_ns: int
+ line_offset: int
+ global_seqno: int
+ line_seqno: int
+
+ def __init__(
+ self,
+ event_type: int,
+ timestamp_ns: int,
+ line_offset: int,
+ global_seqno: int,
+ line_seqno: int,
+ ):
+ object.__setattr__(self, "event_type", EdgeEvent.Type(event_type))
+ object.__setattr__(self, "timestamp_ns", timestamp_ns)
+ object.__setattr__(self, "line_offset", line_offset)
+ object.__setattr__(self, "global_seqno", global_seqno)
+ object.__setattr__(self, "line_seqno", line_seqno)
+
+ def __str__(self):
+ return "<EdgeEvent type={} timestamp_ns={} line_offset={} global_seqno={} line_seqno={}>".format(
+ self.event_type,
+ self.timestamp_ns,
+ self.line_offset,
+ self.global_seqno,
+ self.line_seqno,
+ )
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+
+class ChipClosedError(Exception):
+ """
+ Error raised when an already closed chip is used.
+ """
+
+ def __init__(self):
+ super().__init__("I/O operation on closed chip")
+
+
+class RequestReleasedError(Exception):
+ """
+ Error raised when a released request is used.
+ """
+
+ def __init__(self):
+ super().__init__("GPIO lines have been released")
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+EXTRA_DIST = \
+ chip.c \
+ common.c \
+ internal.h \
+ line-config.c \
+ line-settings.c \
+ module.c \
+ request.c
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include "internal.h"
+
+typedef struct {
+ PyObject_HEAD;
+ struct gpiod_chip *chip;
+} chip_object;
+
+static int
+chip_init(chip_object *self, PyObject *args, PyObject *Py_UNUSED(ignored))
+{
+ struct gpiod_chip *chip;
+ char *path;
+ int ret;
+
+ ret = PyArg_ParseTuple(args, "s", &path);
+ if (!ret)
+ return -1;
+
+ Py_BEGIN_ALLOW_THREADS;
+ chip = gpiod_chip_open(path);
+ Py_END_ALLOW_THREADS;
+ if (!chip) {
+ Py_gpiod_SetErrFromErrno();
+ return -1;
+ }
+
+ self->chip = chip;
+
+ return 0;
+}
+
+static void chip_finalize(chip_object *self)
+{
+ if (self->chip)
+ PyObject_CallMethod((PyObject *)self, "close", "");
+}
+
+static PyObject *chip_path(chip_object *self, void *Py_UNUSED(ignored))
+{
+ return PyUnicode_FromString(gpiod_chip_get_path(self->chip));
+}
+
+static PyObject *chip_fd(chip_object *self, void *Py_UNUSED(ignored))
+{
+ return PyLong_FromLong(gpiod_chip_get_fd(self->chip));
+}
+
+static PyGetSetDef chip_getset[] = {
+ {
+ .name = "path",
+ .get = (getter)chip_path,
+ },
+ {
+ .name = "fd",
+ .get = (getter)chip_fd,
+ },
+ { }
+};
+
+static PyObject *chip_close(chip_object *self, PyObject *Py_UNUSED(ignored))
+{
+ Py_BEGIN_ALLOW_THREADS;
+ gpiod_chip_close(self->chip);
+ Py_END_ALLOW_THREADS;
+ self->chip = NULL;
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *chip_get_info(chip_object *self, PyObject *Py_UNUSED(ignored))
+{
+ struct gpiod_chip_info *info;
+ PyObject *type, *ret;
+
+ type = Py_gpiod_GetGlobalType("ChipInfo");
+ if (!type)
+ return NULL;
+
+ info = gpiod_chip_get_info(self->chip);
+ if (!info)
+ return PyErr_SetFromErrno(PyExc_OSError);
+
+ ret = PyObject_CallFunction(type, "ssI",
+ gpiod_chip_info_get_name(info),
+ gpiod_chip_info_get_label(info),
+ gpiod_chip_info_get_num_lines(info));
+ gpiod_chip_info_free(info);
+ return ret;
+}
+
+static PyObject *make_line_info(struct gpiod_line_info *info)
+{
+ PyObject *type;
+
+ type = Py_gpiod_GetGlobalType("LineInfo");
+ if (!type)
+ return NULL;
+
+ return PyObject_CallFunction(type, "IsOsiOiiiiOk",
+ gpiod_line_info_get_offset(info),
+ gpiod_line_info_get_name(info),
+ gpiod_line_info_is_used(info) ?
+ Py_True : Py_False,
+ gpiod_line_info_get_consumer(info),
+ gpiod_line_info_get_direction(info),
+ gpiod_line_info_is_active_low(info) ?
+ Py_True : Py_False,
+ gpiod_line_info_get_bias(info),
+ gpiod_line_info_get_drive(info),
+ gpiod_line_info_get_edge_detection(info),
+ gpiod_line_info_get_event_clock(info),
+ gpiod_line_info_is_debounced(info) ?
+ Py_True : Py_False,
+ gpiod_line_info_get_debounce_period_us(info));
+}
+
+static PyObject *chip_get_line_info(chip_object *self, PyObject *args)
+{
+ struct gpiod_line_info *info;
+ unsigned int offset;
+ PyObject *info_obj;
+ int ret, watch;
+
+ ret = PyArg_ParseTuple(args, "Ip", &offset, &watch);
+ if (!ret)
+ return NULL;
+
+ Py_BEGIN_ALLOW_THREADS;
+ if (watch)
+ info = gpiod_chip_watch_line_info(self->chip, offset);
+ else
+ info = gpiod_chip_get_line_info(self->chip, offset);
+ Py_END_ALLOW_THREADS;
+ if (!info)
+ return Py_gpiod_SetErrFromErrno();
+
+ info_obj = make_line_info(info);
+ gpiod_line_info_free(info);
+ return info_obj;
+}
+
+static PyObject *
+chip_unwatch_line_info(chip_object *self, PyObject *args)
+{
+ unsigned int offset;
+ int ret;
+
+ ret = PyArg_ParseTuple(args, "I", &offset);
+ if (!ret)
+ return NULL;
+
+ Py_BEGIN_ALLOW_THREADS;
+ ret = gpiod_chip_unwatch_line_info(self->chip, offset);
+ Py_END_ALLOW_THREADS;
+ if (ret)
+ return Py_gpiod_SetErrFromErrno();
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *
+chip_read_info_event(chip_object *self, PyObject *Py_UNUSED(ignored))
+{
+ PyObject *type, *info_obj, *event_obj;
+ struct gpiod_info_event *event;
+ struct gpiod_line_info *info;
+
+ type = Py_gpiod_GetGlobalType("InfoEvent");
+ if (!type)
+ return NULL;
+
+ Py_BEGIN_ALLOW_THREADS;
+ event = gpiod_chip_read_info_event(self->chip);
+ Py_END_ALLOW_THREADS;
+ if (!event)
+ return Py_gpiod_SetErrFromErrno();
+
+ info = gpiod_info_event_get_line_info(event);
+
+ info_obj = make_line_info(info);
+ if (!info_obj) {
+ gpiod_info_event_free(event);
+ return NULL;
+ }
+
+ event_obj = PyObject_CallFunction(type, "iKO",
+ gpiod_info_event_get_event_type(event),
+ gpiod_info_event_get_timestamp_ns(event),
+ info_obj);
+ Py_DECREF(info_obj);
+ gpiod_info_event_free(event);
+ return event_obj;
+}
+
+static PyObject *chip_line_offset_from_id(chip_object *self, PyObject *args)
+{
+ int ret, offset;
+ char *name;
+
+ ret = PyArg_ParseTuple(args, "s", &name);
+ if (!ret)
+ return NULL;
+
+ Py_BEGIN_ALLOW_THREADS;
+ offset = gpiod_chip_get_line_offset_from_name(self->chip, name);
+ Py_END_ALLOW_THREADS;
+ if (offset < 0)
+ return Py_gpiod_SetErrFromErrno();
+
+ return PyLong_FromLong(offset);
+}
+
+static struct gpiod_request_config *
+make_request_config(PyObject *consumer_obj, PyObject *event_buffer_size_obj)
+{
+ struct gpiod_request_config *req_cfg;
+ size_t event_buffer_size;
+ const char *consumer;
+
+ req_cfg = gpiod_request_config_new();
+ if (!req_cfg) {
+ Py_gpiod_SetErrFromErrno();
+ return NULL;
+ }
+
+ if (consumer_obj != Py_None) {
+ consumer = PyUnicode_AsUTF8(consumer_obj);
+ if (!consumer) {
+ gpiod_request_config_free(req_cfg);
+ return NULL;
+ }
+
+ gpiod_request_config_set_consumer(req_cfg, consumer);
+ }
+
+ if (event_buffer_size_obj != Py_None) {
+ event_buffer_size = PyLong_AsSize_t(event_buffer_size_obj);
+ if (PyErr_Occurred()) {
+ gpiod_request_config_free(req_cfg);
+ return NULL;
+ }
+
+ gpiod_request_config_set_event_buffer_size(req_cfg,
+ event_buffer_size);
+ }
+
+ return req_cfg;
+}
+
+static PyObject *chip_request_lines(chip_object *self, PyObject *args)
+{
+ PyObject *line_config, *consumer, *event_buffer_size, *req_obj;
+ struct gpiod_request_config *req_cfg;
+ struct gpiod_line_config *line_cfg;
+ struct gpiod_line_request *request;
+ int ret;
+
+ ret = PyArg_ParseTuple(args, "OOO",
+ &line_config, &consumer, &event_buffer_size);
+ if (!ret)
+ return NULL;
+
+ line_cfg = Py_gpiod_LineConfigGetData(line_config);
+ if (!line_cfg)
+ return NULL;
+
+ req_cfg = make_request_config(consumer, event_buffer_size);
+ if (!req_cfg)
+ return NULL;
+
+ Py_BEGIN_ALLOW_THREADS;
+ request = gpiod_chip_request_lines(self->chip, req_cfg, line_cfg);
+ Py_END_ALLOW_THREADS;
+ gpiod_request_config_free(req_cfg);
+ if (!request)
+ return Py_gpiod_SetErrFromErrno();
+
+ req_obj = Py_gpiod_MakeRequestObject(request,
+ gpiod_request_config_get_event_buffer_size(req_cfg));
+ if (!req_obj)
+ gpiod_line_request_release(request);
+
+ return req_obj;
+}
+
+static PyMethodDef chip_methods[] = {
+ {
+ .ml_name = "close",
+ .ml_meth = (PyCFunction)chip_close,
+ .ml_flags = METH_NOARGS,
+ },
+ {
+ .ml_name = "get_info",
+ .ml_meth = (PyCFunction)chip_get_info,
+ .ml_flags = METH_NOARGS,
+ },
+ {
+ .ml_name = "get_line_info",
+ .ml_meth = (PyCFunction)chip_get_line_info,
+ .ml_flags = METH_VARARGS,
+ },
+ {
+ .ml_name = "unwatch_line_info",
+ .ml_meth = (PyCFunction)chip_unwatch_line_info,
+ .ml_flags = METH_VARARGS,
+ },
+ {
+ .ml_name = "read_info_event",
+ .ml_meth = (PyCFunction)chip_read_info_event,
+ .ml_flags = METH_NOARGS,
+ },
+ {
+ .ml_name = "line_offset_from_id",
+ .ml_meth = (PyCFunction)chip_line_offset_from_id,
+ .ml_flags = METH_VARARGS,
+ },
+ {
+ .ml_name = "request_lines",
+ .ml_meth = (PyCFunction)chip_request_lines,
+ .ml_flags = METH_VARARGS,
+ },
+ { }
+};
+
+PyTypeObject chip_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "gpiod._ext.Chip",
+ .tp_basicsize = sizeof(chip_object),
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_new = PyType_GenericNew,
+ .tp_init = (initproc)chip_init,
+ .tp_finalize = (destructor)chip_finalize,
+ .tp_dealloc = (destructor)Py_gpiod_dealloc,
+ .tp_getset = chip_getset,
+ .tp_methods = chip_methods,
+};
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include "internal.h"
+
+/* Generic dealloc callback for all gpiod objects. */
+void Py_gpiod_dealloc(PyObject *self)
+{
+ int ret;
+
+ ret = PyObject_CallFinalizerFromDealloc(self);
+ if (ret < 0)
+ return;
+
+ PyObject_Del(self);
+}
+
+PyObject *_Py_gpiod_SetErrFromErrno(const char *filename)
+{
+ PyObject *exc;
+
+ if (errno == ENOMEM)
+ return PyErr_NoMemory();
+
+ switch (errno) {
+ case EINVAL:
+ exc = PyExc_ValueError;
+ break;
+ case EOPNOTSUPP:
+ exc = PyExc_NotImplementedError;
+ break;
+ case EPIPE:
+ exc = PyExc_BrokenPipeError;
+ break;
+ case ECHILD:
+ exc = PyExc_ChildProcessError;
+ break;
+ case EINTR:
+ exc = PyExc_InterruptedError;
+ break;
+ case EEXIST:
+ exc = PyExc_FileExistsError;
+ break;
+ case ENOENT:
+ exc = PyExc_FileNotFoundError;
+ break;
+ case EISDIR:
+ exc = PyExc_IsADirectoryError;
+ break;
+ case ENOTDIR:
+ exc = PyExc_NotADirectoryError;
+ break;
+ case EPERM:
+ exc = PyExc_PermissionError;
+ break;
+ case ETIMEDOUT:
+ exc = PyExc_TimeoutError;
+ break;
+ default:
+ exc = PyExc_OSError;
+ break;
+ }
+
+ return PyErr_SetFromErrnoWithFilename(exc, filename);
+}
+
+PyObject *Py_gpiod_GetGlobalType(const char *type_name)
+{
+ PyObject *globals;
+
+ globals = PyEval_GetGlobals();
+ if (!globals)
+ return NULL;
+
+ return PyDict_GetItemString(globals, type_name);
+}
+
+unsigned int Py_gpiod_PyLongAsUnsignedInt(PyObject *pylong)
+{
+ unsigned long tmp;
+
+ tmp = PyLong_AsUnsignedLong(pylong);
+ if (PyErr_Occurred())
+ return 0;
+
+ if (tmp > UINT_MAX) {
+ PyErr_SetString(PyExc_ValueError, "value exceeding UINT_MAX");
+ return 0;
+ }
+
+ return tmp;
+}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/* SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+#ifndef __LIBGPIOD_PYTHON_MODULE_H__
+#define __LIBGPIOD_PYTHON_MODULE_H__
+
+#include <gpiod.h>
+#include <Python.h>
+
+PyObject *_Py_gpiod_SetErrFromErrno(const char *filename);
+#define Py_gpiod_SetErrFromErrno() _Py_gpiod_SetErrFromErrno(__FILE__)
+
+PyObject *Py_gpiod_GetGlobalType(const char *type_name);
+unsigned int Py_gpiod_PyLongAsUnsignedInt(PyObject *pylong);
+void Py_gpiod_dealloc(PyObject *self);
+PyObject *Py_gpiod_MakeRequestObject(struct gpiod_line_request *request,
+ size_t event_buffer_size);
+struct gpiod_line_config *Py_gpiod_LineConfigGetData(PyObject *obj);
+struct gpiod_line_settings *Py_gpiod_LineSettingsGetData(PyObject *obj);
+
+#endif /* __LIBGPIOD_PYTHON_MODULE_H__ */
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include "internal.h"
+
+typedef struct {
+ PyObject_HEAD;
+ struct gpiod_line_config *cfg;
+} line_config_object;
+
+static int line_config_init(line_config_object *self,
+ PyObject *Py_UNUSED(args), PyObject *Py_UNUSED(ignored))
+{
+ self->cfg = gpiod_line_config_new();
+ if (!self->cfg) {
+ Py_gpiod_SetErrFromErrno();
+ return -1;
+ }
+
+ return 0;
+}
+
+static void line_config_finalize(line_config_object *self)
+{
+ if (self->cfg)
+ gpiod_line_config_free(self->cfg);
+}
+
+static unsigned int *make_offsets(PyObject *obj, Py_ssize_t len)
+{
+ unsigned int *offsets;
+ PyObject *offset;
+ Py_ssize_t i;
+
+ offsets = PyMem_Calloc(len, sizeof(unsigned int));
+ if (!offsets)
+ return (unsigned int *)PyErr_NoMemory();
+
+ for (i = 0; i < len; i++) {
+ offset = PyList_GetItem(obj, i);
+ if (!offset) {
+ PyMem_Free(offsets);
+ return NULL;
+ }
+
+ offsets[i] = Py_gpiod_PyLongAsUnsignedInt(offset);
+ if (PyErr_Occurred()) {
+ PyMem_Free(offsets);
+ return NULL;
+ }
+ }
+
+ return offsets;
+}
+
+static PyObject *
+line_config_add_line_settings(line_config_object *self, PyObject *args)
+{
+ PyObject *offsets_obj, *settings_obj;
+ struct gpiod_line_settings *settings;
+ unsigned int *offsets;
+ Py_ssize_t num_offsets;
+ int ret;
+
+ ret = PyArg_ParseTuple(args, "OO", &offsets_obj, &settings_obj);
+ if (!ret)
+ return NULL;
+
+ num_offsets = PyObject_Size(offsets_obj);
+ if (num_offsets < 0)
+ return NULL;
+
+ offsets = make_offsets(offsets_obj, num_offsets);
+ if (!offsets)
+ return NULL;
+
+ settings = Py_gpiod_LineSettingsGetData(settings_obj);
+ if (!settings) {
+ PyMem_Free(offsets);
+ return NULL;
+ }
+
+ ret = gpiod_line_config_add_line_settings(self->cfg, offsets,
+ num_offsets, settings);
+ PyMem_Free(offsets);
+ if (ret)
+ return Py_gpiod_SetErrFromErrno();
+
+ Py_RETURN_NONE;
+}
+
+static PyMethodDef line_config_methods[] = {
+ {
+ .ml_name = "add_line_settings",
+ .ml_meth = (PyCFunction)line_config_add_line_settings,
+ .ml_flags = METH_VARARGS,
+ },
+ { }
+};
+
+PyTypeObject line_config_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "gpiod._ext.LineConfig",
+ .tp_basicsize = sizeof(line_config_object),
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_new = PyType_GenericNew,
+ .tp_init = (initproc)line_config_init,
+ .tp_finalize = (destructor)line_config_finalize,
+ .tp_dealloc = (destructor)Py_gpiod_dealloc,
+ .tp_methods = line_config_methods,
+};
+
+struct gpiod_line_config *Py_gpiod_LineConfigGetData(PyObject *obj)
+{
+ line_config_object *line_cfg;
+ PyObject *type;
+
+ type = PyObject_Type(obj);
+ if (!type)
+ return NULL;
+
+ if ((PyTypeObject *)type != &line_config_type) {
+ PyErr_SetString(PyExc_TypeError,
+ "not a gpiod._ext.LineConfig object");
+ Py_DECREF(type);
+ return NULL;
+ }
+ Py_DECREF(type);
+
+ line_cfg = (line_config_object *)obj;
+
+ return line_cfg->cfg;
+}
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include "internal.h"
+
+typedef struct {
+ PyObject_HEAD;
+ struct gpiod_line_settings *settings;
+} line_settings_object;
+
+static int set_int_prop(struct gpiod_line_settings *settings, int val,
+ int (*func)(struct gpiod_line_settings *, int))
+{
+ int ret;
+
+ ret = func(settings, val);
+ if (ret) {
+ Py_gpiod_SetErrFromErrno();
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+line_settings_init(line_settings_object *self, PyObject *args, PyObject *kwargs)
+{
+ static char *kwlist[] = {
+ "direction",
+ "edge_detection",
+ "bias",
+ "drive",
+ "active_low",
+ "debounce_period",
+ "event_clock",
+ "output_value",
+ NULL
+ };
+
+ int direction, edge, bias, drive, active_low, event_clock, output_value,
+ ret;
+ unsigned long debounce_period;
+
+ ret = PyArg_ParseTupleAndKeywords(args, kwargs, "IIIIpkII", kwlist,
+ &direction, &edge, &bias, &drive, &active_low,
+ &debounce_period, &event_clock, &output_value);
+ if (!ret)
+ return -1;
+
+ self->settings = gpiod_line_settings_new();
+ if (!self->settings) {
+ Py_gpiod_SetErrFromErrno();
+ return -1;
+ }
+
+ ret = set_int_prop(self->settings, direction,
+ gpiod_line_settings_set_direction);
+ if (ret)
+ return -1;
+
+ ret = set_int_prop(self->settings, edge,
+ gpiod_line_settings_set_edge_detection);
+ if (ret)
+ return -1;
+
+ ret = set_int_prop(self->settings, bias,
+ gpiod_line_settings_set_bias);
+ if (ret)
+ return -1;
+
+ ret = set_int_prop(self->settings, drive,
+ gpiod_line_settings_set_drive);
+ if (ret)
+ return -1;
+
+ gpiod_line_settings_set_active_low(self->settings, active_low);
+ gpiod_line_settings_set_debounce_period_us(self->settings,
+ debounce_period);
+
+ ret = set_int_prop(self->settings, edge,
+ gpiod_line_settings_set_edge_detection);
+ if (ret)
+ return -1;
+
+ ret = set_int_prop(self->settings, output_value,
+ gpiod_line_settings_set_output_value);
+ if (ret)
+ return -1;
+
+ return 0;
+}
+
+static void line_settings_finalize(line_settings_object *self)
+{
+ if (self->settings)
+ gpiod_line_settings_free(self->settings);
+}
+
+PyTypeObject line_settings_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "gpiod._ext.LineSettings",
+ .tp_basicsize = sizeof(line_settings_object),
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_new = PyType_GenericNew,
+ .tp_init = (initproc)line_settings_init,
+ .tp_finalize = (destructor)line_settings_finalize,
+ .tp_dealloc = (destructor)Py_gpiod_dealloc,
+};
+
+struct gpiod_line_settings *Py_gpiod_LineSettingsGetData(PyObject *obj)
+{
+ line_settings_object *settings;
+ PyObject *type;
+
+ type = PyObject_Type(obj);
+ if (!type)
+ return NULL;
+
+ if ((PyTypeObject *)type != &line_settings_type) {
+ PyErr_SetString(PyExc_TypeError,
+ "not a gpiod._ext.LineSettings object");
+ Py_DECREF(type);
+ return NULL;
+ }
+ Py_DECREF(type);
+
+ settings = (line_settings_object *)obj;
+
+ return settings->settings;
+}
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <gpiod.h>
+#include <Python.h>
+
+struct module_const {
+ const char *name;
+ long val;
+};
+
+static const struct module_const module_constants[] = {
+ {
+ .name = "VALUE_INACTIVE",
+ .val = GPIOD_LINE_VALUE_INACTIVE,
+ },
+ {
+ .name = "VALUE_ACTIVE",
+ .val = GPIOD_LINE_VALUE_ACTIVE,
+ },
+ {
+ .name = "DIRECTION_AS_IS",
+ .val = GPIOD_LINE_DIRECTION_AS_IS,
+ },
+ {
+ .name = "DIRECTION_INPUT",
+ .val = GPIOD_LINE_DIRECTION_INPUT,
+ },
+ {
+ .name = "DIRECTION_OUTPUT",
+ .val = GPIOD_LINE_DIRECTION_OUTPUT,
+ },
+ {
+ .name = "BIAS_AS_IS",
+ .val = GPIOD_LINE_BIAS_AS_IS,
+ },
+ {
+ .name = "BIAS_UNKNOWN",
+ .val = GPIOD_LINE_BIAS_UNKNOWN,
+ },
+ {
+ .name = "BIAS_DISABLED",
+ .val = GPIOD_LINE_BIAS_DISABLED,
+ },
+ {
+ .name = "BIAS_PULL_UP",
+ .val = GPIOD_LINE_BIAS_PULL_UP,
+ },
+ {
+ .name = "BIAS_PULL_DOWN",
+ .val = GPIOD_LINE_BIAS_PULL_DOWN,
+ },
+ {
+ .name = "DRIVE_PUSH_PULL",
+ .val = GPIOD_LINE_DRIVE_PUSH_PULL,
+ },
+ {
+ .name = "DRIVE_OPEN_DRAIN",
+ .val = GPIOD_LINE_DRIVE_OPEN_DRAIN,
+ },
+ {
+ .name = "DRIVE_OPEN_SOURCE",
+ .val = GPIOD_LINE_DRIVE_OPEN_SOURCE,
+ },
+ {
+ .name = "EDGE_NONE",
+ .val = GPIOD_LINE_EDGE_NONE,
+ },
+ {
+ .name = "EDGE_FALLING",
+ .val = GPIOD_LINE_EDGE_FALLING,
+ },
+ {
+ .name = "EDGE_RISING",
+ .val = GPIOD_LINE_EDGE_RISING,
+ },
+ {
+ .name = "EDGE_BOTH",
+ .val = GPIOD_LINE_EDGE_BOTH,
+ },
+ {
+ .name = "CLOCK_MONOTONIC",
+ .val = GPIOD_LINE_EVENT_CLOCK_MONOTONIC,
+ },
+ {
+ .name = "CLOCK_REALTIME",
+ .val = GPIOD_LINE_EVENT_CLOCK_REALTIME,
+ },
+ {
+ .name = "CLOCK_HTE",
+ .val = GPIOD_LINE_EVENT_CLOCK_HTE,
+ },
+ {
+ .name = "EDGE_EVENT_TYPE_RISING",
+ .val = GPIOD_EDGE_EVENT_RISING_EDGE,
+ },
+ {
+ .name = "EDGE_EVENT_TYPE_FALLING",
+ .val = GPIOD_EDGE_EVENT_FALLING_EDGE,
+ },
+ {
+ .name = "INFO_EVENT_TYPE_LINE_REQUESTED",
+ .val = GPIOD_INFO_EVENT_LINE_REQUESTED,
+ },
+ {
+ .name = "INFO_EVENT_TYPE_LINE_RELEASED",
+ .val = GPIOD_INFO_EVENT_LINE_RELEASED,
+ },
+ {
+ .name = "INFO_EVENT_TYPE_LINE_CONFIG_CHANGED",
+ .val = GPIOD_INFO_EVENT_LINE_CONFIG_CHANGED,
+ },
+ { }
+};
+
+static PyObject *
+module_is_gpiochip_device(PyObject *Py_UNUSED(self), PyObject *args)
+{
+ const char *path;
+ int ret;
+
+ ret = PyArg_ParseTuple(args, "s", &path);
+ if (!ret)
+ return NULL;
+
+ return PyBool_FromLong(gpiod_is_gpiochip_device(path));
+}
+
+static PyMethodDef module_methods[] = {
+ {
+ .ml_name = "is_gpiochip_device",
+ .ml_meth = (PyCFunction)module_is_gpiochip_device,
+ .ml_flags = METH_VARARGS,
+ },
+ { }
+};
+
+static PyModuleDef module_def = {
+ PyModuleDef_HEAD_INIT,
+ .m_name = "gpiod._ext",
+ .m_methods = module_methods,
+};
+
+extern PyTypeObject chip_type;
+extern PyTypeObject line_config_type;
+extern PyTypeObject line_settings_type;
+extern PyTypeObject request_type;
+
+static PyTypeObject *types[] = {
+ &chip_type,
+ &line_config_type,
+ &line_settings_type,
+ &request_type,
+ NULL,
+};
+
+PyMODINIT_FUNC PyInit__ext(void)
+{
+ const struct module_const *modconst;
+ PyTypeObject **type;
+ PyObject *module;
+ int ret;
+
+ module = PyModule_Create(&module_def);
+ if (!module)
+ return NULL;
+
+ ret = PyModule_AddStringConstant(module, "__version__",
+ gpiod_version_string());
+ if (ret) {
+ Py_DECREF(module);
+ return NULL;
+ }
+
+ for (type = types; *type; type++) {
+ ret = PyModule_AddType(module, *type);
+ if (ret) {
+ Py_DECREF(module);
+ return NULL;
+ }
+ }
+
+ for (modconst = module_constants; modconst->name; modconst++) {
+ ret = PyModule_AddIntConstant(module,
+ modconst->name, modconst->val);
+ if (ret) {
+ Py_DECREF(module);
+ return NULL;
+ }
+ }
+
+ return module;
+}
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include "internal.h"
+
+typedef struct {
+ PyObject_HEAD;
+ struct gpiod_line_request *request;
+ unsigned int *offsets;
+ int *values;
+ size_t num_lines;
+ struct gpiod_edge_event_buffer *buffer;
+} request_object;
+
+static int request_init(PyObject *Py_UNUSED(ignored0),
+ PyObject *Py_UNUSED(ignored1),
+ PyObject *Py_UNUSED(ignored2))
+{
+ PyErr_SetString(PyExc_NotImplementedError,
+ "_ext.LineRequest cannot be instantiated");
+
+ return -1;
+}
+
+static void request_finalize(request_object *self)
+{
+ if (self->request)
+ PyObject_CallMethod((PyObject *)self, "release", "");
+
+ if (self->offsets)
+ PyMem_Free(self->offsets);
+
+ if (self->values)
+ PyMem_Free(self->values);
+
+ if (self->buffer)
+ gpiod_edge_event_buffer_free(self->buffer);
+}
+
+static PyObject *
+request_num_lines(request_object *self, void *Py_UNUSED(ignored))
+{
+ return PyLong_FromUnsignedLong(
+ gpiod_line_request_get_num_lines(self->request));
+}
+
+static PyObject *request_offsets(request_object *self, void *Py_UNUSED(ignored))
+{
+ PyObject *lines, *line;
+ unsigned int *offsets;
+ size_t num_lines, i;
+ int ret;
+
+ num_lines = gpiod_line_request_get_num_lines(self->request);
+
+ offsets = PyMem_Calloc(num_lines, sizeof(unsigned int));
+ if (!offsets)
+ return PyErr_NoMemory();
+
+ gpiod_line_request_get_offsets(self->request, offsets);
+
+ lines = PyList_New(num_lines);
+ if (!lines) {
+ PyMem_Free(offsets);
+ return NULL;
+ }
+
+ for (i = 0; i < num_lines; i++) {
+ line = PyLong_FromUnsignedLong(offsets[i]);
+ if (!line) {
+ Py_DECREF(lines);
+ PyMem_Free(offsets);
+ return NULL;
+ }
+
+ ret = PyList_SetItem(lines, i, line);
+ if (ret) {
+ Py_DECREF(line);
+ Py_DECREF(lines);
+ PyMem_Free(offsets);
+ return NULL;
+ }
+ }
+
+ PyMem_Free(offsets);
+ return lines;
+}
+
+static PyObject *request_fd(request_object *self, void *Py_UNUSED(ignored))
+{
+ return PyLong_FromLong(gpiod_line_request_get_fd(self->request));
+}
+
+static PyGetSetDef request_getset[] = {
+ {
+ .name = "num_lines",
+ .get = (getter)request_num_lines,
+ },
+ {
+ .name = "offsets",
+ .get = (getter)request_offsets,
+ },
+ {
+ .name = "fd",
+ .get = (getter)request_fd,
+ },
+ { }
+};
+
+static PyObject *
+request_release(request_object *self, PyObject *Py_UNUSED(ignored))
+{
+ Py_BEGIN_ALLOW_THREADS;
+ gpiod_line_request_release(self->request);
+ Py_END_ALLOW_THREADS;
+ self->request = NULL;
+
+ Py_RETURN_NONE;
+}
+
+static void clear_buffers(request_object *self)
+{
+ memset(self->offsets, 0, self->num_lines * sizeof(unsigned int));
+ memset(self->values, 0, self->num_lines * sizeof(int));
+}
+
+static PyObject *request_get_values(request_object *self, PyObject *args)
+{
+ PyObject *offsets, *values, *val, *type, *iter, *next;
+ Py_ssize_t num_offsets, pos;
+ int ret;
+
+ ret = PyArg_ParseTuple(args, "OO", &offsets, &values);
+ if (!ret)
+ return NULL;
+
+ num_offsets = PyObject_Size(offsets);
+ if (num_offsets < 0)
+ return NULL;
+
+ type = Py_gpiod_GetGlobalType("Value");
+ if (!type)
+ return NULL;
+
+ iter = PyObject_GetIter(offsets);
+ if (!iter)
+ return NULL;
+
+ clear_buffers(self);
+
+ for (pos = 0;; pos++) {
+ next = PyIter_Next(iter);
+ if (!next) {
+ Py_DECREF(iter);
+ break;
+ }
+
+ self->offsets[pos] = Py_gpiod_PyLongAsUnsignedInt(next);
+ Py_DECREF(next);
+ if (PyErr_Occurred()) {
+ Py_DECREF(iter);
+ return NULL;
+ }
+ }
+
+ Py_BEGIN_ALLOW_THREADS;
+ ret = gpiod_line_request_get_values_subset(self->request,
+ num_offsets,
+ self->offsets,
+ self->values);
+ Py_END_ALLOW_THREADS;
+ if (ret)
+ return Py_gpiod_SetErrFromErrno();
+
+ for (pos = 0; pos < num_offsets; pos++) {
+ val = PyObject_CallFunction(type, "i", self->values[pos]);
+ if (!val)
+ return NULL;
+
+ ret = PyList_SetItem(values, pos, val);
+ if (ret) {
+ Py_DECREF(val);
+ return NULL;
+ }
+ }
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *request_set_values(request_object *self, PyObject *args)
+{
+ PyObject *values, *key, *val, *val_stripped;
+ Py_ssize_t pos = 0;
+ int ret;
+
+ ret = PyArg_ParseTuple(args, "O", &values);
+ if (!ret)
+ return NULL;
+
+ clear_buffers(self);
+
+ while (PyDict_Next(values, &pos, &key, &val)) {
+ self->offsets[pos - 1] = Py_gpiod_PyLongAsUnsignedInt(key);
+ if (PyErr_Occurred())
+ return NULL;
+
+ val_stripped = PyObject_GetAttrString(val, "value");
+ if (!val_stripped)
+ return NULL;
+
+ self->values[pos - 1] = PyLong_AsLong(val_stripped);
+ Py_DECREF(val_stripped);
+ if (PyErr_Occurred())
+ return NULL;
+ }
+
+ Py_BEGIN_ALLOW_THREADS;
+ ret = gpiod_line_request_set_values_subset(self->request,
+ pos,
+ self->offsets,
+ self->values);
+ Py_END_ALLOW_THREADS;
+ if (ret)
+ return Py_gpiod_SetErrFromErrno();
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *request_reconfigure_lines(request_object *self, PyObject *args)
+{
+ struct gpiod_line_config *line_cfg;
+ PyObject *line_cfg_obj;
+ int ret;
+
+ ret = PyArg_ParseTuple(args, "O", &line_cfg_obj);
+ if (!ret)
+ return NULL;
+
+ line_cfg = Py_gpiod_LineConfigGetData(line_cfg_obj);
+ if (!line_cfg)
+ return NULL;
+
+ Py_BEGIN_ALLOW_THREADS;
+ ret = gpiod_line_request_reconfigure_lines(self->request, line_cfg);
+ Py_END_ALLOW_THREADS;
+ if (ret)
+ return Py_gpiod_SetErrFromErrno();
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *request_read_edge_event(request_object *self, PyObject *args)
+{
+ PyObject *max_events_obj, *event_obj, *events, *type;
+ size_t max_events, num_events, i;
+ struct gpiod_edge_event *event;
+ int ret;
+
+ ret = PyArg_ParseTuple(args, "O", &max_events_obj);
+ if (!ret)
+ return NULL;
+
+ if (max_events_obj != Py_None) {
+ max_events = PyLong_AsSize_t(max_events_obj);
+ if (PyErr_Occurred())
+ return NULL;
+ } else {
+ max_events = 64;
+ }
+
+ type = Py_gpiod_GetGlobalType("EdgeEvent");
+ if (!type)
+ return NULL;
+
+ Py_BEGIN_ALLOW_THREADS;
+ ret = gpiod_line_request_read_edge_event(self->request,
+ self->buffer, max_events);
+ Py_END_ALLOW_THREADS;
+ if (ret < 0)
+ return Py_gpiod_SetErrFromErrno();
+
+ num_events = ret;
+
+ events = PyList_New(num_events);
+ if (!events)
+ return NULL;
+
+ for (i = 0; i < num_events; i++) {
+ event = gpiod_edge_event_buffer_get_event(self->buffer, i);
+ if (!event) {
+ Py_DECREF(events);
+ return NULL;
+ }
+
+ event_obj = PyObject_CallFunction(type, "iKiii",
+ gpiod_edge_event_get_event_type(event),
+ gpiod_edge_event_get_timestamp_ns(event),
+ gpiod_edge_event_get_line_offset(event),
+ gpiod_edge_event_get_global_seqno(event),
+ gpiod_edge_event_get_line_seqno(event));
+ if (!event_obj) {
+ Py_DECREF(events);
+ return NULL;
+ }
+
+ ret = PyList_SetItem(events, i, event_obj);
+ if (ret) {
+ Py_DECREF(event_obj);
+ Py_DECREF(events);
+ return NULL;
+ }
+ }
+
+ return events;
+}
+
+static PyMethodDef request_methods[] = {
+ {
+ .ml_name = "release",
+ .ml_meth = (PyCFunction)request_release,
+ .ml_flags = METH_NOARGS,
+ },
+ {
+ .ml_name = "get_values",
+ .ml_meth = (PyCFunction)request_get_values,
+ .ml_flags = METH_VARARGS,
+ },
+ {
+ .ml_name = "set_values",
+ .ml_meth = (PyCFunction)request_set_values,
+ .ml_flags = METH_VARARGS,
+ },
+ {
+ .ml_name = "reconfigure_lines",
+ .ml_meth = (PyCFunction)request_reconfigure_lines,
+ .ml_flags = METH_VARARGS,
+ },
+ {
+ .ml_name = "read_edge_event",
+ .ml_meth = (PyCFunction)request_read_edge_event,
+ .ml_flags = METH_VARARGS,
+ },
+ { }
+};
+
+PyTypeObject request_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "gpiod._ext.Request",
+ .tp_basicsize = sizeof(request_object),
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_new = PyType_GenericNew,
+ .tp_init = (initproc)request_init,
+ .tp_finalize = (destructor)request_finalize,
+ .tp_dealloc = (destructor)Py_gpiod_dealloc,
+ .tp_getset = request_getset,
+ .tp_methods = request_methods,
+};
+
+PyObject *Py_gpiod_MakeRequestObject(struct gpiod_line_request *request,
+ size_t event_buffer_size)
+{
+ struct gpiod_edge_event_buffer *buffer;
+ request_object *req_obj;
+ unsigned int *offsets;
+ size_t num_lines;
+ int *values;
+
+ num_lines = gpiod_line_request_get_num_lines(request);
+
+ req_obj = PyObject_New(request_object, &request_type);
+ if (!req_obj)
+ return NULL;
+
+ offsets = PyMem_Calloc(num_lines, sizeof(unsigned int));
+ if (!offsets) {
+ Py_DECREF(req_obj);
+ return NULL;
+ }
+
+ values = PyMem_Calloc(num_lines, sizeof(int));
+ if (!values) {
+ PyMem_Free(offsets);
+ Py_DECREF(req_obj);
+ return NULL;
+ }
+
+ buffer = gpiod_edge_event_buffer_new(event_buffer_size);
+ if (!buffer) {
+ PyMem_Free(values);
+ PyMem_Free(offsets);
+ Py_DECREF(req_obj);
+ return Py_gpiod_SetErrFromErrno();
+ }
+
+ req_obj->request = request;
+ req_obj->offsets = offsets;
+ req_obj->values = values;
+ req_obj->num_lines = num_lines;
+ req_obj->buffer = buffer;
+
+ return (PyObject *)req_obj;
+}
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+from . import _ext
+from .line_info import LineInfo
+from dataclasses import dataclass
+from enum import Enum
+
+
+@dataclass(frozen=True, init=False, repr=False)
+class InfoEvent:
+ """
+ Immutable object containing data about a single line info event.
+ """
+
+ class Type(Enum):
+ LINE_REQUESTED = _ext.INFO_EVENT_TYPE_LINE_REQUESTED
+ LINE_RELEASED = _ext.INFO_EVENT_TYPE_LINE_RELEASED
+ LINE_CONFIG_CHANGED = _ext.INFO_EVENT_TYPE_LINE_CONFIG_CHANGED
+
+ event_type: Type
+ timestamp_ns: int
+ line_info: LineInfo
+
+ def __init__(self, event_type: int, timestamp_ns: int, line_info: LineInfo):
+ object.__setattr__(self, "event_type", InfoEvent.Type(event_type))
+ object.__setattr__(self, "timestamp_ns", timestamp_ns)
+ object.__setattr__(self, "line_info", line_info)
+
+ def __str__(self):
+ return "<InfoEvent type={} timestamp_ns={} line_info={}>".format(
+ self.event_type, self.timestamp_ns, self.line_info
+ )
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+from datetime import timedelta
+from select import select
+from typing import Optional, Union
+
+
+def poll_fd(fd: int, timeout: Optional[Union[timedelta, float]] = None) -> bool:
+ if timeout is None:
+ timeout = 0.0
+
+ if isinstance(timeout, timedelta):
+ sec = timeout.total_seconds()
+ else:
+ sec = timeout
+
+ readable, _, _ = select([fd], [], [], sec)
+ return True if fd in readable else False
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+
+from . import _ext
+from enum import Enum
+
+
+class Value(Enum):
+ """Logical line states."""
+
+ INACTIVE = _ext.VALUE_INACTIVE
+ ACTIVE = _ext.VALUE_ACTIVE
+
+
+class Direction(Enum):
+ """Direction settings."""
+
+ AS_IS = _ext.DIRECTION_AS_IS
+ INPUT = _ext.DIRECTION_INPUT
+ OUTPUT = _ext.DIRECTION_OUTPUT
+
+
+class Bias(Enum):
+ """Internal bias settings."""
+
+ AS_IS = _ext.BIAS_AS_IS
+ UNKNOWN = _ext.BIAS_UNKNOWN
+ DISABLED = _ext.BIAS_DISABLED
+ PULL_UP = _ext.BIAS_PULL_UP
+ PULL_DOWN = _ext.BIAS_PULL_DOWN
+
+
+class Drive(Enum):
+ """Drive settings."""
+
+ PUSH_PULL = _ext.DRIVE_PUSH_PULL
+ OPEN_DRAIN = _ext.DRIVE_OPEN_DRAIN
+ OPEN_SOURCE = _ext.DRIVE_OPEN_SOURCE
+
+
+class Edge(Enum):
+ """Edge detection settings."""
+
+ NONE = _ext.EDGE_NONE
+ RISING = _ext.EDGE_RISING
+ FALLING = _ext.EDGE_FALLING
+ BOTH = _ext.EDGE_BOTH
+
+
+class Clock(Enum):
+ """Event clock settings."""
+
+ MONOTONIC = _ext.CLOCK_MONOTONIC
+ REALTIME = _ext.CLOCK_REALTIME
+ HTE = _ext.CLOCK_HTE
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+from . import _ext
+from dataclasses import dataclass
+from datetime import timedelta
+from gpiod.line import Direction, Bias, Drive, Edge, Clock
+
+
+@dataclass(frozen=True, init=False, repr=False)
+class LineInfo:
+ """
+ Snapshot of a line's status.
+ """
+
+ offset: int
+ name: str
+ used: bool
+ consumer: str
+ direction: Direction
+ active_low: bool
+ bias: Bias
+ drive: Drive
+ edge_detection: Edge
+ event_clock: Clock
+ debounced: bool
+ debounce_period: timedelta
+
+ def __init__(
+ self,
+ offset: int,
+ name: str,
+ used: bool,
+ consumer: str,
+ direction: int,
+ active_low: bool,
+ bias: int,
+ drive: int,
+ edge_detection: int,
+ event_clock: int,
+ debounced: bool,
+ debounce_period_us: int,
+ ):
+ object.__setattr__(self, "offset", offset)
+ object.__setattr__(self, "name", name)
+ object.__setattr__(self, "used", used)
+ object.__setattr__(self, "consumer", consumer)
+ object.__setattr__(self, "direction", Direction(direction))
+ object.__setattr__(self, "active_low", active_low)
+ object.__setattr__(self, "bias", Bias(bias))
+ object.__setattr__(self, "drive", Drive(drive))
+ object.__setattr__(self, "edge_detection", Edge(edge_detection))
+ object.__setattr__(self, "event_clock", Clock(event_clock))
+ object.__setattr__(self, "debounced", debounced)
+ object.__setattr__(
+ self, "debounce_period", timedelta(microseconds=debounce_period_us)
+ )
+
+ def __str__(self):
+ return '<LineInfo offset={} name="{}" used={} consumer="{}" direction={} active_low={} bias={} drive={} edge_detection={} event_clock={} debounced={} debounce_period={}>'.format(
+ self.offset,
+ self.name,
+ self.used,
+ self.consumer,
+ self.direction,
+ self.active_low,
+ self.bias,
+ self.drive,
+ self.edge_detection,
+ self.event_clock,
+ self.debounced,
+ self.debounce_period,
+ )
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+from . import _ext
+from .edge_event import EdgeEvent
+from .exception import RequestReleasedError
+from .internal import poll_fd
+from .line import Value
+from .line_settings import LineSettings, _line_settings_to_ext
+from collections.abc import Iterable
+from datetime import timedelta
+from typing import Optional, Union
+
+
+class LineRequest:
+ """
+ Stores the context of a set of requested GPIO lines.
+ """
+
+ def __init__(self, req: _ext.Request):
+ """
+ DON'T USE
+
+ LineRequest objects can only be instantiated by a Chip parent. This is
+ not part of stable API.
+ """
+ self._req = req
+
+ def __bool__(self) -> bool:
+ """
+ Boolean conversion for GPIO line requests.
+
+ Returns:
+ True if the request is live and False if it's been released.
+ """
+ return True if self._req else False
+
+ def __enter__(self):
+ """
+ Controlled execution enter callback.
+ """
+ self._check_released()
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ """
+ Controlled execution exit callback.
+ """
+ self.release()
+
+ def _check_released(self) -> None:
+ if not self._req:
+ raise RequestReleasedError()
+
+ def release(self) -> None:
+ """
+ Release this request and free all associated resources. The object must
+ not be used after a call to this method.
+ """
+ self._check_released()
+ self._req.release()
+ self._req = None
+
+ def get_value(self, line: Union[int, str]) -> Value:
+ """
+ Get a single GPIO line value.
+
+ Args:
+ line:
+ Offset or name of the line to get value for.
+
+ Returns:
+ Logical value of the line.
+ """
+ return self.get_values([line])[0]
+
+ def _check_line_name(self, line):
+ if isinstance(line, str):
+ if line not in self._name_map:
+ raise ValueError("unknown line name: {}".format(line))
+
+ return True
+
+ return False
+
+ def get_values(
+ self, lines: Optional[Iterable[Union[int, str]]] = None
+ ) -> list[Value]:
+ """
+ Get values of a set of GPIO lines.
+
+ Args:
+ lines:
+ List of names or offsets of GPIO lines to get values for. Can be
+ None in which case all requested lines will be read.
+
+ Returns:
+ List of logical line values.
+ """
+ self._check_released()
+
+ lines = lines or self._lines
+
+ offsets = [
+ self._name_map[line] if self._check_line_name(line) else line
+ for line in lines
+ ]
+
+ buf = [None] * len(lines)
+
+ self._req.get_values(offsets, buf)
+ return buf
+
+ def set_value(self, line: Union[int, str], value: Value) -> None:
+ """
+ Set the value of a single GPIO line.
+
+ Args:
+ line:
+ Offset or name of the line to set.
+ value:
+ New value.
+ """
+ self.set_values({line: value})
+
+ def set_values(self, values: dict[Union[int, str], Value]) -> None:
+ """
+ Set the values of a subset of GPIO lines.
+
+ Args:
+ values:
+ Dictionary mapping line offsets or names to desired values.
+ """
+ self._check_released()
+
+ mapped = {
+ self._name_map[line] if self._check_line_name(line) else line: values[line]
+ for line in values
+ }
+
+ self._req.set_values(mapped)
+
+ def reconfigure_lines(
+ self, config: dict[tuple[Union[int, str]], LineSettings]
+ ) -> None:
+ """
+ Reconfigure requested lines.
+
+ Args:
+ config
+ Dictionary mapping offsets or names (or tuples thereof) to
+ LineSettings. If None is passed as the value of the mapping,
+ default settings are used.
+ """
+ self._check_released()
+
+ line_cfg = _ext.LineConfig()
+
+ for lines, settings in config.items():
+ if isinstance(lines, int) or isinstance(lines, str):
+ lines = [lines]
+
+ offsets = [
+ self._name_map[line] if self._check_line_name(line) else line
+ for line in lines
+ ]
+
+ line_cfg.add_line_settings(offsets, _line_settings_to_ext(settings))
+
+ self._req.reconfigure_lines(line_cfg)
+
+ def wait_edge_event(
+ self, timeout: Optional[Union[timedelta, float]] = None
+ ) -> bool:
+ """
+ Wait for edge events on any of the requested lines.
+
+ Args:
+ timeout:
+ Wait time limit expressed as either a datetime.timedelta object
+ or the number of seconds stored in a float.
+
+ Returns:
+ True if events are ready to be read. False on timeout.
+ """
+ self._check_released()
+
+ return poll_fd(self.fd, timeout)
+
+ def read_edge_event(self, max_events: Optional[int] = None) -> list[EdgeEvent]:
+ """
+ Read a number of edge events from a line request.
+
+ Args:
+ max_events:
+ Maximum number of events to read.
+
+ Returns:
+ List of read EdgeEvent objects.
+ """
+ self._check_released()
+
+ return self._req.read_edge_event(max_events)
+
+ def __str__(self):
+ """
+ Return a user-friendly, human-readable description of this request.
+ """
+ if not self._req:
+ return "<LineRequest RELEASED>"
+
+ return "<LineRequest num_lines={} offsets={} fd={}>".format(
+ self.num_lines, self.offsets, self.fd
+ )
+
+ @property
+ def num_lines(self) -> int:
+ """
+ Number of requested lines.
+ """
+ self._check_released()
+ return len(self._offsets)
+
+ @property
+ def offsets(self) -> list[int]:
+ """
+ List of requested offsets. Lines requested by name are mapped to their
+ offsets.
+ """
+ self._check_released()
+ return self._offsets
+
+ @property
+ def lines(self) -> list[Union[int, str]]:
+ """
+ List of requested lines. Lines requested by name are listed as such.
+ """
+ self._check_released()
+ return self._lines
+
+ @property
+ def fd(self) -> int:
+ """
+ File descriptor associated with this request.
+ """
+ self._check_released()
+ return self._req.fd
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+from . import _ext
+from dataclasses import dataclass
+from datetime import timedelta
+from gpiod.line import Direction, Bias, Drive, Edge, Clock, Value
+
+
+@dataclass(repr=False)
+class LineSettings:
+ """
+ Stores a set of line properties.
+ """
+
+ direction: Direction = Direction.AS_IS
+ edge_detection: Edge = Edge.NONE
+ bias: Bias = Bias.AS_IS
+ drive: Drive = Drive.PUSH_PULL
+ active_low: bool = False
+ debounce_period: timedelta = timedelta()
+ event_clock: Clock = Clock.MONOTONIC
+ output_value: Value = Value.INACTIVE
+
+ # __repr__ generated by @dataclass uses repr for enum members resulting in
+ # an unusable representation as those are of the form: <NAME: $value>
+ def __repr__(self):
+ return "LineSettings(direction={}, edge_detection={} bias={} drive={} active_low={} debounce_period={} event_clock={} output_value={})".format(
+ str(self.direction),
+ str(self.edge_detection),
+ str(self.bias),
+ str(self.drive),
+ self.active_low,
+ repr(self.debounce_period),
+ str(self.event_clock),
+ str(self.output_value),
+ )
+
+ def __str__(self):
+ return "<LineSettings direction={} edge_detection={} bias={} drive={} active_low={} debounce_period={} event_clock={} output_value={}>".format(
+ self.direction,
+ self.edge_detection,
+ self.bias,
+ self.drive,
+ self.active_low,
+ self.debounce_period,
+ self.event_clock,
+ self.output_value,
+ )
+
+
+def _line_settings_to_ext(settings: LineSettings) -> _ext.LineSettings:
+ return _ext.LineSettings(
+ direction=settings.direction.value,
+ edge_detection=settings.edge_detection.value,
+ bias=settings.bias.value,
+ drive=settings.drive.value,
+ active_low=settings.active_low,
+ debounce_period=int(settings.debounce_period.total_seconds() * 1000000),
+ event_clock=settings.event_clock.value,
+ output_value=settings.output_value.value,
+ )
+++ /dev/null
-// SPDX-License-Identifier: LGPL-2.1-or-later
-// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
-
-#include <Python.h>
-#include <gpiod.h>
-
-#define LINE_REQUEST_MAX_LINES 64
-
-typedef struct {
- PyObject_HEAD;
- struct gpiod_chip *chip;
-} gpiod_ChipObject;
-
-typedef struct {
- PyObject_HEAD;
- struct gpiod_line *line;
- gpiod_ChipObject *owner;
-} gpiod_LineObject;
-
-typedef struct {
- PyObject_HEAD;
- struct gpiod_line_event event;
- gpiod_LineObject *source;
-} gpiod_LineEventObject;
-
-typedef struct {
- PyObject_HEAD;
- PyObject **lines;
- Py_ssize_t num_lines;
- Py_ssize_t iter_idx;
-} gpiod_LineBulkObject;
-
-typedef struct {
- PyObject_HEAD;
- unsigned int offset;
- gpiod_ChipObject *owner;
-} gpiod_LineIterObject;
-
-static gpiod_LineBulkObject *gpiod_LineToLineBulk(gpiod_LineObject *line);
-static gpiod_LineObject *gpiod_MakeLineObject(gpiod_ChipObject *owner,
- struct gpiod_line *line);
-
-enum {
- gpiod_LINE_REQ_DIR_AS_IS = 1,
- gpiod_LINE_REQ_DIR_IN,
- gpiod_LINE_REQ_DIR_OUT,
- gpiod_LINE_REQ_EV_FALLING_EDGE,
- gpiod_LINE_REQ_EV_RISING_EDGE,
- gpiod_LINE_REQ_EV_BOTH_EDGES,
-};
-
-enum {
- gpiod_LINE_REQ_FLAG_OPEN_DRAIN = GPIOD_BIT(0),
- gpiod_LINE_REQ_FLAG_OPEN_SOURCE = GPIOD_BIT(1),
- gpiod_LINE_REQ_FLAG_ACTIVE_LOW = GPIOD_BIT(2),
- gpiod_LINE_REQ_FLAG_BIAS_DISABLED = GPIOD_BIT(3),
- gpiod_LINE_REQ_FLAG_BIAS_PULL_DOWN = GPIOD_BIT(4),
- gpiod_LINE_REQ_FLAG_BIAS_PULL_UP = GPIOD_BIT(5),
-};
-
-enum {
- gpiod_DIRECTION_INPUT = 1,
- gpiod_DIRECTION_OUTPUT,
-};
-
-enum {
- gpiod_DRIVE_PUSH_PULL,
- gpiod_DRIVE_OPEN_DRAIN,
- gpiod_DRIVE_OPEN_SOURCE,
-};
-
-enum {
- gpiod_BIAS_UNKNOWN = 1,
- gpiod_BIAS_DISABLED,
- gpiod_BIAS_PULL_UP,
- gpiod_BIAS_PULL_DOWN,
-};
-
-enum {
- gpiod_RISING_EDGE = 1,
- gpiod_FALLING_EDGE,
-};
-
-static bool gpiod_ChipIsClosed(gpiod_ChipObject *chip)
-{
- if (!chip->chip) {
- PyErr_SetString(PyExc_ValueError,
- "I/O operation on closed file");
- return true;
- }
-
- return false;
-}
-
-static PyObject *gpiod_CallMethodPyArgs(PyObject *obj, const char *method,
- PyObject *args, PyObject *kwds)
-{
- PyObject *callable, *ret;
-
- callable = PyObject_GetAttrString((PyObject *)obj, method);
- if (!callable)
- return NULL;
-
- ret = PyObject_Call(callable, args, kwds);
- Py_DECREF(callable);
-
- return ret;
-}
-
-static int gpiod_LineEvent_init(PyObject *Py_UNUSED(ignored0),
- PyObject *Py_UNUSED(ignored1),
- PyObject *Py_UNUSED(ignored2))
-{
- PyErr_SetString(PyExc_NotImplementedError,
- "Only gpiod.Line can create new LineEvent objects.");
- return -1;
-}
-
-static void gpiod_LineEvent_dealloc(gpiod_LineEventObject *self)
-{
- if (self->source)
- Py_DECREF(self->source);
-
- PyObject_Del(self);
-}
-
-PyDoc_STRVAR(gpiod_LineEvent_get_type_doc,
-"Event type of this line event (integer).");
-
-PyObject *gpiod_LineEvent_get_type(gpiod_LineEventObject *self,
- PyObject *Py_UNUSED(ignored))
-{
- int rv;
-
- if (self->event.event_type == GPIOD_LINE_EVENT_RISING_EDGE)
- rv = gpiod_RISING_EDGE;
- else
- rv = gpiod_FALLING_EDGE;
-
- return Py_BuildValue("I", rv);
-}
-
-PyDoc_STRVAR(gpiod_LineEvent_get_sec_doc,
-"Seconds value of the line event timestamp (integer).");
-
-PyObject *gpiod_LineEvent_get_sec(gpiod_LineEventObject *self,
- PyObject *Py_UNUSED(ignored))
-{
- return Py_BuildValue("I", self->event.ts.tv_sec);
-}
-
-PyDoc_STRVAR(gpiod_LineEvent_get_nsec_doc,
-"Nanoseconds value of the line event timestamp (integer).");
-
-PyObject *gpiod_LineEvent_get_nsec(gpiod_LineEventObject *self,
- PyObject *Py_UNUSED(ignored))
-{
- return Py_BuildValue("I", self->event.ts.tv_nsec);
-}
-
-PyDoc_STRVAR(gpiod_LineEvent_get_source_doc,
-"Line object representing the GPIO line on which this event\n"
-"occurred (gpiod.Line object).");
-
-gpiod_LineObject *gpiod_LineEvent_get_source(gpiod_LineEventObject *self,
- PyObject *Py_UNUSED(ignored))
-{
- Py_INCREF(self->source);
- return self->source;
-}
-
-static PyGetSetDef gpiod_LineEvent_getset[] = {
- {
- .name = "type",
- .get = (getter)gpiod_LineEvent_get_type,
- .doc = gpiod_LineEvent_get_type_doc,
- },
- {
- .name = "sec",
- .get = (getter)gpiod_LineEvent_get_sec,
- .doc = gpiod_LineEvent_get_sec_doc,
- },
- {
- .name = "nsec",
- .get = (getter)gpiod_LineEvent_get_nsec,
- .doc = gpiod_LineEvent_get_nsec_doc,
- },
- {
- .name = "source",
- .get = (getter)gpiod_LineEvent_get_source,
- .doc = gpiod_LineEvent_get_source_doc,
- },
- { }
-};
-
-static PyObject *gpiod_LineEvent_repr(gpiod_LineEventObject *self)
-{
- PyObject *line_repr, *ret;
- const char *edge;
-
- if (self->event.event_type == GPIOD_LINE_EVENT_RISING_EDGE)
- edge = "RISING EDGE";
- else
- edge = "FALLING EDGE";
-
- line_repr = PyObject_CallMethod((PyObject *)self->source,
- "__repr__", "");
-
- ret = PyUnicode_FromFormat("'%s (%ld.%ld) source(%S)'",
- edge, self->event.ts.tv_sec,
- self->event.ts.tv_nsec, line_repr);
- Py_DECREF(line_repr);
-
- return ret;
-}
-
-PyDoc_STRVAR(gpiod_LineEventType_doc,
-"Represents a single GPIO line event. This object is immutable and can only\n"
-"be created by an instance of gpiod.Line.");
-
-static PyTypeObject gpiod_LineEventType = {
- PyVarObject_HEAD_INIT(NULL, 0)
- .tp_name = "gpiod.LineEvent",
- .tp_basicsize = sizeof(gpiod_LineEventObject),
- .tp_flags = Py_TPFLAGS_DEFAULT,
- .tp_doc = gpiod_LineEventType_doc,
- .tp_new = PyType_GenericNew,
- .tp_init = (initproc)gpiod_LineEvent_init,
- .tp_dealloc = (destructor)gpiod_LineEvent_dealloc,
- .tp_getset = gpiod_LineEvent_getset,
- .tp_repr = (reprfunc)gpiod_LineEvent_repr,
-};
-
-static int gpiod_Line_init(PyObject *Py_UNUSED(ignored0),
- PyObject *Py_UNUSED(ignored1),
- PyObject *Py_UNUSED(ignored2))
-{
- PyErr_SetString(PyExc_NotImplementedError,
- "Only gpiod.Chip can create new Line objects.");
- return -1;
-}
-
-static void gpiod_Line_dealloc(gpiod_LineObject *self)
-{
- if (self->owner)
- Py_DECREF(self->owner);
-
- PyObject_Del(self);
-}
-
-PyDoc_STRVAR(gpiod_Line_owner_doc,
-"owner() -> Chip object owning the line\n"
-"\n"
-"Get the GPIO chip owning this line.");
-
-static PyObject *gpiod_Line_owner(gpiod_LineObject *self,
- PyObject *Py_UNUSED(ignored))
-{
- Py_INCREF(self->owner);
- return (PyObject *)self->owner;
-}
-
-PyDoc_STRVAR(gpiod_Line_offset_doc,
-"offset() -> integer\n"
-"\n"
-"Get the offset of the GPIO line.");
-
-static PyObject *gpiod_Line_offset(gpiod_LineObject *self,
- PyObject *Py_UNUSED(ignored))
-{
- if (gpiod_ChipIsClosed(self->owner))
- return NULL;
-
- return Py_BuildValue("I", gpiod_line_offset(self->line));
-}
-
-PyDoc_STRVAR(gpiod_Line_name_doc,
-"name() -> string\n"
-"\n"
-"Get the name of the GPIO line.");
-
-static PyObject *gpiod_Line_name(gpiod_LineObject *self,
- PyObject *Py_UNUSED(ignored))
-{
- const char *name;
-
- if (gpiod_ChipIsClosed(self->owner))
- return NULL;
-
- name = gpiod_line_name(self->line);
- if (name)
- return PyUnicode_FromFormat("%s", name);
-
- Py_RETURN_NONE;
-}
-
-PyDoc_STRVAR(gpiod_Line_consumer_doc,
-"consumer() -> string\n"
-"\n"
-"Get the consumer string of the GPIO line.");
-
-static PyObject *gpiod_Line_consumer(gpiod_LineObject *self,
- PyObject *Py_UNUSED(ignored))
-{
- const char *consumer;
-
- if (gpiod_ChipIsClosed(self->owner))
- return NULL;
-
- consumer = gpiod_line_consumer(self->line);
- if (consumer)
- return PyUnicode_FromFormat("%s", consumer);
-
- Py_RETURN_NONE;
-}
-
-PyDoc_STRVAR(gpiod_Line_direction_doc,
-"direction() -> integer\n"
-"\n"
-"Get the direction setting of this GPIO line.");
-
-static PyObject *gpiod_Line_direction(gpiod_LineObject *self,
- PyObject *Py_UNUSED(ignored))
-{
- PyObject *ret;
- int dir;
-
- if (gpiod_ChipIsClosed(self->owner))
- return NULL;
-
- dir = gpiod_line_direction(self->line);
-
- if (dir == GPIOD_LINE_DIRECTION_INPUT)
- ret = Py_BuildValue("I", gpiod_DIRECTION_INPUT);
- else
- ret = Py_BuildValue("I", gpiod_DIRECTION_OUTPUT);
-
- return ret;
-}
-
-PyDoc_STRVAR(gpiod_Line_is_active_low_doc,
-"is_active_low() -> boolean\n"
-"\n"
-"Check if this line's signal is inverted");
-
-static PyObject *gpiod_Line_is_active_low(gpiod_LineObject *self,
- PyObject *Py_UNUSED(ignored))
-{
- if (gpiod_ChipIsClosed(self->owner))
- return NULL;
-
- if (gpiod_line_is_active_low(self->line))
- Py_RETURN_TRUE;
- Py_RETURN_FALSE;
-}
-
-PyDoc_STRVAR(gpiod_Line_bias_doc,
-"bias() -> integer\n"
-"\n"
-"Get the bias setting of this GPIO line.");
-
-static PyObject *gpiod_Line_bias(gpiod_LineObject *self,
- PyObject *Py_UNUSED(ignored))
-{
- int bias;
-
- if (gpiod_ChipIsClosed(self->owner))
- return NULL;
-
- bias = gpiod_line_bias(self->line);
-
- switch (bias) {
- case GPIOD_LINE_BIAS_PULL_UP:
- return Py_BuildValue("I", gpiod_BIAS_PULL_UP);
- case GPIOD_LINE_BIAS_PULL_DOWN:
- return Py_BuildValue("I", gpiod_BIAS_PULL_DOWN);
- case GPIOD_LINE_BIAS_DISABLED:
- return Py_BuildValue("I", gpiod_BIAS_DISABLED);
- case GPIOD_LINE_BIAS_UNKNOWN:
- default:
- return Py_BuildValue("I", gpiod_BIAS_UNKNOWN);
- }
-}
-
-PyDoc_STRVAR(gpiod_Line_is_used_doc,
-"is_used() -> boolean\n"
-"\n"
-"Check if this line is used by the kernel or other user space process.");
-
-static PyObject *gpiod_Line_is_used(gpiod_LineObject *self,
- PyObject *Py_UNUSED(ignored))
-{
- if (gpiod_ChipIsClosed(self->owner))
- return NULL;
-
- if (gpiod_line_is_used(self->line))
- Py_RETURN_TRUE;
-
- Py_RETURN_FALSE;
-}
-
-PyDoc_STRVAR(gpiod_Line_drive_doc,
-"drive() -> integer\n"
-"\n"
-"Get the current drive setting of this GPIO line.");
-
-static PyObject *gpiod_Line_drive(gpiod_LineObject *self,
- PyObject *Py_UNUSED(ignored))
-{
- int drive;
-
- if (gpiod_ChipIsClosed(self->owner))
- return NULL;
-
- drive = gpiod_line_drive(self->line);
-
- switch (drive) {
- case GPIOD_LINE_DRIVE_OPEN_DRAIN:
- return Py_BuildValue("I", gpiod_DRIVE_OPEN_DRAIN);
- case GPIOD_LINE_DRIVE_OPEN_SOURCE:
- return Py_BuildValue("I", gpiod_DRIVE_OPEN_SOURCE);
- case GPIOD_LINE_DRIVE_PUSH_PULL:
- default:
- return Py_BuildValue("I", gpiod_DRIVE_PUSH_PULL);
- }
-}
-
-PyDoc_STRVAR(gpiod_Line_request_doc,
-"request(consumer[, type[, flags[, default_val]]]) -> None\n"
-"\n"
-"Request this GPIO line.\n"
-"\n"
-" consumer\n"
-" Name of the consumer.\n"
-" type\n"
-" Type of the request.\n"
-" flags\n"
-" Other configuration flags.\n"
-" default_val\n"
-" Default value of this line."
-"\n"
-"Note: default_vals argument (sequence of default values passed down to\n"
-"LineBulk.request()) is still supported for backward compatibility but is\n"
-"now deprecated when requesting single lines.");
-
-static PyObject *gpiod_Line_request(gpiod_LineObject *self,
- PyObject *args, PyObject *kwds)
-{
- PyObject *ret, *def_val, *def_vals;
- gpiod_LineBulkObject *bulk_obj;
- int rv;
-
- if (kwds && PyDict_Size(kwds) > 0) {
- def_val = PyDict_GetItemString(kwds, "default_val");
- def_vals = PyDict_GetItemString(kwds, "default_vals");
- } else {
- def_val = def_vals = NULL;
- }
-
- if (def_val && def_vals) {
- PyErr_SetString(PyExc_TypeError,
- "Cannot pass both default_val and default_vals arguments at the same time");
- return NULL;
- }
-
- if (def_val) {
- /*
- * If default_val was passed as a single value, we wrap it
- * in a tuple and add it to the kwds dictionary to be passed
- * down to LineBulk.request(). We also remove the 'default_val'
- * entry from kwds.
- *
- * I'm not sure if it's allowed to modify the kwds dictionary
- * but it doesn't seem to cause any problems. If it does then
- * we can simply copy the dictionary before calling
- * LineBulk.request().
- */
- rv = PyDict_DelItemString(kwds, "default_val");
- if (rv)
- return NULL;
-
- def_vals = Py_BuildValue("(O)", def_val);
- if (!def_vals)
- return NULL;
-
- rv = PyDict_SetItemString(kwds, "default_vals", def_vals);
- if (rv) {
- Py_DECREF(def_vals);
- return NULL;
- }
- }
-
- bulk_obj = gpiod_LineToLineBulk(self);
- if (!bulk_obj)
- return NULL;
-
- ret = gpiod_CallMethodPyArgs((PyObject *)bulk_obj,
- "request", args, kwds);
- Py_DECREF(bulk_obj);
-
- return ret;
-}
-
-PyDoc_STRVAR(gpiod_Line_get_value_doc,
-"get_value() -> integer\n"
-"\n"
-"Read the current value of this GPIO line.");
-
-static PyObject *gpiod_Line_get_value(gpiod_LineObject *self,
- PyObject *Py_UNUSED(ignored))
-{
- gpiod_LineBulkObject *bulk_obj;
- PyObject *vals, *ret;
-
- bulk_obj = gpiod_LineToLineBulk(self);
- if (!bulk_obj)
- return NULL;
-
- vals = PyObject_CallMethod((PyObject *)bulk_obj, "get_values", "");
- Py_DECREF(bulk_obj);
- if (!vals)
- return NULL;
-
- ret = PyList_GetItem(vals, 0);
- Py_INCREF(ret);
- Py_DECREF(vals);
-
- return ret;
-}
-
-PyDoc_STRVAR(gpiod_Line_set_value_doc,
-"set_value(value) -> None\n"
-"\n"
-"Set the value of this GPIO line.\n"
-"\n"
-" value\n"
-" New value (integer)");
-
-static PyObject *gpiod_Line_set_value(gpiod_LineObject *self, PyObject *args)
-{
- gpiod_LineBulkObject *bulk_obj;
- PyObject *val, *vals, *ret;
- int rv;
-
- rv = PyArg_ParseTuple(args, "O", &val);
- if (!rv)
- return NULL;
-
- bulk_obj = gpiod_LineToLineBulk(self);
- if (!bulk_obj)
- return NULL;
-
- vals = Py_BuildValue("(O)", val);
- if (!vals) {
- Py_DECREF(bulk_obj);
- return NULL;
- }
-
- ret = PyObject_CallMethod((PyObject *)bulk_obj,
- "set_values", "(O)", vals);
- Py_DECREF(bulk_obj);
- Py_DECREF(vals);
-
- return ret;
-}
-
-PyDoc_STRVAR(gpiod_Line_set_config_doc,
-"set_config(direction,flags,value) -> None\n"
-"\n"
-"Set the configuration of this GPIO line.\n"
-"\n"
-" direction\n"
-" New direction (integer)\n"
-" flags\n"
-" New flags (integer)\n"
-" value\n"
-" New value (integer)");
-
-static PyObject *gpiod_Line_set_config(gpiod_LineObject *self, PyObject *args)
-{
- gpiod_LineBulkObject *bulk_obj;
- PyObject *dirn, *flags, *val, *vals, *ret;
- int rv;
-
- val = NULL;
- rv = PyArg_ParseTuple(args, "OO|O", &dirn, &flags, &val);
- if (!rv)
- return NULL;
-
- bulk_obj = gpiod_LineToLineBulk(self);
- if (!bulk_obj)
- return NULL;
-
- if (val) {
- vals = Py_BuildValue("(O)", val);
- if (!vals) {
- Py_DECREF(bulk_obj);
- return NULL;
- }
- ret = PyObject_CallMethod((PyObject *)bulk_obj,
- "set_config", "OO(O)", dirn, flags, vals);
- Py_DECREF(vals);
- } else {
- ret = PyObject_CallMethod((PyObject *)bulk_obj,
- "set_config", "OO", dirn, flags);
- }
-
- Py_DECREF(bulk_obj);
-
- return ret;
-}
-
-PyDoc_STRVAR(gpiod_Line_set_flags_doc,
-"set_flags(flags) -> None\n"
-"\n"
-"Set the flags of this GPIO line.\n"
-"\n"
-" flags\n"
-" New flags (integer)");
-
-static PyObject *gpiod_Line_set_flags(gpiod_LineObject *self, PyObject *args)
-{
- gpiod_LineBulkObject *bulk_obj;
- PyObject *ret;
-
- bulk_obj = gpiod_LineToLineBulk(self);
- if (!bulk_obj)
- return NULL;
-
- ret = PyObject_CallMethod((PyObject *)bulk_obj,
- "set_flags", "O", args);
- Py_DECREF(bulk_obj);
-
- return ret;
-}
-
-PyDoc_STRVAR(gpiod_Line_set_direction_input_doc,
-"set_direction_input() -> None\n"
-"\n"
-"Set the direction of this GPIO line to input.\n");
-
-static PyObject *gpiod_Line_set_direction_input(gpiod_LineObject *self,
- PyObject *Py_UNUSED(ignored))
-{
- gpiod_LineBulkObject *bulk_obj;
- PyObject *ret;
-
- bulk_obj = gpiod_LineToLineBulk(self);
- if (!bulk_obj)
- return NULL;
-
- ret = PyObject_CallMethod((PyObject *)bulk_obj,
- "set_direction_input", "");
- Py_DECREF(bulk_obj);
-
- return ret;
-}
-
-PyDoc_STRVAR(gpiod_Line_set_direction_output_doc,
-"set_direction_output(value) -> None\n"
-"\n"
-"Set the direction of this GPIO line to output.\n"
-"\n"
-" value\n"
-" New value (integer)");
-
-static PyObject *gpiod_Line_set_direction_output(gpiod_LineObject *self,
- PyObject *args)
-{
- gpiod_LineBulkObject *bulk_obj;
- PyObject *val, *vals, *ret;
- int rv;
- const char *fmt;
-
- val = NULL;
- rv = PyArg_ParseTuple(args, "|O", &val);
- if (!rv)
- return NULL;
-
- if (val) {
- fmt = "(O)";
- vals = Py_BuildValue(fmt, val);
- } else {
- vals = Py_BuildValue("()");
- fmt = "O"; /* pass empty args to bulk */
- }
- if (!vals)
- return NULL;
-
- bulk_obj = gpiod_LineToLineBulk(self);
- if (!bulk_obj)
- return NULL;
-
- ret = PyObject_CallMethod((PyObject *)bulk_obj,
- "set_direction_output", fmt, vals);
-
- Py_DECREF(bulk_obj);
- Py_DECREF(vals);
-
- return ret;
-}
-
-PyDoc_STRVAR(gpiod_Line_release_doc,
-"release() -> None\n"
-"\n"
-"Release this GPIO line.");
-
-static PyObject *gpiod_Line_release(gpiod_LineObject *self,
- PyObject *Py_UNUSED(ignored))
-{
- gpiod_LineBulkObject *bulk_obj;
- PyObject *ret;
-
- bulk_obj = gpiod_LineToLineBulk(self);
- if (!bulk_obj)
- return NULL;
-
- ret = PyObject_CallMethod((PyObject *)bulk_obj, "release", "");
- Py_DECREF(bulk_obj);
-
- return ret;
-}
-
-PyDoc_STRVAR(gpiod_Line_event_wait_doc,
-"event_wait([sec[ ,nsec]]) -> boolean\n"
-"\n"
-"Wait for a line event to occur on this GPIO line.\n"
-"\n"
-" sec\n"
-" Number of seconds to wait before timeout.\n"
-" nsec\n"
-" Number of nanoseconds to wait before timeout.\n"
-"\n"
-"Returns True if an event occurred on this line before timeout. False\n"
-"otherwise.");
-
-static PyObject *gpiod_Line_event_wait(gpiod_LineObject *self,
- PyObject *args, PyObject *kwds)
-{
- gpiod_LineBulkObject *bulk_obj;
- PyObject *events;
-
- bulk_obj = gpiod_LineToLineBulk(self);
- if (!bulk_obj)
- return NULL;
-
- events = gpiod_CallMethodPyArgs((PyObject *)bulk_obj,
- "event_wait", args, kwds);
- Py_DECREF(bulk_obj);
- if (!events)
- return NULL;
-
- if (events == Py_None) {
- Py_DECREF(Py_None);
- Py_RETURN_FALSE;
- }
-
- Py_DECREF(events);
- Py_RETURN_TRUE;
-}
-
-PyDoc_STRVAR(gpiod_Line_event_read_doc,
-"event_read() -> gpiod.LineEvent object\n"
-"\n"
-"Read a single line event from this GPIO line object.");
-
-static gpiod_LineEventObject *gpiod_Line_event_read(gpiod_LineObject *self,
- PyObject *Py_UNUSED(ignored))
-{
- gpiod_LineEventObject *ret;
- int rv;
-
- if (gpiod_ChipIsClosed(self->owner))
- return NULL;
-
- ret = PyObject_New(gpiod_LineEventObject, &gpiod_LineEventType);
- if (!ret)
- return NULL;
-
- ret->source = NULL;
-
- Py_BEGIN_ALLOW_THREADS;
- rv = gpiod_line_event_read(self->line, &ret->event);
- Py_END_ALLOW_THREADS;
- if (rv) {
- Py_DECREF(ret);
- return (gpiod_LineEventObject *)PyErr_SetFromErrno(
- PyExc_OSError);
- }
-
- Py_INCREF(self);
- ret->source = self;
-
- return ret;
-}
-
-PyDoc_STRVAR(gpiod_Line_event_read_multiple_doc,
-"event_read_multiple() -> list of gpiod.LineEvent object\n"
-"\n"
-"Read multiple line events from this GPIO line object.");
-
-static PyObject *gpiod_Line_event_read_multiple(gpiod_LineObject *self,
- PyObject *Py_UNUSED(ignored))
-{
- struct gpiod_line_event evbuf[16];
- gpiod_LineEventObject *event;
- int rv, num_events, i;
- PyObject *events;
-
- if (gpiod_ChipIsClosed(self->owner))
- return NULL;
-
- memset(evbuf, 0, sizeof(evbuf));
- Py_BEGIN_ALLOW_THREADS;
- num_events = gpiod_line_event_read_multiple(self->line, evbuf,
- sizeof(evbuf) / sizeof(*evbuf));
- Py_END_ALLOW_THREADS;
- if (num_events < 0)
- return PyErr_SetFromErrno(PyExc_OSError);
-
- events = PyList_New(num_events);
- if (!events)
- return NULL;
-
- for (i = 0; i < num_events; i++) {
- event = PyObject_New(gpiod_LineEventObject,
- &gpiod_LineEventType);
- if (!event) {
- Py_DECREF(events);
- return NULL;
- }
-
- memcpy(&event->event, &evbuf[i], sizeof(event->event));
- Py_INCREF(self);
- event->source = self;
-
- rv = PyList_SetItem(events, i, (PyObject *)event);
- if (rv < 0) {
- Py_DECREF(events);
- Py_DECREF(event);
- return NULL;
- }
- }
-
- return events;
-}
-
-PyDoc_STRVAR(gpiod_Line_event_get_fd_doc,
-"event_get_fd() -> integer\n"
-"\n"
-"Get the event file descriptor number associated with this line.");
-
-static PyObject *gpiod_Line_event_get_fd(gpiod_LineObject *self,
- PyObject *Py_UNUSED(ignored))
-{
- int fd;
-
- if (gpiod_ChipIsClosed(self->owner))
- return NULL;
-
- fd = gpiod_line_event_get_fd(self->line);
- if (fd < 0) {
- PyErr_SetFromErrno(PyExc_OSError);
- return NULL;
- }
-
- return PyLong_FromLong(fd);
-}
-
-static PyObject *gpiod_Line_repr(gpiod_LineObject *self)
-{
- PyObject *chip_name, *ret;
- const char *line_name;
-
- if (gpiod_ChipIsClosed(self->owner))
- return NULL;
-
- chip_name = PyObject_CallMethod((PyObject *)self->owner, "name", "");
- if (!chip_name)
- return NULL;
-
- line_name = gpiod_line_name(self->line);
-
- ret = PyUnicode_FromFormat("'%S:%u /%s/'", chip_name,
- gpiod_line_offset(self->line),
- line_name ?: "unnamed");
- Py_DECREF(chip_name);
- return ret;
-}
-
-static PyMethodDef gpiod_Line_methods[] = {
- {
- .ml_name = "owner",
- .ml_meth = (PyCFunction)gpiod_Line_owner,
- .ml_flags = METH_NOARGS,
- .ml_doc = gpiod_Line_owner_doc,
- },
- {
- .ml_name = "offset",
- .ml_meth = (PyCFunction)gpiod_Line_offset,
- .ml_flags = METH_NOARGS,
- .ml_doc = gpiod_Line_offset_doc,
- },
- {
- .ml_name = "name",
- .ml_meth = (PyCFunction)gpiod_Line_name,
- .ml_flags = METH_NOARGS,
- .ml_doc = gpiod_Line_name_doc,
- },
- {
- .ml_name = "consumer",
- .ml_meth = (PyCFunction)gpiod_Line_consumer,
- .ml_flags = METH_NOARGS,
- .ml_doc = gpiod_Line_consumer_doc,
- },
- {
- .ml_name = "direction",
- .ml_meth = (PyCFunction)gpiod_Line_direction,
- .ml_flags = METH_NOARGS,
- .ml_doc = gpiod_Line_direction_doc,
- },
- {
- .ml_name = "is_active_low",
- .ml_meth = (PyCFunction)gpiod_Line_is_active_low,
- .ml_flags = METH_NOARGS,
- .ml_doc = gpiod_Line_is_active_low_doc,
- },
- {
- .ml_name = "bias",
- .ml_meth = (PyCFunction)gpiod_Line_bias,
- .ml_flags = METH_NOARGS,
- .ml_doc = gpiod_Line_bias_doc,
- },
- {
- .ml_name = "is_used",
- .ml_meth = (PyCFunction)gpiod_Line_is_used,
- .ml_flags = METH_NOARGS,
- .ml_doc = gpiod_Line_is_used_doc,
- },
- {
- .ml_name = "drive",
- .ml_meth = (PyCFunction)gpiod_Line_drive,
- .ml_flags = METH_NOARGS,
- .ml_doc = gpiod_Line_drive_doc,
- },
- {
- .ml_name = "request",
- .ml_meth = (PyCFunction)(void (*)(void))gpiod_Line_request,
- .ml_flags = METH_VARARGS | METH_KEYWORDS,
- .ml_doc = gpiod_Line_request_doc,
- },
- {
- .ml_name = "get_value",
- .ml_meth = (PyCFunction)gpiod_Line_get_value,
- .ml_flags = METH_NOARGS,
- .ml_doc = gpiod_Line_get_value_doc,
- },
- {
- .ml_name = "set_value",
- .ml_meth = (PyCFunction)gpiod_Line_set_value,
- .ml_flags = METH_VARARGS,
- .ml_doc = gpiod_Line_set_value_doc,
- },
- {
- .ml_name = "set_config",
- .ml_meth = (PyCFunction)gpiod_Line_set_config,
- .ml_flags = METH_VARARGS,
- .ml_doc = gpiod_Line_set_config_doc,
- },
- {
- .ml_name = "set_flags",
- .ml_meth = (PyCFunction)gpiod_Line_set_flags,
- .ml_flags = METH_VARARGS,
- .ml_doc = gpiod_Line_set_flags_doc,
- },
- {
- .ml_name = "set_direction_input",
- .ml_meth = (PyCFunction)gpiod_Line_set_direction_input,
- .ml_flags = METH_NOARGS,
- .ml_doc = gpiod_Line_set_direction_input_doc,
- },
- {
- .ml_name = "set_direction_output",
- .ml_meth = (PyCFunction)gpiod_Line_set_direction_output,
- .ml_flags = METH_VARARGS,
- .ml_doc = gpiod_Line_set_direction_output_doc,
- },
- {
- .ml_name = "release",
- .ml_meth = (PyCFunction)gpiod_Line_release,
- .ml_flags = METH_NOARGS,
- .ml_doc = gpiod_Line_release_doc,
- },
- {
- .ml_name = "event_wait",
- .ml_meth = (PyCFunction)(void (*)(void))gpiod_Line_event_wait,
- .ml_flags = METH_VARARGS | METH_KEYWORDS,
- .ml_doc = gpiod_Line_event_wait_doc,
- },
- {
- .ml_name = "event_read",
- .ml_meth = (PyCFunction)gpiod_Line_event_read,
- .ml_flags = METH_NOARGS,
- .ml_doc = gpiod_Line_event_read_doc,
- },
- {
- .ml_name = "event_read_multiple",
- .ml_meth = (PyCFunction)gpiod_Line_event_read_multiple,
- .ml_flags = METH_NOARGS,
- .ml_doc = gpiod_Line_event_read_multiple_doc,
- },
- {
- .ml_name = "event_get_fd",
- .ml_meth = (PyCFunction)gpiod_Line_event_get_fd,
- .ml_flags = METH_NOARGS,
- .ml_doc = gpiod_Line_event_get_fd_doc,
- },
- { }
-};
-
-PyDoc_STRVAR(gpiod_LineType_doc,
-"Represents a GPIO line.\n"
-"\n"
-"The lifetime of this object is managed by the chip that owns it. Once\n"
-"the corresponding gpiod.Chip is closed, a gpiod.Line object must not be\n"
-"used.\n"
-"\n"
-"Line objects can only be created by the owning chip.");
-
-static PyTypeObject gpiod_LineType = {
- PyVarObject_HEAD_INIT(NULL, 0)
- .tp_name = "gpiod.Line",
- .tp_basicsize = sizeof(gpiod_LineObject),
- .tp_flags = Py_TPFLAGS_DEFAULT,
- .tp_doc = gpiod_LineType_doc,
- .tp_new = PyType_GenericNew,
- .tp_init = (initproc)gpiod_Line_init,
- .tp_dealloc = (destructor)gpiod_Line_dealloc,
- .tp_repr = (reprfunc)gpiod_Line_repr,
- .tp_methods = gpiod_Line_methods,
-};
-
-static bool gpiod_LineBulkOwnerIsClosed(gpiod_LineBulkObject *self)
-{
- gpiod_LineObject *line = (gpiod_LineObject *)self->lines[0];
-
- return gpiod_ChipIsClosed(line->owner);
-}
-
-static int gpiod_LineBulk_init(gpiod_LineBulkObject *self,
- PyObject *args, PyObject *Py_UNUSED(ignored))
-{
- PyObject *lines, *iter, *next;
- Py_ssize_t i;
- int rv;
-
- rv = PyArg_ParseTuple(args, "O", &lines);
- if (!rv)
- return -1;
-
- self->num_lines = PyObject_Size(lines);
- if (self->num_lines < 1) {
- PyErr_SetString(PyExc_TypeError,
- "Argument must be a non-empty sequence");
- return -1;
- }
- if (self->num_lines > LINE_REQUEST_MAX_LINES) {
- PyErr_SetString(PyExc_TypeError,
- "Too many objects in the sequence");
- return -1;
- }
-
- self->lines = PyMem_Calloc(self->num_lines, sizeof(PyObject *));
- if (!self->lines) {
- PyErr_SetString(PyExc_MemoryError, "Out of memory");
- return -1;
- }
-
- iter = PyObject_GetIter(lines);
- if (!iter) {
- PyMem_Free(self->lines);
- return -1;
- }
-
- for (i = 0;;) {
- next = PyIter_Next(iter);
- if (!next) {
- Py_DECREF(iter);
- break;
- }
-
- if (next->ob_type != &gpiod_LineType) {
- PyErr_SetString(PyExc_TypeError,
- "Argument must be a sequence of GPIO lines");
- Py_DECREF(next);
- Py_DECREF(iter);
- goto errout;
- }
-
- self->lines[i++] = next;
- }
-
- self->iter_idx = -1;
-
- return 0;
-
-errout:
-
- if (i > 0) {
- for (--i; i >= 0; i--)
- Py_DECREF(self->lines[i]);
- }
- PyMem_Free(self->lines);
- self->lines = NULL;
-
- return -1;
-}
-
-static void gpiod_LineBulk_dealloc(gpiod_LineBulkObject *self)
-{
- Py_ssize_t i;
-
- if (!self->lines)
- return;
-
- for (i = 0; i < self->num_lines; i++)
- Py_DECREF(self->lines[i]);
-
- PyMem_Free(self->lines);
- PyObject_Del(self);
-}
-
-static PyObject *gpiod_LineBulk_iternext(gpiod_LineBulkObject *self)
-{
- if (self->iter_idx < 0) {
- self->iter_idx = 0; /* First element */
- } else if (self->iter_idx >= self->num_lines) {
- self->iter_idx = -1;
- return NULL; /* Last element */
- }
-
- Py_INCREF(self->lines[self->iter_idx]);
- return self->lines[self->iter_idx++];
-}
-
-PyDoc_STRVAR(gpiod_LineBulk_to_list_doc,
-"to_list() -> list of gpiod.Line objects\n"
-"\n"
-"Convert this LineBulk to a list");
-
-static PyObject *gpiod_LineBulk_to_list(gpiod_LineBulkObject *self,
- PyObject *Py_UNUSED(ignored))
-{
- PyObject *list;
- Py_ssize_t i;
- int rv;
-
- list = PyList_New(self->num_lines);
- if (!list)
- return NULL;
-
- for (i = 0; i < self->num_lines; i++) {
- Py_INCREF(self->lines[i]);
- rv = PyList_SetItem(list, i, self->lines[i]);
- if (rv < 0) {
- Py_DECREF(list);
- return NULL;
- }
- }
-
- return list;
-}
-
-static struct gpiod_line_bulk *
-gpiod_LineBulkObjToCLineBulk(gpiod_LineBulkObject *bulk_obj)
-{
- struct gpiod_line_bulk *bulk;
- gpiod_LineObject *line_obj;
- Py_ssize_t i;
-
- bulk = gpiod_line_bulk_new(bulk_obj->num_lines);
- if (!bulk) {
- PyErr_SetFromErrno(PyExc_OSError);
- return NULL;
- }
-
- for (i = 0; i < bulk_obj->num_lines; i++) {
- line_obj = (gpiod_LineObject *)bulk_obj->lines[i];
- gpiod_line_bulk_add_line(bulk, line_obj->line);
- }
-
- return bulk;
-}
-
-static void gpiod_MakeRequestConfig(struct gpiod_line_request_config *conf,
- const char *consumer,
- int request_type, int flags)
-{
- memset(conf, 0, sizeof(*conf));
-
- conf->consumer = consumer;
-
- switch (request_type) {
- case gpiod_LINE_REQ_DIR_IN:
- conf->request_type = GPIOD_LINE_REQUEST_DIRECTION_INPUT;
- break;
- case gpiod_LINE_REQ_DIR_OUT:
- conf->request_type = GPIOD_LINE_REQUEST_DIRECTION_OUTPUT;
- break;
- case gpiod_LINE_REQ_EV_FALLING_EDGE:
- conf->request_type = GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE;
- break;
- case gpiod_LINE_REQ_EV_RISING_EDGE:
- conf->request_type = GPIOD_LINE_REQUEST_EVENT_RISING_EDGE;
- break;
- case gpiod_LINE_REQ_EV_BOTH_EDGES:
- conf->request_type = GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES;
- break;
- case gpiod_LINE_REQ_DIR_AS_IS:
- default:
- conf->request_type = GPIOD_LINE_REQUEST_DIRECTION_AS_IS;
- break;
- }
-
- if (flags & gpiod_LINE_REQ_FLAG_OPEN_DRAIN)
- conf->flags |= GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN;
- if (flags & gpiod_LINE_REQ_FLAG_OPEN_SOURCE)
- conf->flags |= GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE;
- if (flags & gpiod_LINE_REQ_FLAG_ACTIVE_LOW)
- conf->flags |= GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW;
- if (flags & gpiod_LINE_REQ_FLAG_BIAS_DISABLED)
- conf->flags |= GPIOD_LINE_REQUEST_FLAG_BIAS_DISABLED;
- if (flags & gpiod_LINE_REQ_FLAG_BIAS_PULL_DOWN)
- conf->flags |= GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN;
- if (flags & gpiod_LINE_REQ_FLAG_BIAS_PULL_UP)
- conf->flags |= GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP;
-}
-
-PyDoc_STRVAR(gpiod_LineBulk_request_doc,
-"request(consumer[, type[, flags[, default_vals]]]) -> None\n"
-"\n"
-"Request all lines held by this LineBulk object.\n"
-"\n"
-" consumer\n"
-" Name of the consumer.\n"
-" type\n"
-" Type of the request.\n"
-" flags\n"
-" Other configuration flags.\n"
-" default_vals\n"
-" List of default values.\n");
-
-static PyObject *gpiod_LineBulk_request(gpiod_LineBulkObject *self,
- PyObject *args, PyObject *kwds)
-{
- static char *kwlist[] = { "consumer",
- "type",
- "flags",
- "default_vals",
- NULL };
-
- int rv, type = gpiod_LINE_REQ_DIR_AS_IS, flags = 0,
- vals[LINE_REQUEST_MAX_LINES], val;
- PyObject *def_vals_obj = NULL, *iter, *next;
- struct gpiod_line_request_config conf;
- const int *default_vals = NULL;
- struct gpiod_line_bulk *bulk;
- Py_ssize_t num_def_vals;
- char *consumer = NULL;
- Py_ssize_t i;
-
- if (gpiod_LineBulkOwnerIsClosed(self))
- return NULL;
-
- rv = PyArg_ParseTupleAndKeywords(args, kwds, "s|iiO", kwlist,
- &consumer, &type,
- &flags, &def_vals_obj);
- if (!rv)
- return NULL;
-
- bulk = gpiod_LineBulkObjToCLineBulk(self);
- if (!bulk)
- return NULL;
-
- gpiod_MakeRequestConfig(&conf, consumer, type, flags);
-
- if (def_vals_obj) {
- memset(vals, 0, sizeof(vals));
-
- num_def_vals = PyObject_Size(def_vals_obj);
- if (num_def_vals != self->num_lines) {
- PyErr_SetString(PyExc_TypeError,
- "Number of default values is not the same as the number of lines");
- return NULL;
- }
-
- iter = PyObject_GetIter(def_vals_obj);
- if (!iter)
- return NULL;
-
- for (i = 0;; i++) {
- next = PyIter_Next(iter);
- if (!next) {
- Py_DECREF(iter);
- break;
- }
-
- val = PyLong_AsUnsignedLong(next);
- Py_DECREF(next);
- if (PyErr_Occurred()) {
- Py_DECREF(iter);
- return NULL;
- }
-
- vals[i] = !!val;
- }
- default_vals = vals;
- }
-
- bulk = gpiod_LineBulkObjToCLineBulk(self);
- if (!bulk)
- return NULL;
-
- Py_BEGIN_ALLOW_THREADS;
- rv = gpiod_line_request_bulk(bulk, &conf, default_vals);
- gpiod_line_bulk_free(bulk);
- Py_END_ALLOW_THREADS;
- if (rv)
- return PyErr_SetFromErrno(PyExc_OSError);
-
- Py_RETURN_NONE;
-}
-
-PyDoc_STRVAR(gpiod_LineBulk_get_values_doc,
-"get_values() -> list of integers\n"
-"\n"
-"Read the values of all the lines held by this LineBulk object. The index\n"
-"of each value in the returned list corresponds to the index of the line\n"
-"in this gpiod.LineBulk object.");
-
-static PyObject *gpiod_LineBulk_get_values(gpiod_LineBulkObject *self,
- PyObject *Py_UNUSED(ignored))
-{
- int rv, vals[LINE_REQUEST_MAX_LINES];
- struct gpiod_line_bulk *bulk;
- PyObject *val_list, *val;
- Py_ssize_t i;
-
- if (gpiod_LineBulkOwnerIsClosed(self))
- return NULL;
-
- bulk = gpiod_LineBulkObjToCLineBulk(self);
- if (!bulk)
- return NULL;
-
- memset(vals, 0, sizeof(vals));
- Py_BEGIN_ALLOW_THREADS;
- rv = gpiod_line_get_value_bulk(bulk, vals);
- gpiod_line_bulk_free(bulk);
- Py_END_ALLOW_THREADS;
- if (rv)
- return PyErr_SetFromErrno(PyExc_OSError);
-
- val_list = PyList_New(self->num_lines);
- if (!val_list)
- return NULL;
-
- for (i = 0; i < self->num_lines; i++) {
- val = Py_BuildValue("i", vals[i]);
- if (!val) {
- Py_DECREF(val_list);
- return NULL;
- }
-
- rv = PyList_SetItem(val_list, i, val);
- if (rv < 0) {
- Py_DECREF(val);
- Py_DECREF(val_list);
- return NULL;
- }
- }
-
- return val_list;
-}
-
-static int gpiod_TupleToIntArray(PyObject *src, int *dst, Py_ssize_t nv)
-{
- Py_ssize_t num_vals, i;
- PyObject *iter, *next;
- int val;
-
- num_vals = PyObject_Size(src);
- if (num_vals != nv) {
- PyErr_SetString(PyExc_TypeError,
- "Number of values must correspond to the number of lines");
- return -1;
- }
-
- iter = PyObject_GetIter(src);
- if (!iter)
- return -1;
-
- for (i = 0;; i++) {
- next = PyIter_Next(iter);
- if (!next) {
- Py_DECREF(iter);
- break;
- }
-
- val = PyLong_AsLong(next);
- Py_DECREF(next);
- if (PyErr_Occurred()) {
- Py_DECREF(iter);
- return -1;
- }
- dst[i] = (int)val;
- }
-
- return 0;
-}
-
-PyDoc_STRVAR(gpiod_LineBulk_set_values_doc,
-"set_values(values) -> None\n"
-"\n"
-"Set the values of all the lines held by this LineBulk object.\n"
-"\n"
-" values\n"
-" List of values (integers) to set.\n"
-"\n"
-"The number of values in the list passed as argument must be the same as\n"
-"the number of lines held by this gpiod.LineBulk object. The index of each\n"
-"value corresponds to the index of each line in the object.\n");
-
-static PyObject *gpiod_LineBulk_set_values(gpiod_LineBulkObject *self,
- PyObject *args)
-{
- int rv, vals[LINE_REQUEST_MAX_LINES];
- struct gpiod_line_bulk *bulk;
- PyObject *val_list;
-
- if (gpiod_LineBulkOwnerIsClosed(self))
- return NULL;
-
- memset(vals, 0, sizeof(vals));
-
- rv = PyArg_ParseTuple(args, "O", &val_list);
- if (!rv)
- return NULL;
-
- rv = gpiod_TupleToIntArray(val_list, vals, self->num_lines);
- if (rv)
- return NULL;
-
- bulk = gpiod_LineBulkObjToCLineBulk(self);
- if (!bulk)
- return NULL;
-
- Py_BEGIN_ALLOW_THREADS;
- rv = gpiod_line_set_value_bulk(bulk, vals);
- gpiod_line_bulk_free(bulk);
- Py_END_ALLOW_THREADS;
- if (rv)
- return PyErr_SetFromErrno(PyExc_OSError);
-
- Py_RETURN_NONE;
-}
-
-PyDoc_STRVAR(gpiod_LineBulk_set_config_doc,
-"set_config(direction,flags,values) -> None\n"
-"\n"
-"Set the configuration of all the lines held by this LineBulk object.\n"
-"\n"
-" direction\n"
-" New direction (integer)\n"
-" flags\n"
-" New flags (integer)\n"
-" values\n"
-" List of values (integers) to set when direction is output.\n"
-"\n"
-"The number of values in the list passed as argument must be the same as\n"
-"the number of lines held by this gpiod.LineBulk object. The index of each\n"
-"value corresponds to the index of each line in the object.\n");
-
-static PyObject *gpiod_LineBulk_set_config(gpiod_LineBulkObject *self,
- PyObject *args)
-{
- int rv, vals[LINE_REQUEST_MAX_LINES];
- struct gpiod_line_bulk *bulk;
- PyObject *val_list;
- const int *valp;
- int dirn, flags;
-
- if (gpiod_LineBulkOwnerIsClosed(self))
- return NULL;
-
- val_list = NULL;
- rv = PyArg_ParseTuple(args, "ii|(O)", &dirn, &flags, &val_list);
- if (!rv)
- return NULL;
-
- if (val_list == NULL) {
- valp = NULL;
- } else {
- memset(vals, 0, sizeof(vals));
- rv = gpiod_TupleToIntArray(val_list, vals, self->num_lines);
- if (rv)
- return NULL;
- valp = vals;
- }
-
- bulk = gpiod_LineBulkObjToCLineBulk(self);
- if (!bulk)
- return NULL;
-
- Py_BEGIN_ALLOW_THREADS;
- rv = gpiod_line_set_config_bulk(bulk, dirn, flags, valp);
- Py_END_ALLOW_THREADS;
- if (rv)
- return PyErr_SetFromErrno(PyExc_OSError);
-
- Py_RETURN_NONE;
-}
-
-PyDoc_STRVAR(gpiod_LineBulk_set_flags_doc,
-"set_flags(flags) -> None\n"
-"\n"
-"Set the flags of all the lines held by this LineBulk object.\n"
-"\n"
-" flags\n"
-" New flags (integer)");
-
-static PyObject *gpiod_LineBulk_set_flags(gpiod_LineBulkObject *self,
- PyObject *args)
-{
- struct gpiod_line_bulk *bulk;
- int rv, flags;
-
- if (gpiod_LineBulkOwnerIsClosed(self))
- return NULL;
-
- rv = PyArg_ParseTuple(args, "i", &flags);
- if (!rv)
- return NULL;
-
- bulk = gpiod_LineBulkObjToCLineBulk(self);
- if (!bulk)
- return NULL;
-
- Py_BEGIN_ALLOW_THREADS;
- rv = gpiod_line_set_flags_bulk(bulk, flags);
- gpiod_line_bulk_free(bulk);
- Py_END_ALLOW_THREADS;
- if (rv)
- return PyErr_SetFromErrno(PyExc_OSError);
-
- Py_RETURN_NONE;
-}
-
-PyDoc_STRVAR(gpiod_LineBulk_set_direction_input_doc,
-"set_direction_input() -> None\n"
-"\n"
-"Set the direction of all the lines held by this LineBulk object to input.\n");
-
-static PyObject *gpiod_LineBulk_set_direction_input(gpiod_LineBulkObject *self,
- PyObject *Py_UNUSED(ignored))
-{
- struct gpiod_line_bulk *bulk;
- int rv;
-
- if (gpiod_LineBulkOwnerIsClosed(self))
- return NULL;
-
- bulk = gpiod_LineBulkObjToCLineBulk(self);
- if (!bulk)
- return NULL;
-
- Py_BEGIN_ALLOW_THREADS;
- rv = gpiod_line_set_direction_input_bulk(bulk);
- gpiod_line_bulk_free(bulk);
- Py_END_ALLOW_THREADS;
- if (rv)
- return PyErr_SetFromErrno(PyExc_OSError);
-
- Py_RETURN_NONE;
-}
-
-PyDoc_STRVAR(gpiod_LineBulk_set_direction_output_doc,
-"set_direction_output(value) -> None\n"
-"\n"
-"Set the direction of all the lines held by this LineBulk object to output.\n"
-"\n"
-" values\n"
-" List of values (integers) to set when direction is output.\n"
-"\n"
-"The number of values in the list passed as argument must be the same as\n"
-"the number of lines held by this gpiod.LineBulk object. The index of each\n"
-"value corresponds to the index of each line in the object.\n");
-
-static PyObject *gpiod_LineBulk_set_direction_output(
- gpiod_LineBulkObject *self,
- PyObject *args)
-{
- int rv, vals[LINE_REQUEST_MAX_LINES];
- struct gpiod_line_bulk *bulk;
- PyObject *val_list;
- const int *valp;
-
- if (gpiod_LineBulkOwnerIsClosed(self))
- return NULL;
-
- val_list = NULL;
- rv = PyArg_ParseTuple(args, "|O", &val_list);
- if (!rv)
- return NULL;
-
- if (val_list == NULL)
- valp = NULL;
- else {
- memset(vals, 0, sizeof(vals));
- rv = gpiod_TupleToIntArray(val_list, vals, self->num_lines);
- if (rv)
- return NULL;
- valp = vals;
- }
-
- bulk = gpiod_LineBulkObjToCLineBulk(self);
- if (!bulk)
- return NULL;
-
- Py_BEGIN_ALLOW_THREADS;
- rv = gpiod_line_set_direction_output_bulk(bulk, valp);
- gpiod_line_bulk_free(bulk);
- Py_END_ALLOW_THREADS;
- if (rv)
- return PyErr_SetFromErrno(PyExc_OSError);
-
- Py_RETURN_NONE;
-}
-
-PyDoc_STRVAR(gpiod_LineBulk_release_doc,
-"release() -> None\n"
-"\n"
-"Release all lines held by this LineBulk object.");
-
-static PyObject *gpiod_LineBulk_release(gpiod_LineBulkObject *self,
- PyObject *Py_UNUSED(ignored))
-{
- struct gpiod_line_bulk *bulk;
-
- if (gpiod_LineBulkOwnerIsClosed(self))
- return NULL;
-
- bulk = gpiod_LineBulkObjToCLineBulk(self);
- if (!bulk)
- return NULL;
-
- gpiod_line_release_bulk(bulk);
- gpiod_line_bulk_free(bulk);
-
- Py_RETURN_NONE;
-}
-
-PyDoc_STRVAR(gpiod_LineBulk_event_wait_doc,
-"event_wait([sec[ ,nsec]]) -> gpiod.LineBulk object or None\n"
-"\n"
-"Poll the lines held by this LineBulk Object for line events.\n"
-"\n"
-" sec\n"
-" Number of seconds to wait before timeout.\n"
-" nsec\n"
-" Number of nanoseconds to wait before timeout.\n"
-"\n"
-"Returns a gpiod.LineBulk object containing references to lines on which\n"
-"events occurred or None if we reached the timeout without any event\n"
-"occurring.");
-
-static PyObject *gpiod_LineBulk_event_wait(gpiod_LineBulkObject *self,
- PyObject *args, PyObject *kwds)
-{
- static char *kwlist[] = { "sec", "nsec", NULL };
-
- struct gpiod_line_bulk *bulk, *ev_bulk;
- gpiod_LineObject *line_obj;
- gpiod_ChipObject *owner;
- long sec = 0, nsec = 0;
- struct timespec ts;
- PyObject *ret;
- unsigned int idx;
- int rv;
-
- if (gpiod_LineBulkOwnerIsClosed(self))
- return NULL;
-
- rv = PyArg_ParseTupleAndKeywords(args, kwds,
- "|ll", kwlist, &sec, &nsec);
- if (!rv)
- return NULL;
-
- ts.tv_sec = sec;
- ts.tv_nsec = nsec;
-
- bulk = gpiod_LineBulkObjToCLineBulk(self);
- if (!bulk)
- return NULL;
-
- ev_bulk = gpiod_line_bulk_new(self->num_lines);
- if (!ev_bulk) {
- gpiod_line_bulk_free(bulk);
- return NULL;
- }
-
- Py_BEGIN_ALLOW_THREADS;
- rv = gpiod_line_event_wait_bulk(bulk, &ts, ev_bulk);
- gpiod_line_bulk_free(bulk);
- Py_END_ALLOW_THREADS;
- if (rv < 0) {
- gpiod_line_bulk_free(ev_bulk);
- return PyErr_SetFromErrno(PyExc_OSError);
- } else if (rv == 0) {
- gpiod_line_bulk_free(ev_bulk);
- Py_RETURN_NONE;
- }
-
- ret = PyList_New(gpiod_line_bulk_num_lines(ev_bulk));
- if (!ret) {
- gpiod_line_bulk_free(ev_bulk);
- return NULL;
- }
-
- owner = ((gpiod_LineObject *)(self->lines[0]))->owner;
-
- for (idx = 0; idx < gpiod_line_bulk_num_lines(ev_bulk); idx++) {
- line_obj = gpiod_MakeLineObject(owner, gpiod_line_bulk_get_line(ev_bulk, idx));
- if (!line_obj) {
- gpiod_line_bulk_free(ev_bulk);
- Py_DECREF(ret);
- return NULL;
- }
-
- rv = PyList_SetItem(ret, idx, (PyObject *)line_obj);
- if (rv < 0) {
- gpiod_line_bulk_free(ev_bulk);
- Py_DECREF(ret);
- return NULL;
- }
- }
-
- gpiod_line_bulk_free(ev_bulk);
-
- return ret;
-}
-
-static PyObject *gpiod_LineBulk_repr(gpiod_LineBulkObject *self)
-{
- PyObject *list, *list_repr, *chip_name, *ret;
- gpiod_LineObject *line;
-
- if (gpiod_LineBulkOwnerIsClosed(self))
- return NULL;
-
- list = gpiod_LineBulk_to_list(self, NULL);
- if (!list)
- return NULL;
-
- list_repr = PyObject_Repr(list);
- Py_DECREF(list);
- if (!list_repr)
- return NULL;
-
- line = (gpiod_LineObject *)self->lines[0];
- chip_name = PyObject_CallMethod((PyObject *)line->owner, "name", "");
- if (!chip_name) {
- Py_DECREF(list_repr);
- return NULL;
- }
-
- ret = PyUnicode_FromFormat("%U%U", chip_name, list_repr);
- Py_DECREF(chip_name);
- Py_DECREF(list_repr);
- return ret;
-}
-
-static PyMethodDef gpiod_LineBulk_methods[] = {
- {
- .ml_name = "to_list",
- .ml_meth = (PyCFunction)gpiod_LineBulk_to_list,
- .ml_doc = gpiod_LineBulk_to_list_doc,
- .ml_flags = METH_NOARGS,
- },
- {
- .ml_name = "request",
- .ml_meth = (PyCFunction)(void (*)(void))gpiod_LineBulk_request,
- .ml_doc = gpiod_LineBulk_request_doc,
- .ml_flags = METH_VARARGS | METH_KEYWORDS,
- },
- {
- .ml_name = "get_values",
- .ml_meth = (PyCFunction)gpiod_LineBulk_get_values,
- .ml_doc = gpiod_LineBulk_get_values_doc,
- .ml_flags = METH_NOARGS,
- },
- {
- .ml_name = "set_values",
- .ml_meth = (PyCFunction)gpiod_LineBulk_set_values,
- .ml_doc = gpiod_LineBulk_set_values_doc,
- .ml_flags = METH_VARARGS,
- },
- {
- .ml_name = "set_config",
- .ml_meth = (PyCFunction)gpiod_LineBulk_set_config,
- .ml_flags = METH_VARARGS,
- .ml_doc = gpiod_LineBulk_set_config_doc,
- },
- {
- .ml_name = "set_flags",
- .ml_meth = (PyCFunction)gpiod_LineBulk_set_flags,
- .ml_flags = METH_VARARGS,
- .ml_doc = gpiod_LineBulk_set_flags_doc,
- },
- {
- .ml_name = "set_direction_input",
- .ml_meth = (PyCFunction)gpiod_LineBulk_set_direction_input,
- .ml_flags = METH_NOARGS,
- .ml_doc = gpiod_LineBulk_set_direction_input_doc,
- },
- {
- .ml_name = "set_direction_output",
- .ml_meth = (PyCFunction)gpiod_LineBulk_set_direction_output,
- .ml_flags = METH_VARARGS,
- .ml_doc = gpiod_LineBulk_set_direction_output_doc,
- },
- {
- .ml_name = "release",
- .ml_meth = (PyCFunction)gpiod_LineBulk_release,
- .ml_doc = gpiod_LineBulk_release_doc,
- .ml_flags = METH_NOARGS,
- },
- {
- .ml_name = "event_wait",
- .ml_meth = (PyCFunction)(void (*)(void))gpiod_LineBulk_event_wait,
- .ml_doc = gpiod_LineBulk_event_wait_doc,
- .ml_flags = METH_VARARGS | METH_KEYWORDS,
- },
- { }
-};
-
-PyDoc_STRVAR(gpiod_LineBulkType_doc,
-"Represents a set of GPIO lines.\n"
-"\n"
-"Objects of this type are immutable. The constructor takes as argument\n"
-"a sequence of gpiod.Line objects. It doesn't accept objects of any other\n"
-"type.");
-
-static PyTypeObject gpiod_LineBulkType = {
- PyVarObject_HEAD_INIT(NULL, 0)
- .tp_name = "gpiod.LineBulk",
- .tp_basicsize = sizeof(gpiod_LineBulkObject),
- .tp_flags = Py_TPFLAGS_DEFAULT,
- .tp_doc = gpiod_LineBulkType_doc,
- .tp_new = PyType_GenericNew,
- .tp_init = (initproc)gpiod_LineBulk_init,
- .tp_dealloc = (destructor)gpiod_LineBulk_dealloc,
- .tp_iter = PyObject_SelfIter,
- .tp_iternext = (iternextfunc)gpiod_LineBulk_iternext,
- .tp_repr = (reprfunc)gpiod_LineBulk_repr,
- .tp_methods = gpiod_LineBulk_methods,
-};
-
-static gpiod_LineBulkObject *gpiod_LineToLineBulk(gpiod_LineObject *line)
-{
- gpiod_LineBulkObject *ret;
- PyObject *args;
-
- args = Py_BuildValue("((O))", line);
- if (!args)
- return NULL;
-
- ret = (gpiod_LineBulkObject *)PyObject_CallObject(
- (PyObject *)&gpiod_LineBulkType,
- args);
- Py_DECREF(args);
-
- return ret;
-}
-
-static int gpiod_Chip_init(gpiod_ChipObject *self,
- PyObject *args, PyObject *Py_UNUSED(ignored))
-{
- char *path;
- int rv;
-
- rv = PyArg_ParseTuple(args, "s", &path);
- if (!rv)
- return -1;
-
- Py_BEGIN_ALLOW_THREADS;
- self->chip = gpiod_chip_open(path);
- Py_END_ALLOW_THREADS;
- if (!self->chip) {
- PyErr_SetFromErrno(PyExc_OSError);
- return -1;
- }
-
- return 0;
-}
-
-static void gpiod_Chip_dealloc(gpiod_ChipObject *self)
-{
- if (self->chip)
- gpiod_chip_unref(self->chip);
-
- PyObject_Del(self);
-}
-
-static PyObject *gpiod_Chip_repr(gpiod_ChipObject *self)
-{
- if (gpiod_ChipIsClosed(self))
- return NULL;
-
- return PyUnicode_FromFormat("'%s /%s/ %u lines'",
- gpiod_chip_get_name(self->chip),
- gpiod_chip_get_label(self->chip),
- gpiod_chip_get_num_lines(self->chip));
-}
-
-PyDoc_STRVAR(gpiod_Chip_close_doc,
-"close() -> None\n"
-"\n"
-"Close the associated gpiochip descriptor. The chip object must no longer\n"
-"be used after this method is called.\n");
-
-static PyObject *gpiod_Chip_close(gpiod_ChipObject *self,
- PyObject *Py_UNUSED(ignored))
-{
- if (gpiod_ChipIsClosed(self))
- return NULL;
-
- gpiod_chip_unref(self->chip);
- self->chip = NULL;
-
- Py_RETURN_NONE;
-}
-
-PyDoc_STRVAR(gpiod_Chip_enter_doc,
-"Controlled execution enter callback.");
-
-static PyObject *gpiod_Chip_enter(gpiod_ChipObject *chip,
- PyObject *Py_UNUSED(ignored))
-{
- Py_INCREF(chip);
- return (PyObject *)chip;
-}
-
-PyDoc_STRVAR(gpiod_Chip_exit_doc,
-"Controlled execution exit callback.");
-
-static PyObject *gpiod_Chip_exit(gpiod_ChipObject *chip,
- PyObject *Py_UNUSED(ignored))
-{
- return PyObject_CallMethod((PyObject *)chip, "close", "");
-}
-
-PyDoc_STRVAR(gpiod_Chip_name_doc,
-"name() -> string\n"
-"\n"
-"Get the name of the GPIO chip");
-
-static PyObject *gpiod_Chip_name(gpiod_ChipObject *self,
- PyObject *Py_UNUSED(ignored))
-{
- if (gpiod_ChipIsClosed(self))
- return NULL;
-
- return PyUnicode_FromFormat("%s", gpiod_chip_get_name(self->chip));
-}
-
-PyDoc_STRVAR(gpiod_Chip_label_doc,
-"label() -> string\n"
-"\n"
-"Get the label of the GPIO chip");
-
-static PyObject *gpiod_Chip_label(gpiod_ChipObject *self,
- PyObject *Py_UNUSED(ignored))
-{
- if (gpiod_ChipIsClosed(self))
- return NULL;
-
- return PyUnicode_FromFormat("%s", gpiod_chip_get_label(self->chip));
-}
-
-PyDoc_STRVAR(gpiod_Chip_num_lines_doc,
-"num_lines() -> integer\n"
-"\n"
-"Get the number of lines exposed by this GPIO chip.");
-
-static PyObject *gpiod_Chip_num_lines(gpiod_ChipObject *self,
- PyObject *Py_UNUSED(ignored))
-{
- if (gpiod_ChipIsClosed(self))
- return NULL;
-
- return Py_BuildValue("I", gpiod_chip_get_num_lines(self->chip));
-}
-
-static gpiod_LineObject *
-gpiod_MakeLineObject(gpiod_ChipObject *owner, struct gpiod_line *line)
-{
- gpiod_LineObject *obj;
-
- obj = PyObject_New(gpiod_LineObject, &gpiod_LineType);
- if (!obj)
- return NULL;
-
- obj->line = line;
- Py_INCREF(owner);
- obj->owner = owner;
-
- return obj;
-}
-
-PyDoc_STRVAR(gpiod_Chip_get_line_doc,
-"get_line(offset) -> gpiod.Line object\n"
-"\n"
-"Get the GPIO line at given offset.\n"
-"\n"
-" offset\n"
-" Line offset (integer)");
-
-static gpiod_LineObject *
-gpiod_Chip_get_line(gpiod_ChipObject *self, PyObject *args)
-{
- struct gpiod_line *line;
- unsigned int offset;
- int rv;
-
- if (gpiod_ChipIsClosed(self))
- return NULL;
-
- rv = PyArg_ParseTuple(args, "I", &offset);
- if (!rv)
- return NULL;
-
- Py_BEGIN_ALLOW_THREADS;
- line = gpiod_chip_get_line(self->chip, offset);
- Py_END_ALLOW_THREADS;
- if (!line)
- return (gpiod_LineObject *)PyErr_SetFromErrno(PyExc_OSError);
-
- return gpiod_MakeLineObject(self, line);
-}
-
-static gpiod_LineBulkObject *gpiod_ListToLineBulk(PyObject *lines)
-{
- gpiod_LineBulkObject *bulk;
- PyObject *arg;
-
- arg = PyTuple_Pack(1, lines);
- if (!arg)
- return NULL;
-
- bulk = (gpiod_LineBulkObject *)PyObject_CallObject(
- (PyObject *)&gpiod_LineBulkType,
- arg);
- Py_DECREF(arg);
-
- return bulk;
-}
-
-static gpiod_LineBulkObject *
-gpiod_LineBulkObjectFromBulk(gpiod_ChipObject *chip, struct gpiod_line_bulk *bulk)
-{
- gpiod_LineBulkObject *bulk_obj;
- gpiod_LineObject *line_obj;
- struct gpiod_line *line;
- unsigned int idx;
- PyObject *list;
- int rv;
-
- list = PyList_New(gpiod_line_bulk_num_lines(bulk));
- if (!list)
- return NULL;
-
- for (idx = 0; idx < gpiod_line_bulk_num_lines(bulk); idx++) {
- line = gpiod_line_bulk_get_line(bulk, idx);
- line_obj = gpiod_MakeLineObject(chip, line);
- if (!line_obj) {
- Py_DECREF(list);
- return NULL;
- }
-
- rv = PyList_SetItem(list, idx, (PyObject *)line_obj);
- if (rv < 0) {
- Py_DECREF(line_obj);
- Py_DECREF(list);
- return NULL;
- }
- }
-
- bulk_obj = gpiod_ListToLineBulk(list);
- Py_DECREF(list);
- if (!bulk_obj)
- return NULL;
-
- return bulk_obj;
-}
-
-PyDoc_STRVAR(gpiod_Chip_find_line_doc,
-"find_line(name) -> integer or None\n"
-"\n"
-"Find the offset of the line with given name among lines exposed by this\n"
-"GPIO chip.\n"
-"\n"
-" name\n"
-" Line name (string)\n"
-"\n"
-"Returns the offset of the line with given name or None if it is not\n"
-"associated with this chip.");
-
-static PyObject *gpiod_Chip_find_line(gpiod_ChipObject *self, PyObject *args)
-{
- const char *name;
- int rv, offset;
-
- if (gpiod_ChipIsClosed(self))
- return NULL;
-
- rv = PyArg_ParseTuple(args, "s", &name);
- if (!rv)
- return NULL;
-
- Py_BEGIN_ALLOW_THREADS;
- offset = gpiod_chip_find_line(self->chip, name);
- Py_END_ALLOW_THREADS;
- if (offset < 0) {
- if (errno == ENOENT)
- Py_RETURN_NONE;
-
- return PyErr_SetFromErrno(PyExc_OSError);
- }
-
- return Py_BuildValue("i", offset);
-}
-
-PyDoc_STRVAR(gpiod_Chip_get_lines_doc,
-"get_lines(offsets) -> gpiod.LineBulk object\n"
-"\n"
-"Get a set of GPIO lines by their offsets.\n"
-"\n"
-" offsets\n"
-" List of lines offsets.");
-
-static gpiod_LineBulkObject *
-gpiod_Chip_get_lines(gpiod_ChipObject *self, PyObject *args)
-{
- PyObject *offsets, *iter, *next, *lines, *arg;
- gpiod_LineBulkObject *bulk;
- Py_ssize_t num_offsets, i;
- gpiod_LineObject *line;
- int rv;
-
- rv = PyArg_ParseTuple(args, "O", &offsets);
- if (!rv)
- return NULL;
-
- num_offsets = PyObject_Size(offsets);
- if (num_offsets < 1) {
- PyErr_SetString(PyExc_TypeError,
- "Argument must be a non-empty sequence of offsets");
- return NULL;
- }
-
- lines = PyList_New(num_offsets);
- if (!lines)
- return NULL;
-
- iter = PyObject_GetIter(offsets);
- if (!iter) {
- Py_DECREF(lines);
- return NULL;
- }
-
- for (i = 0;;) {
- next = PyIter_Next(iter);
- if (!next) {
- Py_DECREF(iter);
- break;
- }
-
- arg = PyTuple_Pack(1, next);
- Py_DECREF(next);
- if (!arg) {
- Py_DECREF(iter);
- Py_DECREF(lines);
- return NULL;
- }
-
- line = gpiod_Chip_get_line(self, arg);
- Py_DECREF(arg);
- if (!line) {
- Py_DECREF(iter);
- Py_DECREF(lines);
- return NULL;
- }
-
- rv = PyList_SetItem(lines, i++, (PyObject *)line);
- if (rv < 0) {
- Py_DECREF(line);
- Py_DECREF(iter);
- Py_DECREF(lines);
- return NULL;
- }
- }
-
- bulk = gpiod_ListToLineBulk(lines);
- Py_DECREF(lines);
- if (!bulk)
- return NULL;
-
- return bulk;
-}
-
-PyDoc_STRVAR(gpiod_Chip_get_all_lines_doc,
-"get_all_lines() -> gpiod.LineBulk object\n"
-"\n"
-"Get all lines exposed by this Chip.");
-
-static gpiod_LineBulkObject *
-gpiod_Chip_get_all_lines(gpiod_ChipObject *self, PyObject *Py_UNUSED(ignored))
-{
- gpiod_LineBulkObject *bulk_obj;
- struct gpiod_line_bulk *bulk;
-
- if (gpiod_ChipIsClosed(self))
- return NULL;
-
- bulk = gpiod_chip_get_all_lines(self->chip);
- if (!bulk)
- return (gpiod_LineBulkObject *)PyErr_SetFromErrno(
- PyExc_OSError);
-
- bulk_obj = gpiod_LineBulkObjectFromBulk(self, bulk);
- gpiod_line_bulk_free(bulk);
- return bulk_obj;
-}
-
-static PyMethodDef gpiod_Chip_methods[] = {
- {
- .ml_name = "close",
- .ml_meth = (PyCFunction)gpiod_Chip_close,
- .ml_flags = METH_NOARGS,
- .ml_doc = gpiod_Chip_close_doc,
- },
- {
- .ml_name = "__enter__",
- .ml_meth = (PyCFunction)gpiod_Chip_enter,
- .ml_flags = METH_NOARGS,
- .ml_doc = gpiod_Chip_enter_doc,
- },
- {
- .ml_name = "__exit__",
- .ml_meth = (PyCFunction)gpiod_Chip_exit,
- .ml_flags = METH_VARARGS,
- .ml_doc = gpiod_Chip_exit_doc,
- },
- {
- .ml_name = "name",
- .ml_meth = (PyCFunction)gpiod_Chip_name,
- .ml_flags = METH_NOARGS,
- .ml_doc = gpiod_Chip_name_doc,
- },
- {
- .ml_name = "label",
- .ml_meth = (PyCFunction)gpiod_Chip_label,
- .ml_flags = METH_NOARGS,
- .ml_doc = gpiod_Chip_label_doc,
- },
- {
- .ml_name = "num_lines",
- .ml_meth = (PyCFunction)gpiod_Chip_num_lines,
- .ml_flags = METH_NOARGS,
- .ml_doc = gpiod_Chip_num_lines_doc,
- },
- {
- .ml_name = "get_line",
- .ml_meth = (PyCFunction)gpiod_Chip_get_line,
- .ml_flags = METH_VARARGS,
- .ml_doc = gpiod_Chip_get_line_doc,
- },
- {
- .ml_name = "find_line",
- .ml_meth = (PyCFunction)gpiod_Chip_find_line,
- .ml_flags = METH_VARARGS,
- .ml_doc = gpiod_Chip_find_line_doc,
- },
- {
- .ml_name = "get_lines",
- .ml_meth = (PyCFunction)gpiod_Chip_get_lines,
- .ml_flags = METH_VARARGS,
- .ml_doc = gpiod_Chip_get_lines_doc,
- },
- {
- .ml_name = "get_all_lines",
- .ml_meth = (PyCFunction)gpiod_Chip_get_all_lines,
- .ml_flags = METH_NOARGS,
- .ml_doc = gpiod_Chip_get_all_lines_doc,
- },
- { }
-};
-
-PyDoc_STRVAR(gpiod_ChipType_doc,
-"Represents a GPIO chip.\n"
-"\n"
-"Chip object manages all resources associated with the GPIO chip\n"
-"it represents.\n"
-"\n"
-"The gpiochip device file is opened during the object's construction.\n"
-"The Chip object's constructor takes a description string as argument the\n"
-"meaning of which depends on the second, optional parameter which defines\n"
-"the way the description string should be interpreted. The available\n"
-"options are: OPEN_BY_NAME, OPEN_BY_NUMBER, OPEN_BY_PATH and OPEN_LOOKUP.\n"
-"The last option means that libgpiod should open the chip based on the best\n"
-"guess what the path is. This is also the default if the second argument is\n"
-"missing.\n"
-"\n"
-"Callers must close the chip by calling the close() method when it's no\n"
-"longer used.\n"
-"\n"
-"Example:\n"
-"\n"
-" chip = gpiod.Chip('gpiochip0', gpiod.Chip.OPEN_BY_NAME)\n"
-" do_something(chip)\n"
-" chip.close()\n"
-"\n"
-"The gpiod.Chip class also supports controlled execution ('with' statement).\n"
-"\n"
-"Example:\n"
-"\n"
-" with gpiod.Chip('0', gpiod.Chip.OPEN_BY_NUMBER) as chip:\n"
-" do_something(chip)");
-
-static PyTypeObject gpiod_ChipType = {
- PyVarObject_HEAD_INIT(NULL, 0)
- .tp_name = "gpiod.Chip",
- .tp_basicsize = sizeof(gpiod_ChipObject),
- .tp_flags = Py_TPFLAGS_DEFAULT,
- .tp_doc = gpiod_ChipType_doc,
- .tp_new = PyType_GenericNew,
- .tp_init = (initproc)gpiod_Chip_init,
- .tp_dealloc = (destructor)gpiod_Chip_dealloc,
- .tp_repr = (reprfunc)gpiod_Chip_repr,
- .tp_methods = gpiod_Chip_methods,
-};
-
-static int gpiod_LineIter_init(gpiod_LineIterObject *self,
- PyObject *args, PyObject *Py_UNUSED(ignored))
-{
- gpiod_ChipObject *chip_obj;
- int rv;
-
- rv = PyArg_ParseTuple(args, "O!", &gpiod_ChipType,
- (PyObject *)&chip_obj);
- if (!rv)
- return -1;
-
- if (gpiod_ChipIsClosed(chip_obj))
- return -1;
-
- self->offset = 0;
- self->owner = chip_obj;
- Py_INCREF(chip_obj);
-
- return 0;
-}
-
-static void gpiod_LineIter_dealloc(gpiod_LineIterObject *self)
-{
- PyObject_Del(self);
-}
-
-static gpiod_LineObject *gpiod_LineIter_next(gpiod_LineIterObject *self)
-{
- struct gpiod_line *line;
-
- if (self->offset == gpiod_chip_get_num_lines(self->owner->chip))
- return NULL; /* Last element. */
-
- line = gpiod_chip_get_line(self->owner->chip, self->offset++);
- if (!line)
- return (gpiod_LineObject *)PyErr_SetFromErrno(PyExc_OSError);
-
- return gpiod_MakeLineObject(self->owner, line);
-}
-
-PyDoc_STRVAR(gpiod_LineIterType_doc,
-"Allows to iterate over all lines exposed by a GPIO chip.\n"
-"\n"
-"New line iterator is created by passing a reference to an open gpiod.Chip\n"
-"object to the constructor of gpiod.LineIter.\n"
-"\n"
-"Caller doesn't need to handle the resource management for lines as their\n"
-"lifetime is managed by the owning chip.\n"
-"\n"
-"Example:\n"
-"\n"
-" chip = gpiod.Chip('gpiochip0')\n"
-" for line in gpiod.LineIter(chip):\n"
-" do_stuff_with_line(line)");
-
-static PyTypeObject gpiod_LineIterType = {
- PyVarObject_HEAD_INIT(NULL, 0)
- .tp_name = "gpiod.LineIter",
- .tp_basicsize = sizeof(gpiod_LineIterObject),
- .tp_flags = Py_TPFLAGS_DEFAULT,
- .tp_doc = gpiod_LineIterType_doc,
- .tp_new = PyType_GenericNew,
- .tp_init = (initproc)gpiod_LineIter_init,
- .tp_dealloc = (destructor)gpiod_LineIter_dealloc,
- .tp_iter = PyObject_SelfIter,
- .tp_iternext = (iternextfunc)gpiod_LineIter_next,
-};
-
-typedef struct {
- const char *name;
- PyTypeObject *typeobj;
-} gpiod_PyType;
-
-static gpiod_PyType gpiod_PyType_list[] = {
- { .name = "Chip", .typeobj = &gpiod_ChipType, },
- { .name = "Line", .typeobj = &gpiod_LineType, },
- { .name = "LineEvent", .typeobj = &gpiod_LineEventType, },
- { .name = "LineBulk", .typeobj = &gpiod_LineBulkType, },
- { .name = "LineIter", .typeobj = &gpiod_LineIterType, },
- { }
-};
-
-typedef struct {
- PyTypeObject *typeobj;
- const char *name;
- long int val;
-} gpiod_ConstDescr;
-
-static gpiod_ConstDescr gpiod_ConstList[] = {
- {
- .typeobj = &gpiod_LineType,
- .name = "DIRECTION_INPUT",
- .val = gpiod_DIRECTION_INPUT,
- },
- {
- .typeobj = &gpiod_LineType,
- .name = "DIRECTION_OUTPUT",
- .val = gpiod_DIRECTION_OUTPUT,
- },
- {
- .typeobj = &gpiod_LineType,
- .name = "DRIVE_PUSH_PULL",
- .val = gpiod_DRIVE_PUSH_PULL,
- },
- {
- .typeobj = &gpiod_LineType,
- .name = "DRIVE_OPEN_DRAIN",
- .val = gpiod_DRIVE_OPEN_DRAIN,
- },
- {
- .typeobj = &gpiod_LineType,
- .name = "DRIVE_OPEN_SOURCE",
- .val = gpiod_DRIVE_OPEN_SOURCE,
- },
- {
- .typeobj = &gpiod_LineType,
- .name = "BIAS_UNKNOWN",
- .val = gpiod_BIAS_UNKNOWN,
- },
- {
- .typeobj = &gpiod_LineType,
- .name = "BIAS_DISABLED",
- .val = gpiod_BIAS_DISABLED,
- },
- {
- .typeobj = &gpiod_LineType,
- .name = "BIAS_PULL_UP",
- .val = gpiod_BIAS_PULL_UP,
- },
- {
- .typeobj = &gpiod_LineType,
- .name = "BIAS_PULL_DOWN",
- .val = gpiod_BIAS_PULL_DOWN,
- },
- {
- .typeobj = &gpiod_LineEventType,
- .name = "RISING_EDGE",
- .val = gpiod_RISING_EDGE,
- },
- {
- .typeobj = &gpiod_LineEventType,
- .name = "FALLING_EDGE",
- .val = gpiod_FALLING_EDGE,
- },
- { }
-};
-
-PyDoc_STRVAR(gpiod_Module_is_gpiochip_device_doc,
-"is_gpiochip_device(path) -> boolean\n"
-"\n"
-"Check if the file pointed to by path is a GPIO chip character device.\n"
-"Returns true if so, False otherwise.\n"
-"\n"
-" path\n"
-" Path to the file that should be checked.\n");
-
-static PyObject *
-gpiod_Module_is_gpiochip_device(PyObject *Py_UNUSED(self), PyObject *args)
-{
- const char *path;
- int ret;
-
- ret = PyArg_ParseTuple(args, "s", &path);
- if (!ret)
- return NULL;
-
- if (gpiod_is_gpiochip_device(path))
- Py_RETURN_TRUE;
-
- Py_RETURN_FALSE;
-}
-
-static PyMethodDef gpiod_module_methods[] = {
- {
- .ml_name = "is_gpiochip_device",
- .ml_meth = (PyCFunction)gpiod_Module_is_gpiochip_device,
- .ml_flags = METH_VARARGS,
- .ml_doc = gpiod_Module_is_gpiochip_device_doc,
- },
- { }
-};
-
-PyDoc_STRVAR(gpiod_Module_doc,
-"Python bindings for libgpiod.\n\
-\n\
-This module wraps the native C API of libgpiod in a set of python classes.");
-
-static PyModuleDef gpiod_Module = {
- PyModuleDef_HEAD_INIT,
- .m_name = "gpiod",
- .m_doc = gpiod_Module_doc,
- .m_size = -1,
- .m_methods = gpiod_module_methods,
-};
-
-typedef struct {
- const char *name;
- long int value;
-} gpiod_ModuleConst;
-
-static gpiod_ModuleConst gpiod_ModuleConsts[] = {
- {
- .name = "LINE_REQ_DIR_AS_IS",
- .value = gpiod_LINE_REQ_DIR_AS_IS,
- },
- {
- .name = "LINE_REQ_DIR_IN",
- .value = gpiod_LINE_REQ_DIR_IN,
- },
- {
- .name = "LINE_REQ_DIR_OUT",
- .value = gpiod_LINE_REQ_DIR_OUT,
- },
- {
- .name = "LINE_REQ_EV_FALLING_EDGE",
- .value = gpiod_LINE_REQ_EV_FALLING_EDGE,
- },
- {
- .name = "LINE_REQ_EV_RISING_EDGE",
- .value = gpiod_LINE_REQ_EV_RISING_EDGE,
- },
- {
- .name = "LINE_REQ_EV_BOTH_EDGES",
- .value = gpiod_LINE_REQ_EV_BOTH_EDGES,
- },
- {
- .name = "LINE_REQ_FLAG_OPEN_DRAIN",
- .value = gpiod_LINE_REQ_FLAG_OPEN_DRAIN,
- },
- {
- .name = "LINE_REQ_FLAG_OPEN_SOURCE",
- .value = gpiod_LINE_REQ_FLAG_OPEN_SOURCE,
- },
- {
- .name = "LINE_REQ_FLAG_ACTIVE_LOW",
- .value = gpiod_LINE_REQ_FLAG_ACTIVE_LOW,
- },
- {
- .name = "LINE_REQ_FLAG_BIAS_DISABLED",
- .value = gpiod_LINE_REQ_FLAG_BIAS_DISABLED,
- },
- {
- .name = "LINE_REQ_FLAG_BIAS_PULL_DOWN",
- .value = gpiod_LINE_REQ_FLAG_BIAS_PULL_DOWN,
- },
- {
- .name = "LINE_REQ_FLAG_BIAS_PULL_UP",
- .value = gpiod_LINE_REQ_FLAG_BIAS_PULL_UP,
- },
- { }
-};
-
-PyMODINIT_FUNC PyInit_gpiod(void)
-{
- gpiod_ConstDescr *const_descr;
- gpiod_ModuleConst *mod_const;
- PyObject *module, *val;
- gpiod_PyType *type;
- unsigned int i;
- int rv;
-
- module = PyModule_Create(&gpiod_Module);
- if (!module)
- return NULL;
-
- for (i = 0; gpiod_PyType_list[i].typeobj; i++) {
- type = &gpiod_PyType_list[i];
-
- rv = PyType_Ready(type->typeobj);
- if (rv)
- return NULL;
-
- Py_INCREF(type->typeobj);
- rv = PyModule_AddObject(module, type->name,
- (PyObject *)type->typeobj);
- if (rv < 0)
- return NULL;
- }
-
- for (i = 0; gpiod_ConstList[i].name; i++) {
- const_descr = &gpiod_ConstList[i];
-
- val = PyLong_FromLong(const_descr->val);
- if (!val)
- return NULL;
-
- rv = PyDict_SetItemString(const_descr->typeobj->tp_dict,
- const_descr->name, val);
- Py_DECREF(val);
- if (rv)
- return NULL;
- }
-
- for (i = 0; gpiod_ModuleConsts[i].name; i++) {
- mod_const = &gpiod_ModuleConsts[i];
-
- rv = PyModule_AddIntConstant(module,
- mod_const->name, mod_const->value);
- if (rv < 0)
- return NULL;
- }
-
- rv = PyModule_AddStringConstant(module, "__version__",
- gpiod_version_string());
- if (rv < 0)
- return NULL;
-
- return module;
-}
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+from os import environ
+from setuptools import setup, Extension, find_packages
+
+gpiod_ext = Extension(
+ "gpiod._ext",
+ sources=[
+ "gpiod/ext/chip.c",
+ "gpiod/ext/common.c",
+ "gpiod/ext/line-config.c",
+ "gpiod/ext/line-settings.c",
+ "gpiod/ext/module.c",
+ "gpiod/ext/request.c",
+ ],
+ define_macros=[("_GNU_SOURCE", "1")],
+ libraries=["gpiod"],
+ extra_compile_args=["-Wall", "-Wextra"],
+)
+
+gpiosim_ext = Extension(
+ "tests.gpiosim._ext",
+ sources=["tests/gpiosim/ext.c"],
+ define_macros=[("_GNU_SOURCE", "1")],
+ libraries=["gpiosim"],
+ extra_compile_args=["-Wall", "-Wextra"],
+)
+
+extensions = [gpiod_ext]
+with_tests = bool(environ["GPIOD_WITH_TESTS"])
+if with_tests:
+ extensions.append(gpiosim_ext)
+
+# FIXME Find a better way to get the version
+version = None
+try:
+ version = environ["GPIOD_VERSION_STR"]
+except KeyError:
+ pass
+
+setup(
+ name="gpiod",
+ packages=find_packages(include=["gpiod"]),
+ ext_modules=extensions,
+ version=version,
+)
-# SPDX-License-Identifier: GPL-2.0-or-later
-# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
-dist_bin_SCRIPTS = gpiod_py_test.py
+SUBDIRS = gpiosim
-pyexec_LTLIBRARIES = gpiomockup.la
-
-gpiomockup_la_SOURCES = gpiomockupmodule.c
-gpiomockup_la_CFLAGS = -I$(top_srcdir)/tests/mockup/
-gpiomockup_la_CFLAGS += -Wall -Wextra -g -std=gnu89 $(PYTHON_CPPFLAGS)
-gpiomockup_la_LDFLAGS = -module -avoid-version
-gpiomockup_la_LIBADD = $(top_builddir)/tests/mockup/libgpiomockup.la
-gpiomockup_la_LIBADD += $(PYTHON_LIBS)
+EXTRA_DIST = \
+ helpers.py \
+ __init__.py \
+ __main__.py \
+ tests_chip_info.py \
+ tests_chip.py \
+ tests_edge_event.py \
+ tests_info_event.py \
+ tests_line_info.py \
+ tests_line_request.py \
+ tests_line_settings.py \
+ tests_module.py
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+import os
+import unittest
+
+from packaging import version
+
+required_kernel_version = "5.19.0"
+current_version = os.uname().release.split("-")[0]
+
+if version.parse(current_version) < version.parse(required_kernel_version):
+ raise NotImplementedError(
+ "linux kernel version must be at least {} - got {}".format(
+ required_kernel_version, current_version
+ )
+ )
--- /dev/null
+#!/usr/bin/python3
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+import unittest
+
+from .tests_chip import *
+from .tests_chip_info import *
+from .tests_edge_event import *
+from .tests_info_event import *
+from .tests_line_info import *
+from .tests_line_settings import *
+from .tests_module import *
+from .tests_line_request import *
+
+unittest.main()
+++ /dev/null
-#!/usr/bin/env python3
-# SPDX-License-Identifier: GPL-2.0-or-later
-# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
-
-import errno
-import gpiod
-import gpiomockup
-import os
-import select
-import time
-import threading
-import unittest
-
-from packaging import version
-
-mockup = None
-default_consumer = 'gpiod-py-test'
-
-class MockupTestCase(unittest.TestCase):
-
- chip_sizes = None
- flags = 0
-
- def setUp(self):
- mockup.probe(self.chip_sizes, flags=self.flags)
-
- def tearDown(self):
- mockup.remove()
-
-class EventThread(threading.Thread):
-
- def __init__(self, chip_idx, line_offset, period_ms):
- threading.Thread.__init__(self)
- self.chip_idx = chip_idx
- self.line_offset = line_offset
- self.period_ms = period_ms
- self.lock = threading.Lock()
- self.cond = threading.Condition(self.lock)
- self.should_stop = False
-
- def run(self):
- i = 0;
- while True:
- with self.lock:
- if self.should_stop:
- break;
-
- if not self.cond.wait(float(self.period_ms) / 1000):
- mockup.chip_set_pull(self.chip_idx,
- self.line_offset, i % 2)
- i += 1
-
- def stop(self):
- with self.lock:
- self.should_stop = True
- self.cond.notify_all()
-
- def __enter__(self):
- self.start()
- return self
-
- def __exit__(self, exc_type, exc_value, traceback):
- self.stop()
- self.join()
-
-def check_kernel(major, minor, release):
- current = os.uname().release.split('-')[0]
- required = '{}.{}.{}'.format(major, minor, release)
- if version.parse(current) < version.parse(required):
- raise NotImplementedError(
- 'linux kernel version must be at least {} - got {}'.format(required, current))
-
-#
-# Chip test cases
-#
-
-class IsGpioDevice(MockupTestCase):
-
- chip_sizes = ( 8, )
-
- def test_is_gpiochip_device_good(self):
- self.assertTrue(gpiod.is_gpiochip_device(mockup.chip_path(0)))
-
- def test_is_gpiochip_device_bad(self):
- self.assertFalse(gpiod.is_gpiochip_device('/dev/null'))
-
- def test_is_gpiochip_device_nonexistent(self):
- self.assertFalse(gpiod.is_gpiochip_device('/dev/nonexistent_device'))
-
-class ChipOpen(MockupTestCase):
-
- chip_sizes = ( 8, 8, 8 )
-
- def test_open_good(self):
- with gpiod.Chip(mockup.chip_path(1)) as chip:
- self.assertEqual(chip.name(), mockup.chip_name(1))
-
- def test_nonexistent_chip(self):
- with self.assertRaises(FileNotFoundError):
- chip = gpiod.Chip('nonexistent-chip')
-
- def test_open_chip_no_arguments(self):
- with self.assertRaises(TypeError):
- chip = gpiod.Chip()
-
-class ChipClose(MockupTestCase):
-
- chip_sizes = ( 8, )
-
- def test_use_chip_after_close(self):
- chip = gpiod.Chip(mockup.chip_path(0))
- self.assertEqual(chip.name(), mockup.chip_name(0))
- chip.close()
- with self.assertRaises(ValueError):
- chip.name()
-
-class ChipInfo(MockupTestCase):
-
- chip_sizes = ( 16, )
-
- def test_chip_get_info(self):
- chip = gpiod.Chip(mockup.chip_path(0))
- self.assertEqual(chip.name(), mockup.chip_name(0))
- self.assertEqual(chip.label(), 'gpio-mockup-A')
- self.assertEqual(chip.num_lines(), 16)
-
-class ChipGetLines(MockupTestCase):
-
- chip_sizes = ( 8, 8, 4 )
- flags = gpiomockup.Mockup.FLAG_NAMED_LINES
-
- def test_get_single_line_by_offset(self):
- with gpiod.Chip(mockup.chip_path(1)) as chip:
- line = chip.get_line(4)
- self.assertEqual(line.name(), 'gpio-mockup-B-4')
-
- def test_find_single_line_by_name(self):
- with gpiod.Chip(mockup.chip_path(1)) as chip:
- offset = chip.find_line('gpio-mockup-B-4')
- self.assertIsNotNone(offset)
- self.assertEqual(offset, 4)
-
- def test_get_single_line_invalid_offset(self):
- with gpiod.Chip(mockup.chip_path(1)) as chip:
- with self.assertRaises(OSError) as err_ctx:
- line = chip.get_line(11)
-
- self.assertEqual(err_ctx.exception.errno, errno.EINVAL)
-
- def test_find_single_line_nonexistent(self):
- with gpiod.Chip(mockup.chip_path(1)) as chip:
- offset = chip.find_line('nonexistent-line')
- self.assertIsNone(offset)
-
- def test_get_multiple_lines_by_offsets_in_tuple(self):
- with gpiod.Chip(mockup.chip_path(1)) as chip:
- lines = chip.get_lines(( 1, 3, 6, 7 )).to_list()
- self.assertEqual(len(lines), 4)
- self.assertEqual(lines[0].name(), 'gpio-mockup-B-1')
- self.assertEqual(lines[1].name(), 'gpio-mockup-B-3')
- self.assertEqual(lines[2].name(), 'gpio-mockup-B-6')
- self.assertEqual(lines[3].name(), 'gpio-mockup-B-7')
-
- def test_get_multiple_lines_by_offsets_in_list(self):
- with gpiod.Chip(mockup.chip_path(1)) as chip:
- lines = chip.get_lines([ 1, 3, 6, 7 ]).to_list()
- self.assertEqual(len(lines), 4)
- self.assertEqual(lines[0].name(), 'gpio-mockup-B-1')
- self.assertEqual(lines[1].name(), 'gpio-mockup-B-3')
- self.assertEqual(lines[2].name(), 'gpio-mockup-B-6')
- self.assertEqual(lines[3].name(), 'gpio-mockup-B-7')
-
- def test_get_multiple_lines_invalid_offset(self):
- with gpiod.Chip(mockup.chip_path(1)) as chip:
- with self.assertRaises(OSError) as err_ctx:
- line = chip.get_lines(( 1, 3, 11, 7 ))
-
- self.assertEqual(err_ctx.exception.errno, errno.EINVAL)
-
- def test_get_all_lines(self):
- with gpiod.Chip(mockup.chip_path(2)) as chip:
- lines = chip.get_all_lines().to_list()
- self.assertEqual(len(lines), 4)
- self.assertEqual(lines[0].name(), 'gpio-mockup-C-0')
- self.assertEqual(lines[1].name(), 'gpio-mockup-C-1')
- self.assertEqual(lines[2].name(), 'gpio-mockup-C-2')
- self.assertEqual(lines[3].name(), 'gpio-mockup-C-3')
-
-#
-# Line test cases
-#
-
-class LineInfo(MockupTestCase):
-
- chip_sizes = ( 8, )
- flags = gpiomockup.Mockup.FLAG_NAMED_LINES
-
- def test_unexported_line(self):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- line = chip.get_line(4)
- self.assertEqual(line.offset(), 4)
- self.assertEqual(line.name(), 'gpio-mockup-A-4')
- self.assertEqual(line.direction(), gpiod.Line.DIRECTION_INPUT)
- self.assertFalse(line.is_active_low())
- self.assertEqual(line.consumer(), None)
- self.assertFalse(line.is_used())
-
- def test_exported_line(self):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- line = chip.get_line(4)
- line.request(consumer=default_consumer,
- type=gpiod.LINE_REQ_DIR_OUT,
- flags=gpiod.LINE_REQ_FLAG_ACTIVE_LOW)
- self.assertEqual(line.offset(), 4)
- self.assertEqual(line.name(), 'gpio-mockup-A-4')
- self.assertEqual(line.direction(), gpiod.Line.DIRECTION_OUTPUT)
- self.assertTrue(line.is_active_low())
- self.assertEqual(line.consumer(), default_consumer)
- self.assertTrue(line.is_used())
-
- def test_exported_line_with_flags(self):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- line = chip.get_line(4)
- flags = (gpiod.LINE_REQ_FLAG_ACTIVE_LOW |
- gpiod.LINE_REQ_FLAG_OPEN_DRAIN)
- line.request(consumer=default_consumer,
- type=gpiod.LINE_REQ_DIR_OUT,
- flags=flags)
- self.assertEqual(line.offset(), 4)
- self.assertEqual(line.name(), 'gpio-mockup-A-4')
- self.assertEqual(line.direction(), gpiod.Line.DIRECTION_OUTPUT)
- self.assertTrue(line.is_active_low())
- self.assertEqual(line.consumer(), default_consumer)
- self.assertTrue(line.is_used())
- self.assertEqual(line.drive(), gpiod.Line.DRIVE_OPEN_DRAIN)
- self.assertEqual(line.bias(), gpiod.Line.BIAS_UNKNOWN)
-
- def test_exported_open_drain_line(self):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- line = chip.get_line(4)
- flags = gpiod.LINE_REQ_FLAG_OPEN_DRAIN
- line.request(consumer=default_consumer,
- type=gpiod.LINE_REQ_DIR_OUT,
- flags=flags)
- self.assertEqual(line.offset(), 4)
- self.assertEqual(line.name(), 'gpio-mockup-A-4')
- self.assertEqual(line.direction(), gpiod.Line.DIRECTION_OUTPUT)
- self.assertFalse(line.is_active_low())
- self.assertEqual(line.consumer(), default_consumer)
- self.assertTrue(line.is_used())
- self.assertEqual(line.drive(), gpiod.Line.DRIVE_OPEN_DRAIN)
- self.assertEqual(line.bias(), gpiod.Line.BIAS_UNKNOWN)
-
- def test_exported_open_source_line(self):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- line = chip.get_line(4)
- flags = gpiod.LINE_REQ_FLAG_OPEN_SOURCE
- line.request(consumer=default_consumer,
- type=gpiod.LINE_REQ_DIR_OUT,
- flags=flags)
- self.assertEqual(line.offset(), 4)
- self.assertEqual(line.name(), 'gpio-mockup-A-4')
- self.assertEqual(line.direction(), gpiod.Line.DIRECTION_OUTPUT)
- self.assertFalse(line.is_active_low())
- self.assertEqual(line.consumer(), default_consumer)
- self.assertTrue(line.is_used())
- self.assertEqual(line.drive(), gpiod.Line.DRIVE_OPEN_SOURCE)
- self.assertEqual(line.bias(), gpiod.Line.BIAS_UNKNOWN)
-
- def test_exported_bias_disable_line(self):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- line = chip.get_line(4)
- flags = gpiod.LINE_REQ_FLAG_BIAS_DISABLED
- line.request(consumer=default_consumer,
- type=gpiod.LINE_REQ_DIR_OUT,
- flags=flags)
- self.assertEqual(line.offset(), 4)
- self.assertEqual(line.name(), 'gpio-mockup-A-4')
- self.assertEqual(line.direction(), gpiod.Line.DIRECTION_OUTPUT)
- self.assertFalse(line.is_active_low())
- self.assertEqual(line.consumer(), default_consumer)
- self.assertTrue(line.is_used())
- self.assertEqual(line.drive(), gpiod.Line.DRIVE_PUSH_PULL)
- self.assertEqual(line.bias(), gpiod.Line.BIAS_DISABLED)
-
- def test_exported_bias_pull_down_line(self):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- line = chip.get_line(4)
- flags = gpiod.LINE_REQ_FLAG_BIAS_PULL_DOWN
- line.request(consumer=default_consumer,
- type=gpiod.LINE_REQ_DIR_OUT,
- flags=flags)
- self.assertEqual(line.offset(), 4)
- self.assertEqual(line.name(), 'gpio-mockup-A-4')
- self.assertEqual(line.direction(), gpiod.Line.DIRECTION_OUTPUT)
- self.assertFalse(line.is_active_low())
- self.assertEqual(line.consumer(), default_consumer)
- self.assertTrue(line.is_used())
- self.assertEqual(line.drive(), gpiod.Line.DRIVE_PUSH_PULL)
- self.assertEqual(line.bias(), gpiod.Line.BIAS_PULL_DOWN)
-
- def test_exported_bias_pull_up_line(self):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- line = chip.get_line(4)
- flags = gpiod.LINE_REQ_FLAG_BIAS_PULL_UP
- line.request(consumer=default_consumer,
- type=gpiod.LINE_REQ_DIR_OUT,
- flags=flags)
- self.assertEqual(line.offset(), 4)
- self.assertEqual(line.name(), 'gpio-mockup-A-4')
- self.assertEqual(line.direction(), gpiod.Line.DIRECTION_OUTPUT)
- self.assertFalse(line.is_active_low())
- self.assertEqual(line.consumer(), default_consumer)
- self.assertTrue(line.is_used())
- self.assertEqual(line.drive(), gpiod.Line.DRIVE_PUSH_PULL)
- self.assertEqual(line.bias(), gpiod.Line.BIAS_PULL_UP)
-
-class LineValues(MockupTestCase):
-
- chip_sizes = ( 8, )
-
- def test_get_value_single_line(self):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- line = chip.get_line(3)
- line.request(consumer=default_consumer,
- type=gpiod.LINE_REQ_DIR_IN)
- self.assertEqual(line.get_value(), 0)
- mockup.chip_set_pull(0, 3, 1)
- self.assertEqual(line.get_value(), 1)
-
- def test_set_value_single_line(self):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- line = chip.get_line(3)
- line.request(consumer=default_consumer,
- type=gpiod.LINE_REQ_DIR_OUT)
- line.set_value(1)
- self.assertEqual(mockup.chip_get_value(0, 3), 1)
- line.set_value(0)
- self.assertEqual(mockup.chip_get_value(0, 3), 0)
-
- def test_set_value_with_default_value_argument(self):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- line = chip.get_line(3)
- line.request(consumer=default_consumer,
- type=gpiod.LINE_REQ_DIR_OUT,
- default_val=1)
- self.assertEqual(mockup.chip_get_value(0, 3), 1)
-
- def test_get_value_multiple_lines(self):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- lines = chip.get_lines(( 0, 3, 4, 6 ))
- lines.request(consumer=default_consumer,
- type=gpiod.LINE_REQ_DIR_IN)
- self.assertEqual(lines.get_values(), [ 0, 0, 0, 0 ])
- mockup.chip_set_pull(0, 0, 1)
- mockup.chip_set_pull(0, 4, 1)
- mockup.chip_set_pull(0, 6, 1)
- self.assertEqual(lines.get_values(), [ 1, 0, 1, 1 ])
-
- def test_set_value_multiple_lines(self):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- lines = chip.get_lines(( 0, 3, 4, 6 ))
- lines.request(consumer=default_consumer,
- type=gpiod.LINE_REQ_DIR_OUT)
- lines.set_values(( 1, 0, 1, 1 ))
- self.assertEqual(mockup.chip_get_value(0, 0), 1)
- self.assertEqual(mockup.chip_get_value(0, 3), 0)
- self.assertEqual(mockup.chip_get_value(0, 4), 1)
- self.assertEqual(mockup.chip_get_value(0, 6), 1)
- lines.set_values(( 0, 0, 1, 0 ))
- self.assertEqual(mockup.chip_get_value(0, 0), 0)
- self.assertEqual(mockup.chip_get_value(0, 3), 0)
- self.assertEqual(mockup.chip_get_value(0, 4), 1)
- self.assertEqual(mockup.chip_get_value(0, 6), 0)
-
- def test_set_multiple_values_with_default_vals_argument(self):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- lines = chip.get_lines(( 0, 3, 4, 6 ))
- lines.request(consumer=default_consumer,
- type=gpiod.LINE_REQ_DIR_OUT,
- default_vals=( 1, 0, 1, 1 ))
- self.assertEqual(mockup.chip_get_value(0, 0), 1)
- self.assertEqual(mockup.chip_get_value(0, 3), 0)
- self.assertEqual(mockup.chip_get_value(0, 4), 1)
- self.assertEqual(mockup.chip_get_value(0, 6), 1)
-
- def test_get_value_active_low(self):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- line = chip.get_line(3)
- line.request(consumer=default_consumer,
- type=gpiod.LINE_REQ_DIR_IN,
- flags=gpiod.LINE_REQ_FLAG_ACTIVE_LOW)
- self.assertEqual(line.get_value(), 1)
- mockup.chip_set_pull(0, 3, 1)
- self.assertEqual(line.get_value(), 0)
-
- def test_set_value_active_low(self):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- line = chip.get_line(3)
- line.request(consumer=default_consumer,
- type=gpiod.LINE_REQ_DIR_OUT,
- flags=gpiod.LINE_REQ_FLAG_ACTIVE_LOW)
- line.set_value(1)
- self.assertEqual(mockup.chip_get_value(0, 3), 0)
- line.set_value(0)
- self.assertEqual(mockup.chip_get_value(0, 3), 1)
-
-class LineConfig(MockupTestCase):
-
- chip_sizes = ( 8, )
-
- def test_set_config_direction(self):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- line = chip.get_line(3)
- line.request(consumer=default_consumer,
- type=gpiod.LINE_REQ_DIR_IN)
- self.assertEqual(line.direction(), gpiod.Line.DIRECTION_INPUT)
- line.set_config(gpiod.LINE_REQ_DIR_IN, 0, 0)
- self.assertEqual(line.direction(), gpiod.Line.DIRECTION_INPUT)
- line.set_config(gpiod.LINE_REQ_DIR_OUT,0,0)
- self.assertEqual(line.direction(), gpiod.Line.DIRECTION_OUTPUT)
-
- def test_set_config_flags(self):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- line = chip.get_line(3)
- line.request(consumer=default_consumer,
- type=gpiod.LINE_REQ_DIR_OUT)
- line.set_config(gpiod.LINE_REQ_DIR_OUT,
- gpiod.LINE_REQ_FLAG_ACTIVE_LOW, 0)
- self.assertEqual(mockup.chip_get_value(0, 3), 1)
- line.set_config(gpiod.LINE_REQ_DIR_OUT, 0, 0)
- self.assertEqual(mockup.chip_get_value(0, 3), 0)
-
- def test_set_config_output_value(self):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- line = chip.get_line(3)
- line.request(consumer=default_consumer,
- type=gpiod.LINE_REQ_DIR_IN)
- line.set_config(gpiod.LINE_REQ_DIR_OUT,0,1)
- self.assertEqual(mockup.chip_get_value(0, 3), 1)
- line.set_config(gpiod.LINE_REQ_DIR_OUT,0,0)
- self.assertEqual(mockup.chip_get_value(0, 3), 0)
-
- def test_set_config_output_no_value(self):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- line = chip.get_line(3)
- line.request(consumer=default_consumer,
- type=gpiod.LINE_REQ_DIR_OUT,
- default_val=1)
- self.assertEqual(mockup.chip_get_value(0, 3), 1)
- line.set_config(gpiod.LINE_REQ_DIR_OUT,0)
- self.assertEqual(mockup.chip_get_value(0, 3), 0)
-
- def test_set_config_bulk_output_no_values(self):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- lines = chip.get_lines(( 0, 3, 4, 6 ))
- lines.request(consumer=default_consumer,
- type=gpiod.LINE_REQ_DIR_OUT,
- default_vals=(1,1,1,1))
- self.assertEqual(mockup.chip_get_value(0, 0), 1)
- self.assertEqual(mockup.chip_get_value(0, 3), 1)
- self.assertEqual(mockup.chip_get_value(0, 4), 1)
- self.assertEqual(mockup.chip_get_value(0, 6), 1)
- lines.set_config(gpiod.LINE_REQ_DIR_OUT,0)
- self.assertEqual(mockup.chip_get_value(0, 0), 0)
- self.assertEqual(mockup.chip_get_value(0, 3), 0)
- self.assertEqual(mockup.chip_get_value(0, 4), 0)
- self.assertEqual(mockup.chip_get_value(0, 6), 0)
-
-class LineFlags(MockupTestCase):
-
- chip_sizes = ( 8, )
-
- def test_set_flags(self):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- line = chip.get_line(3)
- line.request(consumer=default_consumer,
- type=gpiod.LINE_REQ_DIR_OUT,
- default_val=1)
- self.assertEqual(mockup.chip_get_value(0, 3), 1)
- line.set_flags(gpiod.LINE_REQ_FLAG_ACTIVE_LOW)
- self.assertEqual(mockup.chip_get_value(0, 3), 0)
- line.set_flags(0)
- self.assertEqual(mockup.chip_get_value(0, 3), 1)
-
- def test_set_flags_bulk(self):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- lines = chip.get_lines(( 0, 3, 4, 6 ))
- lines.request(consumer=default_consumer,
- type=gpiod.LINE_REQ_DIR_OUT,
- default_vals=(1,1,1,1))
- self.assertEqual(mockup.chip_get_value(0, 0), 1)
- self.assertEqual(mockup.chip_get_value(0, 3), 1)
- self.assertEqual(mockup.chip_get_value(0, 4), 1)
- self.assertEqual(mockup.chip_get_value(0, 6), 1)
- lines.set_flags(gpiod.LINE_REQ_FLAG_ACTIVE_LOW)
- self.assertEqual(mockup.chip_get_value(0, 0), 0)
- self.assertEqual(mockup.chip_get_value(0, 3), 0)
- self.assertEqual(mockup.chip_get_value(0, 4), 0)
- self.assertEqual(mockup.chip_get_value(0, 6), 0)
- lines.set_flags(0)
- self.assertEqual(mockup.chip_get_value(0, 0), 1)
- self.assertEqual(mockup.chip_get_value(0, 3), 1)
- self.assertEqual(mockup.chip_get_value(0, 4), 1)
- self.assertEqual(mockup.chip_get_value(0, 6), 1)
-
-class LineDirection(MockupTestCase):
-
- chip_sizes = ( 8, )
-
- def test_set_direction(self):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- line = chip.get_line(3)
- line.request(consumer=default_consumer,
- type=gpiod.LINE_REQ_DIR_OUT)
- self.assertEqual(line.direction(), gpiod.Line.DIRECTION_OUTPUT)
- line.set_direction_input()
- self.assertEqual(line.direction(), gpiod.Line.DIRECTION_INPUT)
- line.set_direction_output(0)
- self.assertEqual(line.direction(), gpiod.Line.DIRECTION_OUTPUT)
- self.assertEqual(mockup.chip_get_value(0, 3), 0)
- line.set_direction_output(1)
- self.assertEqual(line.direction(), gpiod.Line.DIRECTION_OUTPUT)
- self.assertEqual(mockup.chip_get_value(0, 3), 1)
- line.set_direction_output()
- self.assertEqual(line.direction(), gpiod.Line.DIRECTION_OUTPUT)
- self.assertEqual(mockup.chip_get_value(0, 3), 0)
-
- def test_set_direction_bulk(self):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- lines = chip.get_lines(( 0, 3, 4, 6 ))
- lines.request(consumer=default_consumer,
- type=gpiod.LINE_REQ_DIR_OUT)
- self.assertEqual(lines.to_list()[0].direction(),
- gpiod.Line.DIRECTION_OUTPUT)
- self.assertEqual(lines.to_list()[1].direction(),
- gpiod.Line.DIRECTION_OUTPUT)
- self.assertEqual(lines.to_list()[2].direction(),
- gpiod.Line.DIRECTION_OUTPUT)
- self.assertEqual(lines.to_list()[3].direction(),
- gpiod.Line.DIRECTION_OUTPUT)
- lines.set_direction_input()
- self.assertEqual(lines.to_list()[0].direction(),
- gpiod.Line.DIRECTION_INPUT)
- self.assertEqual(lines.to_list()[1].direction(),
- gpiod.Line.DIRECTION_INPUT)
- self.assertEqual(lines.to_list()[2].direction(),
- gpiod.Line.DIRECTION_INPUT)
- self.assertEqual(lines.to_list()[3].direction(),
- gpiod.Line.DIRECTION_INPUT)
- lines.set_direction_output((0,0,1,0))
- self.assertEqual(lines.to_list()[0].direction(),
- gpiod.Line.DIRECTION_OUTPUT)
- self.assertEqual(lines.to_list()[1].direction(),
- gpiod.Line.DIRECTION_OUTPUT)
- self.assertEqual(lines.to_list()[2].direction(),
- gpiod.Line.DIRECTION_OUTPUT)
- self.assertEqual(lines.to_list()[3].direction(),
- gpiod.Line.DIRECTION_OUTPUT)
- self.assertEqual(mockup.chip_get_value(0, 0), 0)
- self.assertEqual(mockup.chip_get_value(0, 3), 0)
- self.assertEqual(mockup.chip_get_value(0, 4), 1)
- self.assertEqual(mockup.chip_get_value(0, 6), 0)
- lines.set_direction_output((1,1,1,0))
- self.assertEqual(lines.to_list()[0].direction(),
- gpiod.Line.DIRECTION_OUTPUT)
- self.assertEqual(lines.to_list()[1].direction(),
- gpiod.Line.DIRECTION_OUTPUT)
- self.assertEqual(lines.to_list()[2].direction(),
- gpiod.Line.DIRECTION_OUTPUT)
- self.assertEqual(lines.to_list()[3].direction(),
- gpiod.Line.DIRECTION_OUTPUT)
- self.assertEqual(mockup.chip_get_value(0, 0), 1)
- self.assertEqual(mockup.chip_get_value(0, 3), 1)
- self.assertEqual(mockup.chip_get_value(0, 4), 1)
- self.assertEqual(mockup.chip_get_value(0, 6), 0)
- lines.set_direction_output()
- self.assertEqual(lines.to_list()[0].direction(),
- gpiod.Line.DIRECTION_OUTPUT)
- self.assertEqual(lines.to_list()[1].direction(),
- gpiod.Line.DIRECTION_OUTPUT)
- self.assertEqual(lines.to_list()[2].direction(),
- gpiod.Line.DIRECTION_OUTPUT)
- self.assertEqual(lines.to_list()[3].direction(),
- gpiod.Line.DIRECTION_OUTPUT)
- self.assertEqual(mockup.chip_get_value(0, 0), 0)
- self.assertEqual(mockup.chip_get_value(0, 3), 0)
- self.assertEqual(mockup.chip_get_value(0, 4), 0)
- self.assertEqual(mockup.chip_get_value(0, 6), 0)
-
-class LineRequestBehavior(MockupTestCase):
-
- chip_sizes = ( 8, )
-
- def test_line_request_twice_two_calls(self):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- line = chip.get_line(3)
- line.request(consumer=default_consumer,
- type=gpiod.LINE_REQ_DIR_IN)
- with self.assertRaises(OSError) as err_ctx:
- line.request(consumer=default_consumer,
- type=gpiod.LINE_REQ_DIR_IN)
-
- self.assertEqual(err_ctx.exception.errno, errno.EBUSY)
-
- def test_line_request_twice_in_bulk(self):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- lines = chip.get_lines(( 2, 3, 6, 6 ))
- with self.assertRaises(OSError) as err_ctx:
- lines.request(consumer=default_consumer,
- type=gpiod.LINE_REQ_DIR_IN)
-
- self.assertEqual(err_ctx.exception.errno, errno.EBUSY)
-
- def test_use_value_unrequested(self):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- line = chip.get_line(3)
- with self.assertRaises(OSError) as err_ctx:
- line.get_value()
-
- self.assertEqual(err_ctx.exception.errno, errno.EPERM)
-
- def test_request_with_no_kwds(self):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- line = chip.get_line(2)
- line.request(default_consumer)
- self.assertEqual(line.direction(), gpiod.Line.DIRECTION_INPUT)
- line.release()
-
-#
-# Iterator test cases
-#
-
-class LineIterator(MockupTestCase):
-
- chip_sizes = ( 4, )
-
- def test_iterate_over_lines(self):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- count = 0
-
- for line in gpiod.LineIter(chip):
- self.assertEqual(line.offset(), count)
- count += 1
-
- self.assertEqual(count, chip.num_lines())
-
-#
-# Event test cases
-#
-
-class EventSingleLine(MockupTestCase):
-
- chip_sizes = ( 8, )
-
- def test_single_line_rising_edge_event(self):
- with EventThread(0, 4, 200):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- line = chip.get_line(4)
- line.request(consumer=default_consumer,
- type=gpiod.LINE_REQ_EV_RISING_EDGE)
- self.assertTrue(line.event_wait(sec=1))
- event = line.event_read()
- self.assertEqual(event.type, gpiod.LineEvent.RISING_EDGE)
- self.assertEqual(event.source.offset(), 4)
-
- def test_single_line_falling_edge_event(self):
- with EventThread(0, 4, 200):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- line = chip.get_line(4)
- line.request(consumer=default_consumer,
- type=gpiod.LINE_REQ_EV_FALLING_EDGE)
- self.assertTrue(line.event_wait(sec=1))
- event = line.event_read()
- self.assertEqual(event.type, gpiod.LineEvent.FALLING_EDGE)
- self.assertEqual(event.source.offset(), 4)
-
- def test_single_line_both_edges_events(self):
- with EventThread(0, 4, 200):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- line = chip.get_line(4)
- line.request(consumer=default_consumer,
- type=gpiod.LINE_REQ_EV_BOTH_EDGES)
- self.assertTrue(line.event_wait(sec=1))
- event = line.event_read()
- self.assertEqual(event.type, gpiod.LineEvent.RISING_EDGE)
- self.assertEqual(event.source.offset(), 4)
- self.assertTrue(line.event_wait(sec=1))
- event = line.event_read()
- self.assertEqual(event.type, gpiod.LineEvent.FALLING_EDGE)
- self.assertEqual(event.source.offset(), 4)
-
- def test_single_line_both_edges_events_active_low(self):
- with EventThread(0, 4, 200):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- line = chip.get_line(4)
- line.request(consumer=default_consumer,
- type=gpiod.LINE_REQ_EV_BOTH_EDGES,
- flags=gpiod.LINE_REQ_FLAG_ACTIVE_LOW)
- self.assertTrue(line.event_wait(sec=1))
- event = line.event_read()
- self.assertEqual(event.type, gpiod.LineEvent.FALLING_EDGE)
- self.assertEqual(event.source.offset(), 4)
- self.assertTrue(line.event_wait(sec=1))
- event = line.event_read()
- self.assertEqual(event.type, gpiod.LineEvent.RISING_EDGE)
- self.assertEqual(event.source.offset(), 4)
-
- def test_single_line_read_multiple_events(self):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- line = chip.get_line(4)
- line.request(consumer=default_consumer,
- type=gpiod.LINE_REQ_EV_BOTH_EDGES)
- mockup.chip_set_pull(0, 4, 1)
- time.sleep(0.01)
- mockup.chip_set_pull(0, 4, 0)
- time.sleep(0.01)
- mockup.chip_set_pull(0, 4, 1)
- time.sleep(0.01)
- self.assertTrue(line.event_wait(sec=1))
- events = line.event_read_multiple()
- self.assertEqual(len(events), 3)
- self.assertEqual(events[0].type, gpiod.LineEvent.RISING_EDGE)
- self.assertEqual(events[1].type, gpiod.LineEvent.FALLING_EDGE)
- self.assertEqual(events[2].type, gpiod.LineEvent.RISING_EDGE)
- self.assertEqual(events[0].source.offset(), 4)
- self.assertEqual(events[1].source.offset(), 4)
- self.assertEqual(events[2].source.offset(), 4)
-
-class EventBulk(MockupTestCase):
-
- chip_sizes = ( 8, )
-
- def test_watch_multiple_lines_for_events(self):
- with EventThread(0, 2, 200):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- lines = chip.get_lines(( 0, 1, 2, 3, 4 ))
- lines.request(consumer=default_consumer,
- type=gpiod.LINE_REQ_EV_BOTH_EDGES)
- event_lines = lines.event_wait(sec=1)
- self.assertEqual(len(event_lines), 1)
- line = event_lines[0]
- event = line.event_read()
- self.assertEqual(event.type, gpiod.LineEvent.RISING_EDGE)
- self.assertEqual(event.source.offset(), 2)
- event_lines = lines.event_wait(sec=1)
- self.assertEqual(len(event_lines), 1)
- line = event_lines[0]
- event = line.event_read()
- self.assertEqual(event.type, gpiod.LineEvent.FALLING_EDGE)
- self.assertEqual(event.source.offset(), 2)
-
-class EventValues(MockupTestCase):
-
- chip_sizes = ( 8, )
-
- def test_request_for_events_get_value(self):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- line = chip.get_line(3)
- line.request(consumer=default_consumer,
- type=gpiod.LINE_REQ_EV_BOTH_EDGES)
- self.assertEqual(line.get_value(), 0)
- mockup.chip_set_pull(0, 3, 1)
- self.assertEqual(line.get_value(), 1)
-
- def test_request_for_events_get_value_active_low(self):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- line = chip.get_line(3)
- line.request(consumer=default_consumer,
- type=gpiod.LINE_REQ_EV_BOTH_EDGES,
- flags=gpiod.LINE_REQ_FLAG_ACTIVE_LOW)
- self.assertEqual(line.get_value(), 1)
- mockup.chip_set_pull(0, 3, 1)
- self.assertEqual(line.get_value(), 0)
-
-class EventFileDescriptor(MockupTestCase):
-
- chip_sizes = ( 8, )
-
- def test_event_get_fd(self):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- line = chip.get_line(3)
- line.request(consumer=default_consumer,
- type=gpiod.LINE_REQ_EV_BOTH_EDGES)
- fd = line.event_get_fd();
- self.assertGreaterEqual(fd, 0)
-
- def test_event_get_fd_not_requested(self):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- line = chip.get_line(3)
- with self.assertRaises(OSError) as err_ctx:
- fd = line.event_get_fd();
-
- self.assertEqual(err_ctx.exception.errno, errno.EPERM)
-
- def test_event_get_fd_requested_for_values(self):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- line = chip.get_line(3)
- line.request(consumer=default_consumer,
- type=gpiod.LINE_REQ_DIR_IN)
- with self.assertRaises(OSError) as err_ctx:
- fd = line.event_get_fd();
-
- self.assertEqual(err_ctx.exception.errno, errno.EPERM)
-
- def test_event_fd_polling(self):
- with EventThread(0, 2, 200):
- with gpiod.Chip(mockup.chip_path(0)) as chip:
- lines = chip.get_lines(( 0, 1, 2, 3, 4, 5, 6 ))
- lines.request(consumer=default_consumer,
- type=gpiod.LINE_REQ_EV_BOTH_EDGES)
-
- inputs = []
- for line in lines:
- inputs.append(line.event_get_fd())
-
- readable, writable, exceptional = select.select(inputs, [],
- inputs, 1.0)
-
- self.assertEqual(len(readable), 1)
- event = lines.to_list()[2].event_read()
- self.assertEqual(event.type, gpiod.LineEvent.RISING_EDGE)
- self.assertEqual(event.source.offset(), 2)
-
-#
-# Main
-#
-
-if __name__ == '__main__':
- check_kernel(5, 10, 0)
- mockup = gpiomockup.Mockup()
- unittest.main()
+++ /dev/null
-// SPDX-License-Identifier: LGPL-2.1-or-later
-// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
-
-#include <Python.h>
-#include <gpio-mockup.h>
-
-typedef struct {
- PyObject_HEAD
- struct gpio_mockup *mockup;
-} gpiomockup_MockupObject;
-
-enum {
- gpiomockup_FLAG_NAMED_LINES = 1,
-};
-
-static int gpiomockup_Mockup_init(gpiomockup_MockupObject *self,
- PyObject *Py_UNUSED(ignored0),
- PyObject *Py_UNUSED(ignored1))
-{
- Py_BEGIN_ALLOW_THREADS;
- self->mockup = gpio_mockup_new();
- Py_END_ALLOW_THREADS;
- if (!self->mockup) {
- PyErr_SetFromErrno(PyExc_OSError);
- return -1;
- }
-
- return 0;
-}
-
-static void gpiomockup_Mockup_dealloc(gpiomockup_MockupObject *self)
-{
- if (self->mockup) {
- Py_BEGIN_ALLOW_THREADS;
- gpio_mockup_unref(self->mockup);
- Py_END_ALLOW_THREADS;
- }
-
- PyObject_Del(self);
-}
-
-static PyObject *gpiomockup_Mockup_probe(gpiomockup_MockupObject *self,
- PyObject *args, PyObject *kwds)
-{
- static char *kwlist[] = { "chip_sizes",
- "flags",
- NULL };
-
- PyObject *chip_sizes_obj, *iter, *next;
- unsigned int *chip_sizes;
- int ret, flags = 0, i;
- Py_ssize_t num_chips;
-
- ret = PyArg_ParseTupleAndKeywords(args, kwds, "O|i", kwlist,
- &chip_sizes_obj, &flags);
- if (!ret)
- return NULL;
-
- num_chips = PyObject_Size(chip_sizes_obj);
- if (num_chips < 0) {
- return NULL;
- } else if (num_chips == 0) {
- PyErr_SetString(PyExc_TypeError,
- "Number of chips must be greater thatn 0");
- return NULL;
- }
-
- chip_sizes = PyMem_RawCalloc(num_chips, sizeof(unsigned int));
- if (!chip_sizes)
- return NULL;
-
- iter = PyObject_GetIter(chip_sizes_obj);
- if (!iter) {
- PyMem_RawFree(chip_sizes);
- return NULL;
- }
-
- for (i = 0;; i++) {
- next = PyIter_Next(iter);
- if (!next) {
- Py_DECREF(iter);
- break;
- }
-
- chip_sizes[i] = PyLong_AsUnsignedLong(next);
- Py_DECREF(next);
- if (PyErr_Occurred()) {
- Py_DECREF(iter);
- PyMem_RawFree(chip_sizes);
- return NULL;
- }
- }
-
- if (flags & gpiomockup_FLAG_NAMED_LINES)
- flags |= GPIO_MOCKUP_FLAG_NAMED_LINES;
-
- Py_BEGIN_ALLOW_THREADS;
- ret = gpio_mockup_probe(self->mockup, num_chips, chip_sizes, flags);
- Py_END_ALLOW_THREADS;
- PyMem_RawFree(chip_sizes);
- if (ret)
- return NULL;
-
- Py_RETURN_NONE;
-}
-
-static PyObject *gpiomockup_Mockup_remove(gpiomockup_MockupObject *self,
- PyObject *Py_UNUSED(ignored))
-{
- int ret;
-
- Py_BEGIN_ALLOW_THREADS;
- ret = gpio_mockup_remove(self->mockup);
- Py_END_ALLOW_THREADS;
- if (ret) {
- PyErr_SetFromErrno(PyExc_OSError);
- return NULL;
- }
-
- Py_RETURN_NONE;
-}
-
-static PyObject *gpiomockup_Mockup_chip_name(gpiomockup_MockupObject *self,
- PyObject *args)
-{
- unsigned int idx;
- const char *name;
- int ret;
-
- ret = PyArg_ParseTuple(args, "I", &idx);
- if (!ret)
- return NULL;
-
- name = gpio_mockup_chip_name(self->mockup, idx);
- if (!name) {
- PyErr_SetFromErrno(PyExc_OSError);
- return NULL;
- }
-
- return PyUnicode_FromString(name);
-}
-
-static PyObject *gpiomockup_Mockup_chip_path(gpiomockup_MockupObject *self,
- PyObject *args)
-{
- unsigned int idx;
- const char *path;
- int ret;
-
- ret = PyArg_ParseTuple(args, "I", &idx);
- if (!ret)
- return NULL;
-
- path = gpio_mockup_chip_path(self->mockup, idx);
- if (!path) {
- PyErr_SetFromErrno(PyExc_OSError);
- return NULL;
- }
-
- return PyUnicode_FromString(path);
-}
-
-static PyObject *gpiomockup_Mockup_chip_num(gpiomockup_MockupObject *self,
- PyObject *args)
-{
- unsigned int idx;
- int ret, num;
-
- ret = PyArg_ParseTuple(args, "I", &idx);
- if (!ret)
- return NULL;
-
- num = gpio_mockup_chip_num(self->mockup, idx);
- if (num < 0) {
- PyErr_SetFromErrno(PyExc_OSError);
- return NULL;
- }
-
- return PyLong_FromLong(num);
-}
-
-static PyObject *gpiomockup_Mockup_chip_get_value(gpiomockup_MockupObject *self,
- PyObject *args)
-{
- unsigned int chip_idx, line_offset;
- int ret, val;
-
- ret = PyArg_ParseTuple(args, "II", &chip_idx, &line_offset);
- if (!ret)
- return NULL;
-
- Py_BEGIN_ALLOW_THREADS;
- val = gpio_mockup_get_value(self->mockup, chip_idx, line_offset);
- Py_END_ALLOW_THREADS;
- if (val < 0) {
- PyErr_SetFromErrno(PyExc_OSError);
- return NULL;
- }
-
- return PyLong_FromUnsignedLong(val);
-}
-
-static PyObject *gpiomockup_Mockup_chip_set_pull(gpiomockup_MockupObject *self,
- PyObject *args)
-{
- unsigned int chip_idx, line_offset;
- int ret, pull;
-
- ret = PyArg_ParseTuple(args, "IIi", &chip_idx, &line_offset, &pull);
- if (!ret)
- return NULL;
-
- Py_BEGIN_ALLOW_THREADS;
- ret = gpio_mockup_set_pull(self->mockup, chip_idx, line_offset, pull);
- Py_END_ALLOW_THREADS;
- if (ret) {
- PyErr_SetFromErrno(PyExc_OSError);
- return NULL;
- }
-
- Py_RETURN_NONE;
-}
-
-static PyMethodDef gpiomockup_Mockup_methods[] = {
- {
- .ml_name = "probe",
- .ml_meth = (PyCFunction)(void (*)(void))gpiomockup_Mockup_probe,
- .ml_flags = METH_VARARGS | METH_KEYWORDS,
- },
- {
- .ml_name = "remove",
- .ml_meth = (PyCFunction)gpiomockup_Mockup_remove,
- .ml_flags = METH_NOARGS,
- },
- {
- .ml_name = "chip_name",
- .ml_meth = (PyCFunction)gpiomockup_Mockup_chip_name,
- .ml_flags = METH_VARARGS,
- },
- {
- .ml_name = "chip_path",
- .ml_meth = (PyCFunction)gpiomockup_Mockup_chip_path,
- .ml_flags = METH_VARARGS,
- },
- {
- .ml_name = "chip_num",
- .ml_meth = (PyCFunction)gpiomockup_Mockup_chip_num,
- .ml_flags = METH_VARARGS,
- },
- {
- .ml_name = "chip_get_value",
- .ml_meth = (PyCFunction)gpiomockup_Mockup_chip_get_value,
- .ml_flags = METH_VARARGS,
- },
- {
- .ml_name = "chip_set_pull",
- .ml_meth = (PyCFunction)gpiomockup_Mockup_chip_set_pull,
- .ml_flags = METH_VARARGS,
- },
- { }
-};
-
-static PyTypeObject gpiomockup_MockupType = {
- PyVarObject_HEAD_INIT(NULL, 0)
- .tp_name = "gpiomockup.Mockup",
- .tp_basicsize = sizeof(gpiomockup_MockupObject),
- .tp_flags = Py_TPFLAGS_DEFAULT,
- .tp_new = PyType_GenericNew,
- .tp_init = (initproc)gpiomockup_Mockup_init,
- .tp_dealloc = (destructor)gpiomockup_Mockup_dealloc,
- .tp_methods = gpiomockup_Mockup_methods,
-};
-
-static PyModuleDef gpiomockup_Module = {
- PyModuleDef_HEAD_INIT,
- .m_name = "gpiomockup",
- .m_size = -1,
-};
-
-PyMODINIT_FUNC PyInit_gpiomockup(void)
-{
- PyObject *module, *val;
- int ret;
-
- module = PyModule_Create(&gpiomockup_Module);
- if (!module)
- return NULL;
-
- ret = PyType_Ready(&gpiomockup_MockupType);
- if (ret)
- return NULL;
- Py_INCREF(&gpiomockup_MockupType);
-
- ret = PyModule_AddObject(module, "Mockup",
- (PyObject *)&gpiomockup_MockupType);
- if (ret)
- return NULL;
-
- val = PyLong_FromLong(gpiomockup_FLAG_NAMED_LINES);
- if (!val)
- return NULL;
-
- ret = PyDict_SetItemString(gpiomockup_MockupType.tp_dict,
- "FLAG_NAMED_LINES", val);
- if (ret)
- return NULL;
-
- return module;
-}
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+EXTRA_DIST = \
+ chip.py \
+ ext.c \
+ __init__.py
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+from .chip import Chip
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+from . import _ext
+from enum import Enum
+from typing import Optional
+
+
+class Chip:
+ """
+ Represents a simulated GPIO chip.
+ """
+
+ class Pull(Enum):
+ DOWN = _ext.PULL_DOWN
+ UP = _ext.PULL_UP
+
+ class Value(Enum):
+ INACTIVE = _ext.VALUE_INACTIVE
+ ACTIVE = _ext.VALUE_ACTIVE
+
+ class Direction(Enum):
+ INPUT = _ext.DIRECTION_INPUT
+ OUTPUT_HIGH = _ext.DIRECTION_OUTPUT_HIGH
+ OUTPUT_LOW = _ext.DIRECTION_OUTPUT_LOW
+
+ def __init__(
+ self,
+ label: Optional[str] = None,
+ num_lines: Optional[int] = None,
+ line_names: Optional[dict[int, str]] = None,
+ hogs: Optional[dict[int, tuple[str, Direction]]] = None,
+ ):
+ self._chip = _ext.Chip()
+
+ if label:
+ self._chip.set_label(label)
+
+ if num_lines:
+ self._chip.set_num_lines(num_lines)
+
+ if line_names:
+ for off, name in line_names.items():
+ self._chip.set_line_name(off, name)
+
+ if hogs:
+ for off, (name, direction) in hogs.items():
+ self._chip.set_hog(off, name, direction.value)
+
+ self._chip.enable()
+
+ def get_value(self, offset: int) -> Value:
+ val = self._chip.get_value(offset)
+ return Chip.Value(val)
+
+ def set_pull(self, offset: int, pull: Pull) -> None:
+ self._chip.set_pull(offset, pull.value)
+
+ @property
+ def dev_path(self) -> str:
+ return self._chip.dev_path
+
+ @property
+ def name(self) -> str:
+ return self._chip.name
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <gpiosim.h>
+#include <Python.h>
+
+struct module_const {
+ const char *name;
+ long val;
+};
+
+static const struct module_const module_constants[] = {
+ {
+ .name = "PULL_DOWN",
+ .val = GPIOSIM_PULL_DOWN,
+ },
+ {
+ .name = "PULL_UP",
+ .val = GPIOSIM_PULL_UP,
+ },
+ {
+ .name = "VALUE_INACTIVE",
+ .val = GPIOSIM_VALUE_INACTIVE,
+ },
+ {
+ .name = "VALUE_ACTIVE",
+ .val = GPIOSIM_VALUE_ACTIVE,
+ },
+ {
+ .name = "DIRECTION_INPUT",
+ .val = GPIOSIM_HOG_DIR_INPUT,
+ },
+ {
+ .name = "DIRECTION_OUTPUT_HIGH",
+ .val = GPIOSIM_HOG_DIR_OUTPUT_HIGH,
+ },
+ {
+ .name = "DIRECTION_OUTPUT_LOW",
+ .val = GPIOSIM_HOG_DIR_OUTPUT_LOW,
+ },
+ { }
+};
+
+struct module_state {
+ struct gpiosim_ctx *sim_ctx;
+};
+
+static void free_module_state(void *mod)
+{
+ struct module_state *state = PyModule_GetState((PyObject *)mod);
+
+ if (state->sim_ctx)
+ gpiosim_ctx_unref(state->sim_ctx);
+}
+
+static PyModuleDef module_def = {
+ PyModuleDef_HEAD_INIT,
+ .m_name = "gpiosim._ext",
+ .m_size = sizeof(struct module_state),
+ .m_free = free_module_state,
+};
+
+typedef struct {
+ PyObject_HEAD
+ struct gpiosim_dev *dev;
+ struct gpiosim_bank *bank;
+} chip_object;
+
+static int chip_init(chip_object *self,
+ PyObject *Py_UNUSED(ignored0),
+ PyObject *Py_UNUSED(ignored1))
+{
+ struct module_state *state;
+ PyObject *mod;
+
+ mod = PyState_FindModule(&module_def);
+ if (!mod)
+ return -1;
+
+ state = PyModule_GetState(mod);
+
+ self->dev = gpiosim_dev_new(state->sim_ctx);
+ if (!self->dev) {
+ PyErr_SetFromErrno(PyExc_OSError);
+ return -1;
+ }
+
+ self->bank = gpiosim_bank_new(self->dev);
+ if (!self->bank) {
+ PyErr_SetFromErrno(PyExc_OSError);
+ return -1;
+ }
+
+ return 0;
+}
+
+static void chip_finalize(chip_object *self)
+{
+ if (self->bank)
+ gpiosim_bank_unref(self->bank);
+
+ if (self->dev) {
+ if (gpiosim_dev_is_live(self->dev))
+ gpiosim_dev_disable(self->dev);
+
+ gpiosim_dev_unref(self->dev);
+ }
+}
+
+static void chip_dealloc(PyObject *self)
+{
+ int ret;
+
+ ret = PyObject_CallFinalizerFromDealloc(self);
+ if (ret < 0)
+ return;
+
+ PyObject_Del(self);
+}
+
+static PyObject *chip_dev_path(chip_object *self, void *Py_UNUSED(ignored))
+{
+ return PyUnicode_FromString(gpiosim_bank_get_dev_path(self->bank));
+}
+
+static PyObject *chip_name(chip_object *self, void *Py_UNUSED(ignored))
+{
+ return PyUnicode_FromString(gpiosim_bank_get_chip_name(self->bank));
+}
+
+static PyGetSetDef chip_getset[] = {
+ {
+ .name = "dev_path",
+ .get = (getter)chip_dev_path,
+ },
+ {
+ .name = "name",
+ .get = (getter)chip_name,
+ },
+ { }
+};
+
+static PyObject *chip_set_label(chip_object *self, PyObject *args)
+{
+ const char *label;
+ int ret;
+
+ ret = PyArg_ParseTuple(args, "s", &label);
+ if (!ret)
+ return NULL;
+
+ ret = gpiosim_bank_set_label(self->bank, label);
+ if (ret)
+ return PyErr_SetFromErrno(PyExc_OSError);
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *chip_set_num_lines(chip_object *self, PyObject *args)
+{
+ unsigned int num_lines;
+ int ret;
+
+ ret = PyArg_ParseTuple(args, "I", &num_lines);
+ if (!ret)
+ return NULL;
+
+ ret = gpiosim_bank_set_num_lines(self->bank, num_lines);
+ if (ret)
+ return PyErr_SetFromErrno(PyExc_OSError);
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *chip_set_line_name(chip_object *self, PyObject *args)
+{
+ unsigned int offset;
+ const char *name;
+ int ret;
+
+ ret = PyArg_ParseTuple(args, "Is", &offset, &name);
+ if (!ret)
+ return NULL;
+
+ ret = gpiosim_bank_set_line_name(self->bank, offset, name);
+ if (ret)
+ return PyErr_SetFromErrno(PyExc_OSError);
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *chip_set_hog(chip_object *self, PyObject *args)
+{
+ unsigned int offset;
+ const char *name;
+ int ret, dir;
+
+ ret = PyArg_ParseTuple(args, "Isi", &offset, &name, &dir);
+ if (!ret)
+ return NULL;
+
+ ret = gpiosim_bank_hog_line(self->bank, offset, name, dir);
+ if (ret)
+ return PyErr_SetFromErrno(PyExc_OSError);
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *chip_enable(chip_object *self, PyObject *Py_UNUSED(args))
+{
+ int ret;
+
+ ret = gpiosim_dev_enable(self->dev);
+ if (ret)
+ return PyErr_SetFromErrno(PyExc_OSError);
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *chip_get_value(chip_object *self, PyObject *args)
+{
+ unsigned int offset;
+ int ret, val;
+
+ ret = PyArg_ParseTuple(args, "I", &offset);
+ if (!ret)
+ return NULL;
+
+ val = gpiosim_bank_get_value(self->bank, offset);
+ if (val < 0)
+ return PyErr_SetFromErrno(PyExc_OSError);
+
+ return PyLong_FromLong(val);
+}
+
+static PyObject *chip_set_pull(chip_object *self, PyObject *args)
+{
+ unsigned int offset;
+ int ret, pull;
+
+ ret = PyArg_ParseTuple(args, "II", &offset, &pull);
+ if (!ret)
+ return NULL;
+
+ ret = gpiosim_bank_set_pull(self->bank, offset, pull);
+ if (ret)
+ return PyErr_SetFromErrno(PyExc_OSError);
+
+ Py_RETURN_NONE;
+}
+
+static PyMethodDef chip_methods[] = {
+ {
+ .ml_name = "set_label",
+ .ml_meth = (PyCFunction)chip_set_label,
+ .ml_flags = METH_VARARGS,
+ },
+ {
+ .ml_name = "set_num_lines",
+ .ml_meth = (PyCFunction)chip_set_num_lines,
+ .ml_flags = METH_VARARGS,
+ },
+ {
+ .ml_name = "set_line_name",
+ .ml_meth = (PyCFunction)chip_set_line_name,
+ .ml_flags = METH_VARARGS,
+ },
+ {
+ .ml_name = "set_hog",
+ .ml_meth = (PyCFunction)chip_set_hog,
+ .ml_flags = METH_VARARGS,
+ },
+ {
+ .ml_name = "enable",
+ .ml_meth = (PyCFunction)chip_enable,
+ .ml_flags = METH_NOARGS,
+ },
+ {
+ .ml_name = "get_value",
+ .ml_meth = (PyCFunction)chip_get_value,
+ .ml_flags = METH_VARARGS,
+ },
+ {
+ .ml_name = "set_pull",
+ .ml_meth = (PyCFunction)chip_set_pull,
+ .ml_flags = METH_VARARGS,
+ },
+ { }
+};
+
+static PyTypeObject chip_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "gpiosim.Chip",
+ .tp_basicsize = sizeof(chip_object),
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_new = PyType_GenericNew,
+ .tp_init = (initproc)chip_init,
+ .tp_finalize = (destructor)chip_finalize,
+ .tp_dealloc = (destructor)chip_dealloc,
+ .tp_methods = chip_methods,
+ .tp_getset = chip_getset,
+};
+
+PyMODINIT_FUNC PyInit__ext(void)
+{
+ const struct module_const *modconst;
+ struct module_state *state;
+ PyObject *module;
+ int ret;
+
+ module = PyModule_Create(&module_def);
+ if (!module)
+ return NULL;
+
+ ret = PyState_AddModule(module, &module_def);
+ if (ret) {
+ Py_DECREF(module);
+ return NULL;
+ }
+
+ state = PyModule_GetState(module);
+
+ state->sim_ctx = gpiosim_ctx_new();
+ if (!state->sim_ctx) {
+ Py_DECREF(module);
+ return PyErr_SetFromErrno(PyExc_OSError);
+ }
+
+ ret = PyModule_AddType(module, &chip_type);
+ if (ret) {
+ Py_DECREF(module);
+ return NULL;
+ }
+
+ for (modconst = module_constants; modconst->name; modconst++) {
+ ret = PyModule_AddIntConstant(module,
+ modconst->name, modconst->val);
+ if (ret) {
+ Py_DECREF(module);
+ return NULL;
+ }
+ }
+
+ return module;
+}
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+import os
+
+
+class LinkGuard:
+ def __init__(self, src, dst):
+ self.src = src
+ self.dst = dst
+
+ def __enter__(self):
+ os.symlink(self.src, self.dst)
+
+ def __exit__(self, type, val, tb):
+ os.unlink(self.dst)
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+import errno
+import gpiod
+import os
+
+from . import gpiosim
+from .helpers import LinkGuard
+from unittest import TestCase
+
+
+class ChipConstructor(TestCase):
+ def test_open_existing_chip(self):
+ sim = gpiosim.Chip()
+
+ with gpiod.Chip(sim.dev_path):
+ pass
+
+ def test_open_existing_chip_with_keyword(self):
+ sim = gpiosim.Chip()
+
+ with gpiod.Chip(path=sim.dev_path):
+ pass
+
+ def test_open_chip_by_link(self):
+ link = "/tmp/gpiod-py-test-link.{}".format(os.getpid())
+ sim = gpiosim.Chip()
+
+ with LinkGuard(sim.dev_path, link):
+ with gpiod.Chip(link):
+ pass
+
+ def test_open_nonexistent_chip(self):
+ with self.assertRaises(OSError) as ex:
+ gpiod.Chip("/dev/nonexistent")
+
+ self.assertEqual(ex.exception.errno, errno.ENOENT)
+
+ def test_open_not_a_character_device(self):
+ with self.assertRaises(OSError) as ex:
+ gpiod.Chip("/tmp")
+
+ self.assertEqual(ex.exception.errno, errno.ENOTTY)
+
+ def test_open_not_a_gpio_device(self):
+ with self.assertRaises(OSError) as ex:
+ gpiod.Chip("/dev/null")
+
+ self.assertEqual(ex.exception.errno, errno.ENODEV)
+
+ def test_missing_path(self):
+ with self.assertRaises(TypeError):
+ gpiod.Chip()
+
+ def test_invalid_type_for_path(self):
+ with self.assertRaises(TypeError):
+ gpiod.Chip(4)
+
+
+class ChipBooleanConversion(TestCase):
+ def test_chip_bool(self):
+ sim = gpiosim.Chip()
+ chip = gpiod.Chip(sim.dev_path)
+ self.assertTrue(chip)
+ chip.close()
+ self.assertFalse(chip)
+
+
+class ChipProperties(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip()
+ self.chip = gpiod.Chip(self.sim.dev_path)
+
+ def tearDown(self):
+ self.chip.close()
+ self.sim = None
+
+ def test_get_chip_path(self):
+ self.assertEqual(self.sim.dev_path, self.chip.path)
+
+ def test_get_fd(self):
+ self.assertGreaterEqual(self.chip.fd, 0)
+
+ def test_properties_are_immutable(self):
+ with self.assertRaises(AttributeError):
+ self.chip.path = "foobar"
+
+ with self.assertRaises(AttributeError):
+ self.chip.fd = 4
+
+
+class ChipDevPathFromLink(TestCase):
+ def test_dev_path_open_by_link(self):
+ sim = gpiosim.Chip()
+ link = "/tmp/gpiod-py-test-link.{}".format(os.getpid())
+
+ with LinkGuard(sim.dev_path, link):
+ with gpiod.Chip(link) as chip:
+ self.assertEqual(chip.path, link)
+
+
+class ChipMapLine(TestCase):
+ def test_lookup_by_name_good(self):
+ sim = gpiosim.Chip(
+ num_lines=8, line_names={1: "foo", 2: "bar", 4: "baz", 5: "xyz"}
+ )
+
+ with gpiod.Chip(sim.dev_path) as chip:
+ self.assertEqual(chip.line_offset_from_id("baz"), 4)
+
+ def test_lookup_by_name_good_keyword_argument(self):
+ sim = gpiosim.Chip(
+ num_lines=8, line_names={1: "foo", 2: "bar", 4: "baz", 5: "xyz"}
+ )
+
+ with gpiod.Chip(sim.dev_path) as chip:
+ self.assertEqual(chip.line_offset_from_id(id="baz"), 4)
+
+ def test_lookup_bad_name(self):
+ sim = gpiosim.Chip(
+ num_lines=8, line_names={1: "foo", 2: "bar", 4: "baz", 5: "xyz"}
+ )
+
+ with gpiod.Chip(sim.dev_path) as chip:
+ with self.assertRaises(FileNotFoundError):
+ chip.line_offset_from_id("nonexistent")
+
+ def test_lookup_bad_offset(self):
+ sim = gpiosim.Chip()
+
+ with gpiod.Chip(sim.dev_path) as chip:
+ with self.assertRaises(ValueError):
+ chip.line_offset_from_id(4)
+
+ def test_lookup_bad_offset_as_string(self):
+ sim = gpiosim.Chip()
+
+ with gpiod.Chip(sim.dev_path) as chip:
+ with self.assertRaises(ValueError):
+ chip.line_offset_from_id("4")
+
+ def test_duplicate_names(self):
+ sim = gpiosim.Chip(
+ num_lines=8, line_names={1: "foo", 2: "bar", 4: "baz", 5: "bar"}
+ )
+
+ with gpiod.Chip(sim.dev_path) as chip:
+ self.assertEqual(chip.line_offset_from_id("bar"), 2)
+
+ def test_integer_offsets(self):
+ sim = gpiosim.Chip(num_lines=8, line_names={1: "foo", 2: "bar", 6: "baz"})
+
+ with gpiod.Chip(sim.dev_path) as chip:
+ self.assertEqual(chip.line_offset_from_id(4), 4)
+ self.assertEqual(chip.line_offset_from_id(1), 1)
+
+ def test_offsets_as_string(self):
+ sim = gpiosim.Chip(num_lines=8, line_names={1: "foo", 2: "bar", 7: "6"})
+
+ with gpiod.Chip(sim.dev_path) as chip:
+ self.assertEqual(chip.line_offset_from_id("2"), 2)
+ self.assertEqual(chip.line_offset_from_id("6"), 7)
+
+
+class ClosedChipCannotBeUsed(TestCase):
+ def test_close_chip_and_try_to_use_it(self):
+ sim = gpiosim.Chip(label="foobar")
+
+ chip = gpiod.Chip(sim.dev_path)
+ chip.close()
+
+ with self.assertRaises(gpiod.ChipClosedError):
+ chip.path
+
+ def test_close_chip_and_try_controlled_execution(self):
+ sim = gpiosim.Chip()
+
+ chip = gpiod.Chip(sim.dev_path)
+ chip.close()
+
+ with self.assertRaises(gpiod.ChipClosedError):
+ with chip:
+ chip.fd
+
+ def test_close_chip_twice(self):
+ sim = gpiosim.Chip(label="foobar")
+ chip = gpiod.Chip(sim.dev_path)
+ chip.close()
+
+ with self.assertRaises(gpiod.ChipClosedError):
+ chip.close()
+
+
+class StringRepresentation(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(num_lines=4, label="foobar")
+ self.chip = gpiod.Chip(self.sim.dev_path)
+
+ def tearDown(self):
+ self.chip.close()
+ self.sim = None
+
+ def test_repr(self):
+ self.assertEqual(repr(self.chip), 'Chip("{}")'.format(self.sim.dev_path))
+
+ def test_str(self):
+ info = self.chip.get_info()
+ self.assertEqual(
+ str(self.chip),
+ '<Chip path="{}" fd={} info=<ChipInfo name="{}" label="foobar" num_lines=4>>'.format(
+ self.sim.dev_path, self.chip.fd, info.name
+ ),
+ )
+
+
+class StringRepresentationClosed(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(num_lines=4, label="foobar")
+ self.chip = gpiod.Chip(self.sim.dev_path)
+
+ def tearDown(self):
+ self.sim = None
+
+ def test_repr_closed(self):
+ self.chip.close()
+ self.assertEqual(repr(self.chip), "<Chip CLOSED>")
+
+ def test_str_closed(self):
+ self.chip.close()
+ self.assertEqual(str(self.chip), "<Chip CLOSED>")
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+import gpiod
+
+from . import gpiosim
+from unittest import TestCase
+
+
+class ChipInfoProperties(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(label="foobar", num_lines=16)
+ self.chip = gpiod.Chip(self.sim.dev_path)
+ self.info = self.chip.get_info()
+
+ def tearDown(self):
+ self.info = None
+ self.chip.close()
+ self.chip = None
+ self.sim = None
+
+ def test_chip_info_name(self):
+ self.assertEqual(self.info.name, self.sim.name)
+
+ def test_chip_info_label(self):
+ self.assertEqual(self.info.label, "foobar")
+
+ def test_chip_info_num_lines(self):
+ self.assertEqual(self.info.num_lines, 16)
+
+ def test_chip_info_properties_are_immutable(self):
+ with self.assertRaises(AttributeError):
+ self.info.name = "foobar"
+
+ with self.assertRaises(AttributeError):
+ self.info.num_lines = 4
+
+ with self.assertRaises(AttributeError):
+ self.info.label = "foobar"
+
+
+class ChipInfoStringRepresentation(TestCase):
+ def test_chip_info_str(self):
+ sim = gpiosim.Chip(label="foobar", num_lines=16)
+
+ with gpiod.Chip(sim.dev_path) as chip:
+ info = chip.get_info()
+
+ self.assertEqual(
+ str(info),
+ '<ChipInfo name="{}" label="foobar" num_lines=16>'.format(sim.name),
+ )
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+import gpiod
+import time
+
+from . import gpiosim
+from datetime import timedelta
+from functools import partial
+from gpiod.line import Direction, Edge
+from threading import Thread
+from unittest import TestCase
+
+EventType = gpiod.EdgeEvent.Type
+Pull = gpiosim.Chip.Pull
+
+
+class EdgeEventWaitTimeout(TestCase):
+ def test_event_wait_timeout(self):
+ sim = gpiosim.Chip()
+
+ with gpiod.request_lines(
+ sim.dev_path,
+ {0: gpiod.LineSettings(edge_detection=Edge.BOTH)},
+ ) as req:
+ self.assertEqual(req.wait_edge_event(timedelta(microseconds=10000)), False)
+
+ def test_event_wait_timeout_float(self):
+ sim = gpiosim.Chip()
+
+ with gpiod.request_lines(
+ sim.dev_path,
+ {0: gpiod.LineSettings(edge_detection=Edge.BOTH)},
+ ) as req:
+ self.assertEqual(req.wait_edge_event(0.01), False)
+
+
+class EdgeEventInvalidConfig(TestCase):
+ def test_output_mode_and_edge_detection(self):
+ sim = gpiosim.Chip()
+
+ with self.assertRaises(ValueError):
+ gpiod.request_lines(
+ sim.dev_path,
+ {
+ 0: gpiod.LineSettings(
+ direction=Direction.OUTPUT, edge_detection=Edge.BOTH
+ )
+ },
+ )
+
+
+class WaitingForEdgeEvents(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(num_lines=8)
+ self.thread = None
+
+ def tearDown(self):
+ if self.thread:
+ self.thread.join()
+ del self.thread
+ self.sim = None
+
+ def trigger_falling_and_rising_edge(self, offset):
+ time.sleep(0.05)
+ self.sim.set_pull(offset, Pull.UP)
+ time.sleep(0.05)
+ self.sim.set_pull(offset, Pull.DOWN)
+
+ def trigger_rising_edge_events_on_two_offsets(self, offset0, offset1):
+ time.sleep(0.05)
+ self.sim.set_pull(offset0, Pull.UP)
+ time.sleep(0.05)
+ self.sim.set_pull(offset1, Pull.UP)
+
+ def test_both_edge_events(self):
+ with gpiod.request_lines(
+ self.sim.dev_path, {2: gpiod.LineSettings(edge_detection=Edge.BOTH)}
+ ) as req:
+ self.thread = Thread(
+ target=partial(self.trigger_falling_and_rising_edge, 2)
+ )
+ self.thread.start()
+
+ self.assertTrue(req.wait_edge_event(timedelta(seconds=1)))
+ events = req.read_edge_event()
+ self.assertEqual(len(events), 1)
+ event = events[0]
+ self.assertEqual(event.event_type, EventType.RISING_EDGE)
+ self.assertEqual(event.line_offset, 2)
+ ts_rising = event.timestamp_ns
+
+ self.assertTrue(req.wait_edge_event(timedelta(seconds=1)))
+ events = req.read_edge_event()
+ self.assertEqual(len(events), 1)
+ event = events[0]
+ self.assertEqual(event.event_type, EventType.FALLING_EDGE)
+ self.assertEqual(event.line_offset, 2)
+ ts_falling = event.timestamp_ns
+
+ self.assertGreater(ts_falling, ts_rising)
+
+ def test_rising_edge_event(self):
+ with gpiod.request_lines(
+ self.sim.dev_path, {6: gpiod.LineSettings(edge_detection=Edge.RISING)}
+ ) as req:
+ self.thread = Thread(
+ target=partial(self.trigger_falling_and_rising_edge, 6)
+ )
+ self.thread.start()
+
+ self.assertTrue(req.wait_edge_event(timedelta(seconds=1)))
+ events = req.read_edge_event()
+ self.assertEqual(len(events), 1)
+ event = events[0]
+ self.assertEqual(event.event_type, EventType.RISING_EDGE)
+ self.assertEqual(event.line_offset, 6)
+
+ self.assertFalse(req.wait_edge_event(timedelta(microseconds=10000)))
+
+ def test_rising_edge_event(self):
+ with gpiod.request_lines(
+ self.sim.dev_path, {6: gpiod.LineSettings(edge_detection=Edge.FALLING)}
+ ) as req:
+ self.thread = Thread(
+ target=partial(self.trigger_falling_and_rising_edge, 6)
+ )
+ self.thread.start()
+
+ self.assertTrue(req.wait_edge_event(timedelta(seconds=1)))
+ events = req.read_edge_event()
+ self.assertEqual(len(events), 1)
+ event = events[0]
+ self.assertEqual(event.event_type, EventType.FALLING_EDGE)
+ self.assertEqual(event.line_offset, 6)
+
+ self.assertFalse(req.wait_edge_event(timedelta(microseconds=10000)))
+
+ def test_sequence_numbers(self):
+ with gpiod.request_lines(
+ self.sim.dev_path, {(2, 4): gpiod.LineSettings(edge_detection=Edge.BOTH)}
+ ) as req:
+ self.thread = Thread(
+ target=partial(self.trigger_rising_edge_events_on_two_offsets, 2, 4)
+ )
+ self.thread.start()
+
+ self.assertTrue(req.wait_edge_event(timedelta(seconds=1)))
+ events = req.read_edge_event()
+ self.assertEqual(len(events), 1)
+ event = events[0]
+ self.assertEqual(event.event_type, EventType.RISING_EDGE)
+ self.assertEqual(event.line_offset, 2)
+ self.assertEqual(event.global_seqno, 1)
+ self.assertEqual(event.line_seqno, 1)
+
+ self.assertTrue(req.wait_edge_event(timedelta(seconds=1)))
+ events = req.read_edge_event()
+ self.assertEqual(len(events), 1)
+ event = events[0]
+ self.assertEqual(event.event_type, EventType.RISING_EDGE)
+ self.assertEqual(event.line_offset, 4)
+ self.assertEqual(event.global_seqno, 2)
+ self.assertEqual(event.line_seqno, 1)
+
+
+class ReadingMultipleEdgeEvents(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(num_lines=8)
+ self.request = gpiod.request_lines(
+ self.sim.dev_path, {1: gpiod.LineSettings(edge_detection=Edge.BOTH)}
+ )
+ self.line_seqno = 1
+ self.global_seqno = 1
+ self.sim.set_pull(1, Pull.UP)
+ time.sleep(0.05)
+ self.sim.set_pull(1, Pull.DOWN)
+ time.sleep(0.05)
+ self.sim.set_pull(1, Pull.UP)
+ time.sleep(0.05)
+
+ def tearDown(self):
+ self.request.release()
+ del self.request
+ del self.sim
+
+ def test_read_multiple_events(self):
+ self.assertTrue(self.request.wait_edge_event(timedelta(seconds=1)))
+ events = self.request.read_edge_event()
+ self.assertEqual(len(events), 3)
+
+ for event in events:
+ self.assertEqual(event.line_offset, 1)
+ self.assertEqual(event.line_seqno, self.line_seqno)
+ self.assertEqual(event.global_seqno, self.global_seqno)
+ self.line_seqno += 1
+ self.global_seqno += 1
+
+
+class EdgeEventStringRepresentation(TestCase):
+ def test_edge_event_str(self):
+ sim = gpiosim.Chip()
+
+ with gpiod.request_lines(
+ path=sim.dev_path, config={0: gpiod.LineSettings(edge_detection=Edge.BOTH)}
+ ) as req:
+ sim.set_pull(0, Pull.UP)
+ event = req.read_edge_event()[0]
+ self.assertRegex(
+ str(event),
+ "<EdgeEvent type=Type\.RISING_EDGE timestamp_ns=[0-9]+ line_offset=0 global_seqno=1 line_seqno=1>",
+ )
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+import datetime
+import errno
+import gpiod
+import threading
+import time
+import unittest
+
+from . import gpiosim
+from dataclasses import FrozenInstanceError
+from functools import partial
+from gpiod.line import Direction
+from unittest import TestCase
+
+EventType = gpiod.InfoEvent.Type
+
+
+class InfoEventDataclassBehavior(TestCase):
+ def test_info_event_props_are_frozen(self):
+ sim = gpiosim.Chip()
+
+ with gpiod.Chip(sim.dev_path) as chip:
+ chip.watch_line_info(0)
+ with chip.request_lines(config={0: None}) as request:
+ self.assertTrue(chip.wait_info_event(datetime.timedelta(seconds=1)))
+ event = chip.read_info_event()
+
+ with self.assertRaises(FrozenInstanceError):
+ event.event_type = 4
+
+ with self.assertRaises(FrozenInstanceError):
+ event.timestamp_ns = 4
+
+ with self.assertRaises(FrozenInstanceError):
+ event.line_info = 4
+
+
+def request_reconfigure_release_line(chip, offset):
+ time.sleep(0.1)
+ with chip.request_lines(config={offset: None}) as request:
+ time.sleep(0.1)
+ request.reconfigure_lines(
+ config={offset: gpiod.LineSettings(direction=Direction.OUTPUT)}
+ )
+ time.sleep(0.1)
+
+
+class WatchingInfoEventWorks(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(num_lines=8, line_names={4: "foobar"})
+ self.chip = gpiod.Chip(self.sim.dev_path)
+ self.thread = None
+
+ def tearDown(self):
+ if self.thread:
+ self.thread.join()
+ self.thread = None
+
+ self.chip.close()
+ self.chip = None
+ self.sim = None
+
+ def test_watch_line_info_returns_line_info(self):
+ info = self.chip.watch_line_info(7)
+ self.assertEqual(info.offset, 7)
+
+ def test_watch_line_info_keyword_argument(self):
+ info = self.chip.watch_line_info(line=7)
+
+ def test_watch_line_info_offset_out_of_range(self):
+ with self.assertRaises(ValueError):
+ self.chip.watch_line_info(8)
+
+ def test_watch_line_info_no_arguments(self):
+ with self.assertRaises(TypeError):
+ self.chip.watch_line_info()
+
+ def test_watch_line_info_by_line_name(self):
+ self.chip.watch_line_info("foobar")
+
+ def test_watch_line_info_invalid_argument_type(self):
+ with self.assertRaises(TypeError):
+ self.chip.watch_line_info(None)
+
+ def test_wait_for_event_timeout(self):
+ info = self.chip.watch_line_info(7)
+ self.assertFalse(
+ self.chip.wait_info_event(datetime.timedelta(microseconds=10000))
+ )
+
+ def test_request_reconfigure_release_events(self):
+ info = self.chip.watch_line_info(7)
+ self.assertEqual(info.direction, Direction.INPUT)
+
+ self.thread = threading.Thread(
+ target=partial(request_reconfigure_release_line, self.chip, 7)
+ )
+ self.thread.start()
+
+ self.assertTrue(self.chip.wait_info_event(datetime.timedelta(seconds=1)))
+ event = self.chip.read_info_event()
+ self.assertEqual(event.event_type, EventType.LINE_REQUESTED)
+ self.assertEqual(event.line_info.offset, 7)
+ self.assertEqual(event.line_info.direction, Direction.INPUT)
+ ts_req = event.timestamp_ns
+
+ # Check that we can use a float directly instead of datetime.timedelta.
+ self.assertTrue(self.chip.wait_info_event(1.0))
+ event = self.chip.read_info_event()
+ self.assertEqual(event.event_type, EventType.LINE_CONFIG_CHANGED)
+ self.assertEqual(event.line_info.offset, 7)
+ self.assertEqual(event.line_info.direction, Direction.OUTPUT)
+ ts_rec = event.timestamp_ns
+
+ self.assertTrue(self.chip.wait_info_event(datetime.timedelta(seconds=1)))
+ event = self.chip.read_info_event()
+ self.assertEqual(event.event_type, EventType.LINE_RELEASED)
+ self.assertEqual(event.line_info.offset, 7)
+ self.assertEqual(event.line_info.direction, Direction.OUTPUT)
+ ts_rel = event.timestamp_ns
+
+ # No more events.
+ self.assertFalse(
+ self.chip.wait_info_event(datetime.timedelta(microseconds=10000))
+ )
+
+ # Check timestamps are really monotonic.
+ self.assertGreater(ts_rel, ts_rec)
+ self.assertGreater(ts_rec, ts_req)
+
+
+class UnwatchingLineInfo(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(num_lines=8, line_names={4: "foobar"})
+ self.chip = gpiod.Chip(self.sim.dev_path)
+
+ def tearDown(self):
+ self.chip.close()
+ self.chip = None
+ self.sim = None
+
+ def test_unwatch_line_info(self):
+ self.chip.watch_line_info(0)
+ with self.chip.request_lines(config={0: None}) as request:
+ self.assertTrue(self.chip.wait_info_event(datetime.timedelta(seconds=1)))
+ event = self.chip.read_info_event()
+ self.assertEqual(event.event_type, EventType.LINE_REQUESTED)
+ self.chip.unwatch_line_info(0)
+
+ self.assertFalse(
+ self.chip.wait_info_event(datetime.timedelta(microseconds=10000))
+ )
+
+ def test_unwatch_not_watched_line(self):
+ with self.assertRaises(OSError) as ex:
+ self.chip.unwatch_line_info(2)
+
+ self.assertEqual(ex.exception.errno, errno.EBUSY)
+
+ def test_unwatch_line_info_no_argument(self):
+ with self.assertRaises(TypeError):
+ self.chip.unwatch_line_info()
+
+ def test_unwatch_line_info_by_line_name(self):
+ self.chip.watch_line_info(4)
+ with self.chip.request_lines(config={4: None}) as request:
+ self.assertIsNotNone(self.chip.read_info_event())
+ self.chip.unwatch_line_info("foobar")
+
+ self.assertFalse(
+ self.chip.wait_info_event(datetime.timedelta(microseconds=10000))
+ )
+
+
+class InfoEventStringRepresentation(TestCase):
+ def test_info_event_str(self):
+ sim = gpiosim.Chip()
+
+ with gpiod.Chip(sim.dev_path) as chip:
+ chip.watch_line_info(0)
+ with chip.request_lines(config={0: None}) as request:
+ self.assertTrue(chip.wait_info_event(datetime.timedelta(seconds=1)))
+ event = chip.read_info_event()
+ self.assertRegex(
+ str(event),
+ '<InfoEvent type=Type\.LINE_REQUESTED timestamp_ns=[0-9]+ line_info=<LineInfo offset=0 name="None" used=True consumer="\?" direction=Direction\.INPUT active_low=False bias=Bias\.UNKNOWN drive=Drive\.PUSH_PULL edge_detection=Edge\.NONE event_clock=Clock\.MONOTONIC debounced=False debounce_period=0:00:00>>',
+ )
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+import errno
+import gpiod
+import unittest
+
+from . import gpiosim
+from gpiod.line import Direction, Bias, Drive, Clock
+
+HogDir = gpiosim.Chip.Direction
+
+
+class GetLineInfo(unittest.TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(
+ num_lines=4,
+ line_names={0: "foobar"},
+ )
+
+ self.chip = gpiod.Chip(self.sim.dev_path)
+
+ def tearDown(self):
+ self.chip.close()
+ self.chip = None
+ self.sim = None
+
+ def test_get_line_info_by_offset(self):
+ self.chip.get_line_info(0)
+
+ def test_get_line_info_by_offset_keyword(self):
+ self.chip.get_line_info(line=0)
+
+ def test_get_line_info_by_name(self):
+ self.chip.get_line_info("foobar")
+
+ def test_get_line_info_by_name_keyword(self):
+ self.chip.get_line_info(line="foobar")
+
+ def test_get_line_info_by_offset_string(self):
+ self.chip.get_line_info("2")
+
+ def test_offset_out_of_range(self):
+ with self.assertRaises(ValueError) as ex:
+ self.chip.get_line_info(4)
+
+ def test_no_offset(self):
+ with self.assertRaises(TypeError):
+ self.chip.get_line_info()
+
+
+class LinePropertiesCanBeRead(unittest.TestCase):
+ def test_basic_properties(self):
+ sim = gpiosim.Chip(
+ num_lines=8,
+ line_names={1: "foo", 2: "bar", 4: "baz", 5: "xyz"},
+ hogs={3: ("hog3", HogDir.OUTPUT_HIGH), 4: ("hog4", HogDir.OUTPUT_LOW)},
+ )
+
+ with gpiod.Chip(sim.dev_path) as chip:
+ info4 = chip.get_line_info(4)
+ info6 = chip.get_line_info(6)
+
+ self.assertEqual(info4.offset, 4)
+ self.assertEqual(info4.name, "baz")
+ self.assertTrue(info4.used)
+ self.assertEqual(info4.consumer, "hog4")
+ self.assertEqual(info4.direction, Direction.OUTPUT)
+ self.assertFalse(info4.active_low)
+ self.assertEqual(info4.bias, Bias.UNKNOWN)
+ self.assertEqual(info4.drive, Drive.PUSH_PULL)
+ self.assertEqual(info4.event_clock, Clock.MONOTONIC)
+ self.assertFalse(info4.debounced)
+ self.assertEqual(info4.debounce_period.total_seconds(), 0.0)
+
+ self.assertEqual(info6.offset, 6)
+ self.assertEqual(info6.name, None)
+ self.assertFalse(info6.used)
+ self.assertEqual(info6.consumer, None)
+ self.assertEqual(info6.direction, Direction.INPUT)
+ self.assertFalse(info6.active_low)
+ self.assertEqual(info6.bias, Bias.UNKNOWN)
+ self.assertEqual(info6.drive, Drive.PUSH_PULL)
+ self.assertEqual(info6.event_clock, Clock.MONOTONIC)
+ self.assertFalse(info6.debounced)
+ self.assertEqual(info6.debounce_period.total_seconds(), 0.0)
+
+
+class LineInfoStringRepresentation(unittest.TestCase):
+ def test_line_info_str(self):
+ sim = gpiosim.Chip(
+ line_names={0: "foo"}, hogs={0: ("hogger", HogDir.OUTPUT_HIGH)}
+ )
+
+ with gpiod.Chip(sim.dev_path) as chip:
+ info = chip.get_line_info(0)
+
+ self.assertEqual(
+ str(info),
+ '<LineInfo offset=0 name="foo" used=True consumer="hogger" direction=Direction.OUTPUT active_low=False bias=Bias.UNKNOWN drive=Drive.PUSH_PULL edge_detection=Edge.NONE event_clock=Clock.MONOTONIC debounced=False debounce_period=0:00:00>',
+ )
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+import errno
+import gpiod
+
+from . import gpiosim
+from gpiod.line import Direction, Edge, Value
+from unittest import TestCase
+
+Pull = gpiosim.Chip.Pull
+SimVal = gpiosim.Chip.Value
+
+
+class ChipLineRequestsBehaveCorrectlyWithInvalidArguments(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(num_lines=8)
+ self.chip = gpiod.Chip(self.sim.dev_path)
+
+ def tearDown(self):
+ self.chip.close()
+ del self.chip
+ del self.sim
+
+ def test_passing_invalid_types_as_configs(self):
+ with self.assertRaises(AttributeError):
+ self.chip.request_lines("foobar")
+
+ with self.assertRaises(AttributeError):
+ self.chip.request_lines(None, "foobar")
+
+ def test_offset_out_of_range(self):
+ with self.assertRaises(ValueError):
+ self.chip.request_lines(config={(1, 0, 4, 8): None})
+
+ def test_line_name_not_found(self):
+ with self.assertRaises(FileNotFoundError):
+ self.chip.request_lines(config={"foo": None})
+
+ def test_request_no_arguments(self):
+ with self.assertRaises(TypeError):
+ self.chip.request_lines()
+
+
+class ModuleLineRequestsBehaveCorrectlyWithInvalidArguments(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(num_lines=8)
+
+ def tearDown(self):
+ del self.sim
+
+ def test_passing_invalid_types_as_configs(self):
+ with self.assertRaises(AttributeError):
+ gpiod.request_lines(self.sim.dev_path, "foobar")
+
+ with self.assertRaises(AttributeError):
+ gpiod.request_lines(self.sim.dev_path, None, "foobar")
+
+ def test_offset_out_of_range(self):
+ with self.assertRaises(ValueError):
+ gpiod.request_lines(self.sim.dev_path, config={(1, 0, 4, 8): None})
+
+ def test_line_name_not_found(self):
+ with self.assertRaises(FileNotFoundError):
+ gpiod.request_lines(self.sim.dev_path, config={"foo": None})
+
+ def test_request_no_arguments(self):
+ with self.assertRaises(TypeError):
+ gpiod.request_lines()
+
+
+class ChipLineRequestWorks(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(num_lines=8, line_names={5: "foo", 7: "bar"})
+ self.chip = gpiod.Chip(self.sim.dev_path)
+
+ def tearDown(self):
+ self.chip.close()
+ del self.chip
+ del self.sim
+
+ def test_request_with_positional_arguments(self):
+ with self.chip.request_lines({(0, 5, 3, 1): None}, "foobar", 32) as req:
+ self.assertEqual(req.offsets, [0, 5, 3, 1])
+ self.assertEqual(self.chip.get_line_info(0).consumer, "foobar")
+
+ def test_request_with_keyword_arguments(self):
+ with self.chip.request_lines(
+ config={(0, 5, 6): None},
+ consumer="foobar",
+ event_buffer_size=16,
+ ) as req:
+ self.assertEqual(req.offsets, [0, 5, 6])
+ self.assertEqual(self.chip.get_line_info(0).consumer, "foobar")
+
+ def test_request_single_offset_as_int(self):
+ with self.chip.request_lines(config={4: None}) as req:
+ self.assertEqual(req.offsets, [4])
+
+ def test_request_single_offset_as_tuple(self):
+ with self.chip.request_lines(config={(4): None}) as req:
+ self.assertEqual(req.offsets, [4])
+
+ def test_request_by_name(self):
+ with self.chip.request_lines(config={(1, 2, "foo", "bar"): None}) as req:
+ self.assertEqual(req.offsets, [1, 2, 5, 7])
+
+ def test_request_single_line_by_name(self):
+ with self.chip.request_lines(config={"foo": None}) as req:
+ self.assertEqual(req.offsets, [5])
+
+
+class ModuleLineRequestWorks(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(num_lines=8, line_names={5: "foo", 7: "bar"})
+
+ def tearDown(self):
+ del self.sim
+
+ def test_request_with_positional_arguments(self):
+ with gpiod.request_lines(
+ self.sim.dev_path, {(0, 5, 3, 1): None}, "foobar", 32
+ ) as req:
+ self.assertEqual(req.offsets, [0, 5, 3, 1])
+ with gpiod.Chip(self.sim.dev_path) as chip:
+ self.assertEqual(chip.get_line_info(5).consumer, "foobar")
+
+ def test_request_with_keyword_arguments(self):
+ with gpiod.request_lines(
+ path=self.sim.dev_path,
+ config={(0, 5, 6): None},
+ consumer="foobar",
+ event_buffer_size=16,
+ ) as req:
+ self.assertEqual(req.offsets, [0, 5, 6])
+ with gpiod.Chip(self.sim.dev_path) as chip:
+ self.assertEqual(chip.get_line_info(5).consumer, "foobar")
+
+ def test_request_single_offset_as_int(self):
+ with gpiod.request_lines(path=self.sim.dev_path, config={4: None}) as req:
+ self.assertEqual(req.offsets, [4])
+
+ def test_request_single_offset_as_tuple(self):
+ with gpiod.request_lines(path=self.sim.dev_path, config={(4): None}) as req:
+ self.assertEqual(req.offsets, [4])
+
+ def test_request_by_name(self):
+ with gpiod.request_lines(
+ self.sim.dev_path, {(1, 2, "foo", "bar"): None}
+ ) as req:
+ self.assertEqual(req.offsets, [1, 2, 5, 7])
+
+
+class LineRequestGettingValues(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(num_lines=8)
+ self.req = gpiod.request_lines(
+ self.sim.dev_path,
+ {(0, 1, 2, 3): gpiod.LineSettings(direction=Direction.INPUT)},
+ )
+
+ def tearDown(self):
+ self.req.release()
+ del self.req
+ del self.sim
+
+ def test_get_single_value(self):
+ self.sim.set_pull(1, Pull.UP)
+
+ self.assertEqual(self.req.get_values([1]), [Value.ACTIVE])
+
+ def test_get_single_value_helper(self):
+ self.sim.set_pull(1, Pull.UP)
+
+ self.assertEqual(self.req.get_value(1), Value.ACTIVE)
+
+ def test_get_values_for_subset_of_lines(self):
+ self.sim.set_pull(0, Pull.UP)
+ self.sim.set_pull(1, Pull.DOWN)
+ self.sim.set_pull(3, Pull.UP)
+
+ self.assertEqual(
+ self.req.get_values([0, 1, 3]), [Value.ACTIVE, Value.INACTIVE, Value.ACTIVE]
+ )
+
+ def test_get_all_values(self):
+ self.sim.set_pull(0, Pull.DOWN)
+ self.sim.set_pull(1, Pull.UP)
+ self.sim.set_pull(2, Pull.UP)
+ self.sim.set_pull(3, Pull.UP)
+
+ self.assertEqual(
+ self.req.get_values(),
+ [Value.INACTIVE, Value.ACTIVE, Value.ACTIVE, Value.ACTIVE],
+ )
+
+ def test_get_values_invalid_offset(self):
+ with self.assertRaises(ValueError):
+ self.req.get_values([9])
+
+ def test_get_values_invalid_argument_type(self):
+ with self.assertRaises(TypeError):
+ self.req.get_values(True)
+
+
+class LineRequestGettingValuesByName(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(num_lines=4, line_names={2: "foo", 3: "bar", 1: "baz"})
+ self.req = gpiod.request_lines(
+ self.sim.dev_path,
+ {(0, "baz", "bar", "foo"): gpiod.LineSettings(direction=Direction.INPUT)},
+ )
+
+ def tearDown(self):
+ self.req.release()
+ del self.req
+ del self.sim
+
+ def test_get_values_by_name(self):
+ self.sim.set_pull(1, Pull.UP)
+ self.sim.set_pull(2, Pull.DOWN)
+ self.sim.set_pull(3, Pull.UP)
+
+ self.assertEqual(
+ self.req.get_values(["foo", "bar", 1]),
+ [Value.INACTIVE, Value.ACTIVE, Value.ACTIVE],
+ )
+
+ def test_get_values_by_bad_name(self):
+ with self.assertRaises(ValueError):
+ self.req.get_values(["xyz"])
+
+
+class LineRequestSettingValues(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(num_lines=8)
+ self.req = gpiod.request_lines(
+ self.sim.dev_path,
+ {(0, 1, 2, 3): gpiod.LineSettings(direction=Direction.OUTPUT)},
+ )
+
+ def tearDown(self):
+ self.req.release()
+ del self.req
+ del self.sim
+
+ def test_set_single_value(self):
+ self.req.set_values({1: Value.ACTIVE})
+ self.assertEqual(self.sim.get_value(1), SimVal.ACTIVE)
+
+ def test_set_single_value_helper(self):
+ self.req.set_value(1, Value.ACTIVE)
+ self.assertEqual(self.sim.get_value(1), SimVal.ACTIVE)
+
+ def test_set_values_for_subset_of_lines(self):
+ self.req.set_values({0: Value.ACTIVE, 1: Value.INACTIVE, 3: Value.ACTIVE})
+
+ self.assertEqual(self.sim.get_value(0), SimVal.ACTIVE)
+ self.assertEqual(self.sim.get_value(1), SimVal.INACTIVE)
+ self.assertEqual(self.sim.get_value(3), SimVal.ACTIVE)
+
+ def test_set_values_invalid_offset(self):
+ with self.assertRaises(ValueError):
+ self.req.set_values({9: Value.ACTIVE})
+
+
+class LineRequestSettingValuesByName(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(num_lines=4, line_names={2: "foo", 3: "bar", 1: "baz"})
+ self.req = gpiod.request_lines(
+ self.sim.dev_path,
+ {(0, "baz", "bar", "foo"): gpiod.LineSettings(direction=Direction.OUTPUT)},
+ )
+
+ def tearDown(self):
+ self.req.release()
+ del self.req
+ del self.sim
+
+ def test_set_values_by_name(self):
+ self.req.set_values(
+ {"foo": Value.INACTIVE, "bar": Value.ACTIVE, 1: Value.ACTIVE}
+ )
+
+ self.assertEqual(self.sim.get_value(2), SimVal.INACTIVE)
+ self.assertEqual(self.sim.get_value(1), SimVal.ACTIVE)
+ self.assertEqual(self.sim.get_value(3), SimVal.ACTIVE)
+
+ def test_set_values_by_bad_name(self):
+ with self.assertRaises(ValueError):
+ self.req.set_values({"xyz": Value.ACTIVE})
+
+
+class LineRequestComplexConfig(TestCase):
+ def test_complex_config(self):
+ sim = gpiosim.Chip(num_lines=8)
+
+ with gpiod.Chip(sim.dev_path) as chip:
+ with chip.request_lines(
+ config={
+ (0, 2, 4): gpiod.LineSettings(
+ direction=Direction.OUTPUT, output_value=Value.ACTIVE
+ ),
+ (1, 3, 5): gpiod.LineSettings(
+ direction=Direction.INPUT, edge_detection=Edge.BOTH
+ ),
+ },
+ ) as req:
+ self.assertEqual(chip.get_line_info(2).direction, Direction.OUTPUT)
+ self.assertEqual(chip.get_line_info(3).edge_detection, Edge.BOTH)
+
+
+class RepeatingLinesInRequestConfig(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(num_lines=4, line_names={0: "foo", 2: "bar"})
+ self.chip = gpiod.Chip(self.sim.dev_path)
+
+ def tearDown(self):
+ self.chip.close()
+ del self.chip
+ del self.sim
+
+ def test_offsets_repeating_within_the_same_tuple(self):
+ with self.assertRaises(ValueError):
+ self.chip.request_lines({(0, 1, 2, 1): None})
+
+ def test_offsets_repeating_in_different_tuples(self):
+ with self.assertRaises(ValueError):
+ self.chip.request_lines({(0, 1, 2): None, (3, 4, 0): None})
+
+ def test_offset_and_name_conflict_in_the_same_tuple(self):
+ with self.assertRaises(ValueError):
+ self.chip.request_lines({(2, "bar"): None})
+
+ def test_offset_and_name_conflict_in_different_tuples(self):
+ with self.assertRaises(ValueError):
+ self.chip.request_lines({(0, 1, 2): None, (4, 5, "bar"): None})
+
+
+class LineRequestPropertiesWork(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(num_lines=16, line_names={0: "foo", 2: "bar", 5: "baz"})
+
+ def tearDown(self):
+ del self.sim
+
+ def test_property_fd(self):
+ with gpiod.request_lines(
+ self.sim.dev_path,
+ config={
+ 0: gpiod.LineSettings(
+ direction=Direction.INPUT, edge_detection=Edge.BOTH
+ )
+ },
+ ) as req:
+ self.assertGreaterEqual(req.fd, 0)
+
+ def test_property_num_lines(self):
+ with gpiod.request_lines(
+ self.sim.dev_path, config={(0, 2, 3, 5, 6, 8, 12): None}
+ ) as req:
+ self.assertEqual(req.num_lines, 7)
+
+ def test_property_offsets(self):
+ with gpiod.request_lines(
+ self.sim.dev_path, config={(1, 6, 12, 4): None}
+ ) as req:
+ self.assertEqual(req.offsets, [1, 6, 12, 4])
+
+ def test_property_lines(self):
+ with gpiod.request_lines(
+ self.sim.dev_path, config={("foo", 1, "bar", 4, "baz"): None}
+ ) as req:
+ self.assertEqual(req.lines, ["foo", 1, "bar", 4, "baz"])
+
+
+class LineRequestConsumerString(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(num_lines=4)
+ self.chip = gpiod.Chip(self.sim.dev_path)
+
+ def tearDown(self):
+ self.chip.close()
+ del self.chip
+ del self.sim
+
+ def test_custom_consumer(self):
+ with self.chip.request_lines(
+ consumer="foobar", config={(2, 3): None}
+ ) as request:
+ info = self.chip.get_line_info(2)
+ self.assertEqual(info.consumer, "foobar")
+
+ def test_empty_consumer(self):
+ with self.chip.request_lines(consumer="", config={(2, 3): None}) as request:
+ info = self.chip.get_line_info(2)
+ self.assertEqual(info.consumer, "?")
+
+ def test_default_consumer(self):
+ with self.chip.request_lines(config={(2, 3): None}) as request:
+ info = self.chip.get_line_info(2)
+ self.assertEqual(info.consumer, "?")
+
+
+class ReconfigureRequestedLines(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(num_lines=8, line_names={3: "foo", 4: "bar", 6: "baz"})
+ self.chip = gpiod.Chip(self.sim.dev_path)
+ self.req = self.chip.request_lines(
+ {(0, 2, "foo", "baz"): gpiod.LineSettings(direction=Direction.OUTPUT)}
+ )
+
+ def tearDown(self):
+ self.chip.close()
+ del self.chip
+ self.req.release()
+ del self.req
+ del self.sim
+
+ def test_reconfigure_by_offsets(self):
+ info = self.chip.get_line_info(2)
+ self.assertEqual(info.direction, Direction.OUTPUT)
+ self.req.reconfigure_lines(
+ {(0, 2, 3, 6): gpiod.LineSettings(direction=Direction.INPUT)}
+ )
+ info = self.chip.get_line_info(2)
+ self.assertEqual(info.direction, Direction.INPUT)
+
+ def test_reconfigure_by_names(self):
+ info = self.chip.get_line_info(2)
+ self.assertEqual(info.direction, Direction.OUTPUT)
+ self.req.reconfigure_lines(
+ {(0, 2, "foo", "baz"): gpiod.LineSettings(direction=Direction.INPUT)}
+ )
+ info = self.chip.get_line_info(2)
+ self.assertEqual(info.direction, Direction.INPUT)
+
+
+class ReleasedLineRequestCannotBeUsed(TestCase):
+ def test_using_released_line_request(self):
+ sim = gpiosim.Chip()
+
+ with gpiod.Chip(sim.dev_path) as chip:
+ req = chip.request_lines(config={0: None})
+ req.release()
+
+ with self.assertRaises(gpiod.RequestReleasedError):
+ req.fd
+
+
+class LineRequestSurvivesParentChip(TestCase):
+ def test_line_request_survives_parent_chip(self):
+ sim = gpiosim.Chip()
+
+ chip = gpiod.Chip(sim.dev_path)
+ try:
+ req = chip.request_lines(
+ config={0: gpiod.LineSettings(direction=Direction.INPUT)}
+ )
+ except:
+ chip.close()
+ raise
+
+ chip.close()
+ self.assertEqual(req.get_values([0]), [Value.INACTIVE])
+
+
+class LineRequestStringRepresentation(TestCase):
+ def setUp(self):
+ self.sim = gpiosim.Chip(num_lines=8)
+
+ def tearDown(self):
+ del self.sim
+
+ def test_str(self):
+ with gpiod.request_lines(self.sim.dev_path, config={(2, 6, 4, 1): None}) as req:
+ self.assertEqual(
+ str(req),
+ "<LineRequest num_lines=4 offsets=[2, 6, 4, 1] fd={}>".format(req.fd),
+ )
+
+ def test_str_released(self):
+ req = gpiod.request_lines(self.sim.dev_path, config={(2, 6, 4, 1): None})
+ req.release()
+ self.assertEqual(str(req), "<LineRequest RELEASED>")
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+import gpiod
+
+from . import gpiosim
+from datetime import timedelta
+from gpiod.line import Direction, Edge, Bias, Drive, Value, Clock
+from unittest import TestCase
+
+
+class LineSettingsConstructor(TestCase):
+ def test_default_values(self):
+ settings = gpiod.LineSettings()
+
+ self.assertEqual(settings.direction, Direction.AS_IS)
+ self.assertEqual(settings.edge_detection, Edge.NONE)
+ self.assertEqual(settings.bias, Bias.AS_IS)
+ self.assertEqual(settings.drive, Drive.PUSH_PULL)
+ self.assertFalse(settings.active_low)
+ self.assertEqual(settings.debounce_period.total_seconds(), 0.0)
+ self.assertEqual(settings.event_clock, Clock.MONOTONIC)
+ self.assertEqual(settings.output_value, Value.INACTIVE)
+
+ def test_keyword_arguments(self):
+ settings = gpiod.LineSettings(
+ direction=Direction.INPUT,
+ edge_detection=Edge.BOTH,
+ bias=Bias.PULL_UP,
+ event_clock=Clock.REALTIME,
+ )
+
+ self.assertEqual(settings.direction, Direction.INPUT)
+ self.assertEqual(settings.edge_detection, Edge.BOTH)
+ self.assertEqual(settings.bias, Bias.PULL_UP)
+ self.assertEqual(settings.drive, Drive.PUSH_PULL)
+ self.assertFalse(settings.active_low)
+ self.assertEqual(settings.debounce_period.total_seconds(), 0.0)
+ self.assertEqual(settings.event_clock, Clock.REALTIME)
+ self.assertEqual(settings.output_value, Value.INACTIVE)
+
+
+class LineSettingsAttributes(TestCase):
+ def test_line_settings_attributes_are_mutable(self):
+ settings = gpiod.LineSettings()
+
+ settings.direction = Direction.INPUT
+ settings.edge_detection = Edge.BOTH
+ settings.bias = Bias.DISABLED
+ settings.debounce_period = timedelta(microseconds=3000)
+ settings.event_clock = Clock.HTE
+
+ self.assertEqual(settings.direction, Direction.INPUT)
+ self.assertEqual(settings.edge_detection, Edge.BOTH)
+ self.assertEqual(settings.bias, Bias.DISABLED)
+ self.assertEqual(settings.drive, Drive.PUSH_PULL)
+ self.assertFalse(settings.active_low)
+ self.assertEqual(settings.debounce_period.total_seconds(), 0.003)
+ self.assertEqual(settings.event_clock, Clock.HTE)
+ self.assertEqual(settings.output_value, Value.INACTIVE)
+
+
+class LineSettingsStringRepresentation(TestCase):
+ def setUp(self):
+ self.settings = gpiod.LineSettings(
+ direction=Direction.OUTPUT, drive=Drive.OPEN_SOURCE, active_low=True
+ )
+
+ def test_repr(self):
+ self.assertEqual(
+ repr(self.settings),
+ "LineSettings(direction=Direction.OUTPUT, edge_detection=Edge.NONE bias=Bias.AS_IS drive=Drive.OPEN_SOURCE active_low=True debounce_period=datetime.timedelta(0) event_clock=Clock.MONOTONIC output_value=Value.INACTIVE)",
+ )
+
+ def test_str(self):
+ self.assertEqual(
+ str(self.settings),
+ "<LineSettings direction=Direction.OUTPUT edge_detection=Edge.NONE bias=Bias.AS_IS drive=Drive.OPEN_SOURCE active_low=True debounce_period=0:00:00 event_clock=Clock.MONOTONIC output_value=Value.INACTIVE>",
+ )
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+import gpiod
+import os
+import re
+import unittest
+
+from . import gpiosim
+from .helpers import LinkGuard
+from unittest import TestCase
+
+
+class IsGPIOChip(TestCase):
+ def test_is_gpiochip_bad(self):
+ self.assertFalse(gpiod.is_gpiochip_device("/dev/null"))
+ self.assertFalse(gpiod.is_gpiochip_device("/dev/nonexistent"))
+
+ def test_is_gpiochip_invalid_argument(self):
+ with self.assertRaises(TypeError):
+ gpiod.is_gpiochip_device(4)
+
+ def test_is_gpiochip_superfluous_argument(self):
+ with self.assertRaises(TypeError):
+ gpiod.is_gpiochip_device("/dev/null", 4)
+
+ def test_is_gpiochip_missing_argument(self):
+ with self.assertRaises(TypeError):
+ gpiod.is_gpiochip_device()
+
+ def test_is_gpiochip_good(self):
+ sim = gpiosim.Chip()
+ self.assertTrue(gpiod.is_gpiochip_device(sim.dev_path))
+
+ def test_is_gpiochip_good_keyword_argument(self):
+ sim = gpiosim.Chip()
+ self.assertTrue(gpiod.is_gpiochip_device(path=sim.dev_path))
+
+ def test_is_gpiochip_link_good(self):
+ link = "/tmp/gpiod-py-test-link.{}".format(os.getpid())
+ sim = gpiosim.Chip()
+
+ with LinkGuard(sim.dev_path, link):
+ self.assertTrue(gpiod.is_gpiochip_device(link))
+
+ def test_is_gpiochip_link_bad(self):
+ link = "/tmp/gpiod-py-test-link.{}".format(os.getpid())
+
+ with LinkGuard("/dev/null", link):
+ self.assertFalse(gpiod.is_gpiochip_device(link))
+
+
+class VersionString(TestCase):
+ def test_version_string(self):
+ self.assertTrue(
+ re.match(
+ "^[0-9][1-9]?\\.[0-9][1-9]?([\\.0-9]?|\\-devel)$", gpiod.__version__
+ )
+ )
AC_SUBST(ABI_VERSION, [4.1.2])
# Have a separate ABI version for C++ bindings:
AC_SUBST(ABI_CXX_VERSION, [2.1.1])
-# ABI version for libgpiomockup (we need this since it can be installed if we
+# ABI version for libgpiosim (we need this since it can be installed if we
# enable install-tests).
-AC_SUBST(ABI_MOCKUP_VERSION, [0.1.0])
AC_SUBST(ABI_GPIOSIM_VERSION, [0.1.0])
AC_CONFIG_AUX_DIR([autostuff])
[Libraries to link into Python extensions [default: auto-detect]])
AC_CONFIG_SRCDIR([lib])
-AC_CONFIG_HEADER([config.h])
+AC_CONFIG_HEADERS([config.h])
AC_DEFINE([_GNU_SOURCE], [], [We want GNU extensions])
AC_PROG_CC
AC_PROG_CXX
AC_PROG_INSTALL
+AC_PROG_EGREP
LT_INIT
[with_tests=false])
AM_CONDITIONAL([WITH_TESTS], [test "x$with_tests" = xtrue])
+AC_ARG_ENABLE([profiling],
+ [AS_HELP_STRING([--enable-profiling],
+ [enable gcov profiling on the core library and tests [default=no]])],
+ [if test "x$enableval" = xyes; then with_profiling=true; fi],
+ [with_profiling=false])
+if test "x$with_profiling" = xtrue
+then
+ AC_SUBST(PROFILING_CFLAGS, ["-fprofile-arcs -ftest-coverage"])
+ AC_SUBST(PROFILING_LDFLAGS, ["-lgcov"])
+fi
+
AC_DEFUN([FUNC_NOT_FOUND_TESTS],
[ERR_NOT_FOUND([$1()], [tests])])
if test "x$with_tests" = xtrue
then
- # For libgpiomockup & libgpiosim
+ # For libgpiosim
AC_CHECK_FUNC([qsort], [], [FUNC_NOT_FOUND_TESTS([qsort])])
PKG_CHECK_MODULES([KMOD], [libkmod >= 18])
- PKG_CHECK_MODULES([UDEV], [libudev >= 215])
PKG_CHECK_MODULES([MOUNT], [mount >= 2.33.1])
# For core library tests
PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.50])
+ PKG_CHECK_MODULES([GOBJECT], [gobject-2.0 >= 2.50])
if test "x$with_tools" = xtrue
then
if test "x$with_bindings_cxx" = xtrue
then
- AC_LIBTOOL_CXX
+ LT_LANG([C++])
# This needs autoconf-archive
AX_CXX_COMPILE_STDCXX_11([ext], [mandatory])
lib/libgpiod.pc
tools/Makefile
tests/Makefile
- tests/mockup/Makefile
tests/gpiosim/Makefile
bindings/cxx/libgpiodcxx.pc
bindings/Makefile
bindings/cxx/Makefile
+ bindings/cxx/gpiodcxx/Makefile
bindings/cxx/examples/Makefile
bindings/cxx/tests/Makefile
bindings/python/Makefile
+ bindings/python/gpiod/Makefile
+ bindings/python/gpiod/ext/Makefile
bindings/python/examples/Makefile
bindings/python/tests/Makefile
+ bindings/python/tests/gpiosim/Makefile
man/Makefile])
AC_OUTPUT
/* SPDX-License-Identifier: LGPL-2.1-or-later */
-/* SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com> */
+/* SPDX-FileCopyrightText: 2017-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+/**
+ * @file gpiod.h
+ */
#ifndef __LIBGPIOD_GPIOD_H__
#define __LIBGPIOD_GPIOD_H__
#include <stdbool.h>
-#include <stdlib.h>
-#include <time.h>
+#include <stddef.h>
+#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
-/**
- * @file gpiod.h
- */
-
/**
* @mainpage libgpiod public API
*
* <p>The API is logically split into several parts such as: GPIO chip & line
* operators, GPIO events handling etc.
*
- * <p>General note on error handling: all routines exported by libgpiod set
- * errno to one of the error values defined in errno.h upon failure. The way
- * of notifying the caller that an error occurred varies between functions,
- * but in general a function that returns an int, returns -1 on error, while
- * a function returning a pointer bails out on error condition by returning
- * a NULL pointer.
+ * <p>General note on error handling: all functions exported by libgpiod that
+ * can fail, set errno to one of the error values defined in errno.h upon
+ * failure. The way of notifying the caller that an error occurred varies
+ * between functions, but in general a function that returns an int, returns -1
+ * on error, while a function returning a pointer indicates an error condition
+ * by returning a NULL pointer. It's not practical to list all possible error
+ * codes for every function as they propagate errors from the underlying libc
+ * functions.
+ *
+ * <p>In general libgpiod functions are not NULL-aware and it's expected that
+ * users pass valid pointers to objects as arguments. An exception to this rule
+ * are the functions that free/close/release resources - which work when passed
+ * a NULL-pointer as argument. Other exceptions are documented.
*/
struct gpiod_chip;
-struct gpiod_line;
-struct gpiod_line_bulk;
-
-/**
- * @defgroup common Common helper macros
+struct gpiod_chip_info;
+struct gpiod_line_info;
+struct gpiod_line_settings;
+struct gpiod_line_config;
+struct gpiod_request_config;
+struct gpiod_line_request;
+struct gpiod_info_event;
+struct gpiod_edge_event;
+struct gpiod_edge_event_buffer;
+
+/**
+ * @defgroup chips GPIO chips
* @{
*
- * Commonly used utility macros.
- */
-
-/**
- * @brief Shift 1 by given offset.
- * @param nr Bit position.
- * @return 1 shifted by nr.
+ * Functions and data structures for GPIO chip operations.
+ *
+ * A GPIO chip object is associated with an open file descriptor to the GPIO
+ * character device. It exposes basic information about the chip and allows
+ * callers to retrieve information about each line, watch lines for state
+ * changes and make line requests.
*/
-#define GPIOD_BIT(nr) (1UL << (nr))
/**
- * @}
- *
- * @defgroup chips GPIO chip operations
- * @{
- *
- * Functions and data structures dealing with GPIO chips.
+ * @brief Open a chip by path.
+ * @param path Path to the gpiochip device file.
+ * @return GPIO chip object or NULL if an error occurred. The returned object
+ * must be closed by the caller using ::gpiod_chip_close.
*/
+struct gpiod_chip *gpiod_chip_open(const char *path);
/**
- * @brief Check if the file pointed to by path is a GPIO chip character device.
- * @param path Path to check.
- * @return True if the file exists and is a GPIO chip character device or a
- * symbolic link to it.
+ * @brief Close the chip and release all associated resources.
+ * @param chip Chip to close.
*/
-bool gpiod_is_gpiochip_device(const char *path);
+void gpiod_chip_close(struct gpiod_chip *chip);
/**
- * @brief Open a gpiochip by path.
- * @param path Path to the gpiochip device file.
- * @return GPIO chip handle or NULL if an error occurred.
+ * @brief Get information about the chip.
+ * @param chip GPIO chip object.
+ * @return New GPIO chip info object or NULL if an error occurred. The returned
+ * object must be freed by the caller using ::gpiod_chip_info_free.
*/
-struct gpiod_chip *gpiod_chip_open(const char *path);
+struct gpiod_chip_info *gpiod_chip_get_info(struct gpiod_chip *chip);
/**
- * @brief Increase the refcount on this GPIO object.
- * @param chip The GPIO chip object.
- * @return Passed reference to the GPIO chip.
+ * @brief Get the path used to open the chip.
+ * @param chip GPIO chip object.
+ * @return Path to the file passed as argument to ::gpiod_chip_open. The
+ * returned pointer is valid for the lifetime of the chip object and
+ * must not be freed by the caller.
*/
-struct gpiod_chip *gpiod_chip_ref(struct gpiod_chip *chip);
+const char *gpiod_chip_get_path(struct gpiod_chip *chip);
/**
- * @brief Decrease the refcount on this GPIO object. If the refcount reaches 0,
- * close the chip device and free all associated resources.
- * @param chip The GPIO chip object.
+ * @brief Get a snapshot of information about a line.
+ * @param chip GPIO chip object.
+ * @param offset The offset of the GPIO line.
+ * @return New GPIO line info object or NULL if an error occurred. The returned
+ * object must be freed by the caller using ::gpiod_line_info_free.
*/
-void gpiod_chip_unref(struct gpiod_chip *chip);
+struct gpiod_line_info *gpiod_chip_get_line_info(struct gpiod_chip *chip,
+ unsigned int offset);
/**
- * @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.
+ * @brief Get a snapshot of the status of a line and start watching it for
+ * future changes.
+ * @param chip GPIO chip object.
+ * @param offset The offset of the GPIO line.
+ * @return New GPIO line info object or NULL if an error occurred. The returned
+ * object must be freed by the caller using ::gpiod_line_info_free.
+ * @note Line status does not include the line value. To monitor the line
+ * value the line must be requested as an input with edge detection set.
*/
-const char *gpiod_chip_get_name(struct gpiod_chip *chip);
+struct gpiod_line_info *gpiod_chip_watch_line_info(struct gpiod_chip *chip,
+ unsigned int offset);
/**
- * @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.
+ * @brief Stop watching a line for status changes.
+ * @param chip GPIO chip object.
+ * @param offset The offset of the line to stop watching.
+ * @return 0 on success, -1 on failure.
*/
-const char *gpiod_chip_get_label(struct gpiod_chip *chip);
+int gpiod_chip_unwatch_line_info(struct gpiod_chip *chip, unsigned int offset);
/**
- * @brief Get the number of GPIO lines exposed by this chip.
- * @param chip The GPIO chip object.
- * @return Number of GPIO lines.
+ * @brief Get the file descriptor associated with the chip.
+ * @param chip GPIO chip object.
+ * @return File descriptor number for the chip.
+ * This function never fails.
+ * The returned file descriptor must not be closed by the caller.
+ * Call ::gpiod_chip_close to close the file descriptor.
*/
-unsigned int gpiod_chip_get_num_lines(struct gpiod_chip *chip);
+int gpiod_chip_get_fd(struct gpiod_chip *chip);
/**
- * @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.
+ * @brief Wait for line status change events on any of the watched lines
+ * on the chip.
+ * @param chip GPIO chip object.
+ * @param timeout_ns Wait time limit in nanoseconds. If set to 0, the function
+ * returns immediatelly. If set to a negative number, the
+ * function blocks indefinitely until an event becomes
+ * available.
+ * @return 0 if wait timed out, -1 if an error occurred, 1 if an event is
+ * pending.
*/
-struct gpiod_line *
-gpiod_chip_get_line(struct gpiod_chip *chip, unsigned int offset);
+int gpiod_chip_wait_info_event(struct gpiod_chip *chip, int64_t timeout_ns);
/**
- * @brief Retrieve a set of lines and store them in a line bulk object.
- * @param chip The GPIO chip object.
- * @param offsets Array of offsets of lines to retrieve.
- * @param num_offsets Number of lines to retrieve.
- * @return New line bulk object or NULL on error.
+ * @brief Read a single line status change event from the chip.
+ * @param chip GPIO chip object.
+ * @return Newly read watch event object or NULL on error. The event must be
+ * freed by the caller using ::gpiod_info_event_free.
+ * @note If no events are pending, this function will block.
*/
-struct gpiod_line_bulk *
-gpiod_chip_get_lines(struct gpiod_chip *chip, unsigned int *offsets,
- unsigned int num_offsets);
+struct gpiod_info_event *gpiod_chip_read_info_event(struct gpiod_chip *chip);
/**
- * @brief Retrieve all lines exposed by a chip and store them in a bulk object.
- * @param chip The GPIO chip object.
- * @return New line bulk object or NULL on error.
+ * @brief Map a line's name to its offset within the chip.
+ * @param chip GPIO chip object.
+ * @param name Name of the GPIO line to map.
+ * @return Offset of the line within the chip or -1 on error.
+ * @note If a line with given name is not exposed by the chip, the function
+ * sets errno to ENOENT.
*/
-struct gpiod_line_bulk *
-gpiod_chip_get_all_lines(struct gpiod_chip *chip);
+int gpiod_chip_get_line_offset_from_name(struct gpiod_chip *chip,
+ const char *name);
/**
- * @brief Map a GPIO line's name to its offset within the chip.
- * @param chip The GPIO chip object.
- * @param name Name of the GPIO line to map.
- * @return Offset of the line within the chip or -1 if a line with given name
- * is not exposed by the chip.
+ * @brief Request a set of lines for exclusive usage.
+ * @param chip GPIO chip object.
+ * @param req_cfg Request config object. Can be NULL for default settings.
+ * @param line_cfg Line config object.
+ * @return New line request object or NULL if an error occurred. The request
+ * must be released by the caller using ::gpiod_line_request_release.
*/
-int gpiod_chip_find_line(struct gpiod_chip *chip, const char *name);
+struct gpiod_line_request *
+gpiod_chip_request_lines(struct gpiod_chip *chip,
+ struct gpiod_request_config *req_cfg,
+ struct gpiod_line_config *line_cfg);
/**
* @}
*
- * @defgroup lines GPIO line operations
+ * @defgroup chip_info Chip info
* @{
*
- * Functions and data structures dealing with GPIO lines.
+ * Functions for retrieving kernel information about chips.
*
- * @defgroup line_bulk Operating on multiple lines
- * @{
+ * Line info object contains an immutable snapshot of a chip's status.
*
- * Convenience data structures and helper functions for storing and operating
- * on multiple lines at once.
+ * The chip info contains all the publicly available information about a
+ * chip.
+ *
+ * Some accessor methods return pointers. Those pointers refer to internal
+ * fields. The lifetimes of those fields are tied to the lifetime of the
+ * containing chip info object.
+ * Such pointers remain valid until ::gpiod_chip_info_free is called on the
+ * containing chip info object. They must not be freed by the caller.
*/
/**
- * @brief Allocate and initialize a new line bulk object.
- * @param max_lines Maximum number of lines this object can hold.
- * @return New line bulk object or NULL on error.
+ * @brief Free a chip info object and release all associated resources.
+ * @param info GPIO chip info object to free.
*/
-struct gpiod_line_bulk *gpiod_line_bulk_new(unsigned int max_lines);
+void gpiod_chip_info_free(struct gpiod_chip_info *info);
/**
- * @brief Reset a bulk object. Remove all lines and set size to 0.
- * @param bulk Bulk object to reset.
+ * @brief Get the name of the chip as represented in the kernel.
+ * @param info GPIO chip info object.
+ * @return Valid pointer to a human-readable string containing the chip name.
+ * The string lifetime is tied to the chip info object so the pointer
+ * must not be freed by the caller.
*/
-void gpiod_line_bulk_reset(struct gpiod_line_bulk *bulk);
+const char *gpiod_chip_info_get_name(struct gpiod_chip_info *info);
/**
- * @brief Release all resources allocated for this bulk object.
- * @param bulk Bulk object to free.
+ * @brief Get the label of the chip as represented in the kernel.
+ * @param info GPIO chip info object.
+ * @return Valid pointer to a human-readable string containing the chip label.
+ * The string lifetime is tied to the chip info object so the pointer
+ * must not be freed by the caller.
*/
-void gpiod_line_bulk_free(struct gpiod_line_bulk *bulk);
+const char *gpiod_chip_info_get_label(struct gpiod_chip_info *info);
/**
- * @brief Add a single line to a GPIO bulk object.
- * @param bulk Line bulk object.
- * @param line Line to add.
- * @return 0 on success, -1 on error.
- * @note The line is added at the next free bulk index.
+ * @brief Get the number of lines exposed by the chip.
+ * @param info GPIO chip info object.
+ * @return Number of GPIO lines.
+ */
+size_t gpiod_chip_info_get_num_lines(struct gpiod_chip_info *info);
+
+/**
+ * @}
+ *
+ * @defgroup line_defs Line definitions
+ * @{
*
- * The function can fail if this bulk already holds its maximum amount of
- * lines or if the added line is associated with a different chip than all
- * the other lines already held by this object.
+ * These defines are used across the API.
*/
-int gpiod_line_bulk_add_line(struct gpiod_line_bulk *bulk,
- struct gpiod_line *line);
/**
- * @brief Retrieve the line handle from a line bulk object at given index.
- * @param bulk Line bulk object.
- * @param index Index of the line to retrieve.
- * @return Line handle at given index or NULL if index is greater or equal to
- * the number of lines this bulk can hold.
+ * @brief Logical line state.
*/
-struct gpiod_line *
-gpiod_line_bulk_get_line(struct gpiod_line_bulk *bulk, unsigned int index);
+enum {
+ GPIOD_LINE_VALUE_INACTIVE = 0,
+ /**< Line is logically inactive. */
+ GPIOD_LINE_VALUE_ACTIVE = 1,
+ /**< Line is logically active. */
+};
/**
- * @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.
+ * @brief Direction settings.
*/
-unsigned int gpiod_line_bulk_num_lines(struct gpiod_line_bulk *bulk);
+enum {
+ GPIOD_LINE_DIRECTION_AS_IS = 1,
+ /**< Request the line(s), but don't change direction. */
+ GPIOD_LINE_DIRECTION_INPUT,
+ /**< Direction is input - for reading the value of an externally driven GPIO line. */
+ GPIOD_LINE_DIRECTION_OUTPUT
+ /**< Direction is output - for driving the GPIO line. */
+};
/**
- * @brief Values returned by the callback passed to
- * ::gpiod_line_bulk_foreach_line.
+ * @brief Edge detection settings.
*/
enum {
- /**< Continue the loop. */
- GPIOD_LINE_BULK_CB_NEXT = 0,
- /**< Stop the loop. */
- GPIOD_LINE_BULK_CB_STOP,
+ GPIOD_LINE_EDGE_NONE = 1,
+ /**< Line edge detection is disabled. */
+ GPIOD_LINE_EDGE_RISING,
+ /**< Line detects rising edge events. */
+ GPIOD_LINE_EDGE_FALLING,
+ /**< Line detects falling edge events. */
+ GPIOD_LINE_EDGE_BOTH
+ /**< Line detects both rising and falling edge events. */
};
/**
- * @brief Signature of the callback passed to ::gpiod_line_bulk_foreach_line.
- *
- * Takes the current line and additional user data as arguments.
+ * @brief Internal bias settings.
+ */
+enum {
+ GPIOD_LINE_BIAS_AS_IS = 1,
+ /**< Don't change the bias setting when applying line config. */
+ GPIOD_LINE_BIAS_UNKNOWN,
+ /**< The internal bias state is unknown. */
+ GPIOD_LINE_BIAS_DISABLED,
+ /**< The internal bias is disabled. */
+ GPIOD_LINE_BIAS_PULL_UP,
+ /**< The internal pull-up bias is enabled. */
+ GPIOD_LINE_BIAS_PULL_DOWN
+ /**< The internal pull-down bias is enabled. */
+};
+
+/**
+ * @brief Drive settings.
*/
-typedef int (*gpiod_line_bulk_foreach_cb)(struct gpiod_line *, void *);
+enum {
+ GPIOD_LINE_DRIVE_PUSH_PULL = 1,
+ /**< Drive setting is push-pull. */
+ GPIOD_LINE_DRIVE_OPEN_DRAIN,
+ /**< Line output is open-drain. */
+ GPIOD_LINE_DRIVE_OPEN_SOURCE
+ /**< Line output is open-source. */
+};
/**
- * @brief Iterate over all lines held by this bulk object.
- * @param bulk Bulk object to iterate over.
- * @param func Callback to be called for each line.
- * @param data User data pointer that is passed to the callback.
+ * @brief Event clock settings.
*/
-void gpiod_line_bulk_foreach_line(struct gpiod_line_bulk *bulk,
- gpiod_line_bulk_foreach_cb func,
- void *data);
+enum {
+ GPIOD_LINE_EVENT_CLOCK_MONOTONIC = 1,
+ /**< Line uses the monotonic clock for edge event timestamps. */
+ GPIOD_LINE_EVENT_CLOCK_REALTIME,
+ /**< Line uses the realtime clock for edge event timestamps. */
+ GPIOD_LINE_EVENT_CLOCK_HTE,
+ /**< Line uses the hardware timestamp engine for event timestamps. */
+};
/**
* @}
* @defgroup line_info Line info
* @{
*
- * Definitions and functions for retrieving kernel information about both
- * requested and free lines.
+ * Functions for retrieving kernel information about both requested and free
+ * lines.
+ *
+ * Line info object contains an immutable snapshot of a line's status.
+ *
+ * The line info contains all the publicly available information about a
+ * line, which does not include the line value. The line must be requested
+ * to access the line value.
+ *
+ * Some accessor methods return pointers. Those pointers refer to internal
+ * fields. The lifetimes of those fields are tied to the lifetime of the
+ * containing line info object.
+ * Such pointers remain valid until ::gpiod_line_info_free is called on the
+ * containing line info object. They must not be freed by the caller.
*/
/**
- * @brief Possible direction settings.
+ * @brief Free a line info object and release all associated resources.
+ * @param info GPIO line info object to free.
*/
-enum {
- GPIOD_LINE_DIRECTION_INPUT = 1,
- /**< Direction is input - we're reading the state of a GPIO line. */
- GPIOD_LINE_DIRECTION_OUTPUT,
- /**< Direction is output - we're driving the GPIO line. */
-};
+void gpiod_line_info_free(struct gpiod_line_info *info);
/**
- * @brief Possible drive settings.
+ * @brief Copy a line info object.
+ * @param info Line info to copy.
+ * @return Copy of the line info or NULL on error. The returned object must
+ * be freed by the caller using :gpiod_line_info_free.
*/
-enum {
- GPIOD_LINE_DRIVE_PUSH_PULL = 1,
- /**< Drive setting is push-pull. */
- GPIOD_LINE_DRIVE_OPEN_DRAIN,
- /**< Line output is open-drain. */
- GPIOD_LINE_DRIVE_OPEN_SOURCE,
- /**< Line output is open-source. */
-};
+struct gpiod_line_info *gpiod_line_info_copy(struct gpiod_line_info *info);
/**
- * @brief Possible internal bias settings.
+ * @brief Get the offset of the line.
+ * @param info GPIO line info object.
+ * @return Offset of the line within the parent chip.
+ *
+ * The offset uniquely identifies the line on the chip.
+ * The combination of the chip and offset uniquely identifies the line within
+ * the system.
*/
-enum {
- GPIOD_LINE_BIAS_UNKNOWN = 1,
- /**< The internal bias state is unknown. */
- GPIOD_LINE_BIAS_DISABLED,
- /**< The internal bias is disabled. */
- GPIOD_LINE_BIAS_PULL_UP,
- /**< The internal pull-up bias is enabled. */
- GPIOD_LINE_BIAS_PULL_DOWN,
- /**< The internal pull-down bias is enabled. */
-};
+unsigned int gpiod_line_info_get_offset(struct gpiod_line_info *info);
/**
- * @brief Read the GPIO line offset.
- * @param line GPIO line object.
- * @return Line offset.
+ * @brief Get the name of the line.
+ * @param info GPIO line info object.
+ * @return Name of the GPIO line as it is represented in the kernel.
+ * This function returns a valid pointer to a null-terminated string
+ * or NULL if the line is unnamed.
+ * The string lifetime is tied to the line info object so the pointer
+ * must not be freed.
+ */
+const char *gpiod_line_info_get_name(struct gpiod_line_info *info);
+
+/**
+ * @brief Check if the line is in use.
+ * @param info GPIO line object.
+ * @return True if the line is in use, false otherwise.
+ *
+ * The exact reason a line is busy cannot be determined from user space.
+ * It may have been requested by another process or hogged by the kernel.
+ * It only matters that the line is used and can't be requested until
+ * released by the existing consumer.
*/
-unsigned int gpiod_line_offset(struct gpiod_line *line);
+bool gpiod_line_info_is_used(struct gpiod_line_info *info);
/**
- * @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.
+ * @brief Get the name of the consumer of the line.
+ * @param info GPIO line info object.
+ * @return Name of the GPIO consumer as it is represented in the kernel.
+ * This function returns a valid pointer to a null-terminated string
+ * or NULL if the consumer name is not set.
+ * The string lifetime is tied to the line info object so the pointer
+ * must not be freed.
*/
-const char *gpiod_line_name(struct gpiod_line *line);
+const char *gpiod_line_info_get_consumer(struct gpiod_line_info *info);
/**
- * @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.
+ * @brief Get the direction setting of the line.
+ * @param info GPIO line info object.
+ * @return Returns ::GPIOD_LINE_DIRECTION_INPUT or
+ * ::GPIOD_LINE_DIRECTION_OUTPUT.
*/
-const char *gpiod_line_consumer(struct gpiod_line *line);
+int gpiod_line_info_get_direction(struct gpiod_line_info *info);
/**
- * @brief Read the GPIO line direction setting.
- * @param line GPIO line object.
- * @return Returns GPIOD_LINE_DIRECTION_INPUT or GPIOD_LINE_DIRECTION_OUTPUT.
+ * @brief Get the edge detection setting of the line.
+ * @param info GPIO line info object.
+ * @return Returns ::GPIOD_LINE_EDGE_NONE, ::GPIOD_LINE_EDGE_RISING,
+ * ::GPIOD_LINE_EDGE_FALLING or ::GPIOD_LINE_EDGE_BOTH.
*/
-int gpiod_line_direction(struct gpiod_line *line);
+int gpiod_line_info_get_edge_detection(struct gpiod_line_info *info);
/**
- * @brief Check if the signal of this line is inverted.
- * @param line GPIO line object.
- * @return True if this line is "active-low", false otherwise.
+ * @brief Get the bias setting of the line.
+ * @param info GPIO line object.
+ * @return Returns ::GPIOD_LINE_BIAS_PULL_UP, ::GPIOD_LINE_BIAS_PULL_DOWN,
+ * ::GPIOD_LINE_BIAS_DISABLED or ::GPIOD_LINE_BIAS_UNKNOWN.
*/
-bool gpiod_line_is_active_low(struct gpiod_line *line);
+int gpiod_line_info_get_bias(struct gpiod_line_info *info);
/**
- * @brief Read the GPIO line bias setting.
- * @param line GPIO line object.
- * @return Returns GPIOD_LINE_BIAS_PULL_UP, GPIOD_LINE_BIAS_PULL_DOWN,
- * GPIOD_LINE_BIAS_DISABLE or GPIOD_LINE_BIAS_UNKNOWN.
+ * @brief Get the drive setting of the line.
+ * @param info GPIO line info object.
+ * @return Returns ::GPIOD_LINE_DRIVE_PUSH_PULL, ::GPIOD_LINE_DRIVE_OPEN_DRAIN
+ * or ::GPIOD_LINE_DRIVE_OPEN_SOURCE.
*/
-int gpiod_line_bias(struct gpiod_line *line);
+int gpiod_line_info_get_drive(struct gpiod_line_info *info);
/**
- * @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.
+ * @brief Check if the logical value of the line is inverted compared to the
+ * physical.
+ * @param info GPIO line object.
+ * @return True if the line is "active-low", false otherwise.
*/
-bool gpiod_line_is_used(struct gpiod_line *line);
+bool gpiod_line_info_is_active_low(struct gpiod_line_info *info);
/**
- * @brief Read the GPIO line drive setting.
- * @param line GPIO line object.
- * @return Returns GPIOD_LINE_DRIVE_PUSH_PULL, GPIOD_LINE_DRIVE_OPEN_DRAIN or
- * GPIOD_LINE_DRIVE_OPEN_SOURCE.
+ * @brief Check if the line is debounced (either by hardware or by the kernel
+ * software debouncer).
+ * @param info GPIO line info object.
+ * @return True if the line is debounced, false otherwise.
*/
-int gpiod_line_drive(struct gpiod_line *line);
+bool gpiod_line_info_is_debounced(struct gpiod_line_info *info);
/**
- * @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.
+ * @brief Get the debounce period of the line, in microseconds.
+ * @param info GPIO line info object.
+ * @return Debounce period in microseconds.
+ * 0 if the line is not debounced.
*/
-struct gpiod_chip *gpiod_line_get_chip(struct gpiod_line *line);
+unsigned long
+gpiod_line_info_get_debounce_period_us(struct gpiod_line_info *info);
+
+/**
+ * @brief Get the event clock setting used for edge event timestamps for the
+ * line.
+ * @param info GPIO line info object.
+ * @return Returns ::GPIOD_LINE_EVENT_CLOCK_MONOTONIC or
+ * ::GPIOD_LINE_EVENT_CLOCK_REALTIME.
+ */
+int gpiod_line_info_get_event_clock(struct gpiod_line_info *info);
/**
* @}
*
- * @defgroup line_request Line requests
+ * @defgroup line_watch Line status watch events
* @{
*
- * Interface for requesting GPIO lines from userspace for both values and
- * events.
+ * Accessors for the info event objects allowing to monitor changes in GPIO
+ * line status.
+ *
+ * Callers are notified about changes in a line's status due to GPIO uAPI
+ * calls. Each info event contains information about the event itself
+ * (timestamp, type) as well as a snapshot of line's status in the form
+ * of a line-info object.
*/
/**
- * @brief Available types of requests.
+ * @brief Line status change event types.
*/
enum {
- GPIOD_LINE_REQUEST_DIRECTION_AS_IS = 1,
- /**< Request the line(s), but don't change current direction. */
- GPIOD_LINE_REQUEST_DIRECTION_INPUT,
- /**< Request the line(s) for reading the GPIO line state. */
- GPIOD_LINE_REQUEST_DIRECTION_OUTPUT,
- /**< Request the line(s) for setting the GPIO line state. */
- GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE,
- /**< Only watch falling edge events. */
- GPIOD_LINE_REQUEST_EVENT_RISING_EDGE,
- /**< Only watch rising edge events. */
- GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES,
- /**< Monitor both types of events. */
+ GPIOD_INFO_EVENT_LINE_REQUESTED = 1,
+ /**< Line has been requested. */
+ GPIOD_INFO_EVENT_LINE_RELEASED,
+ /**< Previously requested line has been released. */
+ GPIOD_INFO_EVENT_LINE_CONFIG_CHANGED
+ /**< Line configuration has changed. */
};
/**
- * @brief Miscellaneous GPIO request flags.
+ * @brief Free the info event object and release all associated resources.
+ * @param event Info event to free.
*/
-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). */
- GPIOD_LINE_REQUEST_FLAG_BIAS_DISABLED = GPIOD_BIT(3),
- /**< The line has neither either pull-up nor pull-down resistor. */
- GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN = GPIOD_BIT(4),
- /**< The line has pull-down resistor enabled. */
- GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP = GPIOD_BIT(5),
- /**< The line has pull-up resistor enabled. */
-};
+void gpiod_info_event_free(struct gpiod_info_event *event);
/**
- * @brief Structure holding configuration of a line request.
+ * @brief Get the event type of the status change event.
+ * @param event Line status watch event.
+ * @return One of ::GPIOD_INFO_EVENT_LINE_REQUESTED,
+ * ::GPIOD_INFO_EVENT_LINE_RELEASED or
+ * ::GPIOD_INFO_EVENT_LINE_CONFIG_CHANGED.
*/
-struct gpiod_line_request_config {
- const char *consumer;
- /**< Name of the consumer. */
- int request_type;
- /**< Request type. */
- int flags;
- /**< Other configuration flags. */
-};
+int gpiod_info_event_get_event_type(struct gpiod_info_event *event);
+
+/**
+ * @brief Get the timestamp of the event.
+ * @param event Line status watch event.
+ * @return Timestamp in nanoseconds, read from the monotonic clock.
+ */
+uint64_t gpiod_info_event_get_timestamp_ns(struct gpiod_info_event *event);
+
+/**
+ * @brief Get the snapshot of line-info associated with the event.
+ * @param event Line info event object.
+ * @return Returns a pointer to the line-info object associated with the event.
+ * The object lifetime is tied to the event object, so the pointer must
+ * be not be freed by the caller.
+ */
+struct gpiod_line_info *
+gpiod_info_event_get_line_info(struct gpiod_info_event *event);
/**
- * @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.
+ * @}
+ *
+ * @defgroup line_settings Line settings objects
+ * @{
+ *
+ * Functions for manipulating line settings objects.
+ *
+ * Line settings object contains a set of line properties that can be used
+ * when requesting lines or reconfiguring an existing request.
*
- * If this routine succeeds, the caller takes ownership of the GPIO line until
- * it's released.
+ * Mutators in general can only fail if the new property value is invalid. The
+ * return values can be safely ignored - the object remains valid even after
+ * a mutator fails and simply uses the sane default appropriate for given
+ * property.
*/
-int gpiod_line_request(struct gpiod_line *line,
- const struct gpiod_line_request_config *config,
- int default_val);
/**
- * @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.
+ * @brief Create a new line settings object.
+ * @return New line settings object or NULL on error. The returned object must
+ * be freed by the caller using ::gpiod_line_settings_free.
*/
-int gpiod_line_request_input(struct gpiod_line *line, const char *consumer);
+struct gpiod_line_settings *gpiod_line_settings_new(void);
/**
- * @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.
+ * @brief Free the line settings object and release all associated resources.
+ * @param settings Line settings object.
*/
-int gpiod_line_request_output(struct gpiod_line *line,
- const char *consumer, int default_val);
+void gpiod_line_settings_free(struct gpiod_line_settings *settings);
/**
- * @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.
+ * @brief Reset the line settings object to its default values.
+ * @param settings Line settings object.
*/
-int gpiod_line_request_rising_edge_events(struct gpiod_line *line,
- const char *consumer);
+void gpiod_line_settings_reset(struct gpiod_line_settings *settings);
/**
- * @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.
+ * @brief Copy the line settings object.
+ * @param settings Line settings object to copy.
+ * @return New line settings object that must be freed using
+ * ::gpiod_line_settings_free or NULL on failure.
*/
-int gpiod_line_request_falling_edge_events(struct gpiod_line *line,
- const char *consumer);
+struct gpiod_line_settings *
+gpiod_line_settings_copy(struct gpiod_line_settings *settings);
/**
- * @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.
+ * @brief Set direction.
+ * @param settings Line settings object.
+ * @param direction New direction.
+ * @return 0 on success, -1 on error.
*/
-int gpiod_line_request_both_edges_events(struct gpiod_line *line,
- const char *consumer);
+int gpiod_line_settings_set_direction(struct gpiod_line_settings *settings,
+ int direction);
/**
- * @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.
+ * @brief Get direction.
+ * @param settings Line settings object.
+ * @return Current direction.
*/
-int gpiod_line_request_input_flags(struct gpiod_line *line,
- const char *consumer, int flags);
+int gpiod_line_settings_get_direction(struct gpiod_line_settings *settings);
/**
- * @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.
+ * @brief Set edge detection.
+ * @param settings Line settings object.
+ * @param edge New edge detection setting.
+ * @return 0 on success, -1 on failure.
*/
-int gpiod_line_request_output_flags(struct gpiod_line *line,
- const char *consumer, int flags,
- int default_val);
+int gpiod_line_settings_set_edge_detection(struct gpiod_line_settings *settings,
+ int edge);
/**
- * @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.
+ * @brief Get edge detection.
+ * @param settings Line settings object.
+ * @return Current edge detection setting.
*/
-int gpiod_line_request_rising_edge_events_flags(struct gpiod_line *line,
- const char *consumer,
- int flags);
+int
+gpiod_line_settings_get_edge_detection(struct gpiod_line_settings *settings);
/**
- * @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.
+ * @brief Set bias.
+ * @param settings Line settings object.
+ * @param bias New bias.
+ * @return 0 on success, -1 on failure.
*/
-int gpiod_line_request_falling_edge_events_flags(struct gpiod_line *line,
- const char *consumer,
- int flags);
+int gpiod_line_settings_set_bias(struct gpiod_line_settings *settings,
+ int bias);
/**
- * @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.
+ * @brief Get bias.
+ * @param settings Line settings object.
+ * @return Current bias setting.
*/
-int gpiod_line_request_both_edges_events_flags(struct gpiod_line *line,
- const char *consumer,
- int flags);
+int gpiod_line_settings_get_bias(struct gpiod_line_settings *settings);
/**
- * @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 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 provided by the
- * same gpiochip.
+ * @brief Set drive.
+ * @param settings Line settings object.
+ * @param drive New drive setting.
+ * @return 0 on success, -1 on failure.
*/
-int gpiod_line_request_bulk(struct gpiod_line_bulk *bulk,
- const struct gpiod_line_request_config *config,
- const int *default_vals);
+int gpiod_line_settings_set_drive(struct gpiod_line_settings *settings,
+ int drive);
/**
- * @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.
+ * @brief Get drive.
+ * @param settings Line settings object.
+ * @return Current drive setting.
*/
-int gpiod_line_request_bulk_input(struct gpiod_line_bulk *bulk,
- const char *consumer);
+int gpiod_line_settings_get_drive(struct gpiod_line_settings *settings);
/**
- * @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.
+ * @brief Set active-low setting.
+ * @param settings Line settings object.
+ * @param active_low New active-low setting.
*/
-int gpiod_line_request_bulk_output(struct gpiod_line_bulk *bulk,
- const char *consumer,
- const int *default_vals);
+void gpiod_line_settings_set_active_low(struct gpiod_line_settings *settings,
+ bool active_low);
/**
- * @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.
+ * @brief Get active-low setting.
+ * @param settings Line settings object.
+ * @return True if active-low is enabled, false otherwise.
*/
-int gpiod_line_request_bulk_rising_edge_events(struct gpiod_line_bulk *bulk,
- const char *consumer);
+bool gpiod_line_settings_get_active_low(struct gpiod_line_settings *settings);
/**
- * @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.
+ * @brief Set debounce period.
+ * @param settings Line settings object.
+ * @param period New debounce period in microseconds.
*/
-int gpiod_line_request_bulk_falling_edge_events(struct gpiod_line_bulk *bulk,
- const char *consumer);
+void
+gpiod_line_settings_set_debounce_period_us(struct gpiod_line_settings *settings,
+ unsigned long period);
/**
- * @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.
+ * @brief Get debounce period.
+ * @param settings Line settings object.
+ * @return Current debounce period in microseconds.
*/
-int gpiod_line_request_bulk_both_edges_events(struct gpiod_line_bulk *bulk,
- const char *consumer);
+unsigned long
+gpiod_line_settings_get_debounce_period_us(
+ struct gpiod_line_settings *settings);
/**
- * @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.
+ * @brief Set event clock.
+ * @param settings Line settings object.
+ * @param event_clock New event clock.
+ * @return 0 on success, -1 on failure.
*/
-int gpiod_line_request_bulk_input_flags(struct gpiod_line_bulk *bulk,
- const char *consumer, int flags);
+int gpiod_line_settings_set_event_clock(struct gpiod_line_settings *settings,
+ int event_clock);
/**
- * @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.
+ * @brief Get event clock setting.
+ * @param settings Line settings object.
+ * @return Current event clock setting.
*/
-int gpiod_line_request_bulk_output_flags(struct gpiod_line_bulk *bulk,
- const char *consumer, int flags,
- const int *default_vals);
+int gpiod_line_settings_get_event_clock(struct gpiod_line_settings *settings);
/**
- * @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.
+ * @brief Set the output value.
+ * @param settings Line settings object.
+ * @param value New output value.
+ * @return 0 on success, -1 on failure.
*/
-int gpiod_line_request_bulk_rising_edge_events_flags(
- struct gpiod_line_bulk *bulk,
- const char *consumer, int flags);
+int gpiod_line_settings_set_output_value(struct gpiod_line_settings *settings,
+ int value);
/**
- * @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.
+ * @brief Get the output value.
+ * @param settings Line settings object.
+ * @return Current output value.
+ */
+int gpiod_line_settings_get_output_value(struct gpiod_line_settings *settings);
+
+/*
+ * @}
+ *
+ * @defgroup line_config Line configuration objects
+ * @{
+ *
+ * Functions for manipulating line configuration objects.
+ *
+ * The line-config object contains the configuration for lines that can be
+ * used in two cases:
+ * - when making a line request
+ * - when reconfiguring a set of already requested lines.
+ *
+ * A new line-config object is empty. Using it in a request will lead to an
+ * error. In order to a line-config to become useful, it needs to be assigned
+ * at least one offset-to-settings mapping by calling
+ * ::gpiod_line_config_add_line_settings.
+ *
+ * When calling ::gpiod_chip_request_lines, the library will request all
+ * offsets that were assigned settings in the order that they were assigned.
+ * If any of the offsets was duplicated, the last one will take precedence.
*/
-int gpiod_line_request_bulk_falling_edge_events_flags(
- struct gpiod_line_bulk *bulk,
- const char *consumer, int flags);
/**
- * @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.
+ * @brief Create a new line config object.
+ * @return New line config object or NULL on error. The returned object must
+ * be freed by the caller using ::gpiod_line_config_free.
*/
-int gpiod_line_request_bulk_both_edges_events_flags(
- struct gpiod_line_bulk *bulk,
- const char *consumer, int flags);
+struct gpiod_line_config *gpiod_line_config_new(void);
/**
- * @brief Release a previously reserved line.
- * @param line GPIO line object.
+ * @brief Free the line config object and release all associated resources.
+ * @param config Line config object to free.
*/
-void gpiod_line_release(struct gpiod_line *line);
+void gpiod_line_config_free(struct gpiod_line_config *config);
/**
- * @brief Release a set of previously reserved lines.
- * @param bulk Set of GPIO lines to release.
+ * @brief Reset the line config object.
+ * @param config Line config object to free.
*
- * If the lines were not previously requested together, the behavior is
- * undefined.
+ * Resets the entire configuration stored in the object. This is useful if
+ * the user wants to reuse the object without reallocating it.
+ */
+void gpiod_line_config_reset(struct gpiod_line_config *config);
+
+/**
+ * @brief Add line settings for a set of offsets.
+ * @param config Line config object.
+ * @param offsets Array of offsets for which to apply the settings.
+ * @param num_offsets Number of offsets stored in the offsets array.
+ * @param settings Line settings to apply.
+ * @return 0 on success, -1 on failure.
+ */
+int gpiod_line_config_add_line_settings(struct gpiod_line_config *config,
+ const unsigned int *offsets,
+ size_t num_offsets,
+ struct gpiod_line_settings *settings);
+
+/**
+ * @brief Get line settings for offset.
+ * @param config Line config object.
+ * @param offset Offset for which to get line settings.
+ * @return New line settings object (must be freed by the caller) or NULL on
+ * error.
*/
-void gpiod_line_release_bulk(struct gpiod_line_bulk *bulk);
+struct gpiod_line_settings *
+gpiod_line_config_get_line_settings(struct gpiod_line_config *config,
+ unsigned int offset);
+
+/**
+ * @brief Get configured offsets.
+ * @param config Line config object.
+ * @param num_offsets Pointer to a variable in which the number of line offsets
+ * will be stored.
+ * @param offsets Pointer to a pointer which will be set to point to an array
+ * containing the configured offsets. The array will be allocated
+ * using malloc() and must be freed using free().
+ * @return 0 on success, -1 on failure.
+ */
+int gpiod_line_config_get_offsets(struct gpiod_line_config *config,
+ size_t *num_offsets,
+ unsigned int **offsets);
/**
* @}
*
- * @defgroup line_value Reading & setting line values
+ * @defgroup request_config Request configuration objects
* @{
*
- * Functions allowing to read and set GPIO line values for single lines and
- * in bulk.
+ * Functions for manipulating request configuration objects.
+ *
+ * Request config objects are used to pass a set of options to the kernel at
+ * the time of the line request. The mutators don't return error values. If the
+ * values are invalid, in general they are silently adjusted to acceptable
+ * ranges.
*/
/**
- * @brief 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.
+ * @brief Create a new request config object.
+ * @return New request config object or NULL on error. The returned object must
+ * be freed by the caller using ::gpiod_request_config_free.
*/
-int gpiod_line_get_value(struct gpiod_line *line);
+struct gpiod_request_config *gpiod_request_config_new(void);
/**
- * @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.
+ * @brief Free the request config object and release all associated resources.
+ * @param config Line config object.
*/
-int gpiod_line_get_value_bulk(struct gpiod_line_bulk *bulk, int *values);
+void gpiod_request_config_free(struct gpiod_request_config *config);
/**
- * @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.
+ * @brief Set the consumer name for the request.
+ * @param config Request config object.
+ * @param consumer Consumer name.
+ * @note If the consumer string is too long, it will be truncated to the max
+ * accepted length.
*/
-int gpiod_line_set_value(struct gpiod_line *line, int value);
+void gpiod_request_config_set_consumer(struct gpiod_request_config *config,
+ const char *consumer);
/**
- * @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.
- * A NULL pointer is interpreted as a logical low for all 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.
+ * @brief Get the consumer name configured in the request config.
+ * @param config Request config object.
+ * @return Consumer name stored in the request config.
+ */
+const char *
+gpiod_request_config_get_consumer(struct gpiod_request_config *config);
+
+/**
+ * @brief Set the size of the kernel event buffer for the request.
+ * @param config Request config object.
+ * @param event_buffer_size New event buffer size.
+ * @note The kernel may adjust the value if it's too high. If set to 0, the
+ * default value will be used.
+ * @note The kernel buffer is distinct from and independent of the user space
+ * buffer (::gpiod_edge_event_buffer_new).
+ */
+void
+gpiod_request_config_set_event_buffer_size(struct gpiod_request_config *config,
+ size_t event_buffer_size);
+
+/**
+ * @brief Get the edge event buffer size for the request config.
+ * @param config Request config object.
+ * @return Edge event buffer size setting from the request config.
*/
-int gpiod_line_set_value_bulk(struct gpiod_line_bulk *bulk, const int *values);
+size_t
+gpiod_request_config_get_event_buffer_size(struct gpiod_request_config *config);
/**
* @}
*
- * @defgroup line_config Setting line configuration
+ * @defgroup request_request Line request operations
* @{
*
- * Functions allowing modification of config options of GPIO lines requested
- * from user-space.
+ * Functions allowing interactions with requested lines.
*/
/**
- * @brief Update the configuration of a single GPIO line.
- * @param line GPIO line object.
- * @param direction Updated direction which may be one of
- * GPIOD_LINE_REQUEST_DIRECTION_AS_IS,
- * GPIOD_LINE_REQUEST_DIRECTION_INPUT, or
- * GPIOD_LINE_REQUEST_DIRECTION_OUTPUT.
- * @param flags Replacement flags.
- * @param value The new output value for the line when direction is
- * GPIOD_LINE_REQUEST_DIRECTION_OUTPUT.
- * @return 0 is the operation succeeds. In case of an error this routine
- * returns -1 and sets the last error number.
+ * @brief Release the requested lines and free all associated resources.
+ * @param request Line request object to release.
*/
-int gpiod_line_set_config(struct gpiod_line *line, int direction,
- int flags, int value);
+void gpiod_line_request_release(struct gpiod_line_request *request);
/**
- * @brief Update the configuration of a set of GPIO lines.
- * @param bulk Set of GPIO lines.
- * @param direction Updated direction which may be one of
- * GPIOD_LINE_REQUEST_DIRECTION_AS_IS,
- * GPIOD_LINE_REQUEST_DIRECTION_INPUT, or
- * GPIOD_LINE_REQUEST_DIRECTION_OUTPUT.
- * @param flags Replacement flags.
- * @param values An array holding line_bulk->num_lines new logical values
- * for lines when direction is
- * GPIOD_LINE_REQUEST_DIRECTION_OUTPUT.
- * A NULL pointer is interpreted as a logical low for all 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.
+ * @brief Get the number of lines in the request.
+ * @param request Line request object.
+ * @return Number of requested lines.
*/
-int gpiod_line_set_config_bulk(struct gpiod_line_bulk *bulk,
- int direction, int flags, const int *values);
-
+size_t gpiod_line_request_get_num_lines(struct gpiod_line_request *request);
/**
- * @brief Update the configuration flags of a single GPIO line.
- * @param line GPIO line object.
- * @param flags Replacement flags.
- * @return 0 is the operation succeeds. In case of an error this routine
- * returns -1 and sets the last error number.
+ * @brief Get the offsets of the lines in the request.
+ * @param request Line request object.
+ * @param offsets Array to store offsets. Must be sized to hold the number of
+ * lines returned by ::gpiod_line_request_get_num_lines.
*/
-int gpiod_line_set_flags(struct gpiod_line *line, int flags);
+void gpiod_line_request_get_offsets(struct gpiod_line_request *request,
+ unsigned int *offsets);
/**
- * @brief Update the configuration flags of a set of GPIO lines.
- * @param bulk Set of GPIO lines.
- * @param flags Replacement flags.
- * @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.
+ * @brief Get the value of a single requested line.
+ * @param request Line request object.
+ * @param offset The offset of the line of which the value should be read.
+ * @return Returns 1 or 0 on success and -1 on error.
*/
-int gpiod_line_set_flags_bulk(struct gpiod_line_bulk *bulk, int flags);
+int gpiod_line_request_get_value(struct gpiod_line_request *request,
+ unsigned int offset);
/**
- * @brief Set the direction of a single GPIO line to input.
- * @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.
+ * @brief Get the values of a subset of requested lines.
+ * @param request GPIO line request.
+ * @param num_values Number of lines for which to read values.
+ * @param offsets Array of offsets identifying the subset of requested lines
+ * from which to read values.
+ * @param values Array in which the values will be stored. Must be sized
+ * to hold \p num_values entries. Each value is associated with the
+ * line identified by the corresponding entry in \p offsets.
+ * @return 0 on success, -1 on failure.
*/
-int gpiod_line_set_direction_input(struct gpiod_line *line);
+int gpiod_line_request_get_values_subset(struct gpiod_line_request *request,
+ size_t num_values,
+ const unsigned int *offsets,
+ int *values);
/**
- * @brief Set the direction of a set of GPIO lines to input.
- * @param bulk Set of GPIO 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.
+ * @brief Get the values of all requested lines.
+ * @param request GPIO line request.
+ * @param values Array in which the values will be stored. Must be sized to
+ * hold the number of lines returned by
+ * ::gpiod_line_request_get_num_lines.
+ * Each value is associated with the line identified by the
+ * corresponding entry in the offset array returned by
+ * ::gpiod_line_request_get_offsets.
+ * @return 0 on success, -1 on failure.
*/
-int
-gpiod_line_set_direction_input_bulk(struct gpiod_line_bulk *bulk);
+int gpiod_line_request_get_values(struct gpiod_line_request *request,
+ int *values);
/**
- * @brief Set the direction of a single GPIO line to output.
- * @param line GPIO line object.
- * @param value The logical value output on the line.
- * @return 0 is the operation succeeds. In case of an error this routine
- * returns -1 and sets the last error number.
+ * @brief Set the value of a single requested line.
+ * @param request Line request object.
+ * @param offset The offset of the line for which the value should be set.
+ * @param value Value to set.
*/
-int gpiod_line_set_direction_output(struct gpiod_line *line, int value);
+int gpiod_line_request_set_value(struct gpiod_line_request *request,
+ unsigned int offset, int value);
/**
- * @brief Set the direction of a set of GPIO lines to output.
- * @param bulk Set of GPIO lines.
- * @param values An array holding line_bulk->num_lines new logical values
- * for lines. A NULL pointer is interpreted as a logical low
- * for all 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.
+ * @brief Set the values of a subset of requested lines.
+ * @param request GPIO line request.
+ * @param num_values Number of lines for which to set values.
+ * @param offsets Array of offsets, containing the number of entries specified
+ * by \p num_values, identifying the requested lines for
+ * which to set values.
+ * @param values Array of values to set, containing the number of entries
+ * specified by \p num_values. Each value is associated with the
+ * line identified by the corresponding entry in \p offsets.
+ * @return 0 on success, -1 on failure.
*/
-int gpiod_line_set_direction_output_bulk(struct gpiod_line_bulk *bulk,
+int gpiod_line_request_set_values_subset(struct gpiod_line_request *request,
+ size_t num_values,
+ const unsigned int *offsets,
const int *values);
+/**
+ * @brief Set the values of all lines associated with a request.
+ * @param request GPIO line request.
+ * @param values Array containing the values to set. Must be sized to
+ * contain the number of lines returned by
+ * ::gpiod_line_request_get_num_lines.
+ * Each value is associated with the line identified by the
+ * corresponding entry in the offset array returned by
+ * ::gpiod_line_request_get_offsets.
+ */
+int gpiod_line_request_set_values(struct gpiod_line_request *request,
+ const int *values);
+
+/**
+ * @brief Update the configuration of lines associated with a line request.
+ * @param request GPIO line request.
+ * @param config New line config to apply.
+ * @return 0 on success, -1 on failure.
+ * @note The new line configuration completely replaces the old.
+ * @note Any requested lines without overrides are configured to the requested
+ * defaults.
+ * @note Any configured overrides for lines that have not been requested
+ * are silently ignored.
+ */
+int gpiod_line_request_reconfigure_lines(struct gpiod_line_request *request,
+ struct gpiod_line_config *config);
+
+/**
+ * @brief Get the file descriptor associated with a line request.
+ * @param request GPIO line request.
+ * @return The file descriptor associated with the request.
+ * This function never fails.
+ * The returned file descriptor must not be closed by the caller.
+ * Call ::gpiod_line_request_release to close the file.
+ */
+int gpiod_line_request_get_fd(struct gpiod_line_request *request);
+
+/**
+ * @brief Wait for edge events on any of the requested lines.
+ * @param request GPIO line request.
+ * @param timeout_ns Wait time limit in nanoseconds. If set to 0, the function
+ * returns immediatelly. If set to a negative number, the
+ * function blocks indefinitely until an event becomes
+ * available.
+ * @return 0 if wait timed out, -1 if an error occurred, 1 if an event is
+ * pending.
+ *q
+ * Lines must have edge detection set for edge events to be emitted.
+ * By default edge detection is disabled.
+ */
+int gpiod_line_request_wait_edge_event(struct gpiod_line_request *request,
+ int64_t timeout_ns);
+
+/**
+ * @brief Read a number of edge events from a line request.
+ * @param request GPIO line request.
+ * @param buffer Edge event buffer, sized to hold at least \p max_events.
+ * @param max_events Maximum number of events to read.
+ * @return On success returns the number of events read from the file
+ * descriptor, on failure return -1.
+ * @note This function will block if no event was queued for the line request.
+ * @note Any exising events in the buffer are overwritten. This is not an
+ * append operation.
+ */
+int gpiod_line_request_read_edge_event(struct gpiod_line_request *request,
+ struct gpiod_edge_event_buffer *buffer,
+ size_t max_events);
+
/**
* @}
*
- * @defgroup line_event Line events handling
+ * @defgroup edge_event Line edge events handling
* @{
*
- * Structures and functions allowing to poll lines for events and read them,
- * both for individual lines as well as in bulk. Also contains functions for
- * retrieving the associated file descriptors and operate on them for easy
- * integration with standard unix interfaces.
+ * Functions and data types for handling edge events.
+ *
+ * An edge event object contains information about a single line edge event.
+ * It contains the event type, timestamp and the offset of the line on which
+ * the event occurred as well as two sequence numbers (global for all lines
+ * in the associated request and local for this line only).
+ *
+ * Edge events are stored into an edge-event buffer object to improve
+ * performance and to limit the number of memory allocations when a large
+ * number of events are being read.
*/
/**
* @brief Event types.
*/
enum {
- GPIOD_LINE_EVENT_RISING_EDGE = 1,
+ GPIOD_EDGE_EVENT_RISING_EDGE = 1,
/**< Rising edge event. */
- GPIOD_LINE_EVENT_FALLING_EDGE,
+ GPIOD_EDGE_EVENT_FALLING_EDGE
/**< Falling edge event. */
};
/**
- * @brief Structure holding event info.
+ * @brief Free the edge event object.
+ * @param event Edge event object to free.
*/
-struct gpiod_line_event {
- struct timespec ts;
- /**< Best estimate of time of event occurrence. */
- int event_type;
- /**< Type of the event that occurred. */
- int offset;
- /**< Offset of line on which the event occurred. */
-};
+void gpiod_edge_event_free(struct gpiod_edge_event *event);
/**
- * @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.
+ * @brief Copy the edge event object.
+ * @param event Edge event to copy.
+ * @return Copy of the edge event or NULL on error. The returned object must
+ * be freed by the caller using ::gpiod_edge_event_free.
*/
-int gpiod_line_event_wait(struct gpiod_line *line,
- const struct timespec *timeout);
+struct gpiod_edge_event *gpiod_edge_event_copy(struct gpiod_edge_event *event);
/**
- * @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.
+ * @brief Get the event type.
+ * @param event GPIO edge event.
+ * @return The event type (::GPIOD_EDGE_EVENT_RISING_EDGE or
+ * ::GPIOD_EDGE_EVENT_FALLING_EDGE).
*/
-int gpiod_line_event_wait_bulk(struct gpiod_line_bulk *bulk,
- const struct timespec *timeout,
- struct gpiod_line_bulk *event_bulk);
+int gpiod_edge_event_get_event_type(struct gpiod_edge_event *event);
/**
- * @brief Read next pending 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.
+ * @brief Get the timestamp of the event.
+ * @param event GPIO edge event.
+ * @return Timestamp in nanoseconds.
+ * @note The source clock for the timestamp depends on the event_clock
+ * setting for the line.
*/
-int gpiod_line_event_read(struct gpiod_line *line,
- struct gpiod_line_event *event);
+uint64_t gpiod_edge_event_get_timestamp_ns(struct gpiod_edge_event *event);
/**
- * @brief Read up to a certain number of events from the GPIO line.
- * @param line GPIO line object.
- * @param events Buffer to which the event data will be copied. Must hold at
- * least the amount of events specified in num_events.
- * @param num_events Specifies how many events can be stored in the buffer.
- * @return On success returns the number of events stored in the buffer, on
- * failure -1 is returned.
+ * @brief Get the offset of the line which triggered the event.
+ * @param event GPIO edge event.
+ * @return Line offset.
*/
-int gpiod_line_event_read_multiple(struct gpiod_line *line,
- struct gpiod_line_event *events,
- unsigned int num_events);
+unsigned int gpiod_edge_event_get_line_offset(struct gpiod_edge_event *event);
/**
- * @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.
+ * @brief Get the global sequence number of the event.
+ * @param event GPIO edge event.
+ * @return Sequence number of the event in the series of events for all lines
+ * in the associated line request.
*/
-int gpiod_line_event_get_fd(struct gpiod_line *line);
+unsigned long gpiod_edge_event_get_global_seqno(struct gpiod_edge_event *event);
/**
- * @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.
+ * @brief Get the event sequence number specific to the line.
+ * @param event GPIO edge event.
+ * @return Sequence number of the event in the series of events only for this
+ * line within the lifetime of the associated line request.
*/
-int gpiod_line_event_read_fd(int fd, struct gpiod_line_event *event);
+unsigned long gpiod_edge_event_get_line_seqno(struct gpiod_edge_event *event);
/**
- * @brief Read up to a certain number of events directly from a file descriptor.
- * @param fd File descriptor.
- * @param events Buffer to which the event data will be copied. Must hold at
- * least the amount of events specified in num_events.
- * @param num_events Specifies how many events can be stored in the buffer.
- * @return On success returns the number of events stored in the buffer, on
- * failure -1 is returned.
+ * @brief Create a new edge event buffer.
+ * @param capacity Number of events the buffer can store (min = 1, max = 1024).
+ * @return New edge event buffer or NULL on error.
+ * @note If capacity equals 0, it will be set to a default value of 64. If
+ * capacity is larger than 1024, it will be limited to 1024.
+ * @note The user space buffer is independent of the kernel buffer
+ * (::gpiod_request_config_set_event_buffer_size). As the user space
+ * buffer is filled from the kernel buffer, there is no benefit making
+ * the user space buffer larger than the kernel buffer.
+ * The default kernel buffer size for each request is 16*num_lines.
*/
-int gpiod_line_event_read_fd_multiple(int fd, struct gpiod_line_event *events,
- unsigned int num_events);
+struct gpiod_edge_event_buffer *
+gpiod_edge_event_buffer_new(size_t capacity);
+
+/**
+ * @brief Get the capacity (the max number of events that can be stored) of
+ * the event buffer.
+ * @param buffer Edge event buffer.
+ * @return The capacity of the buffer.
+ */
+size_t
+gpiod_edge_event_buffer_get_capacity(struct gpiod_edge_event_buffer *buffer);
+
+/**
+ * @brief Free the edge event buffer and release all associated resources.
+ * @param buffer Edge event buffer to free.
+ */
+void gpiod_edge_event_buffer_free(struct gpiod_edge_event_buffer *buffer);
+
+/**
+ * @brief Get an event stored in the buffer.
+ * @param buffer Edge event buffer.
+ * @param index Index of the event in the buffer.
+ * @return Pointer to an event stored in the buffer. The lifetime of the
+ * event is tied to the buffer object. Users must not free the event
+ * returned by this function.
+ */
+struct gpiod_edge_event *
+gpiod_edge_event_buffer_get_event(struct gpiod_edge_event_buffer *buffer,
+ unsigned long index);
+
+/**
+ * @brief Get the number of events a buffer has stored.
+ * @param buffer Edge event buffer.
+ * @return Number of events stored in the buffer.
+ */
+size_t
+gpiod_edge_event_buffer_get_num_events(struct gpiod_edge_event_buffer *buffer);
/**
- * @}
- *
* @}
*
* @defgroup misc Stuff that didn't fit anywhere else
* Various libgpiod-related functions.
*/
+/**
+ * @brief Check if the file pointed to by path is a GPIO chip character device.
+ * @param path Path to check.
+ * @return True if the file exists and is either a GPIO chip character device
+ * or a symbolic link to one.
+ */
+bool gpiod_is_gpiochip_device(const char *path);
+
/**
* @brief Get the API version of the library as a human-readable string.
- * @return Human-readable string containing the library version.
+ * @return A valid pointer to a human-readable string containing the library
+ * version. The pointer is valid for the lifetime of the program and
+ * must not be freed by the caller.
*/
const char *gpiod_version_string(void);
# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
lib_LTLIBRARIES = libgpiod.la
-libgpiod_la_SOURCES = core.c helpers.c internal.h misc.c uapi/gpio.h
+libgpiod_la_SOURCES = chip.c \
+ chip-info.c \
+ edge-event.c \
+ info-event.c \
+ internal.h \
+ internal.c \
+ line-config.c \
+ line-info.c \
+ line-request.c \
+ line-settings.c \
+ misc.c \
+ request-config.c \
+ uapi/gpio.h
+
libgpiod_la_CFLAGS = -Wall -Wextra -g -std=gnu89
libgpiod_la_CFLAGS += -fvisibility=hidden -I$(top_srcdir)/include/
libgpiod_la_CFLAGS += -include $(top_builddir)/config.h
+libgpiod_la_CFLAGS += $(PROFILING_CFLAGS)
libgpiod_la_LDFLAGS = -version-info $(subst .,:,$(ABI_VERSION))
+libgpiod_la_LDFLAGS+= $(PROFILING_LDFLAGS)
pkgconfigdir = $(libdir)/pkgconfig
pkgconfig_DATA = libgpiod.pc
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <gpiod.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "internal.h"
+
+struct gpiod_chip_info {
+ size_t num_lines;
+ char name[32];
+ char label[32];
+};
+
+GPIOD_API void gpiod_chip_info_free(struct gpiod_chip_info *info)
+{
+ if (!info)
+ return;
+
+ free(info);
+}
+
+GPIOD_API const char *gpiod_chip_info_get_name(struct gpiod_chip_info *info)
+{
+ return info->name;
+}
+
+GPIOD_API const char *gpiod_chip_info_get_label(struct gpiod_chip_info *info)
+{
+ return info->label;
+}
+
+GPIOD_API size_t gpiod_chip_info_get_num_lines(struct gpiod_chip_info *info)
+{
+ return info->num_lines;
+}
+
+struct gpiod_chip_info *
+gpiod_chip_info_from_uapi(struct gpiochip_info *uapi_info)
+{
+ struct gpiod_chip_info *info;
+
+ info = malloc(sizeof(*info));
+ if (!info)
+ return NULL;
+
+ memset(info, 0, sizeof(*info));
+
+ info->num_lines = uapi_info->lines;
+
+ /*
+ * GPIO device must have a name - don't bother checking this field. In
+ * the worst case (would have to be a weird kernel bug) it'll be empty.
+ */
+ strncpy(info->name, uapi_info->name, sizeof(info->name));
+
+ /*
+ * The kernel sets the label of a GPIO device to "unknown" if it
+ * hasn't been defined in DT, board file etc. On the off-chance that
+ * we got an empty string, do the same.
+ */
+ if (uapi_info->label[0] == '\0')
+ strncpy(info->label, "unknown", sizeof(info->label));
+ else
+ strncpy(info->label, uapi_info->label, sizeof(info->label));
+
+ return info;
+}
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <gpiod.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include "internal.h"
+
+struct gpiod_chip {
+ int fd;
+ char *path;
+};
+
+GPIOD_API struct gpiod_chip *gpiod_chip_open(const char *path)
+{
+ struct gpiod_chip *chip;
+ int fd;
+
+ if (!gpiod_check_gpiochip_device(path, true))
+ return NULL;
+
+ fd = open(path, O_RDWR | O_CLOEXEC | O_NONBLOCK);
+ if (fd < 0)
+ return NULL;
+
+ chip = malloc(sizeof(*chip));
+ if (!chip)
+ goto err_close_fd;
+
+ memset(chip, 0, sizeof(*chip));
+
+ chip->path = strdup(path);
+ if (!chip->path)
+ goto err_free_chip;
+
+ chip->fd = fd;
+
+ return chip;
+
+err_free_chip:
+ free(chip);
+err_close_fd:
+ close(fd);
+
+ return NULL;
+}
+
+GPIOD_API void gpiod_chip_close(struct gpiod_chip *chip)
+{
+ if (!chip)
+ return;
+
+ close(chip->fd);
+ free(chip->path);
+ free(chip);
+}
+
+static int chip_read_chip_info(int fd, struct gpiochip_info *info)
+{
+ int ret;
+
+ memset(info, 0, sizeof(*info));
+
+ ret = ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, info);
+ if (ret)
+ return -1;
+
+ return 0;
+}
+
+GPIOD_API struct gpiod_chip_info *gpiod_chip_get_info(struct gpiod_chip *chip)
+{
+ struct gpiochip_info info;
+ int ret;
+
+ ret = chip_read_chip_info(chip->fd, &info);
+ if (ret < 0)
+ return NULL;
+
+ return gpiod_chip_info_from_uapi(&info);
+}
+
+GPIOD_API const char *gpiod_chip_get_path(struct gpiod_chip *chip)
+{
+ return chip->path;
+}
+
+static int chip_read_line_info(int fd, unsigned int offset,
+ struct gpio_v2_line_info *info, bool watch)
+{
+ int ret, cmd;
+
+ memset(info, 0, sizeof(*info));
+ info->offset = offset;
+
+ cmd = watch ? GPIO_V2_GET_LINEINFO_WATCH_IOCTL :
+ GPIO_V2_GET_LINEINFO_IOCTL;
+
+ ret = ioctl(fd, cmd, info);
+ if (ret)
+ return -1;
+
+ return 0;
+}
+
+static struct gpiod_line_info *
+chip_get_line_info(struct gpiod_chip *chip, unsigned int offset, bool watch)
+{
+ struct gpio_v2_line_info info;
+ int ret;
+
+ ret = chip_read_line_info(chip->fd, offset, &info, watch);
+ if (ret)
+ return NULL;
+
+ return gpiod_line_info_from_uapi(&info);
+}
+
+GPIOD_API struct gpiod_line_info *
+gpiod_chip_get_line_info(struct gpiod_chip *chip, unsigned int offset)
+{
+ return chip_get_line_info(chip, offset, false);
+}
+
+GPIOD_API struct gpiod_line_info *
+gpiod_chip_watch_line_info(struct gpiod_chip *chip, unsigned int offset)
+{
+ return chip_get_line_info(chip, offset, true);
+}
+
+GPIOD_API int gpiod_chip_unwatch_line_info(struct gpiod_chip *chip,
+ unsigned int offset)
+{
+ return ioctl(chip->fd, GPIO_GET_LINEINFO_UNWATCH_IOCTL, &offset);
+}
+
+GPIOD_API int gpiod_chip_get_fd(struct gpiod_chip *chip)
+{
+ return chip->fd;
+}
+
+GPIOD_API int gpiod_chip_wait_info_event(struct gpiod_chip *chip,
+ int64_t timeout_ns)
+{
+ return gpiod_poll_fd(chip->fd, timeout_ns);
+}
+
+GPIOD_API struct gpiod_info_event *
+gpiod_chip_read_info_event(struct gpiod_chip *chip)
+{
+ return gpiod_info_event_read_fd(chip->fd);
+}
+
+GPIOD_API int gpiod_chip_get_line_offset_from_name(struct gpiod_chip *chip,
+ const char *name)
+{
+ struct gpio_v2_line_info linfo;
+ struct gpiochip_info chinfo;
+ unsigned int offset;
+ int ret;
+
+ ret = chip_read_chip_info(chip->fd, &chinfo);
+ if (ret < 0)
+ return -1;
+
+ for (offset = 0; offset < chinfo.lines; offset++) {
+ ret = chip_read_line_info(chip->fd, offset, &linfo, false);
+ if (ret)
+ return -1;
+
+ if (strcmp(name, linfo.name) == 0)
+ return offset;
+ }
+
+ errno = ENOENT;
+ return -1;
+}
+
+GPIOD_API struct gpiod_line_request *
+gpiod_chip_request_lines(struct gpiod_chip *chip,
+ struct gpiod_request_config *req_cfg,
+ struct gpiod_line_config *line_cfg)
+{
+ struct gpio_v2_line_request uapi_req;
+ struct gpiod_line_request *request;
+ int ret;
+
+ memset(&uapi_req, 0, sizeof(uapi_req));
+
+ if (req_cfg)
+ gpiod_request_config_to_uapi(req_cfg, &uapi_req);
+
+ ret = gpiod_line_config_to_uapi(line_cfg, &uapi_req);
+ if (ret)
+ return NULL;
+
+ ret = ioctl(chip->fd, GPIO_V2_GET_LINE_IOCTL, &uapi_req);
+ if (ret < 0)
+ return NULL;
+
+ request = gpiod_line_request_from_uapi(&uapi_req);
+ if (!request) {
+ close(uapi_req.fd);
+ return NULL;
+ }
+
+ return request;
+}
+++ /dev/null
-// SPDX-License-Identifier: LGPL-2.1-or-later
-// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
-
-/* Low-level, core library code. */
-
-#include <errno.h>
-#include <fcntl.h>
-#include <gpiod.h>
-#include <limits.h>
-#include <poll.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/ioctl.h>
-#include <sys/stat.h>
-#include <sys/sysmacros.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include "internal.h"
-#include "uapi/gpio.h"
-
-#define LINE_REQUEST_MAX_LINES 64
-
-enum {
- LINE_FREE = 0,
- LINE_REQUESTED_VALUES,
- LINE_REQUESTED_EVENTS,
-};
-
-struct line_fd_handle {
- int fd;
- int refcount;
-};
-
-struct gpiod_line {
- unsigned int offset;
-
- /* The direction of the GPIO line. */
- int direction;
-
- /* Is this line active-low?. */
- bool active_low;
-
- /* The logical value last written to the line. */
- int output_value;
-
- /* The GPIOLINE_FLAGs returned by GPIO_GET_LINEINFO_IOCTL. */
- __u32 info_flags;
-
- /* The GPIOD_LINE_REQUEST_FLAGs provided to request the line. */
- __u32 req_flags;
-
- /*
- * Indicator of LINE_FREE, LINE_REQUESTED_VALUES or
- * LINE_REQUESTED_EVENTS.
- */
- int state;
-
- struct gpiod_chip *chip;
- struct line_fd_handle *fd_handle;
-
- char name[32];
- char consumer[32];
-};
-
-struct gpiod_chip {
- int refcount;
-
- struct gpiod_line **lines;
- unsigned int num_lines;
-
- int fd;
-
- char name[32];
- char label[32];
-};
-
-/*
- * The structure is defined in a way that allows internal users to allocate
- * bulk objects that hold a single line on the stack - that way we can reuse
- * a lot of code between functions that take single lines and those that take
- * bulks as arguments while not unnecessarily allocating memory dynamically.
- */
-struct gpiod_line_bulk {
- unsigned int num_lines;
- unsigned int max_lines;
- struct gpiod_line *lines[1];
-};
-
-#define BULK_SINGLE_LINE_INIT(line) \
- { 1, 1, { (line) } }
-
-GPIOD_API struct gpiod_line_bulk *gpiod_line_bulk_new(unsigned int max_lines)
-{
- struct gpiod_line_bulk *bulk;
- size_t size;
-
- if (max_lines == 0) {
- errno = EINVAL;
- return NULL;
- }
-
- size = sizeof(struct gpiod_line_bulk) +
- (max_lines - 1) * sizeof(struct gpiod_line *);
-
- bulk = malloc(size);
- if (!bulk)
- return NULL;
-
- bulk->max_lines = max_lines;
- gpiod_line_bulk_reset(bulk);
-
- return bulk;
-}
-
-GPIOD_API void gpiod_line_bulk_reset(struct gpiod_line_bulk *bulk)
-{
- bulk->num_lines = 0;
- memset(bulk->lines, 0, bulk->max_lines * sizeof(struct gpiod_line *));
-}
-
-GPIOD_API void gpiod_line_bulk_free(struct gpiod_line_bulk *bulk)
-{
- free(bulk);
-}
-
-GPIOD_API int gpiod_line_bulk_add_line(struct gpiod_line_bulk *bulk,
- struct gpiod_line *line)
-{
- if (bulk->num_lines == bulk->max_lines) {
- errno = EINVAL;
- return -1;
- }
-
- if (bulk->num_lines != 0) {
- if (bulk->lines[0]->chip != gpiod_line_get_chip(line)) {
- errno = EINVAL;
- return -1;
- }
- }
-
- bulk->lines[bulk->num_lines++] = line;
-
- return 0;
-}
-
-GPIOD_API struct gpiod_line *
-gpiod_line_bulk_get_line(struct gpiod_line_bulk *bulk, unsigned int index)
-{
- if (index >= bulk->num_lines) {
- errno = EINVAL;
- return NULL;
- }
-
- return bulk->lines[index];
-}
-
-GPIOD_API unsigned int gpiod_line_bulk_num_lines(struct gpiod_line_bulk *bulk)
-{
- return bulk->num_lines;
-}
-
-GPIOD_API void gpiod_line_bulk_foreach_line(struct gpiod_line_bulk *bulk,
- gpiod_line_bulk_foreach_cb func,
- void *data)
-{
- unsigned int index;
- int ret;
-
- for (index = 0; index < bulk->num_lines; index++) {
- ret = func(bulk->lines[index], data);
- if (ret == GPIOD_LINE_BULK_CB_STOP)
- return;
- }
-}
-
-#define line_bulk_foreach_line(bulk, line, index) \
- for ((index) = 0, (line) = (bulk)->lines[0]; \
- (index) < (bulk)->num_lines; \
- (index)++, (line) = (bulk)->lines[(index)])
-
-GPIOD_API bool gpiod_is_gpiochip_device(const char *path)
-{
- char *realname, *sysfsp, devpath[64];
- struct stat statbuf;
- bool ret = false;
- int rv;
-
- rv = lstat(path, &statbuf);
- if (rv)
- goto out;
-
- /*
- * Is it a symbolic link? We have to resolve it before checking
- * the rest.
- */
- realname = S_ISLNK(statbuf.st_mode) ? realpath(path, NULL)
- : strdup(path);
- if (realname == NULL)
- goto out;
-
- rv = stat(realname, &statbuf);
- if (rv)
- goto out_free_realname;
-
- /* Is it a character device? */
- if (!S_ISCHR(statbuf.st_mode)) {
- /*
- * Passing a file descriptor not associated with a character
- * device to ioctl() makes it set errno to ENOTTY. Let's do
- * the same in order to stay compatible with the versions of
- * libgpiod from before the introduction of this routine.
- */
- errno = ENOTTY;
- goto out_free_realname;
- }
-
- /* Is the device associated with the GPIO subsystem? */
- snprintf(devpath, sizeof(devpath), "/sys/dev/char/%u:%u/subsystem",
- major(statbuf.st_rdev), minor(statbuf.st_rdev));
-
- sysfsp = realpath(devpath, NULL);
- if (!sysfsp)
- goto out_free_realname;
-
- if (strcmp(sysfsp, "/sys/bus/gpio") != 0) {
- /*
- * This is a character device but not the one we're after.
- * Before the introduction of this function, we'd fail with
- * ENOTTY on the first GPIO ioctl() call for this file
- * descriptor. Let's stay compatible here and keep returning
- * the same error code.
- */
- errno = ENOTTY;
- goto out_free_sysfsp;
- }
-
- ret = true;
-
-out_free_sysfsp:
- free(sysfsp);
-out_free_realname:
- free(realname);
-out:
- return ret;
-}
-
-GPIOD_API struct gpiod_chip *gpiod_chip_open(const char *path)
-{
- struct gpiochip_info info;
- struct gpiod_chip *chip;
- int rv, fd;
-
- fd = open(path, O_RDWR | O_CLOEXEC);
- if (fd < 0)
- return NULL;
-
- /*
- * We were able to open the file but is it really a gpiochip character
- * device?
- */
- if (!gpiod_is_gpiochip_device(path))
- goto err_close_fd;
-
- chip = malloc(sizeof(*chip));
- if (!chip)
- goto err_close_fd;
-
- memset(chip, 0, sizeof(*chip));
- memset(&info, 0, sizeof(info));
-
- rv = ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &info);
- if (rv < 0)
- goto err_free_chip;
-
- chip->fd = fd;
- chip->num_lines = info.lines;
- chip->refcount = 1;
-
- /*
- * 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;
-}
-
-GPIOD_API struct gpiod_chip *gpiod_chip_ref(struct gpiod_chip *chip)
-{
- chip->refcount++;
- return chip;
-}
-
-GPIOD_API void gpiod_chip_unref(struct gpiod_chip *chip)
-{
- struct gpiod_line *line;
- unsigned int i;
-
- chip->refcount--;
- if (chip->refcount > 0)
- return;
-
- 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);
-}
-
-GPIOD_API const char *gpiod_chip_get_name(struct gpiod_chip *chip)
-{
- return chip->name;
-}
-
-GPIOD_API const char *gpiod_chip_get_label(struct gpiod_chip *chip)
-{
- return chip->label;
-}
-
-GPIOD_API unsigned int gpiod_chip_get_num_lines(struct gpiod_chip *chip)
-{
- return chip->num_lines;
-}
-
-static int line_update(struct gpiod_line *line);
-
-GPIOD_API struct gpiod_line *
-gpiod_chip_get_line(struct gpiod_chip *chip, unsigned int offset)
-{
- struct gpiod_line *line;
- int rv;
-
- if (offset >= chip->num_lines) {
- errno = EINVAL;
- return NULL;
- }
-
- if (!chip->lines) {
- chip->lines = calloc(chip->num_lines,
- sizeof(struct gpiod_line *));
- if (!chip->lines)
- return NULL;
- }
-
- if (!chip->lines[offset]) {
- line = malloc(sizeof(*line));
- if (!line)
- return NULL;
-
- memset(line, 0, sizeof(*line));
-
- line->offset = offset;
- line->chip = chip;
-
- chip->lines[offset] = line;
- } else {
- line = chip->lines[offset];
- }
-
- rv = line_update(line);
- if (rv < 0)
- return NULL;
-
- return line;
-}
-
-static struct line_fd_handle *line_make_fd_handle(int fd)
-{
- struct line_fd_handle *handle;
-
- handle = malloc(sizeof(*handle));
- if (!handle)
- return NULL;
-
- handle->fd = fd;
- handle->refcount = 0;
-
- return handle;
-}
-
-static void line_fd_incref(struct gpiod_line *line)
-{
- line->fd_handle->refcount++;
-}
-
-static void line_fd_decref(struct gpiod_line *line)
-{
- struct line_fd_handle *handle = line->fd_handle;
-
- handle->refcount--;
-
- if (handle->refcount == 0) {
- close(handle->fd);
- free(handle);
- line->fd_handle = NULL;
- }
-}
-
-static void line_set_fd(struct gpiod_line *line, struct line_fd_handle *handle)
-{
- line->fd_handle = handle;
- line_fd_incref(line);
-}
-
-static int line_get_fd(struct gpiod_line *line)
-{
- return line->fd_handle->fd;
-}
-
-GPIOD_API struct gpiod_chip *gpiod_line_get_chip(struct gpiod_line *line)
-{
- return line->chip;
-}
-
-GPIOD_API unsigned int gpiod_line_offset(struct gpiod_line *line)
-{
- return line->offset;
-}
-
-GPIOD_API const char *gpiod_line_name(struct gpiod_line *line)
-{
- return line->name[0] == '\0' ? NULL : line->name;
-}
-
-GPIOD_API const char *gpiod_line_consumer(struct gpiod_line *line)
-{
- return line->consumer[0] == '\0' ? NULL : line->consumer;
-}
-
-GPIOD_API int gpiod_line_direction(struct gpiod_line *line)
-{
- return line->direction;
-}
-
-GPIOD_API bool gpiod_line_is_active_low(struct gpiod_line *line)
-{
- return line->active_low;
-}
-
-GPIOD_API int gpiod_line_bias(struct gpiod_line *line)
-{
- if (line->info_flags & GPIOLINE_FLAG_BIAS_DISABLE)
- return GPIOD_LINE_BIAS_DISABLED;
- if (line->info_flags & GPIOLINE_FLAG_BIAS_PULL_UP)
- return GPIOD_LINE_BIAS_PULL_UP;
- if (line->info_flags & GPIOLINE_FLAG_BIAS_PULL_DOWN)
- return GPIOD_LINE_BIAS_PULL_DOWN;
-
- return GPIOD_LINE_BIAS_UNKNOWN;
-}
-
-GPIOD_API bool gpiod_line_is_used(struct gpiod_line *line)
-{
- return line->info_flags & GPIOLINE_FLAG_KERNEL;
-}
-
-GPIOD_API int gpiod_line_drive(struct gpiod_line *line)
-{
- if (line->info_flags & GPIOLINE_FLAG_OPEN_DRAIN)
- return GPIOD_LINE_DRIVE_OPEN_DRAIN;
- if (line->info_flags & GPIOLINE_FLAG_OPEN_SOURCE)
- return GPIOD_LINE_DRIVE_OPEN_SOURCE;
-
- return GPIOD_LINE_DRIVE_PUSH_PULL;
-}
-
-static int line_info_v2_to_info_flags(struct gpio_v2_line_info *info)
-{
- int iflags = 0;
-
- if (info->flags & GPIO_V2_LINE_FLAG_USED)
- iflags |= GPIOLINE_FLAG_KERNEL;
-
- if (info->flags & GPIO_V2_LINE_FLAG_OPEN_DRAIN)
- iflags |= GPIOLINE_FLAG_OPEN_DRAIN;
- if (info->flags & GPIO_V2_LINE_FLAG_OPEN_SOURCE)
- iflags |= GPIOLINE_FLAG_OPEN_SOURCE;
-
- if (info->flags & GPIO_V2_LINE_FLAG_BIAS_DISABLED)
- iflags |= GPIOLINE_FLAG_BIAS_DISABLE;
- if (info->flags & GPIO_V2_LINE_FLAG_BIAS_PULL_UP)
- iflags |= GPIOLINE_FLAG_BIAS_PULL_UP;
- if (info->flags & GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN)
- iflags |= GPIOLINE_FLAG_BIAS_PULL_DOWN;
-
- return iflags;
-}
-
-static int line_update(struct gpiod_line *line)
-{
- struct gpio_v2_line_info info;
- int rv;
-
- memset(&info, 0, sizeof(info));
- info.offset = line->offset;
-
- rv = ioctl(line->chip->fd, GPIO_V2_GET_LINEINFO_IOCTL, &info);
- if (rv < 0)
- return -1;
-
- line->direction = info.flags & GPIO_V2_LINE_FLAG_OUTPUT
- ? GPIOD_LINE_DIRECTION_OUTPUT
- : GPIOD_LINE_DIRECTION_INPUT;
-
- line->active_low = !!(info.flags & GPIO_V2_LINE_FLAG_ACTIVE_LOW);
-
- line->info_flags = line_info_v2_to_info_flags(&info);
-
- strncpy(line->name, info.name, sizeof(line->name));
- strncpy(line->consumer, info.consumer, sizeof(line->consumer));
-
- return 0;
-}
-
-static bool line_is_requested(struct gpiod_line *line)
-{
- return (line->state == LINE_REQUESTED_VALUES ||
- line->state == LINE_REQUESTED_EVENTS);
-}
-
-static bool line_bulk_all_requested(struct gpiod_line_bulk *bulk)
-{
- struct gpiod_line *line;
- unsigned int idx;
-
- line_bulk_foreach_line(bulk, line, idx) {
- if (!line_is_requested(line)) {
- errno = EPERM;
- return false;
- }
- }
-
- return true;
-}
-
-static bool line_bulk_all_requested_values(struct gpiod_line_bulk *bulk)
-{
- struct gpiod_line *line;
- unsigned int idx;
-
- line_bulk_foreach_line(bulk, line, idx) {
- if (line->state != LINE_REQUESTED_VALUES) {
- errno = EPERM;
- return false;
- }
- }
-
- return true;
-}
-
-static bool line_request_direction_is_valid(int direction)
-{
- if ((direction == GPIOD_LINE_REQUEST_DIRECTION_AS_IS) ||
- (direction == GPIOD_LINE_REQUEST_DIRECTION_INPUT) ||
- (direction == GPIOD_LINE_REQUEST_DIRECTION_OUTPUT))
- return true;
-
- errno = EINVAL;
- return false;
-}
-
-static void line_request_type_to_gpio_v2_line_config(int reqtype,
- struct gpio_v2_line_config *config)
-{
- if (reqtype == GPIOD_LINE_REQUEST_DIRECTION_AS_IS)
- return;
-
- if (reqtype == GPIOD_LINE_REQUEST_DIRECTION_OUTPUT) {
- config->flags |= GPIO_V2_LINE_FLAG_OUTPUT;
- return;
- }
- config->flags |= GPIO_V2_LINE_FLAG_INPUT;
-
- if (reqtype == GPIOD_LINE_REQUEST_EVENT_RISING_EDGE)
- config->flags |= GPIO_V2_LINE_FLAG_EDGE_RISING;
- else if (reqtype == GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE)
- config->flags |= GPIO_V2_LINE_FLAG_EDGE_FALLING;
- else if (reqtype == GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES)
- config->flags |= (GPIO_V2_LINE_FLAG_EDGE_RISING |
- GPIO_V2_LINE_FLAG_EDGE_FALLING);
-}
-
-static void line_request_flag_to_gpio_v2_line_config(int flags,
- struct gpio_v2_line_config *config)
-{
- if (flags & GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW)
- config->flags |= GPIO_V2_LINE_FLAG_ACTIVE_LOW;
-
- if (flags & GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN)
- config->flags |= GPIO_V2_LINE_FLAG_OPEN_DRAIN;
- else if (flags & GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE)
- config->flags |= GPIO_V2_LINE_FLAG_OPEN_SOURCE;
-
- if (flags & GPIOD_LINE_REQUEST_FLAG_BIAS_DISABLED)
- config->flags |= GPIO_V2_LINE_FLAG_BIAS_DISABLED;
- else if (flags & GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN)
- config->flags |= GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN;
- else if (flags & GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP)
- config->flags |= GPIO_V2_LINE_FLAG_BIAS_PULL_UP;
-}
-
-static void line_request_config_to_gpio_v2_line_config(
- const struct gpiod_line_request_config *reqcfg,
- struct gpio_v2_line_config *lc)
-{
- line_request_type_to_gpio_v2_line_config(reqcfg->request_type, lc);
- line_request_flag_to_gpio_v2_line_config(reqcfg->flags, lc);
-}
-
-static bool line_request_config_validate(
- const struct gpiod_line_request_config *config)
-{
- int bias_flags = 0;
-
- if ((config->request_type != GPIOD_LINE_REQUEST_DIRECTION_OUTPUT) &&
- (config->flags & (GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN |
- GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE)))
- return false;
-
-
- if ((config->flags & GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN) &&
- (config->flags & GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE)) {
- return false;
- }
-
- if (config->flags & GPIOD_LINE_REQUEST_FLAG_BIAS_DISABLED)
- bias_flags++;
- if (config->flags & GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP)
- bias_flags++;
- if (config->flags & GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN)
- bias_flags++;
- if (bias_flags > 1)
- return false;
-
- return true;
-}
-
-static void lines_bitmap_set_bit(__u64 *bits, int nr)
-{
- *bits |= _BITULL(nr);
-}
-
-static void lines_bitmap_clear_bit(__u64 *bits, int nr)
-{
- *bits &= ~_BITULL(nr);
-}
-
-static int lines_bitmap_test_bit(__u64 bits, int nr)
-{
- return !!(bits & _BITULL(nr));
-}
-
-static void lines_bitmap_assign_bit(__u64 *bits, int nr, bool value)
-{
- if (value)
- lines_bitmap_set_bit(bits, nr);
- else
- lines_bitmap_clear_bit(bits, nr);
-}
-
-static int line_request_values(struct gpiod_line_bulk *bulk,
- const struct gpiod_line_request_config *config,
- const int *vals)
-{
- struct gpiod_line *line;
- struct line_fd_handle *line_fd;
- struct gpio_v2_line_request req;
- unsigned int i;
- int rv, fd;
-
- if (!line_request_config_validate(config)) {
- errno = EINVAL;
- return -1;
- }
-
- memset(&req, 0, sizeof(req));
-
- req.num_lines = gpiod_line_bulk_num_lines(bulk);
- line_request_config_to_gpio_v2_line_config(config, &req.config);
-
- line_bulk_foreach_line(bulk, line, i)
- req.offsets[i] = gpiod_line_offset(line);
-
- if (config->request_type == GPIOD_LINE_REQUEST_DIRECTION_OUTPUT &&
- vals) {
- req.config.num_attrs = 1;
- req.config.attrs[0].attr.id = GPIO_V2_LINE_ATTR_ID_OUTPUT_VALUES;
- line_bulk_foreach_line(bulk, line, i) {
- lines_bitmap_assign_bit(
- &req.config.attrs[0].mask, i, 1);
- lines_bitmap_assign_bit(
- &req.config.attrs[0].attr.values,
- i, vals[i]);
- }
- }
-
- if (config->consumer)
- strncpy(req.consumer, config->consumer,
- sizeof(req.consumer) - 1);
-
- line = gpiod_line_bulk_get_line(bulk, 0);
- fd = line->chip->fd;
-
- rv = ioctl(fd, GPIO_V2_GET_LINE_IOCTL, &req);
- if (rv < 0)
- return -1;
-
- line_fd = line_make_fd_handle(req.fd);
- if (!line_fd)
- return -1;
-
- line_bulk_foreach_line(bulk, line, i) {
- line->state = LINE_REQUESTED_VALUES;
- line->req_flags = config->flags;
- if (config->request_type == GPIOD_LINE_REQUEST_DIRECTION_OUTPUT)
- line->output_value = lines_bitmap_test_bit(
- req.config.attrs[0].attr.values, i);
- line_set_fd(line, line_fd);
-
- rv = line_update(line);
- if (rv) {
- gpiod_line_release_bulk(bulk);
- return rv;
- }
- }
-
- return 0;
-}
-
-static int line_request_event_single(struct gpiod_line *line,
- const struct gpiod_line_request_config *config)
-{
- struct line_fd_handle *line_fd;
- struct gpio_v2_line_request req;
- int rv;
-
- memset(&req, 0, sizeof(req));
-
- if (config->consumer)
- strncpy(req.consumer, config->consumer,
- sizeof(req.consumer) - 1);
-
- req.offsets[0] = gpiod_line_offset(line);
- req.num_lines = 1;
- line_request_config_to_gpio_v2_line_config(config, &req.config);
-
- rv = ioctl(line->chip->fd, GPIO_V2_GET_LINE_IOCTL, &req);
- if (rv < 0)
- return -1;
-
- line_fd = line_make_fd_handle(req.fd);
- if (!line_fd)
- return -1;
-
- line->state = LINE_REQUESTED_EVENTS;
- line->req_flags = config->flags;
- line_set_fd(line, line_fd);
-
- rv = line_update(line);
- if (rv) {
- gpiod_line_release(line);
- return rv;
- }
-
- 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;
-
- line_bulk_foreach_line(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;
-}
-
-GPIOD_API int gpiod_line_request(struct gpiod_line *line,
- const struct gpiod_line_request_config *config,
- int default_val)
-{
- struct gpiod_line_bulk bulk = BULK_SINGLE_LINE_INIT(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;
-}
-
-GPIOD_API int
-gpiod_line_request_bulk(struct gpiod_line_bulk *bulk,
- const struct gpiod_line_request_config *config,
- const int *vals)
-{
- if (line_request_is_direction(config->request_type))
- return line_request_values(bulk, config, vals);
- else if (line_request_is_events(config->request_type))
- return line_request_events(bulk, config);
-
- errno = EINVAL;
- return -1;
-}
-
-GPIOD_API void gpiod_line_release(struct gpiod_line *line)
-{
- struct gpiod_line_bulk bulk = BULK_SINGLE_LINE_INIT(line);
-
- gpiod_line_release_bulk(&bulk);
-}
-
-GPIOD_API void gpiod_line_release_bulk(struct gpiod_line_bulk *bulk)
-{
- struct gpiod_line *line;
- unsigned int idx;
-
- line_bulk_foreach_line(bulk, line, idx) {
- if (line->state != LINE_FREE) {
- line_fd_decref(line);
- line->state = LINE_FREE;
- }
- }
-}
-
-GPIOD_API int gpiod_line_get_value(struct gpiod_line *line)
-{
- struct gpiod_line_bulk bulk = BULK_SINGLE_LINE_INIT(line);
- int rv, value;
-
- rv = gpiod_line_get_value_bulk(&bulk, &value);
- if (rv < 0)
- return -1;
-
- return value;
-}
-
-GPIOD_API int gpiod_line_get_value_bulk(struct gpiod_line_bulk *bulk,
- int *values)
-{
- struct gpio_v2_line_values lv;
- struct gpiod_line *line;
- unsigned int i;
- int rv, fd;
-
- if (!line_bulk_all_requested(bulk))
- return -1;
-
- line = gpiod_line_bulk_get_line(bulk, 0);
-
- memset(&lv, 0, sizeof(lv));
-
- if (line->state == LINE_REQUESTED_VALUES) {
- for (i = 0; i < gpiod_line_bulk_num_lines(bulk); i++)
- lines_bitmap_set_bit(&lv.mask, i);
-
- fd = line_get_fd(line);
-
- rv = ioctl(fd, GPIO_V2_LINE_GET_VALUES_IOCTL, &lv);
- if (rv < 0)
- return -1;
-
- for (i = 0; i < gpiod_line_bulk_num_lines(bulk); i++)
- values[i] = lines_bitmap_test_bit(lv.bits, i);
-
- } else if (line->state == LINE_REQUESTED_EVENTS) {
- lines_bitmap_set_bit(&lv.mask, 0);
- for (i = 0; i < gpiod_line_bulk_num_lines(bulk); i++) {
- line = gpiod_line_bulk_get_line(bulk, i);
-
- fd = line_get_fd(line);
- rv = ioctl(fd, GPIO_V2_LINE_GET_VALUES_IOCTL, &lv);
- if (rv < 0)
- return -1;
- values[i] = lines_bitmap_test_bit(lv.bits, 0);
- }
- } else {
- errno = EINVAL;
- return -1;
- }
- return 0;
-}
-
-GPIOD_API int gpiod_line_set_value(struct gpiod_line *line, int value)
-{
- struct gpiod_line_bulk bulk = BULK_SINGLE_LINE_INIT(line);
-
- return gpiod_line_set_value_bulk(&bulk, &value);
-}
-
-GPIOD_API int gpiod_line_set_value_bulk(struct gpiod_line_bulk *bulk,
- const int *values)
-{
- struct gpio_v2_line_values lv;
- struct gpiod_line *line;
- unsigned int i;
- int rv, fd;
-
- if (!line_bulk_all_requested(bulk))
- return -1;
-
- memset(&lv, 0, sizeof(lv));
-
- for (i = 0; i < gpiod_line_bulk_num_lines(bulk); i++) {
- lines_bitmap_set_bit(&lv.mask, i);
- lines_bitmap_assign_bit(&lv.bits, i, values && values[i]);
- }
-
- line = gpiod_line_bulk_get_line(bulk, 0);
- fd = line_get_fd(line);
-
- rv = ioctl(fd, GPIO_V2_LINE_SET_VALUES_IOCTL, &lv);
- if (rv < 0)
- return -1;
-
- line_bulk_foreach_line(bulk, line, i)
- line->output_value = lines_bitmap_test_bit(lv.bits, i);
-
- return 0;
-}
-
-GPIOD_API int gpiod_line_set_config(struct gpiod_line *line, int direction,
- int flags, int value)
-{
- struct gpiod_line_bulk bulk = BULK_SINGLE_LINE_INIT(line);
-
- return gpiod_line_set_config_bulk(&bulk, direction, flags, &value);
-}
-
-GPIOD_API int gpiod_line_set_config_bulk(struct gpiod_line_bulk *bulk,
- int direction, int flags,
- const int *values)
-{
- struct gpio_v2_line_config hcfg;
- struct gpiod_line *line;
- unsigned int i;
- int rv, fd;
-
- if (!line_bulk_all_requested_values(bulk))
- return -1;
-
- if (!line_request_direction_is_valid(direction))
- return -1;
-
- memset(&hcfg, 0, sizeof(hcfg));
-
- line_request_flag_to_gpio_v2_line_config(flags, &hcfg);
- line_request_type_to_gpio_v2_line_config(direction, &hcfg);
- if (direction == GPIOD_LINE_REQUEST_DIRECTION_OUTPUT && values) {
- hcfg.num_attrs = 1;
- hcfg.attrs[0].attr.id = GPIO_V2_LINE_ATTR_ID_OUTPUT_VALUES;
- for (i = 0; i < gpiod_line_bulk_num_lines(bulk); i++) {
- lines_bitmap_assign_bit(&hcfg.attrs[0].mask, i, 1);
- lines_bitmap_assign_bit(
- &hcfg.attrs[0].attr.values, i, values[i]);
- }
- }
-
- line = gpiod_line_bulk_get_line(bulk, 0);
- fd = line_get_fd(line);
-
- rv = ioctl(fd, GPIO_V2_LINE_SET_CONFIG_IOCTL, &hcfg);
- if (rv < 0)
- return -1;
-
- line_bulk_foreach_line(bulk, line, i) {
- line->req_flags = flags;
- if (direction == GPIOD_LINE_REQUEST_DIRECTION_OUTPUT)
- line->output_value = lines_bitmap_test_bit(
- hcfg.attrs[0].attr.values, i);
-
- rv = line_update(line);
- if (rv < 0)
- return rv;
- }
- return 0;
-}
-
-GPIOD_API int gpiod_line_set_flags(struct gpiod_line *line, int flags)
-{
- struct gpiod_line_bulk bulk = BULK_SINGLE_LINE_INIT(line);
-
- return gpiod_line_set_flags_bulk(&bulk, flags);
-}
-
-GPIOD_API int gpiod_line_set_flags_bulk(struct gpiod_line_bulk *bulk, int flags)
-{
- struct gpiod_line *line;
- int values[LINE_REQUEST_MAX_LINES];
- unsigned int i;
- int direction;
-
- line = gpiod_line_bulk_get_line(bulk, 0);
- if (line->direction == GPIOD_LINE_DIRECTION_OUTPUT) {
- line_bulk_foreach_line(bulk, line, i)
- values[i] = line->output_value;
-
- direction = GPIOD_LINE_REQUEST_DIRECTION_OUTPUT;
- } else {
- direction = GPIOD_LINE_REQUEST_DIRECTION_INPUT;
- }
-
- return gpiod_line_set_config_bulk(bulk, direction,
- flags, values);
-}
-
-GPIOD_API int gpiod_line_set_direction_input(struct gpiod_line *line)
-{
- return gpiod_line_set_config(line, GPIOD_LINE_REQUEST_DIRECTION_INPUT,
- line->req_flags, 0);
-}
-
-GPIOD_API int gpiod_line_set_direction_input_bulk(struct gpiod_line_bulk *bulk)
-{
- struct gpiod_line *line;
-
- line = gpiod_line_bulk_get_line(bulk, 0);
- return gpiod_line_set_config_bulk(bulk,
- GPIOD_LINE_REQUEST_DIRECTION_INPUT,
- line->req_flags, NULL);
-}
-
-GPIOD_API int gpiod_line_set_direction_output(struct gpiod_line *line,
- int value)
-{
- return gpiod_line_set_config(line, GPIOD_LINE_REQUEST_DIRECTION_OUTPUT,
- line->req_flags, value);
-}
-
-GPIOD_API int gpiod_line_set_direction_output_bulk(struct gpiod_line_bulk *bulk,
- const int *values)
-{
- struct gpiod_line *line;
-
- line = gpiod_line_bulk_get_line(bulk, 0);
- return gpiod_line_set_config_bulk(bulk,
- GPIOD_LINE_REQUEST_DIRECTION_OUTPUT,
- line->req_flags, values);
-}
-
-GPIOD_API int gpiod_line_event_wait(struct gpiod_line *line,
- const struct timespec *timeout)
-{
- struct gpiod_line_bulk bulk = BULK_SINGLE_LINE_INIT(line);
-
- return gpiod_line_event_wait_bulk(&bulk, timeout, NULL);
-}
-
-GPIOD_API int gpiod_line_event_wait_bulk(struct gpiod_line_bulk *bulk,
- const struct timespec *timeout,
- struct gpiod_line_bulk *event_bulk)
-{
- struct pollfd fds[LINE_REQUEST_MAX_LINES];
- unsigned int off, num_lines;
- struct gpiod_line *line;
- int rv;
-
- if (!line_bulk_all_requested(bulk))
- return -1;
-
- memset(fds, 0, sizeof(fds));
- num_lines = gpiod_line_bulk_num_lines(bulk);
-
- line_bulk_foreach_line(bulk, line, off) {
- fds[off].fd = line_get_fd(line);
- fds[off].events = POLLIN | POLLPRI;
- }
-
- rv = ppoll(fds, num_lines, timeout, NULL);
- if (rv < 0)
- return -1;
- else if (rv == 0)
- return 0;
-
- for (off = 0; off < num_lines; off++) {
- if (fds[off].revents) {
- if (fds[off].revents & POLLNVAL) {
- errno = EINVAL;
- return -1;
- }
-
- if (event_bulk) {
- line = gpiod_line_bulk_get_line(bulk, off);
- rv = gpiod_line_bulk_add_line(event_bulk, line);
- if (rv)
- return -1;
- }
-
- if (!--rv)
- break;
- }
- }
-
- return 1;
-}
-
-GPIOD_API int gpiod_line_event_read(struct gpiod_line *line,
- struct gpiod_line_event *event)
-{
- int ret;
-
- ret = gpiod_line_event_read_multiple(line, event, 1);
- if (ret < 0)
- return -1;
-
- return 0;
-}
-
-GPIOD_API int gpiod_line_event_read_multiple(struct gpiod_line *line,
- struct gpiod_line_event *events,
- unsigned int num_events)
-{
- int fd;
-
- fd = gpiod_line_event_get_fd(line);
- if (fd < 0)
- return -1;
-
- return gpiod_line_event_read_fd_multiple(fd, events, num_events);
-}
-
-GPIOD_API int gpiod_line_event_get_fd(struct gpiod_line *line)
-{
- if (line->state != LINE_REQUESTED_EVENTS) {
- errno = EPERM;
- return -1;
- }
-
- return line_get_fd(line);
-}
-
-GPIOD_API int gpiod_line_event_read_fd(int fd, struct gpiod_line_event *event)
-{
- int ret;
-
- ret = gpiod_line_event_read_fd_multiple(fd, event, 1);
- if (ret < 0)
- return -1;
-
- return 0;
-}
-
-GPIOD_API int gpiod_line_event_read_fd_multiple(int fd,
- struct gpiod_line_event *events,
- unsigned int num_events)
-{
- /*
- * 16 is the maximum number of events the kernel can store in the FIFO
- * so we can allocate the buffer on the stack.
- *
- * NOTE: This is no longer strictly true for uAPI v2. While 16 is
- * the default for single line, a request with multiple lines will
- * have a larger buffer. So need to rethink the allocation here,
- * or at least the comment above...
- */
- struct gpio_v2_line_event evdata[16], *curr;
- struct gpiod_line_event *event;
- unsigned int events_read, i;
- ssize_t rd;
-
- memset(evdata, 0, sizeof(evdata));
-
- if (num_events > 16)
- num_events = 16;
-
- rd = read(fd, evdata, num_events * sizeof(*evdata));
- if (rd < 0) {
- return -1;
- } else if ((unsigned int)rd < sizeof(*evdata)) {
- errno = EIO;
- return -1;
- }
-
- events_read = rd / sizeof(*evdata);
- if (events_read < num_events)
- num_events = events_read;
-
- for (i = 0; i < num_events; i++) {
- curr = &evdata[i];
- event = &events[i];
-
- event->offset = curr->offset;
- event->event_type = curr->id == GPIO_V2_LINE_EVENT_RISING_EDGE
- ? GPIOD_LINE_EVENT_RISING_EDGE
- : GPIOD_LINE_EVENT_FALLING_EDGE;
- event->ts.tv_sec = curr->timestamp_ns / 1000000000ULL;
- event->ts.tv_nsec = curr->timestamp_ns % 1000000000ULL;
- }
-
- return i;
-}
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <errno.h>
+#include <gpiod.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "internal.h"
+
+/* As defined in the kernel. */
+#define EVENT_BUFFER_MAX_CAPACITY (GPIO_V2_LINES_MAX * 16)
+
+struct gpiod_edge_event {
+ int event_type;
+ uint64_t timestamp;
+ unsigned int line_offset;
+ unsigned long global_seqno;
+ unsigned long line_seqno;
+};
+
+struct gpiod_edge_event_buffer {
+ size_t capacity;
+ size_t num_events;
+ struct gpiod_edge_event *events;
+ struct gpio_v2_line_event *event_data;
+};
+
+GPIOD_API void gpiod_edge_event_free(struct gpiod_edge_event *event)
+{
+ if (!event)
+ return;
+
+ free(event);
+}
+
+GPIOD_API struct gpiod_edge_event *
+gpiod_edge_event_copy(struct gpiod_edge_event *event)
+{
+ struct gpiod_edge_event *copy;
+
+ copy = malloc(sizeof(*event));
+ if (!copy)
+ return NULL;
+
+ memcpy(copy, event, sizeof(*event));
+
+ return copy;
+}
+
+GPIOD_API int gpiod_edge_event_get_event_type(struct gpiod_edge_event *event)
+{
+ return event->event_type;
+}
+
+GPIOD_API uint64_t
+gpiod_edge_event_get_timestamp_ns(struct gpiod_edge_event *event)
+{
+ return event->timestamp;
+}
+
+GPIOD_API unsigned int
+gpiod_edge_event_get_line_offset(struct gpiod_edge_event *event)
+{
+ return event->line_offset;
+}
+
+GPIOD_API unsigned long
+gpiod_edge_event_get_global_seqno(struct gpiod_edge_event *event)
+{
+ return event->global_seqno;
+}
+
+GPIOD_API unsigned long
+gpiod_edge_event_get_line_seqno(struct gpiod_edge_event *event)
+{
+ return event->line_seqno;
+}
+
+GPIOD_API struct gpiod_edge_event_buffer *
+gpiod_edge_event_buffer_new(size_t capacity)
+{
+ struct gpiod_edge_event_buffer *buf;
+
+ if (capacity == 0)
+ capacity = 64;
+ if (capacity > EVENT_BUFFER_MAX_CAPACITY)
+ capacity = EVENT_BUFFER_MAX_CAPACITY;
+
+ buf = malloc(sizeof(*buf));
+ if (!buf)
+ return NULL;
+
+ memset(buf, 0, sizeof(*buf));
+ buf->capacity = capacity;
+
+ buf->events = calloc(capacity, sizeof(*buf->events));
+ if (!buf->events) {
+ free(buf);
+ return NULL;
+ }
+
+ buf->event_data = calloc(capacity, sizeof(*buf->event_data));
+ if (!buf->event_data) {
+ free(buf->events);
+ free(buf);
+ return NULL;
+ }
+
+ return buf;
+}
+
+GPIOD_API size_t
+gpiod_edge_event_buffer_get_capacity(struct gpiod_edge_event_buffer *buffer)
+{
+ return buffer->capacity;
+}
+
+GPIOD_API void
+gpiod_edge_event_buffer_free(struct gpiod_edge_event_buffer *buffer)
+{
+ if (!buffer)
+ return;
+
+ free(buffer->events);
+ free(buffer->event_data);
+ free(buffer);
+}
+
+GPIOD_API struct gpiod_edge_event *
+gpiod_edge_event_buffer_get_event(struct gpiod_edge_event_buffer *buffer,
+ unsigned long index)
+{
+ if (index >= buffer->num_events) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ return &buffer->events[index];
+}
+
+GPIOD_API size_t
+gpiod_edge_event_buffer_get_num_events(struct gpiod_edge_event_buffer *buffer)
+{
+ return buffer->num_events;
+}
+
+int gpiod_edge_event_buffer_read_fd(int fd,
+ struct gpiod_edge_event_buffer *buffer,
+ size_t max_events)
+{
+ struct gpio_v2_line_event *curr;
+ struct gpiod_edge_event *event;
+ size_t i;
+ ssize_t rd;
+
+ memset(buffer->event_data, 0,
+ sizeof(*buffer->event_data) * buffer->capacity);
+ memset(buffer->events, 0, sizeof(*buffer->events) * buffer->capacity);
+
+ if (max_events > buffer->capacity)
+ max_events = buffer->capacity;
+
+ rd = read(fd, buffer->event_data,
+ max_events * sizeof(*buffer->event_data));
+ if (rd < 0) {
+ return -1;
+ } else if ((unsigned int)rd < sizeof(*buffer->event_data)) {
+ errno = EIO;
+ return -1;
+ }
+
+ buffer->num_events = rd / sizeof(*buffer->event_data);
+
+ for (i = 0; i < buffer->num_events; i++) {
+ curr = &buffer->event_data[i];
+ event = &buffer->events[i];
+
+ event->line_offset = curr->offset;
+ event->event_type = curr->id == GPIO_V2_LINE_EVENT_RISING_EDGE
+ ? GPIOD_EDGE_EVENT_RISING_EDGE
+ : GPIOD_EDGE_EVENT_FALLING_EDGE;
+ event->timestamp = curr->timestamp_ns;
+ event->global_seqno = curr->seqno;
+ event->line_seqno = curr->line_seqno;
+ }
+
+ return i;
+}
+++ /dev/null
-// SPDX-License-Identifier: LGPL-2.1-or-later
-// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
-
-/*
- * More specific variants of the core API and misc functions that don't need
- * access to neither the internal library data structures nor the kernel UAPI.
- */
-
-#include <errno.h>
-#include <gpiod.h>
-#include <stdio.h>
-#include <string.h>
-
-#include "internal.h"
-
-GPIOD_API struct gpiod_line_bulk *
-gpiod_chip_get_lines(struct gpiod_chip *chip,
- unsigned int *offsets, unsigned int num_offsets)
-{
- struct gpiod_line_bulk *bulk;
- struct gpiod_line *line;
- unsigned int i;
-
- bulk = gpiod_line_bulk_new(num_offsets);
- if (!bulk)
- return NULL;
-
- for (i = 0; i < num_offsets; i++) {
- line = gpiod_chip_get_line(chip, offsets[i]);
- if (!line) {
- gpiod_line_bulk_free(bulk);
- return NULL;
- }
-
- gpiod_line_bulk_add_line(bulk, line);
- }
-
- return bulk;
-}
-
-GPIOD_API struct gpiod_line_bulk *
-gpiod_chip_get_all_lines(struct gpiod_chip *chip)
-{
- struct gpiod_line_bulk *bulk;
- struct gpiod_line *line;
- unsigned int offset;
-
- bulk = gpiod_line_bulk_new(gpiod_chip_get_num_lines(chip));
- if (!bulk)
- return NULL;
-
- for (offset = 0; offset < gpiod_chip_get_num_lines(chip); offset++) {
- line = gpiod_chip_get_line(chip, offset);
- if (!line) {
- gpiod_line_bulk_free(bulk);
- return NULL;
- }
-
- gpiod_line_bulk_add_line(bulk, line);
- }
-
- return bulk;
-}
-
-GPIOD_API int gpiod_chip_find_line(struct gpiod_chip *chip, const char *name)
-{
- unsigned int offset, num_lines;
- struct gpiod_line *line;
- const char *tmp;
-
- num_lines = gpiod_chip_get_num_lines(chip);
-
- for (offset = 0; offset < num_lines; offset++) {
- line = gpiod_chip_get_line(chip, offset);
- if (!line)
- return -1;
-
- tmp = gpiod_line_name(line);
- if (tmp && strcmp(tmp, name) == 0)
- return gpiod_line_offset(line);
- }
-
- errno = ENOENT;
- return -1;
-}
-
-GPIOD_API 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);
-}
-
-GPIOD_API 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);
-}
-
-GPIOD_API 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);
-}
-
-GPIOD_API 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);
-}
-
-GPIOD_API 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);
-}
-
-GPIOD_API 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);
-}
-
-GPIOD_API 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);
-}
-
-GPIOD_API 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);
-}
-
-GPIOD_API 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);
-}
-
-GPIOD_API 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);
-}
-
-GPIOD_API 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);
-}
-
-GPIOD_API 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);
-}
-
-GPIOD_API 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);
-}
-
-GPIOD_API 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);
-}
-
-GPIOD_API 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);
-}
-
-GPIOD_API 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);
-}
-
-GPIOD_API 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);
-}
-
-GPIOD_API 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);
-}
-
-GPIOD_API 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);
-}
-
-GPIOD_API 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);
-}
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "internal.h"
+
+struct gpiod_info_event {
+ int event_type;
+ uint64_t timestamp;
+ struct gpiod_line_info *info;
+};
+
+struct gpiod_info_event *
+gpiod_info_event_from_uapi(struct gpio_v2_line_info_changed *uapi_evt)
+{
+ struct gpiod_info_event *event;
+
+ event = malloc(sizeof(*event));
+ if (!event)
+ return NULL;
+
+ memset(event, 0, sizeof(*event));
+ event->timestamp = uapi_evt->timestamp_ns;
+
+ switch (uapi_evt->event_type) {
+ case GPIOLINE_CHANGED_REQUESTED:
+ event->event_type = GPIOD_INFO_EVENT_LINE_REQUESTED;
+ break;
+ case GPIOLINE_CHANGED_RELEASED:
+ event->event_type = GPIOD_INFO_EVENT_LINE_RELEASED;
+ break;
+ case GPIOLINE_CHANGED_CONFIG:
+ event->event_type = GPIOD_INFO_EVENT_LINE_CONFIG_CHANGED;
+ break;
+ default:
+ /* Can't happen unless there's a bug in the kernel. */
+ errno = ENOMSG;
+ free(event);
+ return NULL;
+ }
+
+ event->info = gpiod_line_info_from_uapi(&uapi_evt->info);
+ if (!event->info) {
+ free(event);
+ return NULL;
+ }
+
+ return event;
+}
+
+GPIOD_API void gpiod_info_event_free(struct gpiod_info_event *event)
+{
+ if (!event)
+ return;
+
+ gpiod_line_info_free(event->info);
+ free(event);
+}
+
+GPIOD_API int gpiod_info_event_get_event_type(struct gpiod_info_event *event)
+{
+ return event->event_type;
+}
+
+GPIOD_API uint64_t
+gpiod_info_event_get_timestamp_ns(struct gpiod_info_event *event)
+{
+ return event->timestamp;
+}
+
+GPIOD_API struct gpiod_line_info *
+gpiod_info_event_get_line_info(struct gpiod_info_event *event)
+{
+ return event->info;
+}
+
+struct gpiod_info_event *gpiod_info_event_read_fd(int fd)
+{
+ struct gpio_v2_line_info_changed uapi_evt;
+ ssize_t rd;
+
+ memset(&uapi_evt, 0, sizeof(uapi_evt));
+
+ rd = read(fd, &uapi_evt, sizeof(uapi_evt));
+ if (rd < 0) {
+ return NULL;
+ } else if ((unsigned int)rd < sizeof(uapi_evt)) {
+ errno = EIO;
+ return NULL;
+ }
+
+ return gpiod_info_event_from_uapi(&uapi_evt);
+}
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <errno.h>
+#include <poll.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "internal.h"
+
+bool gpiod_check_gpiochip_device(const char *path, bool set_errno)
+{
+ char *realname, *sysfsp, devpath[64];
+ struct stat statbuf;
+ bool ret = false;
+ int rv;
+
+ rv = lstat(path, &statbuf);
+ if (rv)
+ goto out;
+
+ /*
+ * Is it a symbolic link? We have to resolve it before checking
+ * the rest.
+ */
+ realname = S_ISLNK(statbuf.st_mode) ? realpath(path, NULL)
+ : strdup(path);
+ if (realname == NULL)
+ goto out;
+
+ rv = stat(realname, &statbuf);
+ if (rv)
+ goto out_free_realname;
+
+ /* Is it a character device? */
+ if (!S_ISCHR(statbuf.st_mode)) {
+ errno = ENOTTY;
+ goto out_free_realname;
+ }
+
+ /* Is the device associated with the GPIO subsystem? */
+ snprintf(devpath, sizeof(devpath), "/sys/dev/char/%u:%u/subsystem",
+ major(statbuf.st_rdev), minor(statbuf.st_rdev));
+
+ sysfsp = realpath(devpath, NULL);
+ if (!sysfsp)
+ goto out_free_realname;
+
+ /*
+ * In glibc, if any of the underlying readlink() calls fail (which is
+ * perfectly normal when resolving paths), errno is not cleared.
+ */
+ errno = 0;
+
+ if (strcmp(sysfsp, "/sys/bus/gpio") != 0) {
+ /* This is a character device but not the one we're after. */
+ errno = ENODEV;
+ goto out_free_sysfsp;
+ }
+
+ ret = true;
+
+out_free_sysfsp:
+ free(sysfsp);
+out_free_realname:
+ free(realname);
+out:
+ if (!set_errno)
+ errno = 0;
+ return ret;
+}
+
+int gpiod_poll_fd(int fd, int64_t timeout_ns)
+{
+ struct timespec ts;
+ struct pollfd pfd;
+ int ret;
+
+ memset(&pfd, 0, sizeof(pfd));
+ pfd.fd = fd;
+ pfd.events = POLLIN | POLLPRI;
+
+ if (timeout_ns >= 0) {
+ ts.tv_sec = timeout_ns / 1000000000ULL;
+ ts.tv_nsec = timeout_ns % 1000000000ULL;
+ }
+
+ ret = ppoll(&pfd, 1, timeout_ns < 0 ? NULL : &ts, NULL);
+ if (ret < 0)
+ return -1;
+ else if (ret == 0)
+ return 0;
+
+ return 1;
+}
+
+void gpiod_line_mask_zero(uint64_t *mask)
+{
+ *mask = 0ULL;
+}
+
+void gpiod_line_mask_fill(uint64_t *mask)
+{
+ *mask = UINT64_MAX;
+}
+
+bool gpiod_line_mask_test_bit(const uint64_t *mask, int nr)
+{
+ return *mask & (1ULL << nr);
+}
+
+void gpiod_line_mask_set_bit(uint64_t *mask, unsigned int nr)
+{
+ *mask |= (1ULL << nr);
+}
+
+void gpiod_line_mask_assign_bit(uint64_t *mask, unsigned int nr, bool value)
+{
+ if (value)
+ gpiod_line_mask_set_bit(mask, nr);
+ else
+ *mask &= ~(1ULL << nr);
+}
#ifndef __LIBGPIOD_GPIOD_INTERNAL_H__
#define __LIBGPIOD_GPIOD_INTERNAL_H__
+#include <gpiod.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "uapi/gpio.h"
+
/* For internal library use only. */
-#define GPIOD_API __attribute__((visibility("default")))
+#define GPIOD_API __attribute__((visibility("default")))
+#define GPIOD_BIT(nr) (1UL << (nr))
+
+bool gpiod_check_gpiochip_device(const char *path, bool set_errno);
+
+struct gpiod_chip_info *
+gpiod_chip_info_from_uapi(struct gpiochip_info *uapi_info);
+struct gpiod_line_info *
+gpiod_line_info_from_uapi(struct gpio_v2_line_info *uapi_info);
+void gpiod_request_config_to_uapi(struct gpiod_request_config *config,
+ struct gpio_v2_line_request *uapi_req);
+int gpiod_line_config_to_uapi(struct gpiod_line_config *config,
+ struct gpio_v2_line_request *uapi_cfg);
+struct gpiod_line_request *
+gpiod_line_request_from_uapi(struct gpio_v2_line_request *uapi_req);
+int gpiod_edge_event_buffer_read_fd(int fd, struct gpiod_edge_event_buffer *buffer,
+ size_t max_events);
+struct gpiod_info_event *
+gpiod_info_event_from_uapi(struct gpio_v2_line_info_changed *uapi_evt);
+struct gpiod_info_event *gpiod_info_event_read_fd(int fd);
+
+int gpiod_poll_fd(int fd, int64_t timeout);
+
+void gpiod_line_mask_zero(uint64_t *mask);
+void gpiod_line_mask_fill(uint64_t *mask);
+bool gpiod_line_mask_test_bit(const uint64_t *mask, int nr);
+void gpiod_line_mask_set_bit(uint64_t *mask, unsigned int nr);
+void gpiod_line_mask_assign_bit(uint64_t *mask, unsigned int nr, bool value);
#endif /* __LIBGPIOD_GPIOD_INTERNAL_H__ */
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <errno.h>
+#include <gpiod.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "internal.h"
+
+#define LINES_MAX (GPIO_V2_LINES_MAX)
+
+struct settings_node {
+ struct settings_node *next;
+ struct gpiod_line_settings *settings;
+};
+
+struct per_line_config {
+ unsigned int offset;
+ struct settings_node *node;
+};
+
+struct gpiod_line_config {
+ struct per_line_config line_configs[LINES_MAX];
+ size_t num_configs;
+ struct settings_node *sref_list;
+};
+
+GPIOD_API struct gpiod_line_config *gpiod_line_config_new(void)
+{
+ struct gpiod_line_config *config;
+
+ config = malloc(sizeof(*config));
+ if (!config)
+ return NULL;
+
+ memset(config, 0, sizeof(*config));
+
+ return config;
+}
+
+static void free_refs(struct gpiod_line_config *config)
+{
+ struct settings_node *node, *tmp;
+
+ for (node = config->sref_list; node;) {
+ tmp = node->next;
+ gpiod_line_settings_free(node->settings);
+ free(node);
+ node = tmp;
+ }
+}
+
+GPIOD_API void gpiod_line_config_free(struct gpiod_line_config *config)
+{
+ free_refs(config);
+ free(config);
+}
+
+GPIOD_API void gpiod_line_config_reset(struct gpiod_line_config *config)
+{
+ free_refs(config);
+ memset(config, 0, sizeof(*config));
+}
+
+static struct per_line_config *
+find_config(struct gpiod_line_config *config, unsigned int offset)
+{
+ struct per_line_config *per_line;
+ size_t i;
+
+ for (i = 0; i < config->num_configs; i++) {
+ per_line = &config->line_configs[i];
+
+ if (offset == per_line->offset)
+ return per_line;
+ }
+
+ return &config->line_configs[config->num_configs++];
+}
+
+GPIOD_API int
+gpiod_line_config_add_line_settings(struct gpiod_line_config *config,
+ const unsigned int *offsets,
+ size_t num_offsets,
+ struct gpiod_line_settings *settings)
+{
+ struct per_line_config *per_line;
+ struct settings_node *node;
+ size_t i;
+
+ if ((config->num_configs + num_offsets) > LINES_MAX) {
+ errno = E2BIG;
+ return -1;
+ }
+
+ node = malloc(sizeof(*node));
+ if (!node)
+ return -1;
+
+ if (!settings)
+ node->settings = gpiod_line_settings_new();
+ else
+ node->settings = gpiod_line_settings_copy(settings);
+ if (!node->settings) {
+ free(node);
+ return -1;
+ }
+
+ node->next = config->sref_list;
+ config->sref_list = node;
+
+ for (i = 0; i < num_offsets; i++) {
+ per_line = find_config(config, offsets[i]);
+
+ per_line->offset = offsets[i];
+ per_line->node = node;
+ }
+
+ return 0;
+}
+
+GPIOD_API struct gpiod_line_settings *
+gpiod_line_config_get_line_settings(struct gpiod_line_config *config,
+ unsigned int offset)
+{
+ struct per_line_config *per_line;
+ size_t i;
+
+ for (i = 0; i < config->num_configs; i++) {
+ per_line = &config->line_configs[i];
+
+ if (per_line->offset == offset)
+ return gpiod_line_settings_copy(
+ per_line->node->settings);
+ }
+
+ errno = ENOENT;
+ return NULL;
+}
+
+GPIOD_API int
+gpiod_line_config_get_offsets(struct gpiod_line_config *config,
+ size_t *num_offsets,
+ unsigned int **offsets)
+{
+ unsigned int *offs;
+ size_t i;
+
+ *num_offsets = config->num_configs;
+ *offsets = NULL;
+
+ if (!config->num_configs)
+ return 0;
+
+ offs = calloc(config->num_configs, sizeof(unsigned int));
+ if (!offs)
+ return -1;
+
+ for (i = 0; i < config->num_configs; i++)
+ offs[i] = config->line_configs[i].offset;
+
+ *offsets = offs;
+
+ return 0;
+}
+
+static void set_offsets(struct gpiod_line_config *config,
+ struct gpio_v2_line_request *uapi_cfg)
+{
+ size_t i;
+
+ uapi_cfg->num_lines = config->num_configs;
+
+ for (i = 0; i < config->num_configs; i++)
+ uapi_cfg->offsets[i] = config->line_configs[i].offset;
+}
+
+static bool has_at_least_one_output_direction(struct gpiod_line_config *config)
+{
+ size_t i;
+
+ for (i = 0; i < config->num_configs; i++) {
+ if (gpiod_line_settings_get_direction(
+ config->line_configs[i].node->settings) ==
+ GPIOD_LINE_DIRECTION_OUTPUT)
+ return true;
+ }
+
+ return false;
+}
+
+static void set_kernel_output_values(uint64_t *mask, uint64_t *vals,
+ struct gpiod_line_config *config)
+{
+ struct per_line_config *per_line;
+ int value;
+ size_t i;
+
+ gpiod_line_mask_zero(mask);
+ gpiod_line_mask_zero(vals);
+
+ for (i = 0; i < config->num_configs; i++) {
+ per_line = &config->line_configs[i];
+
+ if (gpiod_line_settings_get_direction(
+ per_line->node->settings) !=
+ GPIOD_LINE_DIRECTION_OUTPUT)
+ continue;
+
+ gpiod_line_mask_set_bit(mask, i);
+ value = gpiod_line_settings_get_output_value(
+ per_line->node->settings);
+ gpiod_line_mask_assign_bit(vals, i,
+ value == GPIOD_LINE_VALUE_ACTIVE ? 1 : 0);
+ }
+}
+
+static void set_output_values(struct gpiod_line_config *config,
+ struct gpio_v2_line_request *uapi_cfg,
+ unsigned int *attr_idx)
+{
+ struct gpio_v2_line_config_attribute *attr;
+ uint64_t mask, values;
+
+ if (!has_at_least_one_output_direction(config))
+ return;
+
+ attr = &uapi_cfg->config.attrs[(*attr_idx)++];
+ attr->attr.id = GPIO_V2_LINE_ATTR_ID_OUTPUT_VALUES;
+ set_kernel_output_values(&mask, &values, config);
+ attr->attr.values = values;
+ attr->mask = mask;
+}
+
+static int set_debounce_periods(struct gpiod_line_config *config,
+ struct gpio_v2_line_config *uapi_cfg,
+ unsigned int *attr_idx)
+{
+ struct gpio_v2_line_config_attribute *attr;
+ unsigned long period_i, period_j;
+ uint64_t done, mask;
+ size_t i, j;
+
+ gpiod_line_mask_zero(&done);
+
+ for (i = 0; i < config->num_configs; i++) {
+ if (gpiod_line_mask_test_bit(&done, i))
+ continue;
+
+ gpiod_line_mask_set_bit(&done, i);
+ gpiod_line_mask_zero(&mask);
+
+ period_i = gpiod_line_settings_get_debounce_period_us(
+ config->line_configs[i].node->settings);
+ if (!period_i)
+ continue;
+
+ if (*attr_idx == GPIO_V2_LINE_NUM_ATTRS_MAX) {
+ errno = E2BIG;
+ return -1;
+ }
+
+ attr = &uapi_cfg->attrs[(*attr_idx)++];
+ attr->attr.id = GPIO_V2_LINE_ATTR_ID_DEBOUNCE;
+ attr->attr.debounce_period_us = period_i;
+ gpiod_line_mask_set_bit(&mask, i);
+
+ for (j = i; j < config->num_configs; j++) {
+ period_j = gpiod_line_settings_get_debounce_period_us(
+ config->line_configs[j].node->settings);
+ if (period_i == period_j) {
+ gpiod_line_mask_set_bit(&mask, j);
+ gpiod_line_mask_set_bit(&done, j);
+ }
+ }
+
+ attr->mask = mask;
+ }
+
+ return 0;
+}
+
+static uint64_t make_kernel_flags(struct gpiod_line_settings *settings)
+{
+ uint64_t flags = 0;
+
+ switch (gpiod_line_settings_get_direction(settings)) {
+ case GPIOD_LINE_DIRECTION_INPUT:
+ flags |= GPIO_V2_LINE_FLAG_INPUT;
+ break;
+ case GPIOD_LINE_DIRECTION_OUTPUT:
+ flags |= GPIO_V2_LINE_FLAG_OUTPUT;
+ break;
+ }
+
+ switch (gpiod_line_settings_get_edge_detection(settings)) {
+ case GPIOD_LINE_EDGE_FALLING:
+ flags |= (GPIO_V2_LINE_FLAG_EDGE_FALLING |
+ GPIO_V2_LINE_FLAG_INPUT);
+ flags &= ~GPIOD_LINE_DIRECTION_OUTPUT;
+ break;
+ case GPIOD_LINE_EDGE_RISING:
+ flags |= (GPIO_V2_LINE_FLAG_EDGE_RISING |
+ GPIO_V2_LINE_FLAG_INPUT);
+ flags &= ~GPIOD_LINE_DIRECTION_OUTPUT;
+ break;
+ case GPIOD_LINE_EDGE_BOTH:
+ flags |= (GPIO_V2_LINE_FLAG_EDGE_FALLING |
+ GPIO_V2_LINE_FLAG_EDGE_RISING |
+ GPIO_V2_LINE_FLAG_INPUT);
+ flags &= ~GPIOD_LINE_DIRECTION_OUTPUT;
+ break;
+ }
+
+ switch (gpiod_line_settings_get_drive(settings)) {
+ case GPIOD_LINE_DRIVE_OPEN_DRAIN:
+ flags |= GPIO_V2_LINE_FLAG_OPEN_DRAIN;
+ break;
+ case GPIOD_LINE_DRIVE_OPEN_SOURCE:
+ flags |= GPIO_V2_LINE_FLAG_OPEN_SOURCE;
+ break;
+ }
+
+ switch (gpiod_line_settings_get_bias(settings)) {
+ case GPIOD_LINE_BIAS_DISABLED:
+ flags |= GPIO_V2_LINE_FLAG_BIAS_DISABLED;
+ break;
+ case GPIOD_LINE_BIAS_PULL_UP:
+ flags |= GPIO_V2_LINE_FLAG_BIAS_PULL_UP;
+ break;
+ case GPIOD_LINE_BIAS_PULL_DOWN:
+ flags |= GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN;
+ break;
+ }
+
+ if (gpiod_line_settings_get_active_low(settings))
+ flags |= GPIO_V2_LINE_FLAG_ACTIVE_LOW;
+
+ switch (gpiod_line_settings_get_event_clock(settings)) {
+ case GPIOD_LINE_EVENT_CLOCK_REALTIME:
+ flags |= GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME;
+ break;
+ case GPIOD_LINE_EVENT_CLOCK_HTE:
+ flags |= GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE;
+ break;
+ }
+
+ return flags;
+}
+
+static bool settings_equal(struct gpiod_line_settings *left,
+ struct gpiod_line_settings *right)
+{
+ if (gpiod_line_settings_get_direction(left) !=
+ gpiod_line_settings_get_direction(right))
+ return false;
+
+ if (gpiod_line_settings_get_edge_detection(left) !=
+ gpiod_line_settings_get_edge_detection(right))
+ return false;
+
+ if (gpiod_line_settings_get_bias(left) !=
+ gpiod_line_settings_get_bias(right))
+ return false;
+
+ if (gpiod_line_settings_get_drive(left) !=
+ gpiod_line_settings_get_drive(right))
+ return false;
+
+ if (gpiod_line_settings_get_active_low(left) !=
+ gpiod_line_settings_get_active_low(right))
+ return false;
+
+ if (gpiod_line_settings_get_event_clock(left) !=
+ gpiod_line_settings_get_event_clock(right))
+ return false;
+
+ return true;
+}
+
+static int set_flags(struct gpiod_line_config *config,
+ struct gpio_v2_line_config *uapi_cfg,
+ unsigned int *attr_idx)
+{
+ struct gpiod_line_settings *settings_i, *settings_j;
+ struct gpio_v2_line_config_attribute *attr;
+ bool globals_taken = false;
+ uint64_t done, mask;
+ size_t i, j;
+
+ gpiod_line_mask_zero(&done);
+
+ for (i = 0; i < config->num_configs; i++) {
+ if (gpiod_line_mask_test_bit(&done, i))
+ continue;
+
+ gpiod_line_mask_set_bit(&done, i);
+
+ settings_i = config->line_configs[i].node->settings;
+
+ if (!globals_taken) {
+ globals_taken = true;
+ uapi_cfg->flags = make_kernel_flags(settings_i);
+
+ for (j = i; j < config->num_configs; j++) {
+ settings_j =
+ config->line_configs[j].node->settings;
+ if (settings_equal(settings_i, settings_j))
+ gpiod_line_mask_set_bit(&done, j);
+ }
+ } else {
+ gpiod_line_mask_zero(&mask);
+ gpiod_line_mask_set_bit(&mask, i);
+
+ if (*attr_idx == GPIO_V2_LINE_NUM_ATTRS_MAX) {
+ errno = E2BIG;
+ return -1;
+ }
+
+ attr = &uapi_cfg->attrs[(*attr_idx)++];
+ attr->attr.id = GPIO_V2_LINE_ATTR_ID_FLAGS;
+ attr->attr.flags = make_kernel_flags(settings_i);
+
+ for (j = i; j < config->num_configs; j++) {
+ settings_j =
+ config->line_configs[j].node->settings;
+ if (settings_equal(settings_i, settings_j)) {
+ gpiod_line_mask_set_bit(&done, j);
+ gpiod_line_mask_set_bit(&mask, j);
+ }
+ }
+
+ attr->mask = mask;
+ }
+ }
+
+ return 0;
+}
+
+int gpiod_line_config_to_uapi(struct gpiod_line_config *config,
+ struct gpio_v2_line_request *uapi_cfg)
+{
+ unsigned int attr_idx = 0;
+ int ret;
+
+ set_offsets(config, uapi_cfg);
+ set_output_values(config, uapi_cfg, &attr_idx);
+
+ ret = set_debounce_periods(config, &uapi_cfg->config, &attr_idx);
+ if (ret)
+ return -1;
+
+ ret = set_flags(config, &uapi_cfg->config, &attr_idx);
+ if (ret)
+ return -1;
+
+ uapi_cfg->config.num_attrs = attr_idx;
+
+ return 0;
+}
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <gpiod.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "internal.h"
+
+struct gpiod_line_info {
+ unsigned int offset;
+ char name[GPIO_MAX_NAME_SIZE];
+ bool used;
+ char consumer[GPIO_MAX_NAME_SIZE];
+ int direction;
+ bool active_low;
+ int bias;
+ int drive;
+ int edge;
+ int event_clock;
+ bool debounced;
+ unsigned long debounce_period_us;
+};
+
+GPIOD_API void gpiod_line_info_free(struct gpiod_line_info *info)
+{
+ if (!info)
+ return;
+
+ free(info);
+}
+
+GPIOD_API struct gpiod_line_info *
+gpiod_line_info_copy(struct gpiod_line_info *info)
+{
+ struct gpiod_line_info *copy;
+
+ copy = malloc(sizeof(*info));
+ if (!copy)
+ return NULL;
+
+ memcpy(copy, info, sizeof(*info));
+
+ return copy;
+}
+
+GPIOD_API unsigned int gpiod_line_info_get_offset(struct gpiod_line_info *info)
+{
+ return info->offset;
+}
+
+GPIOD_API const char *gpiod_line_info_get_name(struct gpiod_line_info *info)
+{
+ return info->name[0] == '\0' ? NULL : info->name;
+}
+
+GPIOD_API bool gpiod_line_info_is_used(struct gpiod_line_info *info)
+{
+ return info->used;
+}
+
+GPIOD_API const char *gpiod_line_info_get_consumer(struct gpiod_line_info *info)
+{
+ return info->consumer[0] == '\0' ? NULL : info->consumer;
+}
+
+GPIOD_API int gpiod_line_info_get_direction(struct gpiod_line_info *info)
+{
+ return info->direction;
+}
+
+GPIOD_API bool gpiod_line_info_is_active_low(struct gpiod_line_info *info)
+{
+ return info->active_low;
+}
+
+GPIOD_API int gpiod_line_info_get_bias(struct gpiod_line_info *info)
+{
+ return info->bias;
+}
+
+GPIOD_API int gpiod_line_info_get_drive(struct gpiod_line_info *info)
+{
+ return info->drive;
+}
+
+GPIOD_API int gpiod_line_info_get_edge_detection(struct gpiod_line_info *info)
+{
+ return info->edge;
+}
+
+GPIOD_API int gpiod_line_info_get_event_clock(struct gpiod_line_info *info)
+{
+ return info->event_clock;
+}
+
+GPIOD_API bool gpiod_line_info_is_debounced(struct gpiod_line_info *info)
+{
+ return info->debounced;
+}
+
+GPIOD_API unsigned long
+gpiod_line_info_get_debounce_period_us(struct gpiod_line_info *info)
+{
+ return info->debounce_period_us;
+}
+
+struct gpiod_line_info *
+gpiod_line_info_from_uapi(struct gpio_v2_line_info *uapi_info)
+{
+ struct gpio_v2_line_attribute *attr;
+ struct gpiod_line_info *info;
+ size_t i;
+
+ info = malloc(sizeof(*info));
+ if (!info)
+ return NULL;
+
+ memset(info, 0, sizeof(*info));
+
+ info->offset = uapi_info->offset;
+ strncpy(info->name, uapi_info->name, GPIO_MAX_NAME_SIZE);
+
+ info->used = !!(uapi_info->flags & GPIO_V2_LINE_FLAG_USED);
+ strncpy(info->consumer, uapi_info->consumer, GPIO_MAX_NAME_SIZE);
+
+ if (uapi_info->flags & GPIO_V2_LINE_FLAG_OUTPUT)
+ info->direction = GPIOD_LINE_DIRECTION_OUTPUT;
+ else
+ info->direction = GPIOD_LINE_DIRECTION_INPUT;
+
+ if (uapi_info->flags & GPIO_V2_LINE_FLAG_ACTIVE_LOW)
+ info->active_low = true;
+
+ if (uapi_info->flags & GPIO_V2_LINE_FLAG_BIAS_PULL_UP)
+ info->bias = GPIOD_LINE_BIAS_PULL_UP;
+ else if (uapi_info->flags & GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN)
+ info->bias = GPIOD_LINE_BIAS_PULL_DOWN;
+ else if (uapi_info->flags & GPIO_V2_LINE_FLAG_BIAS_DISABLED)
+ info->bias = GPIOD_LINE_BIAS_DISABLED;
+ else
+ info->bias = GPIOD_LINE_BIAS_UNKNOWN;
+
+ if (uapi_info->flags & GPIO_V2_LINE_FLAG_OPEN_DRAIN)
+ info->drive = GPIOD_LINE_DRIVE_OPEN_DRAIN;
+ else if (uapi_info->flags & GPIO_V2_LINE_FLAG_OPEN_SOURCE)
+ info->drive = GPIOD_LINE_DRIVE_OPEN_SOURCE;
+ else
+ info->drive = GPIOD_LINE_DRIVE_PUSH_PULL;
+
+ if ((uapi_info->flags & GPIO_V2_LINE_FLAG_EDGE_RISING) &&
+ (uapi_info->flags & GPIO_V2_LINE_FLAG_EDGE_FALLING))
+ info->edge = GPIOD_LINE_EDGE_BOTH;
+ else if (uapi_info->flags & GPIO_V2_LINE_FLAG_EDGE_RISING)
+ info->edge = GPIOD_LINE_EDGE_RISING;
+ else if (uapi_info->flags & GPIO_V2_LINE_FLAG_EDGE_FALLING)
+ info->edge = GPIOD_LINE_EDGE_FALLING;
+ else
+ info->edge = GPIOD_LINE_EDGE_NONE;
+
+ if (uapi_info->flags & GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME)
+ info->event_clock = GPIOD_LINE_EVENT_CLOCK_REALTIME;
+ else if (uapi_info->flags & GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE)
+ info->event_clock = GPIOD_LINE_EVENT_CLOCK_HTE;
+ else
+ info->event_clock = GPIOD_LINE_EVENT_CLOCK_MONOTONIC;
+
+ /*
+ * We assume that the kernel returns correct configuration and that no
+ * attributes repeat.
+ */
+ for (i = 0; i < uapi_info->num_attrs; i++) {
+ attr = &uapi_info->attrs[i];
+
+ if (attr->id == GPIO_V2_LINE_ATTR_ID_DEBOUNCE) {
+ info->debounced = true;
+ info->debounce_period_us = attr->debounce_period_us;
+ }
+ }
+
+ return info;
+}
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <errno.h>
+#include <gpiod.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include "internal.h"
+
+struct gpiod_line_request {
+ unsigned int offsets[GPIO_V2_LINES_MAX];
+ size_t num_lines;
+ int fd;
+};
+
+struct gpiod_line_request *
+gpiod_line_request_from_uapi(struct gpio_v2_line_request *uapi_req)
+{
+ struct gpiod_line_request *request;
+
+ request = malloc(sizeof(*request));
+ if (!request)
+ return NULL;
+
+ memset(request, 0, sizeof(*request));
+ request->fd = uapi_req->fd;
+ request->num_lines = uapi_req->num_lines;
+ memcpy(request->offsets, uapi_req->offsets,
+ sizeof(*request->offsets) * request->num_lines);
+
+ return request;
+}
+
+GPIOD_API void gpiod_line_request_release(struct gpiod_line_request *request)
+{
+ if (!request)
+ return;
+
+ close(request->fd);
+ free(request);
+}
+
+GPIOD_API size_t
+gpiod_line_request_get_num_lines(struct gpiod_line_request *request)
+{
+ return request->num_lines;
+}
+
+GPIOD_API void
+gpiod_line_request_get_offsets(struct gpiod_line_request *request,
+ unsigned int *offsets)
+{
+ memcpy(offsets, request->offsets,
+ sizeof(*offsets) * request->num_lines);
+}
+
+GPIOD_API int gpiod_line_request_get_value(struct gpiod_line_request *request,
+ unsigned int offset)
+{
+ unsigned int ret;
+ int val;
+
+ ret = gpiod_line_request_get_values_subset(request, 1, &offset, &val);
+ if (ret)
+ return -1;
+
+ return val;
+}
+
+static int offset_to_bit(struct gpiod_line_request *request,
+ unsigned int offset)
+{
+ size_t i;
+
+ for (i = 0; i < request->num_lines; i++) {
+ if (offset == request->offsets[i])
+ return i;
+ }
+
+ return -1;
+}
+
+GPIOD_API int
+gpiod_line_request_get_values_subset(struct gpiod_line_request *request,
+ size_t num_values,
+ const unsigned int *offsets, int *values)
+{
+ struct gpio_v2_line_values uapi_values;
+ uint64_t mask = 0, bits = 0;
+ size_t i;
+ int bit, ret;
+
+ uapi_values.bits = 0;
+
+ for (i = 0; i < num_values; i++) {
+ bit = offset_to_bit(request, offsets[i]);
+ if (bit < 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ gpiod_line_mask_set_bit(&mask, bit);
+ }
+
+ uapi_values.mask = mask;
+
+ ret = ioctl(request->fd, GPIO_V2_LINE_GET_VALUES_IOCTL, &uapi_values);
+ if (ret)
+ return -1;
+
+ bits = uapi_values.bits;
+ memset(values, 0, sizeof(*values) * num_values);
+
+ for (i = 0; i < num_values; i++) {
+ bit = offset_to_bit(request, offsets[i]);
+ values[i] = gpiod_line_mask_test_bit(&bits, bit) ? 1 : 0;
+ }
+
+ return 0;
+}
+
+GPIOD_API int gpiod_line_request_get_values(struct gpiod_line_request *request,
+ int *values)
+{
+ return gpiod_line_request_get_values_subset(request, request->num_lines,
+ request->offsets, values);
+}
+
+GPIOD_API int gpiod_line_request_set_value(struct gpiod_line_request *request,
+ unsigned int offset, int value)
+{
+ return gpiod_line_request_set_values_subset(request, 1,
+ &offset, &value);
+}
+
+GPIOD_API int
+gpiod_line_request_set_values_subset(struct gpiod_line_request *request,
+ size_t num_values,
+ const unsigned int *offsets,
+ const int *values)
+{
+ struct gpio_v2_line_values uapi_values;
+ uint64_t mask = 0, bits = 0;
+ size_t i;
+ int bit;
+
+ for (i = 0; i < num_values; i++) {
+ bit = offset_to_bit(request, offsets[i]);
+ if (bit < 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ gpiod_line_mask_set_bit(&mask, bit);
+ gpiod_line_mask_assign_bit(&bits, bit, values[i]);
+ }
+
+ memset(&uapi_values, 0, sizeof(uapi_values));
+ uapi_values.mask = mask;
+ uapi_values.bits = bits;
+
+ return ioctl(request->fd, GPIO_V2_LINE_SET_VALUES_IOCTL, &uapi_values);
+}
+
+GPIOD_API int gpiod_line_request_set_values(struct gpiod_line_request *request,
+ const int *values)
+{
+ return gpiod_line_request_set_values_subset(request, request->num_lines,
+ request->offsets, values);
+}
+
+static bool offsets_equal(struct gpiod_line_request *request,
+ struct gpio_v2_line_request *uapi_cfg)
+{
+ size_t i;
+
+ if (request->num_lines != uapi_cfg->num_lines)
+ return false;
+
+ for (i = 0; i < request->num_lines; i++) {
+ if (request->offsets[i] != uapi_cfg->offsets[i])
+ return false;
+ }
+
+ return true;
+}
+
+GPIOD_API int
+gpiod_line_request_reconfigure_lines(struct gpiod_line_request *request,
+ struct gpiod_line_config *config)
+{
+ struct gpio_v2_line_request uapi_cfg;
+ int ret;
+
+ memset(&uapi_cfg, 0, sizeof(uapi_cfg));
+
+ ret = gpiod_line_config_to_uapi(config, &uapi_cfg);
+ if (ret)
+ return ret;
+
+ if (!offsets_equal(request, &uapi_cfg)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ ret = ioctl(request->fd, GPIO_V2_LINE_SET_CONFIG_IOCTL,
+ &uapi_cfg.config);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+GPIOD_API int gpiod_line_request_get_fd(struct gpiod_line_request *request)
+{
+ return request->fd;
+}
+
+GPIOD_API int
+gpiod_line_request_wait_edge_event(struct gpiod_line_request *request,
+ int64_t timeout_ns)
+{
+ return gpiod_poll_fd(request->fd, timeout_ns);
+}
+
+GPIOD_API int
+gpiod_line_request_read_edge_event(struct gpiod_line_request *request,
+ struct gpiod_edge_event_buffer *buffer,
+ size_t max_events)
+{
+ return gpiod_edge_event_buffer_read_fd(request->fd, buffer, max_events);
+}
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <errno.h>
+#include <gpiod.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "internal.h"
+
+struct gpiod_line_settings {
+ int direction;
+ int edge_detection;
+ int drive;
+ int bias;
+ bool active_low;
+ int event_clock;
+ long debounce_period_us;
+ int output_value;
+};
+
+GPIOD_API struct gpiod_line_settings *gpiod_line_settings_new(void)
+{
+ struct gpiod_line_settings *settings;
+
+ settings = malloc(sizeof(*settings));
+ if (!settings)
+ return NULL;
+
+ gpiod_line_settings_reset(settings);
+
+ return settings;
+}
+
+GPIOD_API void gpiod_line_settings_free(struct gpiod_line_settings *settings)
+{
+ free(settings);
+}
+
+GPIOD_API void gpiod_line_settings_reset(struct gpiod_line_settings *settings)
+{
+ settings->direction = GPIOD_LINE_DIRECTION_AS_IS;
+ settings->edge_detection = GPIOD_LINE_EDGE_NONE;
+ settings->bias = GPIOD_LINE_BIAS_AS_IS;
+ settings->drive = GPIOD_LINE_DRIVE_PUSH_PULL;
+ settings->active_low = false;
+ settings->debounce_period_us = 0;
+ settings->event_clock = GPIOD_LINE_EVENT_CLOCK_MONOTONIC;
+ settings->output_value = GPIOD_LINE_VALUE_INACTIVE;
+}
+
+GPIOD_API struct gpiod_line_settings *
+gpiod_line_settings_copy(struct gpiod_line_settings *settings)
+{
+ struct gpiod_line_settings *copy;
+
+ copy = malloc(sizeof(*copy));
+ if (!copy)
+ return NULL;
+
+ memcpy(copy, settings, sizeof(*copy));
+
+ return copy;
+}
+
+GPIOD_API int
+gpiod_line_settings_set_direction(struct gpiod_line_settings *settings,
+ int direction)
+{
+ switch (direction) {
+ case GPIOD_LINE_DIRECTION_INPUT:
+ case GPIOD_LINE_DIRECTION_OUTPUT:
+ case GPIOD_LINE_DIRECTION_AS_IS:
+ settings->direction = direction;
+ break;
+ default:
+ settings->direction = GPIOD_LINE_DIRECTION_AS_IS;
+ errno = EINVAL;
+ return -1;
+ }
+
+ return 0;
+}
+
+GPIOD_API int
+gpiod_line_settings_get_direction(struct gpiod_line_settings *settings)
+{
+ return settings->direction;
+}
+
+GPIOD_API int
+gpiod_line_settings_set_edge_detection(struct gpiod_line_settings *settings,
+ int edge)
+{
+ switch (edge) {
+ case GPIOD_LINE_EDGE_NONE:
+ case GPIOD_LINE_EDGE_RISING:
+ case GPIOD_LINE_EDGE_FALLING:
+ case GPIOD_LINE_EDGE_BOTH:
+ settings->edge_detection = edge;
+ break;
+ default:
+ settings->edge_detection = GPIOD_LINE_EDGE_NONE;
+ errno = EINVAL;
+ return -1;
+ }
+
+ return 0;
+}
+
+GPIOD_API int
+gpiod_line_settings_get_edge_detection(struct gpiod_line_settings *settings)
+{
+ return settings->edge_detection;
+}
+
+GPIOD_API int
+gpiod_line_settings_set_bias(struct gpiod_line_settings *settings, int bias)
+{
+ switch (bias) {
+ case GPIOD_LINE_BIAS_AS_IS:
+ case GPIOD_LINE_BIAS_DISABLED:
+ case GPIOD_LINE_BIAS_PULL_UP:
+ case GPIOD_LINE_BIAS_PULL_DOWN:
+ settings->bias = bias;
+ break;
+ default:
+ settings->bias = GPIOD_LINE_BIAS_AS_IS;
+ errno = EINVAL;
+ return -1;
+ }
+
+ return 0;
+}
+
+GPIOD_API int gpiod_line_settings_get_bias(struct gpiod_line_settings *settings)
+{
+ return settings->bias;
+}
+
+GPIOD_API int
+gpiod_line_settings_set_drive(struct gpiod_line_settings *settings, int drive)
+{
+ switch (drive) {
+ case GPIOD_LINE_DRIVE_PUSH_PULL:
+ case GPIOD_LINE_DRIVE_OPEN_DRAIN:
+ case GPIOD_LINE_DRIVE_OPEN_SOURCE:
+ settings->drive = drive;
+ break;
+ default:
+ settings->drive = GPIOD_LINE_DRIVE_PUSH_PULL;
+ errno = EINVAL;
+ return -1;
+ }
+
+ return 0;
+}
+
+GPIOD_API int
+gpiod_line_settings_get_drive(struct gpiod_line_settings *settings)
+{
+ return settings->drive;
+}
+
+GPIOD_API void
+gpiod_line_settings_set_active_low(struct gpiod_line_settings *settings,
+ bool active_low)
+{
+ settings->active_low = active_low;
+}
+
+GPIOD_API bool
+gpiod_line_settings_get_active_low(struct gpiod_line_settings *settings)
+{
+ return settings->active_low;
+}
+
+GPIOD_API void
+gpiod_line_settings_set_debounce_period_us(struct gpiod_line_settings *settings,
+ unsigned long period)
+{
+ settings->debounce_period_us = period;
+}
+
+GPIOD_API unsigned long
+gpiod_line_settings_get_debounce_period_us(struct gpiod_line_settings *settings)
+{
+ return settings->debounce_period_us;
+}
+
+GPIOD_API int
+gpiod_line_settings_set_event_clock(struct gpiod_line_settings *settings,
+ int event_clock)
+{
+ switch (event_clock) {
+ case GPIOD_LINE_EVENT_CLOCK_MONOTONIC:
+ case GPIOD_LINE_EVENT_CLOCK_REALTIME:
+ case GPIOD_LINE_EVENT_CLOCK_HTE:
+ settings->event_clock = event_clock;
+ break;
+ default:
+ settings->event_clock = GPIOD_LINE_EVENT_CLOCK_MONOTONIC;
+ errno = EINVAL;
+ return -1;
+ }
+
+ return 0;
+}
+
+GPIOD_API int
+gpiod_line_settings_get_event_clock(struct gpiod_line_settings *settings)
+{
+ return settings->event_clock;
+}
+
+GPIOD_API int
+gpiod_line_settings_set_output_value(struct gpiod_line_settings *settings,
+ int value)
+{
+ switch (value) {
+ case GPIOD_LINE_VALUE_INACTIVE:
+ case GPIOD_LINE_VALUE_ACTIVE:
+ settings->output_value = value;
+ break;
+ default:
+ settings->output_value = GPIOD_LINE_VALUE_INACTIVE;
+ errno = EINVAL;
+ return -1;
+ }
+
+ return 0;
+}
+
+GPIOD_API int
+gpiod_line_settings_get_output_value(struct gpiod_line_settings *settings)
+{
+ return settings->output_value;
+}
// SPDX-License-Identifier: LGPL-2.1-or-later
-// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
-
-/* Misc code that didn't fit anywhere else. */
+// SPDX-FileCopyrightText: 2017-2022 Bartosz Golaszewski <bartekgola@gmail.com>
#include <gpiod.h>
#include "internal.h"
+GPIOD_API bool gpiod_is_gpiochip_device(const char *path)
+{
+ return gpiod_check_gpiochip_device(path, false);
+}
+
GPIOD_API const char *gpiod_version_string(void)
{
return GPIOD_VERSION_STR;
--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <errno.h>
+#include <gpiod.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "internal.h"
+
+struct gpiod_request_config {
+ char consumer[GPIO_MAX_NAME_SIZE];
+ size_t event_buffer_size;
+};
+
+GPIOD_API struct gpiod_request_config *gpiod_request_config_new(void)
+{
+ struct gpiod_request_config *config;
+
+ config = malloc(sizeof(*config));
+ if (!config)
+ return NULL;
+
+ memset(config, 0, sizeof(*config));
+
+ return config;
+}
+
+GPIOD_API void gpiod_request_config_free(struct gpiod_request_config *config)
+{
+ if (!config)
+ return;
+
+ free(config);
+}
+
+GPIOD_API void
+gpiod_request_config_set_consumer(struct gpiod_request_config *config,
+ const char *consumer)
+{
+ strncpy(config->consumer, consumer, GPIO_MAX_NAME_SIZE - 1);
+ config->consumer[GPIO_MAX_NAME_SIZE - 1] = '\0';
+}
+
+GPIOD_API const char *
+gpiod_request_config_get_consumer(struct gpiod_request_config *config)
+{
+ return config->consumer[0] == '\0' ? NULL : config->consumer;
+}
+
+GPIOD_API void
+gpiod_request_config_set_event_buffer_size(struct gpiod_request_config *config,
+ size_t event_buffer_size)
+{
+ config->event_buffer_size = event_buffer_size;
+}
+
+GPIOD_API size_t
+gpiod_request_config_get_event_buffer_size(struct gpiod_request_config *config)
+{
+ return config->event_buffer_size;
+}
+
+void gpiod_request_config_to_uapi(struct gpiod_request_config *config,
+ struct gpio_v2_line_request *uapi_req)
+{
+ strcpy(uapi_req->consumer, config->consumer);
+ uapi_req->event_buffer_size = config->event_buffer_size;
+}
* @GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN: line has pull-down bias enabled
* @GPIO_V2_LINE_FLAG_BIAS_DISABLED: line has bias disabled
* @GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME: line events contain REALTIME timestamps
+ * @GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE: line events contain timestamps from
+ * hardware timestamp engine
*/
enum gpio_v2_line_flag {
GPIO_V2_LINE_FLAG_USED = _BITULL(0),
GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN = _BITULL(9),
GPIO_V2_LINE_FLAG_BIAS_DISABLED = _BITULL(10),
GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME = _BITULL(11),
+ GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE = _BITULL(12),
};
/**
# SPDX-License-Identifier: GPL-2.0-or-later
# SPDX-FileCopyrightText: 2017-2022 Bartosz Golaszewski <brgl@bgdev.pl>
-SUBDIRS = mockup gpiosim
+SUBDIRS = gpiosim
AM_CFLAGS = -I$(top_srcdir)/include/ -I$(top_srcdir)/tests/gpiosim/
AM_CFLAGS += -include $(top_builddir)/config.h
-AM_CFLAGS += -Wall -Wextra -g -std=gnu89 $(GLIB_CFLAGS)
+AM_CFLAGS += -Wall -Wextra -g -std=gnu89 $(GLIB_CFLAGS) $(GOBJECT_CFLAGS)
AM_CFLAGS += -DG_LOG_DOMAIN=\"gpiod-test\"
+AM_CFLAGS += $(PROFILING_CFLAGS)
AM_LDFLAGS = -pthread
LDADD = $(top_builddir)/lib/libgpiod.la
LDADD += $(top_builddir)/tests/gpiosim/libgpiosim.la
-LDADD += $(GLIB_LIBS)
+LDADD += $(GLIB_LIBS) $(GOBJECT_LIBS)
bin_PROGRAMS = gpiod-test
gpiod_test_SOURCES = \
gpiod-test.c \
gpiod-test.h \
+ gpiod-test-helpers.c \
+ gpiod-test-helpers.h \
+ gpiod-test-sim.c \
+ gpiod-test-sim.h \
tests-chip.c \
- tests-event.c \
- tests-line.c \
- tests-misc.c
+ tests-chip-info.c \
+ tests-edge-event.c \
+ tests-info-event.c \
+ tests-line-config.c \
+ tests-line-info.c \
+ tests-line-request.c \
+ tests-line-settings.c \
+ tests-misc.c \
+ tests-request-config.c
\ No newline at end of file
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* SPDX-FileCopyrightText: 2017-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+/*
+ * Testing framework for the core library.
+ *
+ * This file contains functions and definitions extending the GLib unit testing
+ * framework with functionalities necessary to test the libgpiod core C API as
+ * well as the kernel-to-user-space interface.
+ */
+
+#include "gpiod-test-helpers.h"
+
+GVariant *
+gpiod_test_package_line_names(const struct gpiod_test_line_name *names)
+{
+ const struct gpiod_test_line_name *name;
+ GVariantBuilder *builder;
+ GVariant *ret;
+
+ builder = g_variant_builder_new(G_VARIANT_TYPE("a(us)"));
+
+ for (name = &names[0]; name->name; name++)
+ g_variant_builder_add(builder, "(us)",
+ name->offset, name->name);
+
+ ret = g_variant_new("a(us)", builder);
+ g_variant_builder_unref(builder);
+
+ return ret;
+}
+
+GVariant *gpiod_test_package_hogs(const struct gpiod_test_hog *hogs)
+{
+ const struct gpiod_test_hog *hog;
+ GVariantBuilder *builder;
+ GVariant *ret;
+
+ builder = g_variant_builder_new(G_VARIANT_TYPE("a(usi)"));
+
+ for (hog = &hogs[0]; hog->name; hog++)
+ g_variant_builder_add(builder, "(usi)",
+ hog->offset, hog->name, hog->direction);
+
+ ret = g_variant_new("a(usi)", builder);
+ g_variant_builder_unref(builder);
+
+ return ret;
+}
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+#ifndef __GPIOD_TEST_HELPERS_H__
+#define __GPIOD_TEST_HELPERS_H__
+
+#include <errno.h>
+#include <glib.h>
+#include <gpiod.h>
+
+#include "gpiod-test-sim.h"
+
+/*
+ * These typedefs are needed to make g_autoptr work - it doesn't accept
+ * regular 'struct typename' syntax.
+ */
+
+typedef struct gpiod_chip struct_gpiod_chip;
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(struct_gpiod_chip, gpiod_chip_close);
+
+typedef struct gpiod_chip_info struct_gpiod_chip_info;
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(struct_gpiod_chip_info, gpiod_chip_info_free);
+
+typedef struct gpiod_line_info struct_gpiod_line_info;
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(struct_gpiod_line_info, gpiod_line_info_free);
+
+typedef struct gpiod_info_event struct_gpiod_info_event;
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(struct_gpiod_info_event, gpiod_info_event_free);
+
+typedef struct gpiod_line_config struct_gpiod_line_config;
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(struct_gpiod_line_config, gpiod_line_config_free);
+
+typedef struct gpiod_line_settings struct_gpiod_line_settings;
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(struct_gpiod_line_settings,
+ gpiod_line_settings_free);
+
+typedef struct gpiod_request_config struct_gpiod_request_config;
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(struct_gpiod_request_config,
+ gpiod_request_config_free);
+
+typedef struct gpiod_line_request struct_gpiod_line_request;
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(struct_gpiod_line_request,
+ gpiod_line_request_release);
+
+typedef struct gpiod_edge_event struct_gpiod_edge_event;
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(struct_gpiod_edge_event, gpiod_edge_event_free);
+
+typedef struct gpiod_edge_event_buffer struct_gpiod_edge_event_buffer;
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(struct_gpiod_edge_event_buffer,
+ gpiod_edge_event_buffer_free);
+
+#define gpiod_test_return_if_failed() \
+ do { \
+ if (g_test_failed()) \
+ return; \
+ } while (0)
+
+#define gpiod_test_join_thread_and_return_if_failed(_thread) \
+ do { \
+ if (g_test_failed()) { \
+ g_thread_join(_thread); \
+ return; \
+ } \
+ } while (0)
+
+#define gpiod_test_open_chip_or_fail(_path) \
+ ({ \
+ struct gpiod_chip *_chip = gpiod_chip_open((_path)); \
+ g_assert_nonnull(_chip); \
+ gpiod_test_return_if_failed(); \
+ _chip; \
+ })
+
+#define gpiod_test_get_chip_info_or_fail(_chip) \
+ ({ \
+ struct gpiod_chip_info *_info = gpiod_chip_get_info(_chip); \
+ g_assert_nonnull(_info); \
+ gpiod_test_return_if_failed(); \
+ _info; \
+ })
+
+#define gpiod_test_get_line_info_or_fail(_chip, _offset) \
+ ({ \
+ struct gpiod_line_info *_info = \
+ gpiod_chip_get_line_info((_chip), (_offset)); \
+ g_assert_nonnull(_info); \
+ gpiod_test_return_if_failed(); \
+ _info; \
+ })
+
+#define gpiod_test_create_line_settings_or_fail() \
+ ({ \
+ struct gpiod_line_settings *_settings = \
+ gpiod_line_settings_new(); \
+ g_assert_nonnull(_settings); \
+ gpiod_test_return_if_failed(); \
+ _settings; \
+ })
+
+#define gpiod_test_create_line_config_or_fail() \
+ ({ \
+ struct gpiod_line_config *_config = \
+ gpiod_line_config_new(); \
+ g_assert_nonnull(_config); \
+ gpiod_test_return_if_failed(); \
+ _config; \
+ })
+
+#define gpiod_test_create_edge_event_buffer_or_fail(_capacity) \
+ ({ \
+ struct gpiod_edge_event_buffer *_buffer = \
+ gpiod_edge_event_buffer_new(_capacity); \
+ g_assert_nonnull(_buffer); \
+ gpiod_test_return_if_failed(); \
+ _buffer; \
+ })
+
+#define gpiod_test_line_config_add_line_settings_or_fail(_line_cfg, _offsets, \
+ _num_offsets, _settings) \
+ do { \
+ gint ret = gpiod_line_config_add_line_settings((_line_cfg), \
+ (_offsets), \
+ (_num_offsets), \
+ (_settings)); \
+ g_assert_cmpint(ret, ==, 0); \
+ gpiod_test_return_if_failed(); \
+ } while (0)
+
+#define gpiod_test_create_request_config_or_fail() \
+ ({ \
+ struct gpiod_request_config *_config = \
+ gpiod_request_config_new(); \
+ g_assert_nonnull(_config); \
+ gpiod_test_return_if_failed(); \
+ _config; \
+ })
+
+#define gpiod_test_request_lines_or_fail(_chip, _req_cfg, _line_cfg) \
+ ({ \
+ struct gpiod_line_request *_request = \
+ gpiod_chip_request_lines((_chip), \
+ (_req_cfg), (_line_cfg)); \
+ g_assert_nonnull(_request); \
+ gpiod_test_return_if_failed(); \
+ _request; \
+ })
+
+#define gpiod_test_reconfigure_lines_or_fail(_request, _line_cfg) \
+ do { \
+ gint ret = gpiod_line_request_reconfigure_lines((_request), \
+ (_line_cfg)); \
+ g_assert_cmpint(ret, ==, 0); \
+ gpiod_test_return_if_failed(); \
+ } while (0)
+
+#define gpiod_test_expect_errno(_expected) \
+ g_assert_cmpint((_expected), ==, errno)
+
+struct gpiod_test_line_name {
+ guint offset;
+ const gchar *name;
+};
+
+struct gpiod_test_hog {
+ guint offset;
+ const gchar *name;
+ GPIOSimHogDir direction;
+};
+
+GVariant *
+gpiod_test_package_line_names(const struct gpiod_test_line_name *names);
+GVariant *gpiod_test_package_hogs(const struct gpiod_test_hog *hogs);
+
+#endif /* __GPIOD_TEST_HELPERS_H__ */
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/* SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+#include <errno.h>
+#include <gpiosim.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "gpiod-test-sim.h"
+
+struct _GPIOSimChip {
+ GObject parent_instance;
+ struct gpiosim_bank *bank;
+};
+
+enum {
+ G_GPIOSIM_CHIP_PROP_DEV_PATH = 1,
+ G_GPIOSIM_CHIP_PROP_NAME,
+ G_GPIOSIM_CHIP_PROP_NUM_LINES,
+ G_GPIOSIM_CHIP_PROP_LABEL,
+ G_GPIOSIM_CHIP_PROP_LINE_NAMES,
+ G_GPIOSIM_CHIP_PROP_HOGS,
+};
+
+static struct gpiosim_ctx *sim_ctx;
+
+G_DEFINE_TYPE(GPIOSimChip, g_gpiosim_chip, G_TYPE_OBJECT);
+
+static void g_gpiosim_ctx_unref(void)
+{
+ gpiosim_ctx_unref(sim_ctx);
+}
+
+static void g_gpiosim_ctx_init(void)
+{
+ sim_ctx = gpiosim_ctx_new();
+ if (!sim_ctx)
+ g_error("Unable to initialize libgpiosim: %s",
+ g_strerror(errno));
+
+ atexit(g_gpiosim_ctx_unref);
+}
+
+static void g_gpiosim_chip_constructed(GObject *obj)
+{
+ GPIOSimChip *self = G_GPIOSIM_CHIP(obj);
+ struct gpiosim_dev *dev;
+ gint ret;
+
+ dev = gpiosim_bank_get_dev(self->bank);
+ ret = gpiosim_dev_enable(dev);
+ gpiosim_dev_unref(dev);
+ if (ret)
+ g_error("Error while trying to enable the simulated GPIO device: %s",
+ g_strerror(errno));
+}
+
+static void g_gpiosim_chip_get_property(GObject *obj, guint prop_id,
+ GValue *val, GParamSpec *pspec)
+{
+ GPIOSimChip *self = G_GPIOSIM_CHIP(obj);
+
+ switch (prop_id) {
+ case G_GPIOSIM_CHIP_PROP_DEV_PATH:
+ g_value_set_static_string(val,
+ gpiosim_bank_get_dev_path(self->bank));
+ break;
+ case G_GPIOSIM_CHIP_PROP_NAME:
+ g_value_set_static_string(val,
+ gpiosim_bank_get_chip_name(self->bank));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
+ break;
+ }
+}
+
+static void g_gpiosim_chip_set_property(GObject *obj, guint prop_id,
+ const GValue *val, GParamSpec *pspec)
+{
+ GPIOSimChip *self = G_GPIOSIM_CHIP(obj);
+ gint ret, vdir, dir;
+ GVariantIter *iter;
+ GVariant *variant;
+ guint offset;
+ gchar *name;
+
+ switch (prop_id) {
+ case G_GPIOSIM_CHIP_PROP_NUM_LINES:
+ ret = gpiosim_bank_set_num_lines(self->bank,
+ g_value_get_uint(val));
+ if (ret)
+ g_error("Unable to set the number of lines exposed by the simulated chip: %s",
+ g_strerror(errno));
+ break;
+ case G_GPIOSIM_CHIP_PROP_LABEL:
+ ret = gpiosim_bank_set_label(self->bank,
+ g_value_get_string(val));
+ if (ret)
+ g_error("Unable to set the label of the simulated chip: %s",
+ g_strerror(errno));
+ break;
+ case G_GPIOSIM_CHIP_PROP_LINE_NAMES:
+ variant = g_value_get_variant(val);
+ if (!variant)
+ break;
+
+ iter = g_variant_iter_new(variant);
+
+ while (g_variant_iter_loop(iter, "(us)", &offset, &name)) {
+ ret = gpiosim_bank_set_line_name(self->bank,
+ offset, name);
+ if (ret)
+ g_error("Unable to set the name of the simulated GPIO line: %s",
+ g_strerror(errno));
+ }
+
+ g_variant_iter_free(iter);
+ break;
+ case G_GPIOSIM_CHIP_PROP_HOGS:
+ variant = g_value_get_variant(val);
+ if (!variant)
+ break;
+
+ iter = g_variant_iter_new(variant);
+
+ while (g_variant_iter_loop(iter, "(usi)",
+ &offset, &name, &vdir)) {
+ switch (vdir) {
+ case G_GPIOSIM_HOG_DIR_INPUT:
+ dir = GPIOSIM_HOG_DIR_INPUT;
+ break;
+ case G_GPIOSIM_HOG_DIR_OUTPUT_HIGH:
+ dir = GPIOSIM_HOG_DIR_OUTPUT_HIGH;
+ break;
+ case G_GPIOSIM_HOG_DIR_OUTPUT_LOW:
+ dir = GPIOSIM_HOG_DIR_OUTPUT_LOW;
+ break;
+ default:
+ g_error("Invalid hog direction value: %d",
+ vdir);
+ }
+
+ ret = gpiosim_bank_hog_line(self->bank,
+ offset, name, dir);
+ if (ret)
+ g_error("Unable to hog the simulated GPIO line: %s",
+ g_strerror(errno));
+ }
+
+ g_variant_iter_free(iter);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
+ break;
+ }
+}
+
+static void g_gpiosim_chip_dispose(GObject *obj)
+{
+ GPIOSimChip *self = G_GPIOSIM_CHIP(obj);
+ struct gpiosim_dev *dev;
+ gint ret;
+
+ dev = gpiosim_bank_get_dev(self->bank);
+
+ if (gpiosim_dev_is_live(dev)) {
+ ret = gpiosim_dev_disable(dev);
+ if (ret)
+ g_error("Error while trying to disable the simulated GPIO device: %s",
+ g_strerror(errno));
+ }
+
+ gpiosim_dev_unref(dev);
+}
+
+static void g_gpiosim_chip_finalize(GObject *obj)
+{
+ GPIOSimChip *self = G_GPIOSIM_CHIP(obj);
+
+ gpiosim_bank_unref(self->bank);
+
+ G_OBJECT_CLASS(g_gpiosim_chip_parent_class)->finalize(obj);
+}
+
+static void g_gpiosim_chip_class_init(GPIOSimChipClass *chip_class)
+{
+ GObjectClass *class = G_OBJECT_CLASS(chip_class);
+
+ class->constructed = g_gpiosim_chip_constructed;
+ class->get_property = g_gpiosim_chip_get_property;
+ class->set_property = g_gpiosim_chip_set_property;
+ class->dispose = g_gpiosim_chip_dispose;
+ class->finalize = g_gpiosim_chip_finalize;
+
+ g_object_class_install_property(class, G_GPIOSIM_CHIP_PROP_DEV_PATH,
+ g_param_spec_string("dev-path", "Device path",
+ "Character device filesystem path.", NULL,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property(class, G_GPIOSIM_CHIP_PROP_NAME,
+ g_param_spec_string("name", "Chip name",
+ "Name of this chip device as set by the kernel.", NULL,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property(class, G_GPIOSIM_CHIP_PROP_NUM_LINES,
+ g_param_spec_uint("num-lines", "Number of lines",
+ "Number of lines this simulated chip exposes.",
+ 1, G_MAXUINT, 1,
+ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property(class, G_GPIOSIM_CHIP_PROP_LABEL,
+ g_param_spec_string("label", "Chip label",
+ "Label of this simulated chip.", NULL,
+ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property(class, G_GPIOSIM_CHIP_PROP_LINE_NAMES,
+ g_param_spec_variant("line-names", "Line names",
+ "List of names of the lines exposed by this chip",
+ (GVariantType *)"a(us)", NULL,
+ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property(class, G_GPIOSIM_CHIP_PROP_HOGS,
+ g_param_spec_variant("hogs", "Line hogs",
+ "List of hogged lines and their directions.",
+ (GVariantType *)"a(usi)", NULL,
+ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void g_gpiosim_chip_init(GPIOSimChip *self)
+{
+ g_autofree gchar *dev_name = NULL;
+ struct gpiosim_dev *dev;
+
+ if (!sim_ctx)
+ g_gpiosim_ctx_init();
+
+ dev = gpiosim_dev_new(sim_ctx);
+ if (!dev)
+ g_error("Unable to instantiate new GPIO device: %s",
+ g_strerror(errno));
+
+ self->bank = gpiosim_bank_new(dev);
+ gpiosim_dev_unref(dev);
+ if (!self->bank)
+ g_error("Unable to instantiate new GPIO bank: %s",
+ g_strerror(errno));
+}
+
+static const gchar *
+g_gpiosim_chip_get_string_prop(GPIOSimChip *self, const gchar *prop)
+{
+ GValue val = G_VALUE_INIT;
+ const gchar *str;
+
+ g_object_get_property(G_OBJECT(self), prop, &val);
+ str = g_value_get_string(&val);
+ g_value_unset(&val);
+
+ return str;
+}
+
+const gchar *g_gpiosim_chip_get_dev_path(GPIOSimChip *self)
+{
+ return g_gpiosim_chip_get_string_prop(self, "dev-path");
+}
+
+const gchar *g_gpiosim_chip_get_name(GPIOSimChip *self)
+{
+ return g_gpiosim_chip_get_string_prop(self, "name");
+}
+
+gint g_gpiosim_chip_get_value(GPIOSimChip *chip, guint offset)
+{
+ gint val;
+
+ val = gpiosim_bank_get_value(chip->bank, offset);
+ if (val < 0)
+ g_error("Unable to read the line value: %s", g_strerror(errno));
+
+ return val;
+}
+
+void g_gpiosim_chip_set_pull(GPIOSimChip *chip, guint offset, GPIOSimPull pull)
+{
+ gint ret, sim_pull;
+
+ switch (pull) {
+ case G_GPIOSIM_PULL_DOWN:
+ sim_pull = GPIOSIM_PULL_DOWN;
+ break;
+ case G_GPIOSIM_PULL_UP:
+ sim_pull = GPIOSIM_PULL_UP;
+ break;
+ default:
+ g_error("invalid pull value");
+ }
+
+ ret = gpiosim_bank_set_pull(chip->bank, offset, sim_pull);
+ if (ret)
+ g_error("Unable to set the pull setting for simulated line: %s",
+ g_strerror(errno));
+}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/* SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+#ifndef __GPIOD_TEST_SIM_H__
+#define __GPIOD_TEST_SIM_H__
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+ G_GPIOSIM_PULL_UP = 1,
+ G_GPIOSIM_PULL_DOWN,
+} GPIOSimPull;
+
+typedef enum {
+ G_GPIOSIM_HOG_DIR_INPUT = 1,
+ G_GPIOSIM_HOG_DIR_OUTPUT_HIGH,
+ G_GPIOSIM_HOG_DIR_OUTPUT_LOW,
+} GPIOSimHogDir;
+
+typedef struct _GPIOSimChip GPIOSimChip;
+
+G_DECLARE_FINAL_TYPE(GPIOSimChip, g_gpiosim_chip, G_GPIOSIM, CHIP, GObject);
+
+#define G_GPIOSIM_TYPE_CHIP (g_gpiosim_chip_get_type())
+#define G_GPIOSIM_CHIP(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), G_GPIOSIM_TYPE_CHIP, GPIOSimChip))
+
+#define g_gpiosim_chip_new(...) \
+ G_GPIOSIM_CHIP(g_object_new(G_GPIOSIM_TYPE_CHIP, __VA_ARGS__))
+
+const gchar *g_gpiosim_chip_get_dev_path(GPIOSimChip *self);
+const gchar *g_gpiosim_chip_get_name(GPIOSimChip *self);
+
+gint g_gpiosim_chip_get_value(GPIOSimChip *self, guint offset);
+void g_gpiosim_chip_set_pull(GPIOSimChip *self, guint offset, GPIOSimPull pull);
+
+G_END_DECLS
+
+#endif /* __GPIOD_TEST_SIM_H__ */
// SPDX-FileCopyrightText: 2017-2022 Bartosz Golaszewski <brgl@bgdev.pl>
#include <errno.h>
-#include <glib/gstdio.h>
-#include <gpiosim.h>
#include <linux/version.h>
#include <stdio.h>
#include <sys/utsname.h>
#include "gpiod-test.h"
#define MIN_KERNEL_MAJOR 5
-#define MIN_KERNEL_MINOR 10
+#define MIN_KERNEL_MINOR 19
#define MIN_KERNEL_RELEASE 0
#define MIN_KERNEL_VERSION KERNEL_VERSION(MIN_KERNEL_MAJOR, \
MIN_KERNEL_MINOR, \
MIN_KERNEL_RELEASE)
-struct gpiod_test_event_thread {
- GThread *id;
- GMutex lock;
- GCond cond;
- gboolean should_stop;
- guint chip_index;
- guint line_offset;
- guint period_ms;
-};
-
-static struct {
- GList *tests;
- struct gpiosim_ctx *gpiosim;
- GPtrArray *sim_chips;
- GPtrArray *sim_banks;
-} globals;
+static GList *tests;
static void check_kernel(void)
{
return;
}
-static void remove_gpiosim_chip(gpointer data)
-{
- struct gpiosim_dev *dev = data;
- gint ret;
-
- ret = gpiosim_dev_disable(dev);
- if (ret)
- g_error("unable to uncommit a simulated GPIO device: %s",
- g_strerror(errno));
-
- gpiosim_dev_unref(dev);
-}
-
-static void remove_gpiosim_bank(gpointer data)
-{
- struct gpiosim_bank *bank = data;
-
- gpiosim_bank_unref(bank);
-}
-
static void test_func_wrapper(gconstpointer data)
{
- const _GpiodTestCase *test = data;
- struct gpiosim_bank *sim_bank;
- struct gpiosim_dev *sim_dev;
- gchar *line_name, *label;
- gchar chip_idx;
- guint i, j;
- gint ret;
-
- globals.sim_chips = g_ptr_array_new_full(test->num_chips,
- remove_gpiosim_chip);
- globals.sim_banks = g_ptr_array_new_full(test->num_chips,
- remove_gpiosim_bank);
-
- for (i = 0; i < test->num_chips; i++) {
- chip_idx = i + 65;
-
- sim_dev = gpiosim_dev_new(globals.gpiosim, NULL);
- if (!sim_dev)
- g_error("unable to create a simulated GPIO chip: %s",
- g_strerror(errno));
-
- sim_bank = gpiosim_bank_new(sim_dev, NULL);
- if (!sim_bank)
- g_error("unable to create a simulated GPIO bank: %s",
- g_strerror(errno));
-
- label = g_strdup_printf("gpio-mockup-%c", chip_idx);
- ret = gpiosim_bank_set_label(sim_bank, label);
- g_free(label);
- if (ret)
- g_error("unable to set simulated chip label: %s",
- g_strerror(errno));
-
- ret = gpiosim_bank_set_num_lines(sim_bank, test->chip_sizes[i]);
- if (ret)
- g_error("unable to set the number of lines for a simulated chip: %s",
- g_strerror(errno));
-
- if (test->flags & GPIOD_TEST_FLAG_NAMED_LINES) {
- for (j = 0; j < test->chip_sizes[i]; j++) {
- line_name = g_strdup_printf("gpio-mockup-%c-%u",
- chip_idx, j);
-
- ret = gpiosim_bank_set_line_name(sim_bank, j,
- line_name);
- g_free(line_name);
- if (ret)
- g_error("unable to set the line names for a simulated bank: %s",
- g_strerror(errno));
- }
- }
-
- ret = gpiosim_dev_enable(sim_dev);
- if (ret)
- g_error("unable to commit the simulated GPIO device: %s",
- g_strerror(errno));
-
- g_ptr_array_add(globals.sim_chips, sim_dev);
- g_ptr_array_add(globals.sim_banks, sim_bank);
- }
+ const struct _gpiod_test_case *test = data;
test->func();
-
- g_ptr_array_unref(globals.sim_banks);
- g_ptr_array_unref(globals.sim_chips);
-}
-
-static void unref_gpiosim(void)
-{
- gpiosim_ctx_unref(globals.gpiosim);
}
static void add_test_from_list(gpointer element, gpointer data G_GNUC_UNUSED)
{
- _GpiodTestCase *test = element;
+ struct _gpiod_test_case *test = element;
g_test_add_data_func(test->path, test, test_func_wrapper);
}
g_test_set_nonfatal_assertions();
g_debug("running libgpiod test suite");
- g_debug("%u tests registered", g_list_length(globals.tests));
-
- /*
- * Setup libpiosim first so that it runs its own kernel version
- * check before we tell the user our local requirements are met as
- * well.
- */
- globals.gpiosim = gpiosim_ctx_new();
- if (!globals.gpiosim)
- g_error("unable to initialize gpiosim library: %s",
- g_strerror(errno));
- atexit(unref_gpiosim);
+ g_debug("%u tests registered", g_list_length(tests));
check_kernel();
- g_list_foreach(globals.tests, add_test_from_list, NULL);
- g_list_free(globals.tests);
+ g_list_foreach(tests, add_test_from_list, NULL);
+ g_list_free(tests);
return g_test_run();
}
-void _gpiod_test_register(_GpiodTestCase *test)
-{
- globals.tests = g_list_append(globals.tests, test);
-}
-
-const gchar *gpiod_test_chip_path(guint idx)
-{
- struct gpiosim_bank *bank = g_ptr_array_index(globals.sim_banks, idx);
-
- return gpiosim_bank_get_dev_path(bank);
-}
-
-const gchar *gpiod_test_chip_name(guint idx)
-{
- struct gpiosim_bank *bank = g_ptr_array_index(globals.sim_banks, idx);
-
- return gpiosim_bank_get_chip_name(bank);
-}
-
-gint gpiod_test_chip_get_value(guint chip_index, guint line_offset)
-{
- struct gpiosim_bank *bank = g_ptr_array_index(globals.sim_banks,
- chip_index);
- gint ret;
-
- ret = gpiosim_bank_get_value(bank, line_offset);
- if (ret < 0)
- g_error("unable to read line value from gpiosim: %s",
- g_strerror(errno));
-
- return ret;
-}
-
-void gpiod_test_chip_set_pull(guint chip_index, guint line_offset, gint pull)
-{
- struct gpiosim_bank *bank = g_ptr_array_index(globals.sim_banks,
- chip_index);
- gint ret;
-
- ret = gpiosim_bank_set_pull(bank, line_offset,
- pull ? GPIOSIM_PULL_UP : GPIOSIM_PULL_DOWN);
- if (ret)
- g_error("unable to set line pull in gpiosim: %s",
- g_strerror(errno));
-}
-
-static gpointer event_worker_func(gpointer data)
-{
- GpiodTestEventThread *thread = data;
- gboolean signalled;
- gint64 end_time;
- gint i;
-
- for (i = 0;; i++) {
- g_mutex_lock(&thread->lock);
- if (thread->should_stop) {
- g_mutex_unlock(&thread->lock);
- break;
- }
-
- end_time = g_get_monotonic_time() + thread->period_ms * 1000;
-
- signalled = g_cond_wait_until(&thread->cond,
- &thread->lock, end_time);
- if (!signalled)
- gpiod_test_chip_set_pull(thread->chip_index,
- thread->line_offset, i % 2);
-
- g_mutex_unlock(&thread->lock);
- }
-
- return NULL;
-}
-
-GpiodTestEventThread *
-gpiod_test_start_event_thread(guint chip_index, guint line_offset, guint period_ms)
+void _gpiod_test_register(struct _gpiod_test_case *test)
{
- GpiodTestEventThread *thread = g_malloc0(sizeof(*thread));
-
- g_mutex_init(&thread->lock);
- g_cond_init(&thread->cond);
-
- thread->chip_index = chip_index;
- thread->line_offset = line_offset;
- thread->period_ms = period_ms;
-
- thread->id = g_thread_new("event-worker", event_worker_func, thread);
-
- return thread;
-}
-
-void gpiod_test_stop_event_thread(GpiodTestEventThread *thread)
-{
- g_mutex_lock(&thread->lock);
- thread->should_stop = TRUE;
- g_cond_broadcast(&thread->cond);
- g_mutex_unlock(&thread->lock);
-
- (void)g_thread_join(thread->id);
-
- g_mutex_clear(&thread->lock);
- g_cond_clear(&thread->cond);
- g_free(thread);
+ tests = g_list_append(tests, test);
}
#define __GPIOD_TEST_H__
#include <glib.h>
-#include <gpiod.h>
-
-/*
- * These typedefs are needed to make g_autoptr work - it doesn't accept
- * regular 'struct typename' syntax.
- */
-typedef struct gpiod_chip gpiod_chip_struct;
-typedef struct gpiod_line_bulk gpiod_line_bulk_struct;
-
-G_DEFINE_AUTOPTR_CLEANUP_FUNC(gpiod_chip_struct, gpiod_chip_unref);
-G_DEFINE_AUTOPTR_CLEANUP_FUNC(gpiod_line_bulk_struct, gpiod_line_bulk_free);
/* These are private definitions and should not be used directly. */
-typedef void (*_gpiod_test_func)(void);
-typedef struct _gpiod_test_case _GpiodTestCase;
struct _gpiod_test_case {
const gchar *path;
- _gpiod_test_func func;
-
- guint num_chips;
- guint *chip_sizes;
- gint flags;
+ void (*func)(void);
};
-void _gpiod_test_register(_GpiodTestCase *test);
+void _gpiod_test_register(struct _gpiod_test_case *test);
#define _GPIOD_TEST_PATH(_name) \
"/gpiod/" GPIOD_TEST_GROUP "/" G_STRINGIFY(_name)
-enum {
- /* Dummy lines for this test case should have names assigned. */
- GPIOD_TEST_FLAG_NAMED_LINES = (1 << 0),
-};
-
/*
- * Register a test case function. The last argument is the array of numbers
- * of lines per simulated chip.
+ * Register a test case function.
*/
-#define GPIOD_TEST_CASE(_name, _flags, ...) \
- static void _name(void); \
- static guint _##_name##_chip_sizes[] = __VA_ARGS__; \
- static _GpiodTestCase _##_name##_test_case = { \
- .path = _GPIOD_TEST_PATH(_name), \
- .func = _name, \
- .num_chips = G_N_ELEMENTS(_##_name##_chip_sizes), \
- .chip_sizes = _##_name##_chip_sizes, \
- .flags = _flags, \
- }; \
- static __attribute__((constructor)) void \
- _##_name##_test_register(void) \
- { \
- _gpiod_test_register(&_##_name##_test_case); \
- } \
- static void _name(void)
-
-#define GPIOD_TEST_CONSUMER "gpiod-test"
-
-#define gpiod_test_return_if_failed() \
- do { \
- if (g_test_failed()) \
- return; \
- } while (0)
-
-/* Wrappers around libgpiomockup helpers. */
-const gchar *gpiod_test_chip_path(guint idx);
-const gchar *gpiod_test_chip_name(guint idx);
-gint gpiod_test_chip_get_value(guint chip_index, guint line_offset);
-void gpiod_test_chip_set_pull(guint chip_index, guint line_offset, gint pull);
-
-/* Helpers for triggering line events in a separate thread. */
-struct gpiod_test_event_thread;
-typedef struct gpiod_test_event_thread GpiodTestEventThread;
-
-GpiodTestEventThread *
-gpiod_test_start_event_thread(guint chip_index,
- guint line_offset, guint period_ms);
-void gpiod_test_stop_event_thread(GpiodTestEventThread *thread);
-
-G_DEFINE_AUTOPTR_CLEANUP_FUNC(GpiodTestEventThread,
- gpiod_test_stop_event_thread);
+#define GPIOD_TEST_CASE(_name) \
+ static void _gpiod_test_func_##_name(void); \
+ static struct _gpiod_test_case _##_name##_test_case = { \
+ .path = _GPIOD_TEST_PATH(_name), \
+ .func = _gpiod_test_func_##_name, \
+ }; \
+ static __attribute__((constructor)) void \
+ _##_name##_test_register(void) \
+ { \
+ _gpiod_test_register(&_##_name##_test_case); \
+ } \
+ static void _gpiod_test_func_##_name(void)
#endif /* __GPIOD_TEST_H__ */
libgpiosim_la_SOURCES = gpiosim.c gpiosim.h
libgpiosim_la_CFLAGS = $(AM_CFLAGS) $(KMOD_CFLAGS) $(MOUNT_CFLAGS)
libgpiosim_la_LDFLAGS = -version-info $(subst .,:,$(ABI_GPIOSIM_VERSION))
-libgpiosim_la_LDFLAGS += $(KMOD_LIBS) $(MOUNT_LIBS)
+libgpiosim_la_LDFLAGS += $(KMOD_LIBS) $(MOUNT_LIBS) -pthread
gpiosim_selftest_SOURCES = gpiosim-selftest.c
gpiosim_selftest_LDADD = libgpiosim.la
return EXIT_FAILURE;
}
- printf("Creating a chip with random name\n");
+ printf("Creating a chip\n");
- dev = gpiosim_dev_new(ctx, NULL);
+ dev = gpiosim_dev_new(ctx);
if (!dev) {
- perror("Unable to create a chip with random name");
+ perror("Unable to create a chip");
return EXIT_FAILURE;
}
- printf("Creating a bank with a random name\n");
+ printf("Creating a bank\n");
- bank0 = gpiosim_bank_new(dev, NULL);
+ bank0 = gpiosim_bank_new(dev);
if (!bank0) {
- perror("Unable to create a bank with random name");
+ perror("Unable to create a bank");
return EXIT_FAILURE;
}
- printf("Creating a bank with a specific name\n");
+ printf("Creating a second bank\n");
- bank1 = gpiosim_bank_new(dev, "foobar");
+ bank1 = gpiosim_bank_new(dev);
if (!bank1) {
- perror("Unable to create a bank with a specific name");
+ perror("Unable to create a bank");
return EXIT_FAILURE;
}
#include <libkmod.h>
#include <libmount.h>
#include <linux/version.h>
+#include <pthread.h>
+#include <search.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#define GPIOSIM_API __attribute__((visibility("default")))
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x)))
-/* FIXME Change the minimum version to v5.17.0 once released. */
-#define MIN_KERNEL_VERSION KERNEL_VERSION(5, 16, 0)
+#define MIN_KERNEL_VERSION KERNEL_VERSION(5, 17, 4)
+
+static pthread_mutex_t id_lock = PTHREAD_MUTEX_INITIALIZER;
+static pthread_once_t id_init_once = PTHREAD_ONCE_INIT;
+static void *id_root;
+
+struct id_find_next_ctx {
+ int lowest;
+ bool found;
+};
+
+struct id_del_ctx {
+ int id;
+ int *idp;
+};
+
+static void id_cleanup(void)
+{
+ tdestroy(id_root, free);
+}
+
+static void id_schedule_cleanup(void)
+{
+ atexit(id_cleanup);
+}
+
+static int id_compare(const void *p1, const void *p2)
+{
+ int id1 = *(int *)p1;
+ int id2 = *(int *)p2;
+
+ if (id1 < id2)
+ return -1;
+ if (id1 > id2)
+ return 1;
+ return 0;
+}
+
+static void id_find_next(const void *node, VISIT which, void *data)
+{
+ struct id_find_next_ctx *ctx = data;
+ int *id = *(int **)node;
+
+ if (ctx->found)
+ return;
+
+ switch (which) {
+ case postorder:
+ case leaf:
+ if (*id != ctx->lowest)
+ ctx->found = true;
+ else
+ ctx->lowest++;
+ break;
+ default:
+ break;
+ };
+}
+
+static void id_del(const void *node, VISIT which, void *data)
+{
+ struct id_del_ctx *ctx = data;
+ int *id = *(int **)node;
+
+ if (ctx->idp)
+ return;
+
+ switch (which) {
+ case postorder:
+ case leaf:
+ if (*id == ctx->id)
+ ctx->idp = id;
+ break;
+ default:
+ break;
+ }
+}
+
+static int id_alloc(void)
+{
+ struct id_find_next_ctx ctx;
+ void *ret;
+ int *id;
+
+ pthread_once(&id_init_once, id_schedule_cleanup);
+
+ ctx.lowest = 0;
+ ctx.found = false;
+
+ pthread_mutex_lock(&id_lock);
+
+ twalk_r(id_root, id_find_next, &ctx);
+
+ id = malloc(sizeof(*id));
+ if (!id) {
+ pthread_mutex_unlock(&id_lock);
+ return -1;
+ }
+
+ *id = ctx.lowest;
+
+ ret = tsearch(id, &id_root, id_compare);
+ if (!ret) {
+ pthread_mutex_unlock(&id_lock);
+ /* tsearch() doesn't set errno. */
+ errno = ENOMEM;
+ return -1;
+ }
+
+ pthread_mutex_unlock(&id_lock);
+
+ return *id;
+}
+
+static void id_free(int id)
+{
+ struct id_del_ctx ctx;
+
+ ctx.id = id;
+ ctx.idp = NULL;
+
+ pthread_mutex_lock(&id_lock);
+
+ twalk_r(id_root, id_del, &ctx);
+ if (ctx.idp) {
+ tdelete(ctx.idp, &id_root, id_compare);
+ free(ctx.idp);
+ }
+
+ pthread_mutex_unlock(&id_lock);
+}
struct refcount {
unsigned int cnt;
return ret;
}
-/* We don't have mkdtempat()... :( */
-static char *make_random_dir_at(int at)
+static char *configfs_make_item(int at, int id)
{
- static const char chars[] = "abcdefghijklmnoprstquvwxyz"
- "ABCDEFGHIJKLMNOPRSTQUVWXYZ"
- "0123456789";
-
- char name[] = "XXXXXXXXXXXX\0";
- unsigned int idx, i;
+ char *item_name;
int ret;
-again:
- for (i = 0; i < sizeof(name) - 1; i++) {
- ret = getrandom(&idx, sizeof(idx), GRND_NONBLOCK);
- if (ret != sizeof(idx)) {
- if (ret >= 0)
- errno = EAGAIN;
-
- return NULL;
- }
-
- name[i] = chars[idx % (ARRAY_SIZE(chars) - 1)];
- }
+ ret = asprintf(&item_name, "%s.%u.%d",
+ program_invocation_short_name, getpid(), id);
+ if (ret < 0)
+ return NULL;
- ret = mkdirat(at, name, 0600);
+ ret = mkdirat(at, item_name, 0600);
if (ret) {
- if (errno == EEXIST)
- goto again;
-
+ free(item_name);
return NULL;
}
- return strdup(name);
-}
-
-static char *configfs_make_item_name(int at, const char *name)
-{
- char *item_name;
- int ret;
-
- if (name) {
- item_name = strdup(name);
- if (!item_name)
- return NULL;
-
- ret = mkdirat(at, item_name, 0600);
- if (ret) {
- free(item_name);
- return NULL;
- }
- } else {
- item_name = make_random_dir_at(at);
- if (!item_name)
- return NULL;
- }
-
return item_name;
}
struct gpiosim_ctx *ctx;
bool live;
char *item_name;
+ int id;
char *dev_name;
int cfs_dir_fd;
int sysfs_dir_fd;
struct gpiosim_dev *dev;
struct list_head siblings;
char *item_name;
+ int id;
char *chip_name;
char *dev_path;
int cfs_dir_fd;
close(dev->cfs_dir_fd);
free(dev->dev_name);
free(dev->item_name);
+ id_free(dev->id);
gpiosim_ctx_unref(ctx);
free(dev);
}
GPIOSIM_API struct gpiosim_dev *
-gpiosim_dev_new(struct gpiosim_ctx *ctx, const char *name)
+gpiosim_dev_new(struct gpiosim_ctx *ctx)
{
+ int configfs_fd, ret, id;
struct gpiosim_dev *dev;
- int configfs_fd, ret;
char devname[128];
char *item_name;
- item_name = configfs_make_item_name(ctx->cfs_dir_fd, name);
- if (!item_name)
+ id = id_alloc();
+ if (id < 0)
return NULL;
+ item_name = configfs_make_item(ctx->cfs_dir_fd, id);
+ if (!item_name)
+ goto err_free_id;
+
configfs_fd = openat(ctx->cfs_dir_fd, item_name, O_RDONLY);
if (configfs_fd < 0)
goto err_unlink;
dev->cfs_dir_fd = configfs_fd;
dev->sysfs_dir_fd = -1;
dev->item_name = item_name;
+ dev->id = id;
dev->dev_name = strdup(devname);
if (!dev->dev_name)
err_unlink:
unlinkat(ctx->cfs_dir_fd, item_name, AT_REMOVEDIR);
free(item_name);
+err_free_id:
+ id_free(id);
return NULL;
}
unsigned int i;
char buf[64];
+ /* FIXME should be based on dirent because num_lines can change. */
for (i = 0; i < bank->num_lines; i++) {
snprintf(buf, sizeof(buf), "line%u/hog", i);
unlinkat(bank->cfs_dir_fd, buf, AT_REMOVEDIR);
/* If the device wasn't disabled yet, this fd is still open. */
close(bank->sysfs_dir_fd);
free(bank->item_name);
+ id_free(bank->id);
free(bank->chip_name);
free(bank->dev_path);
free(bank);
}
GPIOSIM_API struct gpiosim_bank*
-gpiosim_bank_new(struct gpiosim_dev *dev, const char *name)
+gpiosim_bank_new(struct gpiosim_dev *dev)
{
struct gpiosim_bank *bank;
- int configfs_fd;
+ int configfs_fd, id;
char *item_name;
if (!dev_check_pending(dev))
return NULL;
- item_name = configfs_make_item_name(dev->cfs_dir_fd, name);
- if (!item_name)
+ id = id_alloc();
+ if (id < 0)
return NULL;
+ item_name = configfs_make_item(dev->cfs_dir_fd, id);
+ if (!item_name)
+ goto err_free_id;
+
configfs_fd = openat(dev->cfs_dir_fd, item_name, O_RDONLY);
if (configfs_fd < 0)
goto err_unlink;
bank->dev = gpiosim_dev_ref(dev);
bank->item_name = item_name;
bank->num_lines = 1;
+ bank->id = id;
return bank;
close(configfs_fd);
err_unlink:
unlinkat(dev->cfs_dir_fd, item_name, AT_REMOVEDIR);
+err_free_id:
+ id_free(id);
return NULL;
}
void gpiosim_ctx_unref(struct gpiosim_ctx *ctx);
struct gpiosim_dev *
-gpiosim_dev_new(struct gpiosim_ctx *ctx, const char *name);
+gpiosim_dev_new(struct gpiosim_ctx *ctx);
struct gpiosim_dev *gpiosim_dev_ref(struct gpiosim_dev *dev);
void gpiosim_dev_unref(struct gpiosim_dev *dev);
struct gpiosim_ctx *gpiosim_dev_get_ctx(struct gpiosim_dev *dev);
bool gpiosim_dev_is_live(struct gpiosim_dev *dev);
struct gpiosim_bank*
-gpiosim_bank_new(struct gpiosim_dev *dev, const char *name);
+gpiosim_bank_new(struct gpiosim_dev *dev);
struct gpiosim_bank *gpiosim_bank_ref(struct gpiosim_bank *bank);
void gpiosim_bank_unref(struct gpiosim_bank *bank);
struct gpiosim_dev *gpiosim_bank_get_dev(struct gpiosim_bank *bank);
+++ /dev/null
-# SPDX-License-Identifier: GPL-2.0-or-later
-# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
-
-lib_LTLIBRARIES = libgpiomockup.la
-
-libgpiomockup_la_SOURCES = gpio-mockup.c gpio-mockup.h
-libgpiomockup_la_CFLAGS = -Wall -Wextra -g -fvisibility=hidden -std=gnu89
-libgpiomockup_la_CFLAGS += -include $(top_builddir)/config.h
-libgpiomockup_la_CFLAGS += $(KMOD_CFLAGS) $(UDEV_CFLAGS)
-libgpiomockup_la_LDFLAGS = -version-info $(subst .,:,$(ABI_MOCKUP_VERSION))
-libgpiomockup_la_LDFLAGS += $(KMOD_LIBS) $(UDEV_LIBS)
+++ /dev/null
-// SPDX-License-Identifier: LGPL-2.1-or-later
-// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
-
-#include <errno.h>
-#include <libkmod.h>
-#include <libudev.h>
-#include <linux/version.h>
-#include <poll.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/utsname.h>
-#include <unistd.h>
-
-#include "gpio-mockup.h"
-
-#define EXPORT __attribute__((visibility("default")))
-/*
- * The gpio-mockup features (including the debugfs interface) we're using
- * in this library have first been released in the linux kernel version below.
- */
-#define MIN_KERNEL_VERSION KERNEL_VERSION(5, 10, 0)
-
-struct gpio_mockup_chip {
- char *name;
- char *path;
- unsigned int num;
-};
-
-struct gpio_mockup {
- struct gpio_mockup_chip **chips;
- unsigned int num_chips;
- struct kmod_ctx *kmod;
- struct kmod_module *module;
- int refcount;
-};
-
-static void free_chip(struct gpio_mockup_chip *chip)
-{
- free(chip->name);
- free(chip->path);
- free(chip);
-}
-
-static bool check_kernel_version(void)
-{
- unsigned int major, minor, release;
- struct utsname un;
- int rv;
-
- rv = uname(&un);
- if (rv)
- return false;
-
- rv = sscanf(un.release, "%u.%u.%u", &major, &minor, &release);
- if (rv != 3) {
- errno = EFAULT;
- return false;
- }
-
- if (KERNEL_VERSION(major, minor, release) < MIN_KERNEL_VERSION) {
- errno = EOPNOTSUPP;
- return false;
- }
-
- return true;
-}
-
-EXPORT struct gpio_mockup *gpio_mockup_new(void)
-{
- struct gpio_mockup *ctx;
- const char *modpath;
- int rv;
-
- if (!check_kernel_version())
- goto err_out;
-
- ctx = malloc(sizeof(*ctx));
- if (!ctx)
- goto err_out;
-
- memset(ctx, 0, sizeof(*ctx));
- ctx->refcount = 1;
-
- ctx->kmod = kmod_new(NULL, NULL);
- if (!ctx->kmod)
- goto err_free_kmod;
-
- rv = kmod_module_new_from_name(ctx->kmod, "gpio-mockup", &ctx->module);
- if (rv)
- goto err_unref_module;
-
- /* First see if we can find the module. */
- modpath = kmod_module_get_path(ctx->module);
- if (!modpath) {
- errno = ENOENT;
- goto err_unref_module;
- }
-
- /*
- * Then see if we can freely load and unload it. If it's already
- * loaded - no problem, we'll remove it next anyway.
- */
- rv = kmod_module_probe_insert_module(ctx->module,
- KMOD_PROBE_IGNORE_LOADED,
- "gpio_mockup_ranges=-1,4",
- NULL, NULL, NULL);
- if (rv)
- goto err_unref_module;
-
- /* We need to check that the gpio-mockup debugfs directory exists. */
- rv = access("/sys/kernel/debug/gpio-mockup", R_OK | W_OK);
- if (rv)
- goto err_unref_module;
-
- rv = kmod_module_remove_module(ctx->module, 0);
- if (rv)
- goto err_unref_module;
-
- return ctx;
-
-err_unref_module:
- kmod_unref(ctx->kmod);
-err_free_kmod:
- free(ctx);
-err_out:
- return NULL;
-}
-
-EXPORT void gpio_mockup_ref(struct gpio_mockup *ctx)
-{
- ctx->refcount++;
-}
-
-EXPORT void gpio_mockup_unref(struct gpio_mockup *ctx)
-{
- ctx->refcount--;
-
- if (ctx->refcount == 0) {
- if (ctx->chips)
- gpio_mockup_remove(ctx);
-
- kmod_module_unref(ctx->module);
- kmod_unref(ctx->kmod);
- free(ctx);
- }
-}
-
-static char *make_module_param_string(unsigned int num_chips,
- const unsigned int *num_lines, int flags)
-{
- char *params, *new;
- unsigned int i;
- int rv;
-
- params = strdup("gpio_mockup_ranges=");
- if (!params)
- return NULL;
-
- for (i = 0; i < num_chips; i++) {
- rv = asprintf(&new, "%s-1,%u,", params, num_lines[i]);
- free(params);
- if (rv < 0)
- return NULL;
-
- params = new;
- }
- params[strlen(params) - 1] = '\0'; /* Remove the last comma. */
-
- if (flags & GPIO_MOCKUP_FLAG_NAMED_LINES) {
- rv = asprintf(&new, "%s gpio_mockup_named_lines", params);
- free(params);
- if (rv < 0)
- return NULL;
-
- params = new;
- }
-
- return params;
-}
-
-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 int chipcmp(const void *c1, const void *c2)
-{
- const struct gpio_mockup_chip *chip1, *chip2;
-
- chip1 = *(const struct gpio_mockup_chip **)c1;
- chip2 = *(const struct gpio_mockup_chip **)c2;
-
- return chip1->num > chip2->num;
-}
-
-static struct gpio_mockup_chip *make_chip(const char *sysname,
- const char *devnode)
-{
- struct gpio_mockup_chip *chip;
- int rv;
-
- chip = malloc(sizeof(*chip));
- if (!chip)
- return NULL;
-
- chip->name = strdup(sysname);
- if (!chip->name) {
- free(chip);
- return NULL;
- }
-
- chip->path = strdup(devnode);
- if (!chip->path) {
- free(chip->name);
- free(chip);
- return NULL;
- }
-
- rv = sscanf(sysname, "gpiochip%u", &chip->num);
- if (rv != 1) {
- errno = EINVAL;
- free(chip->path);
- free(chip->name);
- free(chip);
- return NULL;
- }
-
- return chip;
-}
-
-EXPORT int gpio_mockup_probe(struct gpio_mockup *ctx, unsigned int num_chips,
- const unsigned int *chip_sizes, int flags)
-{
- const char *devpath, *devnode, *sysname, *action;
- struct gpio_mockup_chip *chip;
- struct udev_monitor *monitor;
- unsigned int i, detected = 0;
- struct udev_device *dev;
- struct udev *udev_ctx;
- struct pollfd pfd;
- char *params;
- int rv;
-
- if (ctx->chips) {
- errno = EBUSY;
- goto err_out;
- }
-
- if (num_chips < 1) {
- errno = EINVAL;
- goto err_out;
- }
-
- udev_ctx = udev_new();
- if (!udev_ctx)
- goto err_out;
-
- monitor = udev_monitor_new_from_netlink(udev_ctx, "udev");
- if (!monitor)
- goto err_unref_udev;
-
- rv = udev_monitor_filter_add_match_subsystem_devtype(monitor,
- "gpio", NULL);
- if (rv < 0)
- goto err_unref_monitor;
-
- rv = udev_monitor_enable_receiving(monitor);
- if (rv < 0)
- goto err_unref_monitor;
-
- params = make_module_param_string(num_chips, chip_sizes, flags);
- if (!params)
- goto err_unref_monitor;
-
- rv = kmod_module_probe_insert_module(ctx->module,
- KMOD_PROBE_FAIL_ON_LOADED,
- params, NULL, NULL, NULL);
- free(params);
- if (rv)
- goto err_unref_monitor;
-
- ctx->chips = calloc(num_chips, sizeof(struct gpio_mockup_chip *));
- if (!ctx->chips)
- goto err_remove_module;
-
- ctx->num_chips = num_chips;
-
- pfd.fd = udev_monitor_get_fd(monitor);
- pfd.events = POLLIN | POLLPRI;
-
- while (num_chips > detected) {
- rv = poll(&pfd, 1, 5000);
- if (rv < 0) {
- goto err_free_chips;
- } if (rv == 0) {
- errno = EAGAIN;
- goto err_free_chips;
- }
-
- dev = udev_monitor_receive_device(monitor);
- if (!dev)
- goto err_free_chips;
-
- devpath = udev_device_get_devpath(dev);
- devnode = udev_device_get_devnode(dev);
- sysname = udev_device_get_sysname(dev);
- action = udev_device_get_action(dev);
-
- if (!devpath || !devnode || !sysname ||
- !devpath_is_mockup(devpath) || strcmp(action, "add") != 0) {
- udev_device_unref(dev);
- continue;
- }
-
- chip = make_chip(sysname, devnode);
- if (!chip)
- goto err_free_chips;
-
- 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 to 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);
-
- return 0;
-
-err_free_chips:
- for (i = 0; i < detected; i++)
- free_chip(ctx->chips[i]);
- free(ctx->chips);
-err_remove_module:
- kmod_module_remove_module(ctx->module, 0);
-err_unref_monitor:
- udev_monitor_unref(monitor);
-err_unref_udev:
- udev_unref(udev_ctx);
-err_out:
- return -1;
-}
-
-EXPORT int gpio_mockup_remove(struct gpio_mockup *ctx)
-{
- unsigned int i;
- int rv;
-
- if (!ctx->chips) {
- errno = ENODEV;
- return -1;
- }
-
- rv = kmod_module_remove_module(ctx->module, 0);
- if (rv)
- return -1;
-
- for (i = 0; i < ctx->num_chips; i++)
- free_chip(ctx->chips[i]);
- free(ctx->chips);
- ctx->chips = NULL;
- ctx->num_chips = 0;
-
- return 0;
-}
-
-static bool index_valid(struct gpio_mockup *ctx, unsigned int idx)
-{
- if (!ctx->chips) {
- errno = ENODEV;
- return false;
- }
-
- if (idx >= ctx->num_chips) {
- errno = EINVAL;
- return false;
- }
-
- return true;
-}
-
-EXPORT const char *
-gpio_mockup_chip_name(struct gpio_mockup *ctx, unsigned int idx)
-{
- if (!index_valid(ctx, idx))
- return NULL;
-
- return ctx->chips[idx]->name;
-}
-
-EXPORT const char *
-gpio_mockup_chip_path(struct gpio_mockup *ctx, unsigned int idx)
-{
- if (!index_valid(ctx, idx))
- return NULL;
-
- return ctx->chips[idx]->path;
-}
-
-EXPORT int gpio_mockup_chip_num(struct gpio_mockup *ctx, unsigned int idx)
-{
- if (!index_valid(ctx, idx))
- return -1;
-
- return ctx->chips[idx]->num;
-}
-
-static int debugfs_open(unsigned int chip_num,
- unsigned int line_offset, int flags)
-{
- char *path;
- int fd, rv;
-
- rv = asprintf(&path, "/sys/kernel/debug/gpio-mockup/gpiochip%u/%u",
- chip_num, line_offset);
- if (rv < 0)
- return -1;
-
- fd = open(path, flags);
- free(path);
-
- return fd;
-}
-
-EXPORT int gpio_mockup_get_value(struct gpio_mockup *ctx,
- unsigned int chip_idx,
- unsigned int line_offset)
-{
- ssize_t rd;
- char buf;
- int fd;
-
- if (!index_valid(ctx, chip_idx))
- return -1;
-
- fd = debugfs_open(ctx->chips[chip_idx]->num, line_offset, O_RDONLY);
- if (fd < 0)
- return fd;
-
- rd = read(fd, &buf, 1);
- close(fd);
- if (rd < 0)
- return rd;
- if (rd != 1) {
- errno = ENOTTY;
- return -1;
- }
- if (buf != '0' && buf != '1') {
- errno = EIO;
- return -1;
- }
-
- return buf == '0' ? 0 : 1;
-}
-
-EXPORT int gpio_mockup_set_pull(struct gpio_mockup *ctx,
- unsigned int chip_idx,
- unsigned int line_offset, int pull)
-{
- char buf[2];
- ssize_t wr;
- int fd;
-
- if (!index_valid(ctx, chip_idx))
- return -1;
-
- fd = debugfs_open(ctx->chips[chip_idx]->num, line_offset, O_WRONLY);
- if (fd < 0)
- return fd;
-
- buf[0] = pull ? '1' : '0';
- buf[1] = '\n';
-
- wr = write(fd, &buf, sizeof(buf));
- close(fd);
- if (wr < 0)
- return wr;
- if (wr != sizeof(buf)) {
- errno = EAGAIN;
- return -1;
- }
-
- return 0;
-}
+++ /dev/null
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-/* SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com> */
-
-#ifndef __GPIO_MOCKUP_H__
-#define __GPIO_MOCKUP_H__
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-struct gpio_mockup;
-
-#define GPIO_MOCKUP_FLAG_NAMED_LINES (1 << 0)
-
-struct gpio_mockup *gpio_mockup_new(void);
-void gpio_mockup_ref(struct gpio_mockup *ctx);
-void gpio_mockup_unref(struct gpio_mockup *ctx);
-
-int gpio_mockup_probe(struct gpio_mockup *ctx, unsigned int num_chips,
- const unsigned int *chip_sizes, int flags);
-int gpio_mockup_remove(struct gpio_mockup *ctx);
-
-const char *gpio_mockup_chip_name(struct gpio_mockup *ctx, unsigned int idx);
-const char *gpio_mockup_chip_path(struct gpio_mockup *ctx, unsigned int idx);
-int gpio_mockup_chip_num(struct gpio_mockup *ctx, unsigned int idx);
-
-int gpio_mockup_get_value(struct gpio_mockup *ctx,
- unsigned int chip_idx, unsigned int line_offset);
-int gpio_mockup_set_pull(struct gpio_mockup *ctx, unsigned int chip_idx,
- unsigned int line_offset, int pull);
-
-#ifdef __cplusplus
-} /* extern "C" */
-#endif
-
-#endif /* __GPIO_MOCKUP_H__ */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2017-2022 Bartosz Golaszewski <bartekgola@gmail.com>
+
+#include <errno.h>
+#include <glib.h>
+#include <gpiod.h>
+
+#include "gpiod-test.h"
+#include "gpiod-test-helpers.h"
+#include "gpiod-test-sim.h"
+
+#define GPIOD_TEST_GROUP "chip-info"
+
+GPIOD_TEST_CASE(get_chip_info_name)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new(NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_chip_info) info = NULL;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ info = gpiod_test_get_chip_info_or_fail(chip);
+
+ g_assert_cmpstr(gpiod_chip_info_get_name(info), ==,
+ g_gpiosim_chip_get_name(sim));
+}
+
+GPIOD_TEST_CASE(get_chip_info_label)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("label", "foobar",
+ NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_chip_info) info = NULL;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ info = gpiod_test_get_chip_info_or_fail(chip);
+
+ g_assert_cmpstr(gpiod_chip_info_get_label(info), ==, "foobar");
+}
+
+GPIOD_TEST_CASE(get_num_lines)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 16, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_chip_info) info = NULL;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ info = gpiod_test_get_chip_info_or_fail(chip);
+
+ g_assert_cmpuint(gpiod_chip_info_get_num_lines(info), ==, 16);
+}
+
// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
#include <errno.h>
+#include <glib.h>
+#include <gpiod.h>
#include "gpiod-test.h"
+#include "gpiod-test-helpers.h"
+#include "gpiod-test-sim.h"
#define GPIOD_TEST_GROUP "chip"
-GPIOD_TEST_CASE(is_gpiochip_good, 0, { 8 })
+GPIOD_TEST_CASE(open_chip_good)
{
- g_assert_true(gpiod_is_gpiochip_device(gpiod_test_chip_path(0)));
-}
-
-GPIOD_TEST_CASE(is_gpiochip_bad, 0, { 8 })
-{
- g_assert_false(gpiod_is_gpiochip_device("/dev/null"));
-}
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new(NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
-GPIOD_TEST_CASE(is_gpiochip_nonexistent, 0, { 8 })
-{
- g_assert_false(gpiod_is_gpiochip_device("/dev/nonexistent_gpiochip"));
+ chip = gpiod_chip_open(g_gpiosim_chip_get_dev_path(sim));
+ g_assert_nonnull(chip);
}
-GPIOD_TEST_CASE(open_good, 0, { 8 })
+GPIOD_TEST_CASE(open_chip_nonexistent)
{
- g_autoptr(gpiod_chip_struct) chip = NULL;
+ g_autoptr(struct_gpiod_chip) chip = NULL;
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
+ chip = gpiod_chip_open("/dev/nonexistent");
+ g_assert_null(chip);
+ gpiod_test_expect_errno(ENOENT);
}
-GPIOD_TEST_CASE(open_nonexistent, 0, { 8 })
+GPIOD_TEST_CASE(open_chip_not_a_character_device)
{
- struct gpiod_chip *chip;
+ g_autoptr(struct_gpiod_chip) chip = NULL;
- chip = gpiod_chip_open("/dev/nonexistent_gpiochip");
+ chip = gpiod_chip_open("/tmp");
g_assert_null(chip);
- g_assert_cmpint(errno, ==, ENOENT);
+ gpiod_test_expect_errno(ENOTTY);
}
-GPIOD_TEST_CASE(open_notty, 0, { 8 })
+GPIOD_TEST_CASE(open_chip_not_a_gpio_device)
{
- struct gpiod_chip *chip;
+ g_autoptr(struct_gpiod_chip) chip = NULL;
chip = gpiod_chip_open("/dev/null");
g_assert_null(chip);
- g_assert_cmpint(errno, ==, ENOTTY);
+ gpiod_test_expect_errno(ENODEV);
}
-GPIOD_TEST_CASE(get_name, 0, { 8, 8, 8})
+GPIOD_TEST_CASE(get_chip_path)
{
- g_autoptr(gpiod_chip_struct) chip0 = NULL;
- g_autoptr(gpiod_chip_struct) chip1 = NULL;
- g_autoptr(gpiod_chip_struct) chip2 = NULL;
-
- chip0 = gpiod_chip_open(gpiod_test_chip_path(0));
- chip1 = gpiod_chip_open(gpiod_test_chip_path(1));
- chip2 = gpiod_chip_open(gpiod_test_chip_path(2));
-
- g_assert_nonnull(chip0);
- g_assert_nonnull(chip1);
- g_assert_nonnull(chip2);
- gpiod_test_return_if_failed();
-
- g_assert_cmpstr(gpiod_chip_get_name(chip0), ==,
- gpiod_test_chip_name(0));
- g_assert_cmpstr(gpiod_chip_get_name(chip1), ==,
- gpiod_test_chip_name(1));
- g_assert_cmpstr(gpiod_chip_get_name(chip2), ==,
- gpiod_test_chip_name(2));
-}
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new(NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ const gchar *path = g_gpiosim_chip_get_dev_path(sim);
-GPIOD_TEST_CASE(get_label, 0, { 8, 8, 8})
-{
- g_autoptr(gpiod_chip_struct) chip0 = NULL;
- g_autoptr(gpiod_chip_struct) chip1 = NULL;
- g_autoptr(gpiod_chip_struct) chip2 = NULL;
-
- chip0 = gpiod_chip_open(gpiod_test_chip_path(0));
- chip1 = gpiod_chip_open(gpiod_test_chip_path(1));
- chip2 = gpiod_chip_open(gpiod_test_chip_path(2));
-
- g_assert_nonnull(chip0);
- g_assert_nonnull(chip1);
- g_assert_nonnull(chip2);
- gpiod_test_return_if_failed();
-
- g_assert_cmpstr(gpiod_chip_get_label(chip0), ==, "gpio-mockup-A");
- g_assert_cmpstr(gpiod_chip_get_label(chip1), ==, "gpio-mockup-B");
- g_assert_cmpstr(gpiod_chip_get_label(chip2), ==, "gpio-mockup-C");
-}
+ chip = gpiod_test_open_chip_or_fail(path);
-GPIOD_TEST_CASE(num_lines, 0, { 1, 4, 8, 16, 32 })
-{
- g_autoptr(gpiod_chip_struct) chip0 = NULL;
- g_autoptr(gpiod_chip_struct) chip1 = NULL;
- g_autoptr(gpiod_chip_struct) chip2 = NULL;
- g_autoptr(gpiod_chip_struct) chip3 = NULL;
- g_autoptr(gpiod_chip_struct) chip4 = NULL;
-
- chip0 = gpiod_chip_open(gpiod_test_chip_path(0));
- chip1 = gpiod_chip_open(gpiod_test_chip_path(1));
- chip2 = gpiod_chip_open(gpiod_test_chip_path(2));
- chip3 = gpiod_chip_open(gpiod_test_chip_path(3));
- chip4 = gpiod_chip_open(gpiod_test_chip_path(4));
-
- g_assert_nonnull(chip0);
- g_assert_nonnull(chip1);
- g_assert_nonnull(chip2);
- g_assert_nonnull(chip3);
- g_assert_nonnull(chip4);
- gpiod_test_return_if_failed();
-
- g_assert_cmpuint(gpiod_chip_get_num_lines(chip0), ==, 1);
- g_assert_cmpuint(gpiod_chip_get_num_lines(chip1), ==, 4);
- g_assert_cmpuint(gpiod_chip_get_num_lines(chip2), ==, 8);
- g_assert_cmpuint(gpiod_chip_get_num_lines(chip3), ==, 16);
- g_assert_cmpuint(gpiod_chip_get_num_lines(chip4), ==, 32);
+ g_assert_cmpstr(gpiod_chip_get_path(chip), ==, path);
}
-GPIOD_TEST_CASE(get_line, 0, { 16 })
+GPIOD_TEST_CASE(get_fd)
{
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct gpiod_line *line;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new(NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
- line = gpiod_chip_get_line(chip, 3);
- g_assert_nonnull(line);
- g_assert_cmpuint(gpiod_line_offset(line), ==, 3);
-}
-
-GPIOD_TEST_CASE(get_lines, 0, { 16 })
-{
- struct gpiod_line *line0, *line1, *line2, *line3;
- g_autoptr(gpiod_line_bulk_struct) bulk = NULL;
- g_autoptr(gpiod_chip_struct) chip = NULL;
- guint offsets[4];
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- offsets[0] = 1;
- offsets[1] = 3;
- offsets[2] = 4;
- offsets[3] = 7;
-
- bulk = gpiod_chip_get_lines(chip, offsets, 4);
- g_assert_nonnull(bulk);
- gpiod_test_return_if_failed();
- g_assert_cmpuint(gpiod_line_bulk_num_lines(bulk), ==, 4);
- gpiod_test_return_if_failed();
-
- line0 = gpiod_line_bulk_get_line(bulk, 0);
- line1 = gpiod_line_bulk_get_line(bulk, 1);
- line2 = gpiod_line_bulk_get_line(bulk, 2);
- line3 = gpiod_line_bulk_get_line(bulk, 3);
-
- g_assert_cmpuint(gpiod_line_offset(line0), ==, 1);
- g_assert_cmpuint(gpiod_line_offset(line1), ==, 3);
- g_assert_cmpuint(gpiod_line_offset(line2), ==, 4);
- g_assert_cmpuint(gpiod_line_offset(line3), ==, 7);
+ g_assert_cmpint(gpiod_chip_get_fd(chip), >=, 0);
}
-GPIOD_TEST_CASE(get_all_lines, 0, { 4 })
+GPIOD_TEST_CASE(find_line_bad)
{
- struct gpiod_line *line0, *line1, *line2, *line3;
- g_autoptr(gpiod_line_bulk_struct) bulk = NULL;
- g_autoptr(gpiod_chip_struct) chip = NULL;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- bulk = gpiod_chip_get_all_lines(chip);
- g_assert_nonnull(bulk);
- gpiod_test_return_if_failed();
- g_assert_cmpuint(gpiod_line_bulk_num_lines(bulk), ==, 4);
- gpiod_test_return_if_failed();
-
- line0 = gpiod_line_bulk_get_line(bulk, 0);
- line1 = gpiod_line_bulk_get_line(bulk, 1);
- line2 = gpiod_line_bulk_get_line(bulk, 2);
- line3 = gpiod_line_bulk_get_line(bulk, 3);
-
- g_assert_cmpuint(gpiod_line_offset(line0), ==, 0);
- g_assert_cmpuint(gpiod_line_offset(line1), ==, 1);
- g_assert_cmpuint(gpiod_line_offset(line2), ==, 2);
- g_assert_cmpuint(gpiod_line_offset(line3), ==, 3);
+ static const struct gpiod_test_line_name names[] = {
+ { .offset = 1, .name = "foo", },
+ { .offset = 2, .name = "bar", },
+ { .offset = 4, .name = "baz", },
+ { .offset = 5, .name = "xyz", },
+ { }
+ };
+
+ g_autoptr(GPIOSimChip) sim = NULL;
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+
+ sim = g_gpiosim_chip_new(
+ "num-lines", 8,
+ "line-names", gpiod_test_package_line_names(names),
+ NULL);
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+
+ g_assert_cmpint(
+ gpiod_chip_get_line_offset_from_name(chip,
+ "nonexistent"), ==, -1);
+ gpiod_test_expect_errno(ENOENT);
}
-GPIOD_TEST_CASE(find_line_good, GPIOD_TEST_FLAG_NAMED_LINES, { 8, 8, 8 })
+GPIOD_TEST_CASE(find_line_good)
{
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct gpiod_line *line;
- int offset;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(1));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- offset = gpiod_chip_find_line(chip, "gpio-mockup-B-4");
- g_assert_cmpint(offset, ==, 4);
- gpiod_test_return_if_failed();
-
- line = gpiod_chip_get_line(chip, 4);
- g_assert_nonnull(line);
- gpiod_test_return_if_failed();
-
- g_assert_cmpstr(gpiod_line_name(line), ==, "gpio-mockup-B-4");
+ static const struct gpiod_test_line_name names[] = {
+ { .offset = 1, .name = "foo", },
+ { .offset = 2, .name = "bar", },
+ { .offset = 4, .name = "baz", },
+ { .offset = 5, .name = "xyz", },
+ { }
+ };
+
+ g_autoptr(GPIOSimChip) sim = NULL;
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+
+ sim = g_gpiosim_chip_new(
+ "num-lines", 8,
+ "line-names", gpiod_test_package_line_names(names),
+ NULL);
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+
+ g_assert_cmpint(gpiod_chip_get_line_offset_from_name(chip, "baz"),
+ ==, 4);
}
-GPIOD_TEST_CASE(find_line_unique_not_found,
- GPIOD_TEST_FLAG_NAMED_LINES, { 8, 8, 8 })
+/* Verify that for duplicated line names, the first one is returned. */
+GPIOD_TEST_CASE(find_line_duplicate)
{
- g_autoptr(gpiod_chip_struct) chip = NULL;
- int offset;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(1));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- offset = gpiod_chip_find_line(chip, "nonexistent");
- g_assert_cmpint(offset, ==, -1);
- g_assert_cmpint(errno, ==, ENOENT);
+ static const struct gpiod_test_line_name names[] = {
+ { .offset = 1, .name = "foo", },
+ { .offset = 2, .name = "baz", },
+ { .offset = 4, .name = "baz", },
+ { .offset = 5, .name = "xyz", },
+ { }
+ };
+
+ g_autoptr(GPIOSimChip) sim = NULL;
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+
+ sim = g_gpiosim_chip_new(
+ "num-lines", 8,
+ "line-names", gpiod_test_package_line_names(names),
+ NULL);
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+
+ g_assert_cmpint(gpiod_chip_get_line_offset_from_name(chip, "baz"),
+ ==, 2);
}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+
+#include <glib.h>
+#include <gpiod.h>
+#include <poll.h>
+
+#include "gpiod-test.h"
+#include "gpiod-test-helpers.h"
+#include "gpiod-test-sim.h"
+
+#define GPIOD_TEST_GROUP "edge-event"
+
+GPIOD_TEST_CASE(edge_event_buffer_capacity)
+{
+ g_autoptr(struct_gpiod_edge_event_buffer) buffer = NULL;
+
+ buffer = gpiod_test_create_edge_event_buffer_or_fail(32);
+
+ g_assert_cmpuint(gpiod_edge_event_buffer_get_capacity(buffer), ==, 32);
+}
+
+GPIOD_TEST_CASE(edge_event_buffer_max_capacity)
+{
+ g_autoptr(struct_gpiod_edge_event_buffer) buffer = NULL;
+
+ buffer = gpiod_test_create_edge_event_buffer_or_fail(16 * 64 * 2);
+
+ g_assert_cmpuint(gpiod_edge_event_buffer_get_capacity(buffer),
+ ==, 16 * 64);
+}
+
+GPIOD_TEST_CASE(edge_event_wait_timeout)
+{
+ static const guint offset = 4;
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ gint ret;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
+
+ ret = gpiod_line_request_wait_edge_event(request, 1000000);
+ g_assert_cmpint(ret, ==, 0);
+}
+
+GPIOD_TEST_CASE(cannot_request_lines_in_output_mode_with_edge_detection)
+{
+ static const guint offset = 4;
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_chip_request_lines(chip, NULL, line_cfg);
+ g_assert_null(request);
+ gpiod_test_expect_errno(EINVAL);
+}
+
+static gpointer falling_and_rising_edge_events(gpointer data)
+{
+ GPIOSimChip *sim = data;
+
+ g_usleep(1000);
+
+ g_gpiosim_chip_set_pull(sim, 2, G_GPIOSIM_PULL_UP);
+
+ g_usleep(1000);
+
+ g_gpiosim_chip_set_pull(sim, 2, G_GPIOSIM_PULL_DOWN);
+
+ return NULL;
+}
+
+GPIOD_TEST_CASE(read_both_events)
+{
+ static const guint offset = 2;
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ g_autoptr(GThread) thread = NULL;
+ g_autoptr(struct_gpiod_edge_event_buffer) buffer = NULL;
+ struct gpiod_edge_event *event;
+ guint64 ts_rising, ts_falling;
+ gint ret;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+ buffer = gpiod_test_create_edge_event_buffer_or_fail(64);
+
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
+
+ thread = g_thread_new("request-release",
+ falling_and_rising_edge_events, sim);
+ g_thread_ref(thread);
+
+ /* First event. */
+
+ ret = gpiod_line_request_wait_edge_event(request, 1000000000);
+ g_assert_cmpint(ret, >, 0);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ ret = gpiod_line_request_read_edge_event(request, buffer, 1);
+ g_assert_cmpint(ret, ==, 1);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpuint(gpiod_edge_event_buffer_get_num_events(buffer), ==, 1);
+ event = gpiod_edge_event_buffer_get_event(buffer, 0);
+ g_assert_nonnull(event);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpint(gpiod_edge_event_get_event_type(event),
+ ==, GPIOD_EDGE_EVENT_RISING_EDGE);
+ g_assert_cmpuint(gpiod_edge_event_get_line_offset(event), ==, 2);
+ ts_rising = gpiod_edge_event_get_timestamp_ns(event);
+
+ /* Second event. */
+
+ ret = gpiod_line_request_wait_edge_event(request, 1000000000);
+ g_assert_cmpint(ret, >, 0);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ ret = gpiod_line_request_read_edge_event(request, buffer, 1);
+ g_assert_cmpint(ret, ==, 1);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpuint(gpiod_edge_event_buffer_get_num_events(buffer), ==, 1);
+ event = gpiod_edge_event_buffer_get_event(buffer, 0);
+ g_assert_nonnull(event);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpint(gpiod_edge_event_get_event_type(event),
+ ==, GPIOD_EDGE_EVENT_FALLING_EDGE);
+ g_assert_cmpuint(gpiod_edge_event_get_line_offset(event), ==, 2);
+ ts_falling = gpiod_edge_event_get_timestamp_ns(event);
+
+ g_thread_join(thread);
+
+ g_assert_cmpuint(ts_falling, >, ts_rising);
+}
+
+GPIOD_TEST_CASE(read_rising_edge_event)
+{
+ static const guint offset = 2;
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ g_autoptr(GThread) thread = NULL;
+ g_autoptr(struct_gpiod_edge_event_buffer) buffer = NULL;
+ struct gpiod_edge_event *event;
+ gint ret;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+ buffer = gpiod_test_create_edge_event_buffer_or_fail(64);
+
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_line_settings_set_edge_detection(settings,
+ GPIOD_LINE_EDGE_RISING);
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
+
+ thread = g_thread_new("edge-generator",
+ falling_and_rising_edge_events, sim);
+ g_thread_ref(thread);
+
+ /* First event. */
+
+ ret = gpiod_line_request_wait_edge_event(request, 1000000000);
+ g_assert_cmpint(ret, >, 0);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ ret = gpiod_line_request_read_edge_event(request, buffer, 1);
+ g_assert_cmpint(ret, ==, 1);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpuint(gpiod_edge_event_buffer_get_num_events(buffer), ==, 1);
+ event = gpiod_edge_event_buffer_get_event(buffer, 0);
+ g_assert_nonnull(event);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpint(gpiod_edge_event_get_event_type(event),
+ ==, GPIOD_EDGE_EVENT_RISING_EDGE);
+ g_assert_cmpuint(gpiod_edge_event_get_line_offset(event), ==, 2);
+
+ /* Second event. */
+
+ ret = gpiod_line_request_wait_edge_event(request, 1000000);
+ g_assert_cmpint(ret, ==, 0); /* Time-out. */
+
+ g_thread_join(thread);
+}
+
+GPIOD_TEST_CASE(read_falling_edge_event)
+{
+ static const guint offset = 2;
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ g_autoptr(GThread) thread = NULL;
+ g_autoptr(struct_gpiod_edge_event_buffer) buffer = NULL;
+ struct gpiod_edge_event *event;
+ gint ret;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+ buffer = gpiod_test_create_edge_event_buffer_or_fail(64);
+
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_line_settings_set_edge_detection(settings,
+ GPIOD_LINE_EDGE_FALLING);
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
+
+ thread = g_thread_new("request-release",
+ falling_and_rising_edge_events, sim);
+ g_thread_ref(thread);
+
+ /* First event is the second generated. */
+
+ ret = gpiod_line_request_wait_edge_event(request, 1000000000);
+ g_assert_cmpint(ret, >, 0);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ ret = gpiod_line_request_read_edge_event(request, buffer, 1);
+ g_assert_cmpint(ret, ==, 1);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpuint(gpiod_edge_event_buffer_get_num_events(buffer), ==, 1);
+ event = gpiod_edge_event_buffer_get_event(buffer, 0);
+ g_assert_nonnull(event);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpint(gpiod_edge_event_get_event_type(event),
+ ==, GPIOD_EDGE_EVENT_FALLING_EDGE);
+ g_assert_cmpuint(gpiod_edge_event_get_line_offset(event), ==, 2);
+
+ /* No more events. */
+
+ ret = gpiod_line_request_wait_edge_event(request, 1000000);
+ g_assert_cmpint(ret, ==, 0); /* Time-out. */
+
+ g_thread_join(thread);
+}
+
+GPIOD_TEST_CASE(read_rising_edge_event_polled)
+{
+ static const guint offset = 2;
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ g_autoptr(GThread) thread = NULL;
+ g_autoptr(struct_gpiod_edge_event_buffer) buffer = NULL;
+ struct gpiod_edge_event *event;
+ struct timespec ts;
+ struct pollfd pfd;
+ gint ret, fd;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+ buffer = gpiod_test_create_edge_event_buffer_or_fail(64);
+
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_line_settings_set_edge_detection(settings,
+ GPIOD_LINE_EDGE_RISING);
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
+
+ thread = g_thread_new("edge-generator",
+ falling_and_rising_edge_events, sim);
+ g_thread_ref(thread);
+
+ /* First event. */
+
+ fd = gpiod_line_request_get_fd(request);
+
+ memset(&pfd, 0, sizeof(pfd));
+ pfd.fd = fd;
+ pfd.events = POLLIN | POLLPRI;
+
+ ts.tv_sec = 1;
+ ts.tv_nsec = 0;
+
+ ret = ppoll(&pfd, 1, &ts, NULL);
+ g_assert_cmpint(ret, >, 0);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ ret = gpiod_line_request_read_edge_event(request, buffer, 1);
+ g_assert_cmpint(ret, ==, 1);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpuint(gpiod_edge_event_buffer_get_num_events(buffer), ==, 1);
+ event = gpiod_edge_event_buffer_get_event(buffer, 0);
+ g_assert_nonnull(event);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpint(gpiod_edge_event_get_event_type(event),
+ ==, GPIOD_EDGE_EVENT_RISING_EDGE);
+ g_assert_cmpuint(gpiod_edge_event_get_line_offset(event), ==, 2);
+
+ /* Second event. */
+
+ ret = gpiod_line_request_wait_edge_event(request, 1000000);
+ g_assert_cmpint(ret, ==, 0); /* Time-out. */
+
+ g_thread_join(thread);
+}
+
+GPIOD_TEST_CASE(read_both_events_blocking)
+{
+ /*
+ * This time without polling so that the read gets a chance to block
+ * and we can make sure it doesn't immediately return an error.
+ */
+
+ static const guint offset = 2;
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ g_autoptr(GThread) thread = NULL;
+ g_autoptr(struct_gpiod_edge_event_buffer) buffer = NULL;
+ struct gpiod_edge_event *event;
+ gint ret;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+ buffer = gpiod_test_create_edge_event_buffer_or_fail(64);
+
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
+
+ thread = g_thread_new("request-release",
+ falling_and_rising_edge_events, sim);
+ g_thread_ref(thread);
+
+ /* First event. */
+
+ ret = gpiod_line_request_read_edge_event(request, buffer, 1);
+ g_assert_cmpint(ret, ==, 1);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpuint(gpiod_edge_event_buffer_get_num_events(buffer), ==, 1);
+ event = gpiod_edge_event_buffer_get_event(buffer, 0);
+ g_assert_nonnull(event);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpint(gpiod_edge_event_get_event_type(event),
+ ==, GPIOD_EDGE_EVENT_RISING_EDGE);
+ g_assert_cmpuint(gpiod_edge_event_get_line_offset(event), ==, 2);
+
+ /* Second event. */
+
+ ret = gpiod_line_request_read_edge_event(request, buffer, 1);
+ g_assert_cmpint(ret, ==, 1);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpuint(gpiod_edge_event_buffer_get_num_events(buffer), ==, 1);
+ event = gpiod_edge_event_buffer_get_event(buffer, 0);
+ g_assert_nonnull(event);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpint(gpiod_edge_event_get_event_type(event),
+ ==, GPIOD_EDGE_EVENT_FALLING_EDGE);
+ g_assert_cmpuint(gpiod_edge_event_get_line_offset(event), ==, 2);
+
+ g_thread_join(thread);
+}
+
+static gpointer rising_edge_events_on_two_offsets(gpointer data)
+{
+ GPIOSimChip *sim = data;
+
+ g_usleep(1000);
+
+ g_gpiosim_chip_set_pull(sim, 2, G_GPIOSIM_PULL_UP);
+
+ g_usleep(1000);
+
+ g_gpiosim_chip_set_pull(sim, 3, G_GPIOSIM_PULL_UP);
+
+ return NULL;
+}
+
+GPIOD_TEST_CASE(seqno)
+{
+ static const guint offsets[] = { 2, 3 };
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ g_autoptr(GThread) thread = NULL;
+ g_autoptr(struct_gpiod_edge_event_buffer) buffer = NULL;
+ struct gpiod_edge_event *event;
+ gint ret;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+ buffer = gpiod_test_create_edge_event_buffer_or_fail(64);
+
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 2,
+ settings);
+
+ request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
+
+ thread = g_thread_new("request-release",
+ rising_edge_events_on_two_offsets, sim);
+ g_thread_ref(thread);
+
+ /* First event. */
+
+ ret = gpiod_line_request_wait_edge_event(request, 1000000000);
+ g_assert_cmpint(ret, >, 0);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ ret = gpiod_line_request_read_edge_event(request, buffer, 1);
+ g_assert_cmpint(ret, ==, 1);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpuint(gpiod_edge_event_buffer_get_num_events(buffer), ==, 1);
+ event = gpiod_edge_event_buffer_get_event(buffer, 0);
+ g_assert_nonnull(event);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpuint(gpiod_edge_event_get_line_offset(event), ==, 2);
+ g_assert_cmpuint(gpiod_edge_event_get_global_seqno(event), ==, 1);
+ g_assert_cmpuint(gpiod_edge_event_get_line_seqno(event), ==, 1);
+
+ /* Second event. */
+
+ ret = gpiod_line_request_wait_edge_event(request, 1000000000);
+ g_assert_cmpint(ret, >, 0);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ ret = gpiod_line_request_read_edge_event(request, buffer, 1);
+ g_assert_cmpint(ret, ==, 1);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpuint(gpiod_edge_event_buffer_get_num_events(buffer), ==, 1);
+ event = gpiod_edge_event_buffer_get_event(buffer, 0);
+ g_assert_nonnull(event);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpuint(gpiod_edge_event_get_line_offset(event), ==, 3);
+ g_assert_cmpuint(gpiod_edge_event_get_global_seqno(event), ==, 2);
+ g_assert_cmpuint(gpiod_edge_event_get_line_seqno(event), ==, 1);
+}
+
+GPIOD_TEST_CASE(event_copy)
+{
+ static const guint offset = 2;
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ g_autoptr(GThread) thread = NULL;
+ g_autoptr(struct_gpiod_edge_event_buffer) buffer = NULL;
+ g_autoptr(struct_gpiod_edge_event) copy = NULL;
+ struct gpiod_edge_event *event;
+ gint ret;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+ buffer = gpiod_test_create_edge_event_buffer_or_fail(64);
+
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
+
+ g_gpiosim_chip_set_pull(sim, 2, G_GPIOSIM_PULL_UP);
+
+ ret = gpiod_line_request_wait_edge_event(request, 1000000000);
+ g_assert_cmpint(ret, >, 0);
+ gpiod_test_return_if_failed();
+
+ ret = gpiod_line_request_read_edge_event(request, buffer, 1);
+ g_assert_cmpint(ret, ==, 1);
+ gpiod_test_return_if_failed();
+
+ event = gpiod_edge_event_buffer_get_event(buffer, 0);
+ g_assert_nonnull(event);
+ gpiod_test_return_if_failed();
+
+ copy = gpiod_edge_event_copy(event);
+ g_assert_nonnull(copy);
+ g_assert_true(copy != event);
+}
+
+GPIOD_TEST_CASE(reading_more_events_than_the_queue_contains_doesnt_block)
+{
+ static const guint offset = 2;
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ g_autoptr(GThread) thread = NULL;
+ g_autoptr(struct_gpiod_edge_event_buffer) buffer = NULL;
+ gint ret;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+ buffer = gpiod_test_create_edge_event_buffer_or_fail(64);
+
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
+
+ g_gpiosim_chip_set_pull(sim, 2, G_GPIOSIM_PULL_UP);
+ g_usleep(500);
+ g_gpiosim_chip_set_pull(sim, 2, G_GPIOSIM_PULL_DOWN);
+ g_usleep(500);
+ g_gpiosim_chip_set_pull(sim, 2, G_GPIOSIM_PULL_UP);
+ g_usleep(500);
+ g_gpiosim_chip_set_pull(sim, 2, G_GPIOSIM_PULL_DOWN);
+ g_usleep(500);
+ g_gpiosim_chip_set_pull(sim, 2, G_GPIOSIM_PULL_UP);
+ g_usleep(500);
+ g_gpiosim_chip_set_pull(sim, 2, G_GPIOSIM_PULL_DOWN);
+ g_usleep(500);
+ g_gpiosim_chip_set_pull(sim, 2, G_GPIOSIM_PULL_UP);
+ g_usleep(500);
+
+ ret = gpiod_line_request_read_edge_event(request, buffer, 12);
+ g_assert_cmpint(ret, ==, 7);
+ gpiod_test_return_if_failed();
+
+ ret = gpiod_line_request_wait_edge_event(request, 1000);
+ g_assert_cmpint(ret, ==, 0);
+ gpiod_test_return_if_failed();
+}
+++ /dev/null
-// SPDX-License-Identifier: GPL-2.0-or-later
-// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
-
-#include <errno.h>
-#include <unistd.h>
-
-#include "gpiod-test.h"
-
-#define GPIOD_TEST_GROUP "event"
-
-GPIOD_TEST_CASE(rising_edge_good, 0, { 8 })
-{
- g_autoptr(GpiodTestEventThread) ev_thread = NULL;
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct timespec ts = { 1, 0 };
- struct gpiod_line_event ev;
- struct gpiod_line *line;
- gint ret;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line = gpiod_chip_get_line(chip, 7);
- g_assert_nonnull(line);
- gpiod_test_return_if_failed();
-
- ret = gpiod_line_request_rising_edge_events(line, GPIOD_TEST_CONSUMER);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
-
- ev_thread = gpiod_test_start_event_thread(0, 7, 100);
-
- ret = gpiod_line_event_wait(line, &ts);
- g_assert_cmpint(ret, ==, 1);
-
- ret = gpiod_line_event_read(line, &ev);
- g_assert_cmpint(ret, ==, 0);
-
- g_assert_cmpint(ev.event_type, ==, GPIOD_LINE_EVENT_RISING_EDGE);
-}
-
-GPIOD_TEST_CASE(falling_edge_good, 0, { 8 })
-{
- g_autoptr(GpiodTestEventThread) ev_thread = NULL;
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct timespec ts = { 1, 0 };
- struct gpiod_line_event ev;
- struct gpiod_line *line;
- gint ret;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line = gpiod_chip_get_line(chip, 7);
- g_assert_nonnull(line);
- gpiod_test_return_if_failed();
-
- ret = gpiod_line_request_falling_edge_events(line,
- GPIOD_TEST_CONSUMER);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
-
- ev_thread = gpiod_test_start_event_thread(0, 7, 100);
-
- ret = gpiod_line_event_wait(line, &ts);
- g_assert_cmpint(ret, ==, 1);
-
- ret = gpiod_line_event_read(line, &ev);
- g_assert_cmpint(ret, ==, 0);
-
- g_assert_cmpint(ev.event_type, ==, GPIOD_LINE_EVENT_FALLING_EDGE);
-}
-
-GPIOD_TEST_CASE(rising_edge_ignore_falling, 0, { 8 })
-{
- g_autoptr(GpiodTestEventThread) ev_thread = NULL;
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct timespec ts = { 1, 0 };
- struct gpiod_line_event ev[3];
- struct gpiod_line *line;
- gint ret;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line = gpiod_chip_get_line(chip, 7);
- g_assert_nonnull(line);
- gpiod_test_return_if_failed();
-
- ret = gpiod_line_request_rising_edge_events(line, GPIOD_TEST_CONSUMER);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
-
- ev_thread = gpiod_test_start_event_thread(0, 7, 100);
-
- ret = gpiod_line_event_wait(line, &ts);
- g_assert_cmpint(ret, ==, 1);
- ret = gpiod_line_event_read(line, &ev[0]);
- g_assert_cmpint(ret, ==, 0);
-
- ret = gpiod_line_event_wait(line, &ts);
- g_assert_cmpint(ret, ==, 1);
- ret = gpiod_line_event_read(line, &ev[1]);
- g_assert_cmpint(ret, ==, 0);
-
- ret = gpiod_line_event_wait(line, &ts);
- g_assert_cmpint(ret, ==, 1);
- ret = gpiod_line_event_read(line, &ev[2]);
- g_assert_cmpint(ret, ==, 0);
-
- g_assert_cmpint(ev[0].event_type, ==, GPIOD_LINE_EVENT_RISING_EDGE);
- g_assert_cmpint(ev[1].event_type, ==, GPIOD_LINE_EVENT_RISING_EDGE);
- g_assert_cmpint(ev[2].event_type, ==, GPIOD_LINE_EVENT_RISING_EDGE);
-}
-
-GPIOD_TEST_CASE(both_edges, 0, { 8 })
-{
- g_autoptr(GpiodTestEventThread) ev_thread = NULL;
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct timespec ts = { 1, 0 };
- struct gpiod_line_event ev;
- struct gpiod_line *line;
- gint ret;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line = gpiod_chip_get_line(chip, 7);
- g_assert_nonnull(line);
- gpiod_test_return_if_failed();
-
- ret = gpiod_line_request_both_edges_events(line, GPIOD_TEST_CONSUMER);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
-
- ev_thread = gpiod_test_start_event_thread(0, 7, 100);
-
- ret = gpiod_line_event_wait(line, &ts);
- g_assert_cmpint(ret, ==, 1);
-
- ret = gpiod_line_event_read(line, &ev);
- g_assert_cmpint(ret, ==, 0);
-
- g_assert_cmpint(ev.event_type, ==, GPIOD_LINE_EVENT_RISING_EDGE);
-
- ret = gpiod_line_event_wait(line, &ts);
- g_assert_cmpint(ret, ==, 1);
-
- ret = gpiod_line_event_read(line, &ev);
- g_assert_cmpint(ret, ==, 0);
-
- g_assert_cmpint(ev.event_type, ==, GPIOD_LINE_EVENT_FALLING_EDGE);
-}
-
-GPIOD_TEST_CASE(both_edges_active_low, 0, { 8 })
-{
- g_autoptr(GpiodTestEventThread) ev_thread = NULL;
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct timespec ts = { 1, 0 };
- struct gpiod_line_event ev;
- struct gpiod_line *line;
- gint ret;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line = gpiod_chip_get_line(chip, 7);
- g_assert_nonnull(line);
- gpiod_test_return_if_failed();
-
- ret = gpiod_line_request_both_edges_events_flags(line,
- GPIOD_TEST_CONSUMER, GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
-
- ev_thread = gpiod_test_start_event_thread(0, 7, 100);
-
- ret = gpiod_line_event_wait(line, &ts);
- g_assert_cmpint(ret, ==, 1);
-
- ret = gpiod_line_event_read(line, &ev);
- g_assert_cmpint(ret, ==, 0);
-
- g_assert_cmpint(ev.event_type, ==, GPIOD_LINE_EVENT_FALLING_EDGE);
-
- ret = gpiod_line_event_wait(line, &ts);
- g_assert_cmpint(ret, ==, 1);
-
- ret = gpiod_line_event_read(line, &ev);
- g_assert_cmpint(ret, ==, 0);
-
- g_assert_cmpint(ev.event_type, ==, GPIOD_LINE_EVENT_RISING_EDGE);
-}
-
-GPIOD_TEST_CASE(both_edges_bias_disable, 0, { 8 })
-{
- g_autoptr(GpiodTestEventThread) ev_thread = NULL;
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct timespec ts = { 1, 0 };
- struct gpiod_line_event ev;
- struct gpiod_line *line;
- gint ret;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line = gpiod_chip_get_line(chip, 7);
- g_assert_nonnull(line);
- gpiod_test_return_if_failed();
-
- ret = gpiod_line_request_both_edges_events_flags(line,
- GPIOD_TEST_CONSUMER, GPIOD_LINE_REQUEST_FLAG_BIAS_DISABLED);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
-
- ev_thread = gpiod_test_start_event_thread(0, 7, 100);
-
- ret = gpiod_line_event_wait(line, &ts);
- g_assert_cmpint(ret, ==, 1);
-
- ret = gpiod_line_event_read(line, &ev);
- g_assert_cmpint(ret, ==, 0);
-
- g_assert_cmpint(ev.event_type, ==, GPIOD_LINE_EVENT_RISING_EDGE);
-
- ret = gpiod_line_event_wait(line, &ts);
- g_assert_cmpint(ret, ==, 1);
-
- ret = gpiod_line_event_read(line, &ev);
- g_assert_cmpint(ret, ==, 0);
-
- g_assert_cmpint(ev.event_type, ==, GPIOD_LINE_EVENT_FALLING_EDGE);
-}
-
-GPIOD_TEST_CASE(both_edges_bias_pull_down, 0, { 8 })
-{
- g_autoptr(GpiodTestEventThread) ev_thread = NULL;
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct timespec ts = { 1, 0 };
- struct gpiod_line_event ev;
- struct gpiod_line *line;
- gint ret;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line = gpiod_chip_get_line(chip, 7);
- g_assert_nonnull(line);
- gpiod_test_return_if_failed();
-
- ret = gpiod_line_request_both_edges_events_flags(line,
- GPIOD_TEST_CONSUMER, GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
-
- ev_thread = gpiod_test_start_event_thread(0, 7, 100);
-
- ret = gpiod_line_event_wait(line, &ts);
- g_assert_cmpint(ret, ==, 1);
-
- ret = gpiod_line_event_read(line, &ev);
- g_assert_cmpint(ret, ==, 0);
-
- g_assert_cmpint(ev.event_type, ==, GPIOD_LINE_EVENT_RISING_EDGE);
-
- ret = gpiod_line_event_wait(line, &ts);
- g_assert_cmpint(ret, ==, 1);
-
- ret = gpiod_line_event_read(line, &ev);
- g_assert_cmpint(ret, ==, 0);
-
- g_assert_cmpint(ev.event_type, ==, GPIOD_LINE_EVENT_FALLING_EDGE);
-}
-
-GPIOD_TEST_CASE(both_edges_bias_pull_up, 0, { 8 })
-{
- g_autoptr(GpiodTestEventThread) ev_thread = NULL;
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct timespec ts = { 1, 0 };
- struct gpiod_line_event ev;
- struct gpiod_line *line;
- gint ret;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line = gpiod_chip_get_line(chip, 7);
- g_assert_nonnull(line);
- gpiod_test_return_if_failed();
-
- ret = gpiod_line_request_both_edges_events_flags(line,
- GPIOD_TEST_CONSUMER, GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
-
- ev_thread = gpiod_test_start_event_thread(0, 7, 100);
-
- ret = gpiod_line_event_wait(line, &ts);
- g_assert_cmpint(ret, ==, 1);
-
- ret = gpiod_line_event_read(line, &ev);
- g_assert_cmpint(ret, ==, 0);
-
- g_assert_cmpint(ev.event_type, ==, GPIOD_LINE_EVENT_FALLING_EDGE);
-
- ret = gpiod_line_event_wait(line, &ts);
- g_assert_cmpint(ret, ==, 1);
-
- ret = gpiod_line_event_read(line, &ev);
- g_assert_cmpint(ret, ==, 0);
-
- g_assert_cmpint(ev.event_type, ==, GPIOD_LINE_EVENT_RISING_EDGE);
-}
-
-GPIOD_TEST_CASE(falling_edge_active_low, 0, { 8 })
-{
- g_autoptr(GpiodTestEventThread) ev_thread = NULL;
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct timespec ts = { 1, 0 };
- struct gpiod_line_event ev;
- struct gpiod_line *line;
- gint ret;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line = gpiod_chip_get_line(chip, 7);
- g_assert_nonnull(line);
- gpiod_test_return_if_failed();
-
- ret = gpiod_line_request_falling_edge_events_flags(line,
- GPIOD_TEST_CONSUMER, GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
-
- ev_thread = gpiod_test_start_event_thread(0, 7, 100);
-
- ret = gpiod_line_event_wait(line, &ts);
- g_assert_cmpint(ret, ==, 1);
-
- ret = gpiod_line_event_read(line, &ev);
- g_assert_cmpint(ret, ==, 0);
-
- g_assert_cmpint(ev.event_type, ==, GPIOD_LINE_EVENT_FALLING_EDGE);
-}
-
-GPIOD_TEST_CASE(get_value, 0, { 8 })
-{
- g_autoptr(GpiodTestEventThread) ev_thread = NULL;
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct timespec ts = { 1, 0 };
- struct gpiod_line_event ev;
- struct gpiod_line *line;
- gint ret;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line = gpiod_chip_get_line(chip, 7);
- g_assert_nonnull(line);
- gpiod_test_return_if_failed();
-
- gpiod_test_chip_set_pull(0, 7, 1);
-
- ret = gpiod_line_request_falling_edge_events(line, GPIOD_TEST_CONSUMER);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
-
- ret = gpiod_line_get_value(line);
- g_assert_cmpint(ret, ==, 1);
-
- ev_thread = gpiod_test_start_event_thread(0, 7, 100);
-
- ret = gpiod_line_event_wait(line, &ts);
- g_assert_cmpint(ret, ==, 1);
-
- ret = gpiod_line_event_read(line, &ev);
- g_assert_cmpint(ret, ==, 0);
-
- g_assert_cmpint(ev.event_type, ==, GPIOD_LINE_EVENT_FALLING_EDGE);
-}
-
-GPIOD_TEST_CASE(get_value_active_low, 0, { 8 })
-{
- g_autoptr(GpiodTestEventThread) ev_thread = NULL;
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct timespec ts = { 1, 0 };
- struct gpiod_line_event ev;
- struct gpiod_line *line;
- gint ret;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line = gpiod_chip_get_line(chip, 7);
- g_assert_nonnull(line);
- gpiod_test_return_if_failed();
-
- gpiod_test_chip_set_pull(0, 7, 1);
-
- ret = gpiod_line_request_falling_edge_events_flags(line,
- GPIOD_TEST_CONSUMER, GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
-
- ret = gpiod_line_get_value(line);
- g_assert_cmpint(ret, ==, 0);
-
- ev_thread = gpiod_test_start_event_thread(0, 7, 100);
-
- ret = gpiod_line_event_wait(line, &ts);
- g_assert_cmpint(ret, ==, 1);
-
- ret = gpiod_line_event_read(line, &ev);
- g_assert_cmpint(ret, ==, 0);
-
- g_assert_cmpint(ev.event_type, ==, GPIOD_LINE_EVENT_FALLING_EDGE);
-}
-
-GPIOD_TEST_CASE(get_values, 0, { 8 })
-{
- g_autoptr(gpiod_line_bulk_struct) bulk = NULL;
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct gpiod_line *line;
- gint i, ret, vals[8];
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- bulk = gpiod_line_bulk_new(8);
- g_assert_nonnull(bulk);
- gpiod_test_return_if_failed();
-
- for (i = 0; i < 8; i++) {
- line = gpiod_chip_get_line(chip, i);
- g_assert_nonnull(line);
- gpiod_test_return_if_failed();
-
- gpiod_line_bulk_add_line(bulk, line);
- gpiod_test_chip_set_pull(0, i, 1);
- }
-
- ret = gpiod_line_request_bulk_rising_edge_events(bulk,
- GPIOD_TEST_CONSUMER);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
-
- memset(vals, 0, sizeof(vals));
- ret = gpiod_line_get_value_bulk(bulk, vals);
- g_assert_cmpint(ret, ==, 0);
- g_assert_cmpint(vals[0], ==, 1);
- g_assert_cmpint(vals[1], ==, 1);
- g_assert_cmpint(vals[2], ==, 1);
- g_assert_cmpint(vals[3], ==, 1);
- g_assert_cmpint(vals[4], ==, 1);
- g_assert_cmpint(vals[5], ==, 1);
- g_assert_cmpint(vals[6], ==, 1);
- g_assert_cmpint(vals[7], ==, 1);
-
- gpiod_test_chip_set_pull(0, 1, 0);
- gpiod_test_chip_set_pull(0, 3, 0);
- gpiod_test_chip_set_pull(0, 4, 0);
- gpiod_test_chip_set_pull(0, 7, 0);
-
- memset(vals, 0, sizeof(vals));
- ret = gpiod_line_get_value_bulk(bulk, vals);
- g_assert_cmpint(ret, ==, 0);
- g_assert_cmpint(vals[0], ==, 1);
- g_assert_cmpint(vals[1], ==, 0);
- g_assert_cmpint(vals[2], ==, 1);
- g_assert_cmpint(vals[3], ==, 0);
- g_assert_cmpint(vals[4], ==, 0);
- g_assert_cmpint(vals[5], ==, 1);
- g_assert_cmpint(vals[6], ==, 1);
- g_assert_cmpint(vals[7], ==, 0);
-}
-
-GPIOD_TEST_CASE(get_values_active_low, 0, { 8 })
-{
- g_autoptr(gpiod_line_bulk_struct) bulk = NULL;
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct gpiod_line *line;
- gint i, ret, vals[8];
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- bulk = gpiod_line_bulk_new(8);
- g_assert_nonnull(bulk);
- gpiod_test_return_if_failed();
-
- for (i = 0; i < 8; i++) {
- line = gpiod_chip_get_line(chip, i);
- g_assert_nonnull(line);
- gpiod_test_return_if_failed();
-
- gpiod_line_bulk_add_line(bulk, line);
- gpiod_test_chip_set_pull(0, i, 0);
- }
-
- ret = gpiod_line_request_bulk_rising_edge_events_flags(bulk,
- GPIOD_TEST_CONSUMER, GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
-
- memset(vals, 0, sizeof(vals));
- ret = gpiod_line_get_value_bulk(bulk, vals);
- g_assert_cmpint(ret, ==, 0);
- g_assert_cmpint(vals[0], ==, 1);
- g_assert_cmpint(vals[1], ==, 1);
- g_assert_cmpint(vals[2], ==, 1);
- g_assert_cmpint(vals[3], ==, 1);
- g_assert_cmpint(vals[4], ==, 1);
- g_assert_cmpint(vals[5], ==, 1);
- g_assert_cmpint(vals[6], ==, 1);
- g_assert_cmpint(vals[7], ==, 1);
-
- gpiod_test_chip_set_pull(0, 1, 1);
- gpiod_test_chip_set_pull(0, 3, 1);
- gpiod_test_chip_set_pull(0, 4, 1);
- gpiod_test_chip_set_pull(0, 7, 1);
-
- memset(vals, 0, sizeof(vals));
- ret = gpiod_line_get_value_bulk(bulk, vals);
- g_assert_cmpint(ret, ==, 0);
- g_assert_cmpint(vals[0], ==, 1);
- g_assert_cmpint(vals[1], ==, 0);
- g_assert_cmpint(vals[2], ==, 1);
- g_assert_cmpint(vals[3], ==, 0);
- g_assert_cmpint(vals[4], ==, 0);
- g_assert_cmpint(vals[5], ==, 1);
- g_assert_cmpint(vals[6], ==, 1);
- g_assert_cmpint(vals[7], ==, 0);
-}
-
-GPIOD_TEST_CASE(wait_multiple, 0, { 8 })
-{
- g_autoptr(GpiodTestEventThread) ev_thread = NULL;
- g_autoptr(gpiod_line_bulk_struct) ev_bulk = NULL;
- g_autoptr(gpiod_line_bulk_struct) bulk = NULL;
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct timespec ts = { 1, 0 };
- struct gpiod_line_event ev;
- struct gpiod_line *line;
- gint ret, i;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- bulk = gpiod_line_bulk_new(8);
- ev_bulk = gpiod_line_bulk_new(8);
- g_assert_nonnull(bulk);
- g_assert_nonnull(ev_bulk);
- gpiod_test_return_if_failed();
-
- for (i = 1; i < 8; i++) {
- line = gpiod_chip_get_line(chip, i);
- g_assert_nonnull(line);
- gpiod_test_return_if_failed();
-
- gpiod_line_bulk_add_line(bulk, line);
- }
-
- ret = gpiod_line_request_bulk_rising_edge_events(bulk,
- GPIOD_TEST_CONSUMER);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
-
- ev_thread = gpiod_test_start_event_thread(0, 4, 100);
-
- ret = gpiod_line_event_wait_bulk(bulk, &ts, ev_bulk);
- g_assert_cmpint(ret, ==, 1);
-
- g_assert_cmpuint(gpiod_line_bulk_num_lines(ev_bulk), ==, 1);
- line = gpiod_line_bulk_get_line(ev_bulk, 0);
- g_assert_cmpuint(gpiod_line_offset(line), ==, 4);
-
- ret = gpiod_line_event_read(line, &ev);
- g_assert_cmpint(ret, ==, 0);
- g_assert_cmpint(ev.event_type, ==, GPIOD_LINE_EVENT_RISING_EDGE);
- g_assert_cmpint(ev.offset, ==, 4);
-}
-
-GPIOD_TEST_CASE(get_fd_when_values_requested, 0, { 8 })
-{
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct gpiod_line *line;
- gint ret, fd;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line = gpiod_chip_get_line(chip, 3);
- g_assert_nonnull(line);
- gpiod_test_return_if_failed();
-
- ret = gpiod_line_request_input(line, GPIOD_TEST_CONSUMER);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
-
- fd = gpiod_line_event_get_fd(line);
- g_assert_cmpint(fd, ==, -1);
- g_assert_cmpint(errno, ==, EPERM);
-}
-
-GPIOD_TEST_CASE(request_bulk_fail, 0, { 8 })
-{
- g_autoptr(gpiod_line_bulk_struct) bulk = NULL;
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct gpiod_line *line;
- gint ret, i;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line = gpiod_chip_get_line(chip, 7);
- g_assert_nonnull(line);
- gpiod_test_return_if_failed();
-
- ret = gpiod_line_request_input(line, GPIOD_TEST_CONSUMER);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
-
- bulk = gpiod_line_bulk_new(8);
- g_assert_nonnull(bulk);
- gpiod_test_return_if_failed();
-
- for (i = 0; i < 8; i++) {
- line = gpiod_chip_get_line(chip, i);
- g_assert_nonnull(line);
- gpiod_test_return_if_failed();
- gpiod_line_bulk_add_line(bulk, line);
- }
-
- ret = gpiod_line_request_bulk_both_edges_events(bulk,
- GPIOD_TEST_CONSUMER);
- g_assert_cmpint(ret, ==, -1);
- g_assert_cmpint(errno, ==, EBUSY);
-}
-
-GPIOD_TEST_CASE(invalid_fd, 0, { 8 })
-{
- g_autoptr(gpiod_line_bulk_struct) ev_bulk = NULL;
- g_autoptr(gpiod_line_bulk_struct) bulk = NULL;
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct timespec ts = { 1, 0 };
- struct gpiod_line *line;
- gint ret, fd;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line = gpiod_chip_get_line(chip, 7);
- g_assert_nonnull(line);
- gpiod_test_return_if_failed();
-
- ret = gpiod_line_request_both_edges_events(line, GPIOD_TEST_CONSUMER);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
-
- fd = gpiod_line_event_get_fd(line);
- close(fd);
-
- ret = gpiod_line_event_wait(line, &ts);
- g_assert_cmpint(ret, ==, -1);
- g_assert_cmpint(errno, ==, EINVAL);
-
- bulk = gpiod_line_bulk_new(1);
- ev_bulk = gpiod_line_bulk_new(1);
- g_assert_nonnull(bulk);
- g_assert_nonnull(ev_bulk);
-
- /*
- * The single line variant calls gpiod_line_event_wait_bulk() with the
- * event_bulk argument set to NULL, so test this use case explicitly
- * as well.
- */
- gpiod_line_bulk_add_line(bulk, line);
- ret = gpiod_line_event_wait_bulk(bulk, &ts, ev_bulk);
- g_assert_cmpint(ret, ==, -1);
- g_assert_cmpint(errno, ==, EINVAL);
-}
-
-GPIOD_TEST_CASE(read_events_individually, 0, { 8 })
-{
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct timespec ts = { 1, 0 };
- struct gpiod_line_event ev;
- struct gpiod_line *line;
- gint ret;
- guint i;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line = gpiod_chip_get_line(chip, 7);
- g_assert_nonnull(line);
- gpiod_test_return_if_failed();
-
- ret = gpiod_line_request_both_edges_events_flags(line,
- GPIOD_TEST_CONSUMER, GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
-
- /* generate multiple events */
- for (i = 0; i < 3; i++) {
- gpiod_test_chip_set_pull(0, 7, i & 1);
- usleep(10000);
- }
-
- /* read them individually... */
- ret = gpiod_line_event_wait(line, &ts);
- g_assert_cmpint(ret, ==, 1);
- if (!ret)
- return;
-
- ret = gpiod_line_event_read(line, &ev);
- g_assert_cmpint(ret, ==, 0);
-
- g_assert_cmpint(ev.event_type, ==, GPIOD_LINE_EVENT_FALLING_EDGE);
-
- ret = gpiod_line_event_wait(line, &ts);
- g_assert_cmpint(ret, ==, 1);
- if (!ret)
- return;
-
- ret = gpiod_line_event_read(line, &ev);
- g_assert_cmpint(ret, ==, 0);
-
- g_assert_cmpint(ev.event_type, ==, GPIOD_LINE_EVENT_RISING_EDGE);
-
- ret = gpiod_line_event_wait(line, &ts);
- g_assert_cmpint(ret, ==, 1);
- if (!ret)
- return;
-
- ret = gpiod_line_event_read(line, &ev);
- g_assert_cmpint(ret, ==, 0);
-
- g_assert_cmpint(ev.event_type, ==, GPIOD_LINE_EVENT_FALLING_EDGE);
-
- ret = gpiod_line_event_wait(line, &ts);
- g_assert_cmpint(ret, ==, 0);
-}
-
-GPIOD_TEST_CASE(read_multiple_events, 0, { 8 })
-{
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct gpiod_line_event events[5];
- struct timespec ts = { 1, 0 };
- struct gpiod_line *line;
- gint ret;
- guint i;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line = gpiod_chip_get_line(chip, 4);
- g_assert_nonnull(line);
- gpiod_test_return_if_failed();
-
- ret = gpiod_line_request_both_edges_events(line, GPIOD_TEST_CONSUMER);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
-
- /* generate multiple events */
- for (i = 0; i < 7; i++) {
- gpiod_test_chip_set_pull(0, 4, !(i & 1));
- /*
- * We sleep for a short period of time here and in other
- * test cases for multiple events to let the kernel service
- * each simulated interrupt. Otherwise we'd risk triggering
- * an interrupt while the previous one is still being
- * handled.
- */
- usleep(10000);
- }
-
- ret = gpiod_line_event_wait(line, &ts);
- g_assert_cmpint(ret, ==, 1);
- if (!ret)
- return;
-
- /* read a chunk */
- ret = gpiod_line_event_read_multiple(line, events, 3);
- g_assert_cmpint(ret, ==, 3);
-
- g_assert_cmpint(events[0].event_type, ==,
- GPIOD_LINE_EVENT_RISING_EDGE);
- g_assert_cmpint(events[1].event_type, ==,
- GPIOD_LINE_EVENT_FALLING_EDGE);
- g_assert_cmpint(events[2].event_type, ==,
- GPIOD_LINE_EVENT_RISING_EDGE);
-
- ret = gpiod_line_event_wait(line, &ts);
- g_assert_cmpint(ret, ==, 1);
- if (!ret)
- return;
-
- /*
- * read the remainder
- * - note the attempt to read more than are available
- */
- ret = gpiod_line_event_read_multiple(line, events, 5);
- g_assert_cmpint(ret, ==, 4);
-
- g_assert_cmpint(events[0].event_type, ==,
- GPIOD_LINE_EVENT_FALLING_EDGE);
- g_assert_cmpint(events[1].event_type, ==,
- GPIOD_LINE_EVENT_RISING_EDGE);
- g_assert_cmpint(events[2].event_type, ==,
- GPIOD_LINE_EVENT_FALLING_EDGE);
- g_assert_cmpint(events[3].event_type, ==,
- GPIOD_LINE_EVENT_RISING_EDGE);
-
- ret = gpiod_line_event_wait(line, &ts);
- g_assert_cmpint(ret, ==, 0);
-}
-
-GPIOD_TEST_CASE(read_multiple_events_fd, 0, { 8 })
-{
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct gpiod_line_event events[5];
- struct timespec ts = { 1, 0 };
- struct gpiod_line *line;
- gint ret, fd;
- guint i;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line = gpiod_chip_get_line(chip, 4);
- g_assert_nonnull(line);
- gpiod_test_return_if_failed();
-
- ret = gpiod_line_request_both_edges_events(line, GPIOD_TEST_CONSUMER);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
-
- /* generate multiple events */
- for (i = 0; i < 7; i++) {
- gpiod_test_chip_set_pull(0, 4, !(i & 1));
- usleep(10000);
- }
-
- ret = gpiod_line_event_wait(line, &ts);
- g_assert_cmpint(ret, ==, 1);
- if (!ret)
- return;
-
- fd = gpiod_line_event_get_fd(line);
- g_assert_cmpint(fd, >=, 0);
-
- /* read a chunk */
- ret = gpiod_line_event_read_fd_multiple(fd, events, 3);
- g_assert_cmpint(ret, ==, 3);
-
- g_assert_cmpint(events[0].event_type, ==,
- GPIOD_LINE_EVENT_RISING_EDGE);
- g_assert_cmpint(events[1].event_type, ==,
- GPIOD_LINE_EVENT_FALLING_EDGE);
- g_assert_cmpint(events[2].event_type, ==,
- GPIOD_LINE_EVENT_RISING_EDGE);
-
- ret = gpiod_line_event_wait(line, &ts);
- g_assert_cmpint(ret, ==, 1);
- if (!ret)
- return;
-
- /*
- * read the remainder
- * - note the attempt to read more than are available
- */
- ret = gpiod_line_event_read_fd_multiple(fd, events, 5);
- g_assert_cmpint(ret, ==, 4);
-
- g_assert_cmpint(events[0].event_type, ==,
- GPIOD_LINE_EVENT_FALLING_EDGE);
- g_assert_cmpint(events[1].event_type, ==,
- GPIOD_LINE_EVENT_RISING_EDGE);
- g_assert_cmpint(events[2].event_type, ==,
- GPIOD_LINE_EVENT_FALLING_EDGE);
- g_assert_cmpint(events[3].event_type, ==,
- GPIOD_LINE_EVENT_RISING_EDGE);
-
- ret = gpiod_line_event_wait(line, &ts);
- g_assert_cmpint(ret, ==, 0);
-}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+
+#include <glib.h>
+#include <gpiod.h>
+#include <poll.h>
+
+#include "gpiod-test.h"
+#include "gpiod-test-helpers.h"
+#include "gpiod-test-sim.h"
+
+#define GPIOD_TEST_GROUP "info-event"
+
+GPIOD_TEST_CASE(watching_info_events_returns_line_info)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_info) info = NULL;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+
+ info = gpiod_chip_watch_line_info(chip, 3);
+ g_assert_nonnull(info);
+ g_assert_cmpuint(gpiod_line_info_get_offset(info), ==, 3);
+}
+
+GPIOD_TEST_CASE(try_offset_out_of_range)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_info) info = NULL;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+
+ info = gpiod_chip_watch_line_info(chip, 10);
+ g_assert_null(info);
+ gpiod_test_expect_errno(EINVAL);
+}
+
+GPIOD_TEST_CASE(event_timeout)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_info) info = NULL;
+ gint ret;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+
+ info = gpiod_chip_watch_line_info(chip, 6);
+ g_assert_nonnull(info);
+ gpiod_test_return_if_failed();
+
+ ret = gpiod_chip_wait_info_event(chip, 100000000);
+ g_assert_cmpint(ret, ==, 0);
+}
+
+struct request_ctx {
+ struct gpiod_chip *chip;
+ struct gpiod_line_config *line_cfg;
+ struct gpiod_line_settings *settings;
+ guint offset;
+};
+
+static gpointer request_reconfigure_release_line(gpointer data)
+{
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ struct request_ctx *ctx = data;
+ gint ret;
+
+ g_usleep(1000);
+
+ ret = gpiod_line_config_add_line_settings(ctx->line_cfg, &ctx->offset,
+ 1, ctx->settings);
+ g_assert_cmpint(ret, ==, 0);
+ if (g_test_failed())
+ return NULL;
+
+ request = gpiod_chip_request_lines(ctx->chip, NULL, ctx->line_cfg);
+ g_assert_nonnull(request);
+ if (g_test_failed())
+ return NULL;
+
+ g_usleep(1000);
+
+ gpiod_line_config_reset(ctx->line_cfg);
+ gpiod_line_settings_set_direction(ctx->settings,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+ gpiod_line_config_add_line_settings(ctx->line_cfg, &ctx->offset, 1,
+ ctx->settings);
+
+ ret = gpiod_line_request_reconfigure_lines(request, ctx->line_cfg);
+ g_assert_cmpint(ret, ==, 0);
+ if (g_test_failed())
+ return NULL;
+
+ g_usleep(1000);
+
+ gpiod_line_request_release(request);
+ request = NULL;
+
+ return NULL;
+}
+
+GPIOD_TEST_CASE(request_reconfigure_release_events)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_info) info = NULL;
+ g_autoptr(struct_gpiod_info_event) request_event = NULL;
+ g_autoptr(struct_gpiod_info_event) reconfigure_event = NULL;
+ g_autoptr(struct_gpiod_info_event) release_event = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(GThread) thread = NULL;
+ struct gpiod_line_info *request_info, *reconfigure_info, *release_info;
+ guint64 request_ts, reconfigure_ts, release_ts;
+ struct request_ctx ctx;
+ gint ret;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ line_cfg = gpiod_test_create_line_config_or_fail();
+ settings = gpiod_test_create_line_settings_or_fail();
+
+ info = gpiod_chip_watch_line_info(chip, 3);
+ g_assert_nonnull(info);
+ gpiod_test_return_if_failed();
+
+ g_assert_false(gpiod_line_info_is_used(info));
+
+ ctx.chip = chip;
+ ctx.line_cfg = line_cfg;
+ ctx.settings = settings;
+ ctx.offset = 3;
+
+ thread = g_thread_new("request-release",
+ request_reconfigure_release_line, &ctx);
+ g_thread_ref(thread);
+
+ ret = gpiod_chip_wait_info_event(chip, 1000000000);
+ g_assert_cmpint(ret, >, 0);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ request_event = gpiod_chip_read_info_event(chip);
+ g_assert_nonnull(request_event);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpint(gpiod_info_event_get_event_type(request_event), ==,
+ GPIOD_INFO_EVENT_LINE_REQUESTED);
+
+ request_info = gpiod_info_event_get_line_info(request_event);
+
+ g_assert_cmpuint(gpiod_line_info_get_offset(request_info), ==, 3);
+ g_assert_true(gpiod_line_info_is_used(request_info));
+ g_assert_cmpint(gpiod_line_info_get_direction(request_info), ==,
+ GPIOD_LINE_DIRECTION_INPUT);
+
+ ret = gpiod_chip_wait_info_event(chip, 1000000000);
+ g_assert_cmpint(ret, >, 0);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ reconfigure_event = gpiod_chip_read_info_event(chip);
+ g_assert_nonnull(reconfigure_event);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpint(gpiod_info_event_get_event_type(reconfigure_event), ==,
+ GPIOD_INFO_EVENT_LINE_CONFIG_CHANGED);
+
+ reconfigure_info = gpiod_info_event_get_line_info(reconfigure_event);
+
+ g_assert_cmpuint(gpiod_line_info_get_offset(reconfigure_info), ==, 3);
+ g_assert_true(gpiod_line_info_is_used(reconfigure_info));
+ g_assert_cmpint(gpiod_line_info_get_direction(reconfigure_info), ==,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+
+ ret = gpiod_chip_wait_info_event(chip, 1000000000);
+ g_assert_cmpint(ret, >, 0);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ release_event = gpiod_chip_read_info_event(chip);
+ g_assert_nonnull(release_event);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpint(gpiod_info_event_get_event_type(release_event), ==,
+ GPIOD_INFO_EVENT_LINE_RELEASED);
+
+ release_info = gpiod_info_event_get_line_info(release_event);
+
+ g_assert_cmpuint(gpiod_line_info_get_offset(release_info), ==, 3);
+ g_assert_false(gpiod_line_info_is_used(release_info));
+
+ g_thread_join(thread);
+
+ request_ts = gpiod_info_event_get_timestamp_ns(request_event);
+ reconfigure_ts = gpiod_info_event_get_timestamp_ns(reconfigure_event);
+ release_ts = gpiod_info_event_get_timestamp_ns(release_event);
+
+ g_assert_cmpuint(request_ts, <, reconfigure_ts);
+ g_assert_cmpuint(reconfigure_ts, <, release_ts);
+}
+
+GPIOD_TEST_CASE(chip_fd_can_be_polled)
+{\
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_info) info = NULL;
+ g_autoptr(struct_gpiod_info_event) event = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ g_autoptr(GThread) thread = NULL;
+ struct gpiod_line_info *evinfo;
+ struct request_ctx ctx;
+ struct timespec ts;
+ struct pollfd pfd;
+ gint ret, fd;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ info = gpiod_chip_watch_line_info(chip, 3);
+ g_assert_nonnull(info);
+ gpiod_test_return_if_failed();
+
+ g_assert_false(gpiod_line_info_is_used(info));
+
+ ctx.chip = chip;
+ ctx.line_cfg = line_cfg;
+ ctx.settings = settings;
+ ctx.offset = 3;
+
+ thread = g_thread_new("request-release",
+ request_reconfigure_release_line, &ctx);
+ g_thread_ref(thread);
+
+ fd = gpiod_chip_get_fd(chip);
+
+ memset(&pfd, 0, sizeof(pfd));
+ pfd.fd = fd;
+ pfd.events = POLLIN | POLLPRI;
+
+ ts.tv_sec = 1;
+ ts.tv_nsec = 0;
+
+ ret = ppoll(&pfd, 1, &ts, NULL);
+ g_assert_cmpint(ret, >, 0);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ event = gpiod_chip_read_info_event(chip);
+ g_assert_nonnull(event);
+ gpiod_test_join_thread_and_return_if_failed(thread);
+
+ g_assert_cmpint(gpiod_info_event_get_event_type(event), ==,
+ GPIOD_INFO_EVENT_LINE_REQUESTED);
+
+ evinfo = gpiod_info_event_get_line_info(event);
+
+ g_assert_cmpuint(gpiod_line_info_get_offset(evinfo), ==, 3);
+ g_assert_true(gpiod_line_info_is_used(evinfo));
+
+ g_thread_join(thread);
+}
+
+GPIOD_TEST_CASE(unwatch_and_check_that_no_events_are_generated)
+{
+ static const guint offset = 3;
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_info) info = NULL;
+ g_autoptr(struct_gpiod_info_event) event = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ gint ret;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ NULL);
+
+ info = gpiod_chip_watch_line_info(chip, 3);
+ g_assert_nonnull(info);
+ gpiod_test_return_if_failed();
+
+ request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
+
+ ret = gpiod_chip_wait_info_event(chip, 100000000);
+ g_assert_cmpint(ret, >, 0);
+ gpiod_test_return_if_failed();
+
+ event = gpiod_chip_read_info_event(chip);
+ g_assert_nonnull(event);
+ gpiod_test_return_if_failed();
+
+ ret = gpiod_chip_unwatch_line_info(chip, 3);
+ g_assert_cmpint(ret, ==, 0);
+ gpiod_test_return_if_failed();
+
+ gpiod_line_request_release(request);
+ request = NULL;
+
+ ret = gpiod_chip_wait_info_event(chip, 100000000);
+ g_assert_cmpint(ret, ==, 0);
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+
+#include <errno.h>
+#include <glib.h>
+#include <gpiod.h>
+
+#include "gpiod-test.h"
+#include "gpiod-test-helpers.h"
+#include "gpiod-test-sim.h"
+
+#define GPIOD_TEST_GROUP "line-config"
+
+GPIOD_TEST_CASE(too_many_lines)
+{
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) config = NULL;
+ guint offsets[65], i;
+ gint ret;
+
+ settings = gpiod_test_create_line_settings_or_fail();
+ config = gpiod_test_create_line_config_or_fail();
+
+ for (i = 0; i < 65; i++)
+ offsets[i] = i;
+
+ ret = gpiod_line_config_add_line_settings(config, offsets, 65,
+ settings);
+ g_assert_cmpint(ret, <, 0);
+ g_assert_cmpint(errno, ==, E2BIG);
+}
+
+GPIOD_TEST_CASE(get_line_settings)
+{
+ static const guint offsets[] = { 0, 1, 2, 3 };
+
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_settings) retrieved = NULL;
+ g_autoptr(struct_gpiod_line_config) config = NULL;
+
+ settings = gpiod_test_create_line_settings_or_fail();
+ config = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_PULL_DOWN);
+ gpiod_test_line_config_add_line_settings_or_fail(config, offsets, 4,
+ settings);
+
+ retrieved = gpiod_line_config_get_line_settings(config, 2);
+ g_assert_nonnull(retrieved);
+ gpiod_test_return_if_failed();
+
+ g_assert_cmpint(gpiod_line_settings_get_direction(settings), ==,
+ GPIOD_LINE_DIRECTION_INPUT);
+ g_assert_cmpint(gpiod_line_settings_get_bias(settings), ==,
+ GPIOD_LINE_BIAS_PULL_DOWN);
+}
+
+GPIOD_TEST_CASE(too_many_attrs)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ guint offset;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+ gpiod_line_settings_set_output_value(settings, GPIOD_LINE_VALUE_ACTIVE);
+ offset = 0;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_line_settings_set_debounce_period_us(settings, 1000);
+ gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+ offset = 1;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_PULL_UP);
+ offset = 2;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_PULL_DOWN);
+ offset = 3;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_DISABLED);
+ offset = 4;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ gpiod_line_settings_set_active_low(settings, true);
+ offset = 5;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ gpiod_line_settings_set_edge_detection(settings,
+ GPIOD_LINE_EDGE_FALLING);
+ offset = 6;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ gpiod_line_settings_set_event_clock(settings,
+ GPIOD_LINE_EVENT_CLOCK_REALTIME);
+ offset = 7;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ gpiod_line_settings_reset(settings);
+
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+ gpiod_line_settings_set_drive(settings, GPIOD_LINE_DRIVE_OPEN_DRAIN);
+ offset = 8;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ gpiod_line_settings_set_drive(settings, GPIOD_LINE_DRIVE_OPEN_SOURCE);
+ offset = 9;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_chip_request_lines(chip, NULL, line_cfg);
+ g_assert_null(request);
+ g_assert_cmpint(errno, ==, E2BIG);
+}
+
+GPIOD_TEST_CASE(reset_config)
+{
+ static const guint offsets[] = { 0, 1, 2, 3 };
+
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_settings) retrieved0 = NULL;
+ g_autoptr(struct_gpiod_line_settings) retrieved1 = NULL;
+ g_autoptr(struct_gpiod_line_config) config = NULL;
+
+ settings = gpiod_test_create_line_settings_or_fail();
+ config = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_PULL_DOWN);
+ gpiod_test_line_config_add_line_settings_or_fail(config, offsets, 4,
+ settings);
+
+ retrieved0 = gpiod_line_config_get_line_settings(config, 2);
+ g_assert_nonnull(retrieved0);
+ gpiod_test_return_if_failed();
+
+ g_assert_cmpint(gpiod_line_settings_get_direction(settings), ==,
+ GPIOD_LINE_DIRECTION_INPUT);
+ g_assert_cmpint(gpiod_line_settings_get_bias(settings), ==,
+ GPIOD_LINE_BIAS_PULL_DOWN);
+
+ gpiod_line_config_reset(config);
+
+ retrieved1 = gpiod_line_config_get_line_settings(config, 2);
+ g_assert_null(retrieved1);
+}
+
+GPIOD_TEST_CASE(get_offsets)
+{
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) config = NULL;
+ g_autofree guint *config_offs = NULL;
+ guint offsets[8];
+ size_t num_offsets;
+ gint ret;
+
+ settings = gpiod_test_create_line_settings_or_fail();
+ config = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_PULL_DOWN);
+ offsets[0] = 2;
+ offsets[1] = 4;
+ gpiod_test_line_config_add_line_settings_or_fail(config, offsets, 2,
+ settings);
+
+ gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+ offsets[0] = 6;
+ offsets[1] = 7;
+ gpiod_test_line_config_add_line_settings_or_fail(config, offsets, 2,
+ settings);
+
+ ret = gpiod_line_config_get_offsets(config, &num_offsets, &config_offs);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpuint(num_offsets, ==, 4);
+ g_assert_cmpuint(config_offs[0], ==, 2);
+ g_assert_cmpuint(config_offs[1], ==, 4);
+ g_assert_cmpuint(config_offs[2], ==, 6);
+ g_assert_cmpuint(config_offs[3], ==, 7);
+}
+
+GPIOD_TEST_CASE(get_0_offsets)
+{
+ g_autoptr(struct_gpiod_line_config) config = NULL;
+ g_autofree guint *offsets = NULL;
+ size_t num_offsets;
+ gint ret;
+
+ config = gpiod_test_create_line_config_or_fail();
+
+ ret = gpiod_line_config_get_offsets(config, &num_offsets, &offsets);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpuint(num_offsets, ==, 0);
+ g_assert_null(offsets);
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+
+#include <errno.h>
+#include <glib.h>
+#include <gpiod.h>
+
+#include "gpiod-test.h"
+#include "gpiod-test-helpers.h"
+#include "gpiod-test-sim.h"
+
+#define GPIOD_TEST_GROUP "line-info"
+
+GPIOD_TEST_CASE(get_line_info_good)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_info) info = NULL;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+
+ info = gpiod_chip_get_line_info(chip, 3);
+ g_assert_nonnull(info);
+ g_assert_cmpuint(gpiod_line_info_get_offset(info), ==, 3);
+}
+
+GPIOD_TEST_CASE(get_line_info_offset_out_of_range)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_info) info = NULL;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+
+ info = gpiod_chip_get_line_info(chip, 8);
+ g_assert_null(info);
+ gpiod_test_expect_errno(EINVAL);
+}
+
+GPIOD_TEST_CASE(line_info_basic_properties)
+{
+ static const struct gpiod_test_line_name names[] = {
+ { .offset = 1, .name = "foo", },
+ { .offset = 2, .name = "bar", },
+ { .offset = 4, .name = "baz", },
+ { .offset = 5, .name = "xyz", },
+ { }
+ };
+
+ static const struct gpiod_test_hog hogs[] = {
+ {
+ .offset = 3,
+ .name = "hog3",
+ .direction = G_GPIOSIM_HOG_DIR_OUTPUT_HIGH,
+ },
+ {
+ .offset = 4,
+ .name = "hog4",
+ .direction = G_GPIOSIM_HOG_DIR_OUTPUT_LOW,
+ },
+ { }
+ };
+
+ g_autoptr(GPIOSimChip) sim = NULL;
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_info) info4 = NULL;
+ g_autoptr(struct_gpiod_line_info) info6 = NULL;
+
+ sim = g_gpiosim_chip_new(
+ "num-lines", 8,
+ "line-names", gpiod_test_package_line_names(names),
+ "hogs", gpiod_test_package_hogs(hogs),
+ NULL);
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ info4 = gpiod_test_get_line_info_or_fail(chip, 4);
+ info6 = gpiod_test_get_line_info_or_fail(chip, 6);
+
+ g_assert_cmpuint(gpiod_line_info_get_offset(info4), ==, 4);
+ g_assert_cmpstr(gpiod_line_info_get_name(info4), ==, "baz");
+ g_assert_cmpstr(gpiod_line_info_get_consumer(info4), ==, "hog4");
+ g_assert_true(gpiod_line_info_is_used(info4));
+ g_assert_cmpint(gpiod_line_info_get_direction(info4), ==,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+ g_assert_cmpint(gpiod_line_info_get_edge_detection(info4), ==,
+ GPIOD_LINE_EDGE_NONE);
+ g_assert_false(gpiod_line_info_is_active_low(info4));
+ g_assert_cmpint(gpiod_line_info_get_bias(info4), ==,
+ GPIOD_LINE_BIAS_UNKNOWN);
+ g_assert_cmpint(gpiod_line_info_get_drive(info4), ==,
+ GPIOD_LINE_DRIVE_PUSH_PULL);
+ g_assert_cmpint(gpiod_line_info_get_event_clock(info4), ==,
+ GPIOD_LINE_EVENT_CLOCK_MONOTONIC);
+ g_assert_false(gpiod_line_info_is_debounced(info4));
+ g_assert_cmpuint(gpiod_line_info_get_debounce_period_us(info4), ==, 0);
+}
+
+GPIOD_TEST_CASE(copy_line_info)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_info) info = NULL;
+ g_autoptr(struct_gpiod_line_info) copy = NULL;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ info = gpiod_test_get_line_info_or_fail(chip, 3);
+
+ copy = gpiod_line_info_copy(info);
+ g_assert_nonnull(copy);
+ g_assert_true(info != copy);
+ g_assert_cmpuint(gpiod_line_info_get_offset(info), ==,
+ gpiod_line_info_get_offset(copy));
+}
+
+GPIOD_TEST_CASE(direction_settings)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ g_autoptr(struct_gpiod_line_info) info0 = NULL;
+ g_autoptr(struct_gpiod_line_info) info1 = NULL;
+ g_autoptr(struct_gpiod_line_info) info2 = NULL;
+ guint offset;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+ offset = 0;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+ offset = 1;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_AS_IS);
+ offset = 2;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
+ info0 = gpiod_test_get_line_info_or_fail(chip, 0);
+ info1 = gpiod_test_get_line_info_or_fail(chip, 1);
+ info2 = gpiod_test_get_line_info_or_fail(chip, 2);
+
+ g_assert_cmpint(gpiod_line_info_get_direction(info0), ==,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+ g_assert_cmpint(gpiod_line_info_get_direction(info1), ==,
+ GPIOD_LINE_DIRECTION_INPUT);
+ g_assert_cmpint(gpiod_line_info_get_direction(info2), ==,
+ GPIOD_LINE_DIRECTION_INPUT);
+}
+
+GPIOD_TEST_CASE(active_high)
+{
+ static const guint offset = 5;
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ g_autoptr(struct_gpiod_line_info) info = NULL;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_active_low(settings, true);
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset,
+ 1, settings);
+
+ request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
+ info = gpiod_test_get_line_info_or_fail(chip, 5);
+
+ g_assert_true(gpiod_line_info_is_active_low(info));
+}
+
+GPIOD_TEST_CASE(edge_detection_settings)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_info) info0 = NULL;
+ g_autoptr(struct_gpiod_line_info) info1 = NULL;
+ g_autoptr(struct_gpiod_line_info) info2 = NULL;
+ g_autoptr(struct_gpiod_line_info) info3 = NULL;
+ guint offset;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ line_cfg = gpiod_test_create_line_config_or_fail();
+ settings = gpiod_test_create_line_settings_or_fail();
+
+ gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_NONE);
+ offset = 0;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+ gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_RISING);
+ offset = 1;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+ gpiod_line_settings_set_edge_detection(settings,
+ GPIOD_LINE_EDGE_FALLING);
+ offset = 2;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+ gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+ offset = 3;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
+ info0 = gpiod_test_get_line_info_or_fail(chip, 0);
+ info1 = gpiod_test_get_line_info_or_fail(chip, 1);
+ info2 = gpiod_test_get_line_info_or_fail(chip, 2);
+ info3 = gpiod_test_get_line_info_or_fail(chip, 3);
+
+ g_assert_cmpint(gpiod_line_info_get_edge_detection(info0), ==,
+ GPIOD_LINE_EDGE_NONE);
+ g_assert_cmpint(gpiod_line_info_get_edge_detection(info1), ==,
+ GPIOD_LINE_EDGE_RISING);
+ g_assert_cmpint(gpiod_line_info_get_edge_detection(info2), ==,
+ GPIOD_LINE_EDGE_FALLING);
+ g_assert_cmpint(gpiod_line_info_get_edge_detection(info3), ==,
+ GPIOD_LINE_EDGE_BOTH);
+}
+
+GPIOD_TEST_CASE(bias_settings)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ g_autoptr(struct_gpiod_line_info) info0 = NULL;
+ g_autoptr(struct_gpiod_line_info) info1 = NULL;
+ g_autoptr(struct_gpiod_line_info) info2 = NULL;
+ g_autoptr(struct_gpiod_line_info) info3 = NULL;
+ guint offset;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_direction(settings,GPIOD_LINE_DIRECTION_OUTPUT);
+ offset = 0;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+ gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_DISABLED);
+ offset = 1;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+ gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_PULL_DOWN);
+ offset = 2;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+ gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_PULL_UP);
+ offset = 3;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
+ info0 = gpiod_test_get_line_info_or_fail(chip, 0);
+ info1 = gpiod_test_get_line_info_or_fail(chip, 1);
+ info2 = gpiod_test_get_line_info_or_fail(chip, 2);
+ info3 = gpiod_test_get_line_info_or_fail(chip, 3);
+
+ g_assert_cmpint(gpiod_line_info_get_bias(info0), ==,
+ GPIOD_LINE_BIAS_UNKNOWN);
+ g_assert_cmpint(gpiod_line_info_get_bias(info1), ==,
+ GPIOD_LINE_BIAS_DISABLED);
+ g_assert_cmpint(gpiod_line_info_get_bias(info2), ==,
+ GPIOD_LINE_BIAS_PULL_DOWN);
+ g_assert_cmpint(gpiod_line_info_get_bias(info3), ==,
+ GPIOD_LINE_BIAS_PULL_UP);
+}
+
+GPIOD_TEST_CASE(drive_settings)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ g_autoptr(struct_gpiod_line_info) info0 = NULL;
+ g_autoptr(struct_gpiod_line_info) info1 = NULL;
+ g_autoptr(struct_gpiod_line_info) info2 = NULL;
+ guint offset;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ line_cfg = gpiod_test_create_line_config_or_fail();
+ settings = gpiod_test_create_line_settings_or_fail();
+
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+ offset = 0;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+ gpiod_line_settings_set_drive(settings, GPIOD_LINE_DRIVE_OPEN_DRAIN);
+ offset = 1;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+ gpiod_line_settings_set_drive(settings, GPIOD_LINE_DRIVE_OPEN_SOURCE);
+ offset = 2;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
+ info0 = gpiod_test_get_line_info_or_fail(chip, 0);
+ info1 = gpiod_test_get_line_info_or_fail(chip, 1);
+ info2 = gpiod_test_get_line_info_or_fail(chip, 2);
+
+ g_assert_cmpint(gpiod_line_info_get_drive(info0), ==,
+ GPIOD_LINE_DRIVE_PUSH_PULL);
+ g_assert_cmpint(gpiod_line_info_get_drive(info1), ==,
+ GPIOD_LINE_DRIVE_OPEN_DRAIN);
+ g_assert_cmpint(gpiod_line_info_get_drive(info2), ==,
+ GPIOD_LINE_DRIVE_OPEN_SOURCE);
+}
+
+GPIOD_TEST_CASE(debounce_period)
+{
+ static const guint offset = 5;
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ g_autoptr(struct_gpiod_line_info) info = NULL;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ line_cfg = gpiod_test_create_line_config_or_fail();
+ settings = gpiod_test_create_line_settings_or_fail();
+
+ gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+ gpiod_line_settings_set_debounce_period_us(settings, 1000);
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
+ info = gpiod_test_get_line_info_or_fail(chip, 5);
+
+ g_assert_cmpuint(gpiod_line_info_get_debounce_period_us(info),
+ ==, 1000);
+}
+
+GPIOD_TEST_CASE(event_clock)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ g_autoptr(struct_gpiod_line_info) info0 = NULL;
+ g_autoptr(struct_gpiod_line_info) info1 = NULL;
+ g_autoptr(struct_gpiod_line_info) info2 = NULL;
+ guint offset;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ line_cfg = gpiod_test_create_line_config_or_fail();
+ settings = gpiod_test_create_line_settings_or_fail();
+
+ offset = 0;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+ gpiod_line_settings_set_event_clock(settings,
+ GPIOD_LINE_EVENT_CLOCK_REALTIME);
+ offset = 1;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ gpiod_line_settings_set_event_clock(settings,
+ GPIOD_LINE_EVENT_CLOCK_HTE);
+ offset = 2;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_chip_request_lines(chip, NULL, line_cfg);
+ if (!request && errno == EOPNOTSUPP) {
+ g_test_skip("HTE support not available");
+ return;
+ }
+
+ gpiod_test_return_if_failed();
+
+ info0 = gpiod_test_get_line_info_or_fail(chip, 0);
+ info1 = gpiod_test_get_line_info_or_fail(chip, 1);
+ info2 = gpiod_test_get_line_info_or_fail(chip, 2);
+
+ g_assert_cmpint(gpiod_line_info_get_event_clock(info0), ==,
+ GPIOD_LINE_EVENT_CLOCK_MONOTONIC);
+ g_assert_cmpint(gpiod_line_info_get_event_clock(info1), ==,
+ GPIOD_LINE_EVENT_CLOCK_REALTIME);
+ g_assert_cmpint(gpiod_line_info_get_event_clock(info2), ==,
+ GPIOD_LINE_EVENT_CLOCK_HTE);
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+
+#include <glib.h>
+#include <gpiod.h>
+
+#include "gpiod-test.h"
+#include "gpiod-test-helpers.h"
+#include "gpiod-test-sim.h"
+
+#define GPIOD_TEST_GROUP "line-request"
+
+GPIOD_TEST_CASE(request_fails_with_no_offsets)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 4, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+
+ request = gpiod_chip_request_lines(chip, NULL, line_cfg);
+ g_assert_null(request);
+ gpiod_test_expect_errno(EINVAL);
+}
+
+GPIOD_TEST_CASE(request_fails_with_duplicate_offsets)
+{
+ static const guint offsets[] = { 0, 2, 2, 3 };
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 4, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ size_t num_requested_offsets;
+ guint requested_offsets[3];
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 4,
+ NULL);
+
+ request = gpiod_chip_request_lines(chip, NULL, line_cfg);
+ g_assert_nonnull(request);
+ num_requested_offsets = gpiod_line_request_get_num_lines(request);
+ g_assert_cmpuint(num_requested_offsets, ==, 3);
+ gpiod_line_request_get_offsets(request, requested_offsets);
+ g_assert_cmpuint(requested_offsets[0], ==, 0);
+ g_assert_cmpuint(requested_offsets[1], ==, 2);
+ g_assert_cmpuint(requested_offsets[2], ==, 3);
+}
+
+GPIOD_TEST_CASE(request_fails_with_offset_out_of_bounds)
+{
+ static const guint offsets[] = { 2, 6 };
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 4, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 2,
+ NULL);
+
+ request = gpiod_chip_request_lines(chip, NULL, line_cfg);
+ g_assert_null(request);
+ gpiod_test_expect_errno(EINVAL);
+}
+
+GPIOD_TEST_CASE(set_consumer)
+{
+ static const guint offset = 2;
+ static const gchar *const consumer = "foobar";
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 4, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_request_config) req_cfg = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ g_autoptr(struct_gpiod_line_info) info = NULL;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ req_cfg = gpiod_test_create_request_config_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_request_config_set_consumer(req_cfg, consumer);
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ NULL);
+
+ request = gpiod_test_request_lines_or_fail(chip, req_cfg, line_cfg);
+
+ info = gpiod_test_get_line_info_or_fail(chip, offset);
+
+ g_assert_true(gpiod_line_info_is_used(info));
+ g_assert_cmpstr(gpiod_line_info_get_consumer(info), ==, consumer);
+}
+
+GPIOD_TEST_CASE(empty_consumer)
+{
+ static const guint offset = 2;
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 4, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ g_autoptr(struct_gpiod_line_info) info = NULL;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ NULL);
+
+ request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
+
+ info = gpiod_test_get_line_info_or_fail(chip, offset);
+
+ g_assert_cmpstr(gpiod_line_info_get_consumer(info), ==, "?");
+}
+
+GPIOD_TEST_CASE(default_output_value)
+{
+ /*
+ * Have a hole in offsets on purpose - make sure it's not set by
+ * accident.
+ */
+ static const guint offsets[] = { 0, 1, 3, 4 };
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ guint i;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+ gpiod_line_settings_set_output_value(settings, GPIOD_LINE_VALUE_ACTIVE);
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 4,
+ settings);
+
+ g_gpiosim_chip_set_pull(sim, 2, G_GPIOSIM_PULL_DOWN);
+
+ request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
+
+ for (i = 0; i < 4; i++)
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, offsets[i]),
+ ==, GPIOD_LINE_VALUE_ACTIVE);
+
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 2), ==,
+ GPIOD_LINE_VALUE_INACTIVE);
+}
+
+GPIOD_TEST_CASE(read_all_values)
+{
+ static const guint offsets[] = { 0, 2, 4, 5, 7 };
+ static const gint pulls[] = { 0, 1, 0, 1, 1 };
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ gint ret, values[5];
+ guint i;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 5,
+ settings);
+
+ request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
+
+ for (i = 0; i < 5; i++)
+ g_gpiosim_chip_set_pull(sim, offsets[i],
+ pulls[i] ? G_GPIOSIM_PULL_UP : G_GPIOSIM_PULL_DOWN);
+
+ ret = gpiod_line_request_get_values(request, values);
+ g_assert_cmpint(ret, ==, 0);
+ gpiod_test_return_if_failed();
+
+ for (i = 0; i < 5; i++)
+ g_assert_cmpint(values[i], ==, pulls[i]);
+}
+
+GPIOD_TEST_CASE(request_multiple_values_but_read_one)
+{
+ static const guint offsets[] = { 0, 2, 4, 5, 7 };
+ static const gint pulls[] = { 0, 1, 0, 1, 1 };
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ gint ret;
+ guint i;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 5,
+ settings);
+
+ request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
+
+ for (i = 0; i < 5; i++)
+ g_gpiosim_chip_set_pull(sim, offsets[i],
+ pulls[i] ? G_GPIOSIM_PULL_UP : G_GPIOSIM_PULL_DOWN);
+
+ ret = gpiod_line_request_get_value(request, 5);
+ g_assert_cmpint(ret, ==, 1);
+}
+
+GPIOD_TEST_CASE(set_all_values)
+{
+ static const guint offsets[] = { 0, 2, 4, 5, 6 };
+ static const gint values[] = { GPIOD_LINE_VALUE_ACTIVE,
+ GPIOD_LINE_VALUE_INACTIVE,
+ GPIOD_LINE_VALUE_ACTIVE,
+ GPIOD_LINE_VALUE_ACTIVE,
+ GPIOD_LINE_VALUE_ACTIVE };
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ gint ret;
+ guint i;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 5,
+ settings);
+
+ request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
+
+ ret = gpiod_line_request_set_values(request, values);
+ g_assert_cmpint(ret, ==, 0);
+ gpiod_test_return_if_failed();
+
+ for (i = 0; i < 5; i++)
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, offsets[i]),
+ ==, values[i]);
+}
+
+GPIOD_TEST_CASE(set_values_subset_of_lines)
+{
+ static const guint offsets[] = { 0, 1, 2, 3 };
+ static const guint offsets_to_set[] = { 0, 1, 3 };
+ static const gint values[] = { GPIOD_LINE_VALUE_ACTIVE,
+ GPIOD_LINE_VALUE_INACTIVE,
+ GPIOD_LINE_VALUE_ACTIVE };
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 4, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ gint ret;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 4,
+ settings);
+
+ request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
+
+ ret = gpiod_line_request_set_values_subset(request, 3,
+ offsets_to_set, values);
+ g_assert_cmpint(ret, ==, 0);
+ gpiod_test_return_if_failed();
+
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 0),
+ ==, GPIOD_LINE_VALUE_ACTIVE);
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 1),
+ ==, GPIOD_LINE_VALUE_INACTIVE);
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 3),
+ ==, GPIOD_LINE_VALUE_ACTIVE);
+}
+
+GPIOD_TEST_CASE(set_line_after_requesting)
+{
+ static const guint offsets[] = { 0, 1, 3, 4 };
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+ gpiod_line_settings_set_output_value(settings,
+ GPIOD_LINE_VALUE_INACTIVE);
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 4,
+ settings);
+
+ request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
+
+ gpiod_line_request_set_value(request, 1, GPIOD_LINE_VALUE_ACTIVE);
+
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 0), ==, 0);
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 1), ==, 1);
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 3), ==, 0);
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 4), ==, 0);
+}
+
+GPIOD_TEST_CASE(request_survives_parent_chip)
+{
+ static const guint offset = 0;
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 4, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ gint ret;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+ gpiod_line_settings_set_output_value(settings,
+ GPIOD_LINE_VALUE_ACTIVE);
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
+
+ g_assert_cmpint(gpiod_line_request_get_value(request, offset), ==,
+ GPIOD_LINE_VALUE_ACTIVE);
+
+ gpiod_chip_close(chip);
+ chip = NULL;
+
+ ret = gpiod_line_request_set_value(request, offset,
+ GPIOD_LINE_VALUE_ACTIVE);
+ g_assert_cmpint(ret, ==, 0);
+ gpiod_test_return_if_failed();
+
+ ret = gpiod_line_request_set_value(request, offset,
+ GPIOD_LINE_VALUE_ACTIVE);
+ g_assert_cmpint(ret, ==, 0);
+ gpiod_test_return_if_failed();
+}
+
+GPIOD_TEST_CASE(num_lines_and_offsets)
+{
+ static const guint offsets[] = { 0, 1, 2, 3, 7, 8, 11, 14 };
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 16, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ guint read_back[8], i;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 8,
+ NULL);
+
+ request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
+
+ g_assert_cmpuint(gpiod_line_request_get_num_lines(request), ==, 8);
+ gpiod_test_return_if_failed();
+ gpiod_line_request_get_offsets(request, read_back);
+ for (i = 0; i < 8; i++)
+ g_assert_cmpuint(read_back[i], ==, offsets[i]);
+}
+
+GPIOD_TEST_CASE(active_low_read_value)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ guint offset;
+ gint value;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_active_low(settings, true);
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+ offset = 2;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+ gpiod_line_settings_set_output_value(settings, GPIOD_LINE_VALUE_ACTIVE);
+ offset = 3;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
+
+ g_gpiosim_chip_set_pull(sim, 2, G_GPIOSIM_PULL_DOWN);
+ value = gpiod_line_request_get_value(request, 2);
+ g_assert_cmpint(value, ==, GPIOD_LINE_VALUE_ACTIVE);
+
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 3), ==, 0);
+}
+
+GPIOD_TEST_CASE(reconfigure_lines)
+{
+ //static const guint offsets[] = { 0, 1, 2, 3 };
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 4, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ guint offsets[2];
+ gint ret;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+
+ gpiod_line_settings_set_output_value(settings, GPIOD_LINE_VALUE_ACTIVE);
+ offsets[0] = 0;
+ offsets[1] = 2;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 2,
+ settings);
+ gpiod_line_settings_set_output_value(settings,
+ GPIOD_LINE_VALUE_INACTIVE);
+ offsets[0] = 1;
+ offsets[1] = 3;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 2,
+ settings);
+
+ request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
+
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 0), ==, 1);
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 1), ==, 0);
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 2), ==, 1);
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 3), ==, 0);
+
+ gpiod_line_config_reset(line_cfg);
+
+ gpiod_line_settings_set_output_value(settings,
+ GPIOD_LINE_VALUE_INACTIVE);
+ offsets[0] = 0;
+ offsets[1] = 2;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 2,
+ settings);
+ gpiod_line_settings_set_output_value(settings, GPIOD_LINE_VALUE_ACTIVE);
+ offsets[0] = 1;
+ offsets[1] = 3;
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 2,
+ settings);
+
+ ret = gpiod_line_request_reconfigure_lines(request, line_cfg);
+ g_assert_cmpint(ret, ==, 0);
+ gpiod_test_return_if_failed();
+
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 0), ==, 0);
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 1), ==, 1);
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 2), ==, 0);
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 3), ==, 1);
+}
+
+GPIOD_TEST_CASE(request_lines_with_unordered_offsets)
+{
+ static const guint offsets[] = { 5, 1, 7, 2, 0, 6 };
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ guint set_offsets[4];
+ gint values[4];
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+ gpiod_line_settings_set_output_value(settings, GPIOD_LINE_VALUE_ACTIVE);
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, offsets, 6,
+ settings);
+
+ request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
+
+ values[0] = 0;
+ values[1] = 1;
+ values[2] = 0;
+ values[3] = 0;
+ set_offsets[0] = 7;
+ set_offsets[1] = 1;
+ set_offsets[2] = 6;
+ set_offsets[3] = 0;
+ gpiod_line_request_set_values_subset(request, 4, set_offsets, values);
+
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 0), ==,
+ GPIOD_LINE_VALUE_INACTIVE);
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 1), ==,
+ GPIOD_LINE_VALUE_ACTIVE);
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 2), ==,
+ GPIOD_LINE_VALUE_ACTIVE);
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 5), ==,
+ GPIOD_LINE_VALUE_ACTIVE);
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 6), ==,
+ GPIOD_LINE_VALUE_INACTIVE);
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 7), ==,
+ GPIOD_LINE_VALUE_INACTIVE);
+}
+
+GPIOD_TEST_CASE(request_with_bias_set_to_pull_up)
+{
+ static const guint offset = 3;
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_PULL_UP);
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_test_request_lines_or_fail(chip, NULL, line_cfg);
+
+ g_assert_cmpint(g_gpiosim_chip_get_value(sim, 3), ==,
+ GPIOD_LINE_VALUE_ACTIVE);
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <errno.h>
+#include <glib.h>
+#include <gpiod.h>
+
+#include "gpiod-test.h"
+#include "gpiod-test-helpers.h"
+
+#define GPIOD_TEST_GROUP "line-settings"
+
+GPIOD_TEST_CASE(default_config)
+{
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+
+ settings = gpiod_test_create_line_settings_or_fail();
+
+ g_assert_cmpint(gpiod_line_settings_get_direction(settings), ==,
+ GPIOD_LINE_DIRECTION_AS_IS);
+ g_assert_cmpint(gpiod_line_settings_get_edge_detection(settings), ==,
+ GPIOD_LINE_EDGE_NONE);
+ g_assert_cmpint(gpiod_line_settings_get_bias(settings), ==,
+ GPIOD_LINE_BIAS_AS_IS);
+ g_assert_cmpint(gpiod_line_settings_get_drive(settings), ==,
+ GPIOD_LINE_DRIVE_PUSH_PULL);
+ g_assert_false(gpiod_line_settings_get_active_low(settings));
+ g_assert_cmpuint(gpiod_line_settings_get_debounce_period_us(settings),
+ ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_event_clock(settings), ==,
+ GPIOD_LINE_EVENT_CLOCK_MONOTONIC);
+ g_assert_cmpint(gpiod_line_settings_get_output_value(settings), ==,
+ GPIOD_LINE_VALUE_INACTIVE);
+}
+
+GPIOD_TEST_CASE(set_direction)
+{
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ gint ret;
+
+ settings = gpiod_test_create_line_settings_or_fail();
+
+ ret = gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_INPUT);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_direction(settings), ==,
+ GPIOD_LINE_DIRECTION_INPUT);
+
+ ret = gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_AS_IS);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_direction(settings), ==,
+ GPIOD_LINE_DIRECTION_AS_IS);
+
+ ret = gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_direction(settings), ==,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+
+ ret = gpiod_line_settings_set_direction(settings, 999);
+ g_assert_cmpint(ret, <, 0);
+ g_assert_cmpint(errno, ==, EINVAL);
+ g_assert_cmpint(gpiod_line_settings_get_direction(settings), ==,
+ GPIOD_LINE_DIRECTION_AS_IS);
+}
+
+GPIOD_TEST_CASE(set_edge_detection)
+{
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ gint ret;
+
+ settings = gpiod_test_create_line_settings_or_fail();
+
+ ret = gpiod_line_settings_set_edge_detection(settings,
+ GPIOD_LINE_EDGE_BOTH);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_edge_detection(settings), ==,
+ GPIOD_LINE_EDGE_BOTH);
+
+ ret = gpiod_line_settings_set_edge_detection(settings,
+ GPIOD_LINE_EDGE_NONE);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_edge_detection(settings), ==,
+ GPIOD_LINE_EDGE_NONE);
+
+ ret = gpiod_line_settings_set_edge_detection(settings,
+ GPIOD_LINE_EDGE_FALLING);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_edge_detection(settings), ==,
+ GPIOD_LINE_EDGE_FALLING);
+
+ ret = gpiod_line_settings_set_edge_detection(settings,
+ GPIOD_LINE_EDGE_RISING);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_edge_detection(settings), ==,
+ GPIOD_LINE_EDGE_RISING);
+
+ ret = gpiod_line_settings_set_edge_detection(settings, 999);
+ g_assert_cmpint(ret, <, 0);
+ g_assert_cmpint(errno, ==, EINVAL);
+ g_assert_cmpint(gpiod_line_settings_get_edge_detection(settings), ==,
+ GPIOD_LINE_EDGE_NONE);
+}
+
+GPIOD_TEST_CASE(set_bias)
+{
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ gint ret;
+
+ settings = gpiod_test_create_line_settings_or_fail();
+
+ ret = gpiod_line_settings_set_bias(settings,
+ GPIOD_LINE_BIAS_DISABLED);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_bias(settings), ==,
+ GPIOD_LINE_BIAS_DISABLED);
+
+ ret = gpiod_line_settings_set_bias(settings,
+ GPIOD_LINE_BIAS_AS_IS);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_bias(settings), ==,
+ GPIOD_LINE_BIAS_AS_IS);
+
+ ret = gpiod_line_settings_set_bias(settings,
+ GPIOD_LINE_BIAS_PULL_DOWN);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_bias(settings), ==,
+ GPIOD_LINE_BIAS_PULL_DOWN);
+
+ ret = gpiod_line_settings_set_bias(settings,
+ GPIOD_LINE_BIAS_PULL_UP);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_bias(settings), ==,
+ GPIOD_LINE_BIAS_PULL_UP);
+
+ ret = gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_UNKNOWN);
+ g_assert_cmpint(ret, <, 0);
+ g_assert_cmpint(errno, ==, EINVAL);
+ g_assert_cmpint(gpiod_line_settings_get_bias(settings), ==,
+ GPIOD_LINE_BIAS_AS_IS);
+
+ ret = gpiod_line_settings_set_bias(settings, 999);
+ g_assert_cmpint(ret, <, 0);
+ g_assert_cmpint(errno, ==, EINVAL);
+ g_assert_cmpint(gpiod_line_settings_get_bias(settings), ==,
+ GPIOD_LINE_BIAS_AS_IS);
+}
+
+GPIOD_TEST_CASE(set_drive)
+{
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ gint ret;
+
+ settings = gpiod_test_create_line_settings_or_fail();
+
+ ret = gpiod_line_settings_set_drive(settings,
+ GPIOD_LINE_DRIVE_OPEN_DRAIN);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_drive(settings), ==,
+ GPIOD_LINE_DRIVE_OPEN_DRAIN);
+
+ ret = gpiod_line_settings_set_drive(settings,
+ GPIOD_LINE_DRIVE_PUSH_PULL);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_drive(settings), ==,
+ GPIOD_LINE_DRIVE_PUSH_PULL);
+
+ ret = gpiod_line_settings_set_drive(settings,
+ GPIOD_LINE_DRIVE_OPEN_SOURCE);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_drive(settings), ==,
+ GPIOD_LINE_DRIVE_OPEN_SOURCE);
+
+ ret = gpiod_line_settings_set_drive(settings, 999);
+ g_assert_cmpint(ret, <, 0);
+ g_assert_cmpint(errno, ==, EINVAL);
+ g_assert_cmpint(gpiod_line_settings_get_drive(settings), ==,
+ GPIOD_LINE_DRIVE_PUSH_PULL);
+}
+
+GPIOD_TEST_CASE(set_active_low)
+{
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+
+ settings = gpiod_test_create_line_settings_or_fail();
+
+ gpiod_line_settings_set_active_low(settings, true);
+ g_assert_true(gpiod_line_settings_get_active_low(settings));
+
+ gpiod_line_settings_set_active_low(settings, false);
+ g_assert_false(gpiod_line_settings_get_active_low(settings));
+}
+
+GPIOD_TEST_CASE(set_debounce_period)
+{
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+
+ settings = gpiod_test_create_line_settings_or_fail();
+
+ gpiod_line_settings_set_debounce_period_us(settings, 4000);
+ g_assert_cmpint(gpiod_line_settings_get_debounce_period_us(settings),
+ ==, 4000);
+}
+
+GPIOD_TEST_CASE(set_event_clock)
+{
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ gint ret;
+
+ settings = gpiod_test_create_line_settings_or_fail();
+
+ ret = gpiod_line_settings_set_event_clock(settings,
+ GPIOD_LINE_EVENT_CLOCK_MONOTONIC);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_event_clock(settings), ==,
+ GPIOD_LINE_EVENT_CLOCK_MONOTONIC);
+
+ ret = gpiod_line_settings_set_event_clock(settings,
+ GPIOD_LINE_EVENT_CLOCK_REALTIME);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_event_clock(settings), ==,
+ GPIOD_LINE_EVENT_CLOCK_REALTIME);
+
+ ret = gpiod_line_settings_set_event_clock(settings,
+ GPIOD_LINE_EVENT_CLOCK_HTE);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_event_clock(settings), ==,
+ GPIOD_LINE_EVENT_CLOCK_HTE);
+
+ ret = gpiod_line_settings_set_event_clock(settings, 999);
+ g_assert_cmpint(ret, <, 0);
+ g_assert_cmpint(errno, ==, EINVAL);
+ g_assert_cmpint(gpiod_line_settings_get_event_clock(settings), ==,
+ GPIOD_LINE_EVENT_CLOCK_MONOTONIC);
+}
+
+GPIOD_TEST_CASE(set_output_value)
+{
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ gint ret;
+
+ settings = gpiod_test_create_line_settings_or_fail();
+
+ ret = gpiod_line_settings_set_output_value(settings,
+ GPIOD_LINE_VALUE_ACTIVE);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_output_value(settings), ==,
+ GPIOD_LINE_VALUE_ACTIVE);
+
+ ret = gpiod_line_settings_set_output_value(settings,
+ GPIOD_LINE_VALUE_INACTIVE);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_output_value(settings), ==,
+ GPIOD_LINE_VALUE_INACTIVE);
+
+ ret = gpiod_line_settings_set_output_value(settings, 999);
+ g_assert_cmpint(ret, <, 0);
+ g_assert_cmpint(errno, ==, EINVAL);
+ g_assert_cmpint(gpiod_line_settings_get_output_value(settings), ==,
+ GPIOD_LINE_VALUE_INACTIVE);
+}
+
+GPIOD_TEST_CASE(copy_line_settings)
+{
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_settings) copy = NULL;
+
+ settings = gpiod_test_create_line_settings_or_fail();
+
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+ gpiod_line_settings_set_debounce_period_us(settings, 2000);
+ gpiod_line_settings_set_event_clock(settings,
+ GPIOD_LINE_EVENT_CLOCK_REALTIME);
+
+ copy = gpiod_line_settings_copy(settings);
+ g_assert_nonnull(copy);
+ gpiod_test_return_if_failed();
+ g_assert_false(settings == copy);
+ g_assert_cmpint(gpiod_line_settings_get_direction(copy), ==,
+ GPIOD_LINE_DIRECTION_INPUT);
+ g_assert_cmpint(gpiod_line_settings_get_edge_detection(copy), ==,
+ GPIOD_LINE_EDGE_BOTH);
+ g_assert_cmpint(gpiod_line_settings_get_debounce_period_us(copy), ==,
+ 2000);
+ g_assert_cmpint(gpiod_line_settings_get_event_clock(copy), ==,
+ GPIOD_LINE_EVENT_CLOCK_REALTIME);
+}
+
+GPIOD_TEST_CASE(reset_settings)
+{
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+
+ settings = gpiod_test_create_line_settings_or_fail();
+
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
+ gpiod_line_settings_set_debounce_period_us(settings, 2000);
+ gpiod_line_settings_set_event_clock(settings,
+ GPIOD_LINE_EVENT_CLOCK_REALTIME);
+
+ gpiod_line_settings_reset(settings);
+
+ g_assert_cmpint(gpiod_line_settings_get_direction(settings), ==,
+ GPIOD_LINE_DIRECTION_AS_IS);
+ g_assert_cmpint(gpiod_line_settings_get_edge_detection(settings), ==,
+ GPIOD_LINE_EDGE_NONE);
+ g_assert_cmpint(gpiod_line_settings_get_bias(settings), ==,
+ GPIOD_LINE_BIAS_AS_IS);
+ g_assert_cmpint(gpiod_line_settings_get_drive(settings), ==,
+ GPIOD_LINE_DRIVE_PUSH_PULL);
+ g_assert_false(gpiod_line_settings_get_active_low(settings));
+ g_assert_cmpuint(gpiod_line_settings_get_debounce_period_us(settings),
+ ==, 0);
+ g_assert_cmpint(gpiod_line_settings_get_event_clock(settings), ==,
+ GPIOD_LINE_EVENT_CLOCK_MONOTONIC);
+ g_assert_cmpint(gpiod_line_settings_get_output_value(settings), ==,
+ GPIOD_LINE_VALUE_INACTIVE);
+}
+++ /dev/null
-// SPDX-License-Identifier: GPL-2.0-or-later
-// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
-
-#include <errno.h>
-#include <string.h>
-
-#include "gpiod-test.h"
-
-#define GPIOD_TEST_GROUP "line"
-
-GPIOD_TEST_CASE(request_output, 0, { 8 })
-{
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct gpiod_line *line0;
- struct gpiod_line *line1;
- gint ret;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line0 = gpiod_chip_get_line(chip, 2);
- line1 = gpiod_chip_get_line(chip, 5);
- g_assert_nonnull(line0);
- g_assert_nonnull(line1);
- gpiod_test_return_if_failed();
-
- ret = gpiod_line_request_output(line0, GPIOD_TEST_CONSUMER, 0);
- g_assert_cmpint(ret, ==, 0);
- ret = gpiod_line_request_output(line1, GPIOD_TEST_CONSUMER, 1);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
-
- g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 0);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 5), ==, 1);
-}
-
-GPIOD_TEST_CASE(request_already_requested, 0, { 8 })
-{
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct gpiod_line *line;
- gint ret;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line = gpiod_chip_get_line(chip, 0);
- g_assert_nonnull(line);
- gpiod_test_return_if_failed();
-
- ret = gpiod_line_request_input(line, GPIOD_TEST_CONSUMER);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
-
- ret = gpiod_line_request_input(line, GPIOD_TEST_CONSUMER);
- g_assert_cmpint(ret, ==, -1);
- g_assert_cmpint(errno, ==, EBUSY);
-}
-
-GPIOD_TEST_CASE(consumer, 0, { 8 })
-{
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct gpiod_line *line;
- gint ret;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line = gpiod_chip_get_line(chip, 0);
- g_assert_nonnull(line);
- gpiod_test_return_if_failed();
-
- g_assert_null(gpiod_line_consumer(line));
-
- ret = gpiod_line_request_input(line, GPIOD_TEST_CONSUMER);
- g_assert_cmpint(ret, ==, 0);
- g_assert_cmpstr(gpiod_line_consumer(line), ==, GPIOD_TEST_CONSUMER);
-}
-
-GPIOD_TEST_CASE(consumer_long_string, 0, { 8 })
-{
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct gpiod_line *line;
- gint ret;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line = gpiod_chip_get_line(chip, 0);
- g_assert_nonnull(line);
- gpiod_test_return_if_failed();
-
- g_assert_null(gpiod_line_consumer(line));
-
- ret = gpiod_line_request_input(line,
- "consumer string over 32 characters long");
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
- g_assert_cmpstr(gpiod_line_consumer(line), ==,
- "consumer string over 32 charact");
- g_assert_cmpuint(strlen(gpiod_line_consumer(line)), ==, 31);
-}
-
-GPIOD_TEST_CASE(request_bulk_output, 0, { 8, 8 })
-{
- g_autoptr(gpiod_line_bulk_struct) bulkA = NULL;
- g_autoptr(gpiod_line_bulk_struct) bulkB = NULL;
- g_autoptr(gpiod_chip_struct) chipA = NULL;
- g_autoptr(gpiod_chip_struct) chipB = NULL;
- struct gpiod_line *lineA0, *lineA1, *lineA2, *lineA3,
- *lineB0, *lineB1, *lineB2, *lineB3;
- gint valA[4], valB[4], ret;
-
- chipA = gpiod_chip_open(gpiod_test_chip_path(0));
- chipB = gpiod_chip_open(gpiod_test_chip_path(1));
- g_assert_nonnull(chipA);
- g_assert_nonnull(chipB);
- gpiod_test_return_if_failed();
-
- 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);
-
- g_assert_nonnull(lineA0);
- g_assert_nonnull(lineA1);
- g_assert_nonnull(lineA2);
- g_assert_nonnull(lineA3);
- g_assert_nonnull(lineB0);
- g_assert_nonnull(lineB1);
- g_assert_nonnull(lineB2);
- g_assert_nonnull(lineB3);
- gpiod_test_return_if_failed();
-
- bulkA = gpiod_line_bulk_new(4);
- bulkB = gpiod_line_bulk_new(4);
- g_assert_nonnull(bulkA);
- g_assert_nonnull(bulkB);
- gpiod_test_return_if_failed();
-
- gpiod_line_bulk_add_line(bulkA, lineA0);
- gpiod_line_bulk_add_line(bulkA, lineA1);
- gpiod_line_bulk_add_line(bulkA, lineA2);
- gpiod_line_bulk_add_line(bulkA, lineA3);
- gpiod_line_bulk_add_line(bulkB, lineB0);
- gpiod_line_bulk_add_line(bulkB, lineB1);
- gpiod_line_bulk_add_line(bulkB, lineB2);
- gpiod_line_bulk_add_line(bulkB, lineB3);
-
- valA[0] = 1;
- valA[1] = 0;
- valA[2] = 0;
- valA[3] = 1;
- ret = gpiod_line_request_bulk_output(bulkA,
- GPIOD_TEST_CONSUMER, valA);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
-
- valB[0] = 0;
- valB[1] = 1;
- valB[2] = 0;
- valB[3] = 1;
- ret = gpiod_line_request_bulk_output(bulkB,
- GPIOD_TEST_CONSUMER, valB);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
-
- g_assert_cmpint(gpiod_test_chip_get_value(0, 0), ==, 1);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 1), ==, 0);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 0);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 3), ==, 1);
-
- g_assert_cmpint(gpiod_test_chip_get_value(1, 0), ==, 0);
- g_assert_cmpint(gpiod_test_chip_get_value(1, 1), ==, 1);
- g_assert_cmpint(gpiod_test_chip_get_value(1, 2), ==, 0);
- g_assert_cmpint(gpiod_test_chip_get_value(1, 3), ==, 1);
-}
-
-GPIOD_TEST_CASE(request_null_default_vals_for_output, 0, { 8 })
-{
- g_autoptr(gpiod_line_bulk_struct) bulk = NULL;
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct gpiod_line *line0, *line1, *line2;
- gint ret;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line0 = gpiod_chip_get_line(chip, 0);
- line1 = gpiod_chip_get_line(chip, 1);
- line2 = gpiod_chip_get_line(chip, 2);
-
- g_assert_nonnull(line0);
- g_assert_nonnull(line1);
- g_assert_nonnull(line2);
- gpiod_test_return_if_failed();
-
- bulk = gpiod_line_bulk_new(3);
- g_assert_nonnull(bulk);
- gpiod_test_return_if_failed();
-
- gpiod_line_bulk_add_line(bulk, line0);
- gpiod_line_bulk_add_line(bulk, line1);
- gpiod_line_bulk_add_line(bulk, line2);
-
- ret = gpiod_line_request_bulk_output(bulk, GPIOD_TEST_CONSUMER, NULL);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
-
- g_assert_cmpint(gpiod_test_chip_get_value(0, 0), ==, 0);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 1), ==, 0);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 0);
-}
-
-GPIOD_TEST_CASE(set_value, 0, { 8 })
-{
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct gpiod_line *line;
- gint ret;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line = gpiod_chip_get_line(chip, 2);
- g_assert_nonnull(line);
- gpiod_test_return_if_failed();
-
- ret = gpiod_line_request_output(line, GPIOD_TEST_CONSUMER, 0);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
- g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 0);
-
- ret = gpiod_line_set_value(line, 1);
- g_assert_cmpint(ret, ==, 0);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 1);
- ret = gpiod_line_set_value(line, 0);
- g_assert_cmpint(ret, ==, 0);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 0);
-}
-
-GPIOD_TEST_CASE(set_value_bulk, 0, { 8 })
-{
- g_autoptr(gpiod_line_bulk_struct) bulk = NULL;
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct gpiod_line *line0, *line1, *line2;
- int values[3];
- gint ret;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line0 = gpiod_chip_get_line(chip, 0);
- line1 = gpiod_chip_get_line(chip, 1);
- line2 = gpiod_chip_get_line(chip, 2);
-
- g_assert_nonnull(line0);
- g_assert_nonnull(line1);
- g_assert_nonnull(line2);
- gpiod_test_return_if_failed();
-
- bulk = gpiod_line_bulk_new(3);
- g_assert_nonnull(bulk);
- gpiod_test_return_if_failed();
-
- gpiod_line_bulk_add_line(bulk, line0);
- gpiod_line_bulk_add_line(bulk, line1);
- gpiod_line_bulk_add_line(bulk, line2);
-
- values[0] = 0;
- values[1] = 1;
- values[2] = 2;
-
- ret = gpiod_line_request_bulk_output(bulk,
- GPIOD_TEST_CONSUMER, values);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
- g_assert_cmpint(gpiod_test_chip_get_value(0, 0), ==, 0);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 1), ==, 1);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 1);
-
- values[0] = 2;
- values[1] = 1;
- values[2] = 0;
-
- ret = gpiod_line_set_value_bulk(bulk, values);
- g_assert_cmpint(ret, ==, 0);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 0), ==, 1);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 1), ==, 1);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 0);
-
- ret = gpiod_line_set_value_bulk(bulk, NULL);
- g_assert_cmpint(ret, ==, 0);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 0), ==, 0);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 1), ==, 0);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 0);
-}
-
-GPIOD_TEST_CASE(set_config_bulk_null_values, 0, { 8 })
-{
- g_autoptr(gpiod_line_bulk_struct) bulk = NULL;
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct gpiod_line *line0, *line1, *line2;
- gint ret;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line0 = gpiod_chip_get_line(chip, 0);
- line1 = gpiod_chip_get_line(chip, 1);
- line2 = gpiod_chip_get_line(chip, 2);
-
- g_assert_nonnull(line0);
- g_assert_nonnull(line1);
- g_assert_nonnull(line2);
- gpiod_test_return_if_failed();
-
- bulk = gpiod_line_bulk_new(3);
- g_assert_nonnull(bulk);
- gpiod_test_return_if_failed();
-
- gpiod_line_bulk_add_line(bulk, line0);
- gpiod_line_bulk_add_line(bulk, line1);
- gpiod_line_bulk_add_line(bulk, line2);
-
- ret = gpiod_line_request_bulk_output(bulk, GPIOD_TEST_CONSUMER, 0);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
- g_assert_false(gpiod_line_is_active_low(line0));
- g_assert_false(gpiod_line_is_active_low(line1));
- g_assert_false(gpiod_line_is_active_low(line2));
-
- g_assert_cmpint(gpiod_test_chip_get_value(0, 0), ==, 0);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 1), ==, 0);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 0);
-
- ret = gpiod_line_set_config_bulk(bulk,
- GPIOD_LINE_REQUEST_DIRECTION_OUTPUT,
- GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW, NULL);
- g_assert_cmpint(ret, ==, 0);
- g_assert_true(gpiod_line_is_active_low(line0));
- g_assert_true(gpiod_line_is_active_low(line1));
- g_assert_true(gpiod_line_is_active_low(line2));
- g_assert_cmpint(gpiod_test_chip_get_value(0, 0), ==, 1);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 1), ==, 1);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 1);
-
- ret = gpiod_line_set_config_bulk(bulk,
- GPIOD_LINE_REQUEST_DIRECTION_OUTPUT, 0, NULL);
- g_assert_cmpint(ret, ==, 0);
- g_assert_false(gpiod_line_is_active_low(line0));
- g_assert_false(gpiod_line_is_active_low(line1));
- g_assert_false(gpiod_line_is_active_low(line2));
- g_assert_cmpint(gpiod_test_chip_get_value(0, 0), ==, 0);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 1), ==, 0);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 0);
-}
-
-GPIOD_TEST_CASE(set_flags_active_state, 0, { 8 })
-{
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct gpiod_line *line;
- gint ret;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line = gpiod_chip_get_line(chip, 2);
- g_assert_nonnull(line);
- gpiod_test_return_if_failed();
-
- ret = gpiod_line_request_output(line, GPIOD_TEST_CONSUMER, 1);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
- g_assert_false(gpiod_line_is_active_low(line));
- g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 1);
-
- ret = gpiod_line_set_flags(line, GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW);
- g_assert_cmpint(ret, ==, 0);
- g_assert_true(gpiod_line_is_active_low(line));
- g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 0);
-
- ret = gpiod_line_set_flags(line, 0);
- g_assert_cmpint(ret, ==, 0);
- g_assert_false(gpiod_line_is_active_low(line));
- g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 1);
-}
-
-GPIOD_TEST_CASE(set_flags_bias, 0, { 8 })
-{
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct gpiod_line *line;
- gint ret;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line = gpiod_chip_get_line(chip, 2);
- g_assert_nonnull(line);
- gpiod_test_return_if_failed();
-
- ret = gpiod_line_request_input(line, GPIOD_TEST_CONSUMER);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
- g_assert_cmpint(gpiod_line_bias(line), ==, GPIOD_LINE_BIAS_UNKNOWN);
-
- ret = gpiod_line_set_flags(line,
- GPIOD_LINE_REQUEST_FLAG_BIAS_DISABLED);
- g_assert_cmpint(ret, ==, 0);
- g_assert_cmpint(gpiod_line_bias(line), ==, GPIOD_LINE_BIAS_DISABLED);
-
- ret = gpiod_line_set_flags(line,
- GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP);
- g_assert_cmpint(ret, ==, 0);
- g_assert_cmpint(gpiod_line_bias(line), ==, GPIOD_LINE_BIAS_PULL_UP);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 1);
-
- ret = gpiod_line_set_flags(line,
- GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN);
- g_assert_cmpint(ret, ==, 0);
- g_assert_cmpint(gpiod_line_bias(line), ==, GPIOD_LINE_BIAS_PULL_DOWN);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 0);
-}
-
-GPIOD_TEST_CASE(set_flags_drive, 0, { 8 })
-{
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct gpiod_line *line;
- gint ret;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line = gpiod_chip_get_line(chip, 2);
- g_assert_nonnull(line);
- gpiod_test_return_if_failed();
-
- ret = gpiod_line_request_output(line, GPIOD_TEST_CONSUMER, 0);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
- g_assert_cmpint(gpiod_line_drive(line), ==, GPIOD_LINE_DRIVE_PUSH_PULL);
-
- ret = gpiod_line_set_flags(line,
- GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN);
- g_assert_cmpint(ret, ==, 0);
- g_assert_cmpint(gpiod_line_drive(line), ==,
- GPIOD_LINE_DRIVE_OPEN_DRAIN);
-
- ret = gpiod_line_set_flags(line,
- GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE);
- g_assert_cmpint(ret, ==, 0);
- g_assert_cmpint(gpiod_line_drive(line), ==,
- GPIOD_LINE_DRIVE_OPEN_SOURCE);
-}
-
-GPIOD_TEST_CASE(set_direction, 0, { 8 })
-{
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct gpiod_line *line;
- gint ret;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line = gpiod_chip_get_line(chip, 2);
- g_assert_nonnull(line);
- gpiod_test_return_if_failed();
-
- ret = gpiod_line_request_output(line, GPIOD_TEST_CONSUMER, 0);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
- g_assert_cmpint(gpiod_line_direction(line), ==,
- GPIOD_LINE_DIRECTION_OUTPUT);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 0);
-
- ret = gpiod_line_set_direction_input(line);
- g_assert_cmpint(ret, ==, 0);
- g_assert_cmpint(gpiod_line_direction(line), ==,
- GPIOD_LINE_DIRECTION_INPUT);
-
- ret = gpiod_line_set_direction_output(line, 1);
- g_assert_cmpint(ret, ==, 0);
- g_assert_cmpint(gpiod_line_direction(line), ==,
- GPIOD_LINE_DIRECTION_OUTPUT);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 1);
-}
-
-GPIOD_TEST_CASE(set_direction_bulk, 0, { 8 })
-{
- g_autoptr(gpiod_line_bulk_struct) bulk = NULL;
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct gpiod_line *line0, *line1, *line2;
- int values[3];
- gint ret;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line0 = gpiod_chip_get_line(chip, 0);
- line1 = gpiod_chip_get_line(chip, 1);
- line2 = gpiod_chip_get_line(chip, 2);
-
- g_assert_nonnull(line0);
- g_assert_nonnull(line1);
- g_assert_nonnull(line2);
- gpiod_test_return_if_failed();
-
- bulk = gpiod_line_bulk_new(3);
- g_assert_nonnull(bulk);
- gpiod_test_return_if_failed();
-
- gpiod_line_bulk_add_line(bulk, line0);
- gpiod_line_bulk_add_line(bulk, line1);
- gpiod_line_bulk_add_line(bulk, line2);
-
- values[0] = 0;
- values[1] = 1;
- values[2] = 2;
-
- ret = gpiod_line_request_bulk_output(bulk,
- GPIOD_TEST_CONSUMER, values);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
- g_assert_cmpint(gpiod_line_direction(line0), ==,
- GPIOD_LINE_DIRECTION_OUTPUT);
- g_assert_cmpint(gpiod_line_direction(line1), ==,
- GPIOD_LINE_DIRECTION_OUTPUT);
- g_assert_cmpint(gpiod_line_direction(line2), ==,
- GPIOD_LINE_DIRECTION_OUTPUT);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 0), ==, 0);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 1), ==, 1);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 1);
-
- ret = gpiod_line_set_direction_input_bulk(bulk);
- g_assert_cmpint(ret, ==, 0);
- g_assert_cmpint(gpiod_line_direction(line0), ==,
- GPIOD_LINE_DIRECTION_INPUT);
- g_assert_cmpint(gpiod_line_direction(line1), ==,
- GPIOD_LINE_DIRECTION_INPUT);
- g_assert_cmpint(gpiod_line_direction(line2), ==,
- GPIOD_LINE_DIRECTION_INPUT);
-
- values[0] = 2;
- values[1] = 1;
- values[2] = 0;
-
- ret = gpiod_line_set_direction_output_bulk(bulk, values);
- g_assert_cmpint(ret, ==, 0);
- g_assert_cmpint(gpiod_line_direction(line0), ==,
- GPIOD_LINE_DIRECTION_OUTPUT);
- g_assert_cmpint(gpiod_line_direction(line1), ==,
- GPIOD_LINE_DIRECTION_OUTPUT);
- g_assert_cmpint(gpiod_line_direction(line2), ==,
- GPIOD_LINE_DIRECTION_OUTPUT);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 0), ==, 1);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 1), ==, 1);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 0);
-
- ret = gpiod_line_set_direction_output_bulk(bulk, NULL);
- g_assert_cmpint(ret, ==, 0);
- g_assert_cmpint(gpiod_line_direction(line0), ==,
- GPIOD_LINE_DIRECTION_OUTPUT);
- g_assert_cmpint(gpiod_line_direction(line1), ==,
- GPIOD_LINE_DIRECTION_OUTPUT);
- g_assert_cmpint(gpiod_line_direction(line2), ==,
- GPIOD_LINE_DIRECTION_OUTPUT);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 0), ==, 0);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 1), ==, 0);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 0);
-}
-
-GPIOD_TEST_CASE(output_value_caching, 0, { 8 })
-{
- g_autoptr(gpiod_line_bulk_struct) bulk = NULL;
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct gpiod_line *line;
- gint ret;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line = gpiod_chip_get_line(chip, 2);
- g_assert_nonnull(line);
- gpiod_test_return_if_failed();
-
- /* check cached by request... */
- ret = gpiod_line_request_output(line, GPIOD_TEST_CONSUMER, 1);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
- g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 1);
-
- /* ...by checking cached value applied by set_flags */
- ret = gpiod_line_set_flags(line, 0);
- g_assert_cmpint(ret, ==, 0);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 1);
-
- /* check cached by set_value */
- ret = gpiod_line_set_value(line, 0);
- g_assert_cmpint(ret, ==, 0);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 0);
-
- ret = gpiod_line_set_flags(line, 0);
- g_assert_cmpint(ret, ==, 0);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 0);
-
- ret = gpiod_line_set_value(line, 1);
- g_assert_cmpint(ret, ==, 0);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 1);
-
- ret = gpiod_line_set_flags(line, 0);
- g_assert_cmpint(ret, ==, 0);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 1);
-
- /* check cached by set_config */
- ret = gpiod_line_set_config(line, GPIOD_LINE_REQUEST_DIRECTION_OUTPUT,
- 0, 0);
- g_assert_cmpint(ret, ==, 0);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 0);
-
- ret = gpiod_line_set_flags(line, 0);
- g_assert_cmpint(ret, ==, 0);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 0);
-
- /* check cached by set_value_bulk default */
- ret = gpiod_line_set_value(line, 1);
- g_assert_cmpint(ret, ==, 0);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 1);
-
- bulk = gpiod_line_bulk_new(1);
- g_assert_nonnull(bulk);
- gpiod_test_return_if_failed();
-
- gpiod_line_bulk_add_line(bulk, line);
- ret = gpiod_line_set_value_bulk(bulk, NULL);
- g_assert_cmpint(ret, ==, 0);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 0);
-
- ret = gpiod_line_set_flags(line, 0);
- g_assert_cmpint(ret, ==, 0);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 0);
-}
-
-GPIOD_TEST_CASE(direction, 0, { 8 })
-{
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct gpiod_line *line;
- gint ret;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line = gpiod_chip_get_line(chip, 5);
- g_assert_nonnull(line);
- gpiod_test_return_if_failed();
-
- ret = gpiod_line_request_output(line, GPIOD_TEST_CONSUMER, 1);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
- g_assert_cmpint(gpiod_line_direction(line), ==,
- GPIOD_LINE_DIRECTION_OUTPUT);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 5), ==, 1);
-
- gpiod_line_release(line);
-
- ret = gpiod_line_request_input(line, GPIOD_TEST_CONSUMER);
- g_assert_cmpint(ret, ==, 0);
- g_assert_cmpint(gpiod_line_direction(line), ==,
- GPIOD_LINE_DIRECTION_INPUT);
-}
-
-GPIOD_TEST_CASE(active_state, 0, { 8 })
-{
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct gpiod_line *line;
- gint ret;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line = gpiod_chip_get_line(chip, 5);
- g_assert_nonnull(line);
- gpiod_test_return_if_failed();
-
- ret = gpiod_line_request_input(line, GPIOD_TEST_CONSUMER);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
-
- g_assert_false(gpiod_line_is_active_low(line));
-
- gpiod_line_release(line);
-
- ret = gpiod_line_request_input_flags(line, GPIOD_TEST_CONSUMER,
- GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
-
- g_assert_cmpint(gpiod_line_direction(line), ==,
- GPIOD_LINE_DIRECTION_INPUT);
-
- gpiod_line_release(line);
-
- ret = gpiod_line_request_output_flags(line, GPIOD_TEST_CONSUMER,
- GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW, 0);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
-
- g_assert_cmpint(gpiod_line_direction(line), ==,
- GPIOD_LINE_DIRECTION_OUTPUT);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 5), ==, 1);
-
- gpiod_line_release(line);
-
- ret = gpiod_line_request_output_flags(line,
- GPIOD_TEST_CONSUMER, 0, 0);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
-
- g_assert_cmpint(gpiod_line_direction(line), ==,
- GPIOD_LINE_DIRECTION_OUTPUT);
- g_assert_cmpint(gpiod_test_chip_get_value(0, 5), ==, 0);
-
-}
-
-GPIOD_TEST_CASE(misc_flags, 0, { 8 })
-{
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct gpiod_line_request_config config;
- struct gpiod_line *line;
- gint ret;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line = gpiod_chip_get_line(chip, 2);
- g_assert_nonnull(line);
- gpiod_test_return_if_failed();
-
- g_assert_false(gpiod_line_is_used(line));
- g_assert_cmpint(gpiod_line_drive(line), ==, GPIOD_LINE_DRIVE_PUSH_PULL);
- g_assert_cmpint(gpiod_line_bias(line), ==, GPIOD_LINE_BIAS_UNKNOWN);
-
- config.request_type = GPIOD_LINE_REQUEST_DIRECTION_OUTPUT;
- config.consumer = GPIOD_TEST_CONSUMER;
- config.flags = GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN;
-
- ret = gpiod_line_request(line, &config, 0);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
-
- g_assert_true(gpiod_line_is_used(line));
- g_assert_cmpint(gpiod_line_drive(line), ==,
- GPIOD_LINE_DRIVE_OPEN_DRAIN);
- g_assert_cmpint(gpiod_line_bias(line), ==, GPIOD_LINE_BIAS_UNKNOWN);
- g_assert_cmpint(gpiod_line_direction(line), ==,
- GPIOD_LINE_DIRECTION_OUTPUT);
-
- gpiod_line_release(line);
-
- config.flags = GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE;
-
- ret = gpiod_line_request(line, &config, 0);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
-
- g_assert_true(gpiod_line_is_used(line));
- g_assert_cmpint(gpiod_line_drive(line), ==,
- GPIOD_LINE_DRIVE_OPEN_SOURCE);
- g_assert_cmpint(gpiod_line_bias(line), ==, GPIOD_LINE_BIAS_UNKNOWN);
- g_assert_cmpint(gpiod_line_direction(line), ==,
- GPIOD_LINE_DIRECTION_OUTPUT);
-
- gpiod_line_release(line);
-}
-
-GPIOD_TEST_CASE(misc_flags_work_together, 0, { 8 })
-{
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct gpiod_line_request_config config;
- struct gpiod_line *line;
- gint ret;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line = gpiod_chip_get_line(chip, 2);
- g_assert_nonnull(line);
- gpiod_test_return_if_failed();
-
- /*
- * Verify that open drain/source flags work together
- * with active_low.
- */
-
- config.request_type = GPIOD_LINE_REQUEST_DIRECTION_OUTPUT;
- config.consumer = GPIOD_TEST_CONSUMER;
- config.flags = GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN |
- GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW;
-
- ret = gpiod_line_request(line, &config, 0);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
-
- g_assert_true(gpiod_line_is_used(line));
- g_assert_cmpint(gpiod_line_drive(line), ==,
- GPIOD_LINE_DRIVE_OPEN_DRAIN);
- g_assert_cmpint(gpiod_line_bias(line), ==, GPIOD_LINE_BIAS_UNKNOWN);
- g_assert_true(gpiod_line_is_active_low(line));
- g_assert_cmpint(gpiod_line_direction(line), ==,
- GPIOD_LINE_DIRECTION_OUTPUT);
-
- gpiod_line_release(line);
-
- config.flags = GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE |
- GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW;
-
- ret = gpiod_line_request(line, &config, 0);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
-
- g_assert_true(gpiod_line_is_used(line));
- g_assert_cmpint(gpiod_line_drive(line), ==,
- GPIOD_LINE_DRIVE_OPEN_SOURCE);
- g_assert_cmpint(gpiod_line_bias(line), ==, GPIOD_LINE_BIAS_UNKNOWN);
- g_assert_true(gpiod_line_is_active_low(line));
-
- gpiod_line_release(line);
-
- /*
- * Verify that pull-up/down flags work together
- * with active_low.
- */
-
- config.request_type = GPIOD_LINE_REQUEST_DIRECTION_INPUT;
- config.flags = GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN |
- GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW;
-
- ret = gpiod_line_request(line, &config, 0);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
-
- g_assert_true(gpiod_line_is_used(line));
- g_assert_cmpint(gpiod_line_drive(line), ==, GPIOD_LINE_DRIVE_PUSH_PULL);
- g_assert_cmpint(gpiod_line_bias(line), ==, GPIOD_LINE_BIAS_PULL_DOWN);
- g_assert_true(gpiod_line_is_active_low(line));
- g_assert_cmpint(gpiod_line_direction(line), ==,
- GPIOD_LINE_DIRECTION_INPUT);
-
- ret = gpiod_line_get_value(line);
- g_assert_cmpint(ret, ==, 1);
-
- gpiod_line_release(line);
-
- config.flags = GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP |
- GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW;
-
- ret = gpiod_line_request(line, &config, 0);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
-
- g_assert_true(gpiod_line_is_used(line));
- g_assert_cmpint(gpiod_line_drive(line), ==, GPIOD_LINE_DRIVE_PUSH_PULL);
- g_assert_cmpint(gpiod_line_bias(line), ==, GPIOD_LINE_BIAS_PULL_UP);
- g_assert_true(gpiod_line_is_active_low(line));
- g_assert_cmpint(gpiod_line_direction(line), ==,
- GPIOD_LINE_DIRECTION_INPUT);
-
- ret = gpiod_line_get_value(line);
- g_assert_cmpint(ret, ==, 0);
-
- gpiod_line_release(line);
-}
-
-GPIOD_TEST_CASE(open_source_open_drain_input_mode, 0, { 8 })
-{
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct gpiod_line *line;
- gint ret;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line = gpiod_chip_get_line(chip, 2);
- g_assert_nonnull(line);
- gpiod_test_return_if_failed();
-
- ret = gpiod_line_request_input_flags(line, GPIOD_TEST_CONSUMER,
- GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN);
- g_assert_cmpint(ret, ==, -1);
- g_assert_cmpint(errno, ==, EINVAL);
-
- ret = gpiod_line_request_input_flags(line, GPIOD_TEST_CONSUMER,
- GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE);
- g_assert_cmpint(ret, ==, -1);
- g_assert_cmpint(errno, ==, EINVAL);
-}
-
-GPIOD_TEST_CASE(open_source_open_drain_simultaneously, 0, { 8 })
-{
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct gpiod_line *line;
- gint ret;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line = gpiod_chip_get_line(chip, 2);
- g_assert_nonnull(line);
- gpiod_test_return_if_failed();
-
- ret = gpiod_line_request_output_flags(line, GPIOD_TEST_CONSUMER,
- GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE |
- GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN, 1);
- g_assert_cmpint(ret, ==, -1);
- g_assert_cmpint(errno, ==, EINVAL);
-}
-
-GPIOD_TEST_CASE(multiple_bias_flags, 0, { 8 })
-{
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct gpiod_line *line;
- gint ret;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line = gpiod_chip_get_line(chip, 2);
- g_assert_nonnull(line);
- gpiod_test_return_if_failed();
-
- ret = gpiod_line_request_input_flags(line, GPIOD_TEST_CONSUMER,
- GPIOD_LINE_REQUEST_FLAG_BIAS_DISABLED |
- GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN);
- g_assert_cmpint(ret, ==, -1);
- g_assert_cmpint(errno, ==, EINVAL);
-
- ret = gpiod_line_request_input_flags(line, GPIOD_TEST_CONSUMER,
- GPIOD_LINE_REQUEST_FLAG_BIAS_DISABLED |
- GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP);
- g_assert_cmpint(ret, ==, -1);
- g_assert_cmpint(errno, ==, EINVAL);
-
- ret = gpiod_line_request_input_flags(line, GPIOD_TEST_CONSUMER,
- GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN |
- GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP);
- g_assert_cmpint(ret, ==, -1);
- g_assert_cmpint(errno, ==, EINVAL);
-
- ret = gpiod_line_request_input_flags(line, GPIOD_TEST_CONSUMER,
- GPIOD_LINE_REQUEST_FLAG_BIAS_DISABLED |
- GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN |
- GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP);
- g_assert_cmpint(ret, ==, -1);
- g_assert_cmpint(errno, ==, EINVAL);
-}
-
-
-/* Verify that the reference counting of the line fd handle works correctly. */
-GPIOD_TEST_CASE(release_one_use_another, 0, { 8 })
-{
- g_autoptr(gpiod_line_bulk_struct) bulk = NULL;
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct gpiod_line *line0;
- struct gpiod_line *line1;
- gint ret, vals[2];
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line0 = gpiod_chip_get_line(chip, 2);
- line1 = gpiod_chip_get_line(chip, 3);
- g_assert_nonnull(line0);
- g_assert_nonnull(line1);
- gpiod_test_return_if_failed();
-
- bulk = gpiod_line_bulk_new(2);
- g_assert_nonnull(bulk);
- gpiod_test_return_if_failed();
-
- gpiod_line_bulk_add_line(bulk, line0);
- gpiod_line_bulk_add_line(bulk, line1);
-
- vals[0] = vals[1] = 1;
-
- ret = gpiod_line_request_bulk_output(bulk, GPIOD_TEST_CONSUMER, vals);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
-
- gpiod_line_release(line0);
-
- ret = gpiod_line_get_value(line0);
- g_assert_cmpint(ret, ==, -1);
- g_assert_cmpint(errno, ==, EPERM);
-}
-
-GPIOD_TEST_CASE(null_consumer, 0, { 8 })
-{
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct gpiod_line_request_config config;
- struct gpiod_line *line;
- gint ret;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line = gpiod_chip_get_line(chip, 2);
- g_assert_nonnull(line);
- gpiod_test_return_if_failed();
-
- config.request_type = GPIOD_LINE_REQUEST_DIRECTION_INPUT;
- config.consumer = NULL;
- config.flags = 0;
-
- ret = gpiod_line_request(line, &config, 0);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
- g_assert_cmpstr(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;
-
- ret = gpiod_line_request(line, &config, 0);
- g_assert_cmpint(ret, ==, 0);
- g_assert_cmpstr(gpiod_line_consumer(line), ==, "?");
-}
-
-GPIOD_TEST_CASE(empty_consumer, 0, { 8 })
-{
- g_autoptr(gpiod_chip_struct) chip = NULL;
- struct gpiod_line_request_config config;
- struct gpiod_line *line;
- gint ret;
-
- chip = gpiod_chip_open(gpiod_test_chip_path(0));
- g_assert_nonnull(chip);
- gpiod_test_return_if_failed();
-
- line = gpiod_chip_get_line(chip, 2);
- g_assert_nonnull(line);
- gpiod_test_return_if_failed();
-
- config.request_type = GPIOD_LINE_REQUEST_DIRECTION_INPUT;
- config.consumer = "";
- config.flags = 0;
-
- ret = gpiod_line_request(line, &config, 0);
- g_assert_cmpint(ret, ==, 0);
- gpiod_test_return_if_failed();
- g_assert_cmpstr(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;
-
- ret = gpiod_line_request(line, &config, 0);
- g_assert_cmpint(ret, ==, 0);
- g_assert_cmpstr(gpiod_line_consumer(line), ==, "?");
-}
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
-#include <string.h>
+#include <errno.h>
+#include <glib.h>
+#include <gpiod.h>
+#include <unistd.h>
#include "gpiod-test.h"
+#include "gpiod-test-helpers.h"
+#include "gpiod-test-sim.h"
#define GPIOD_TEST_GROUP "misc"
-GPIOD_TEST_CASE(version_string, 0, { 1 })
+GPIOD_TEST_CASE(is_gpiochip_bad)
{
+ g_assert_false(gpiod_is_gpiochip_device("/dev/null"));
+ g_assert_cmpint(errno, ==, 0);
+ g_assert_false(gpiod_is_gpiochip_device("/dev/nonexistent"));
+ g_assert_cmpint(errno, ==, 0);
+}
+
+GPIOD_TEST_CASE(is_gpiochip_good)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new(NULL);
+
+ g_assert_true(gpiod_is_gpiochip_device(
+ g_gpiosim_chip_get_dev_path(sim)));
+}
+
+GPIOD_TEST_CASE(is_gpiochip_link_bad)
+{
+ g_autofree gchar *link = NULL;
+ gint ret;
+
+ link = g_strdup_printf("/tmp/gpiod-test-link.%u", getpid());
+ ret = symlink("/dev/null", link);
+ g_assert_cmpint(ret, ==, 0);
+ gpiod_test_return_if_failed();
+
+ g_assert_false(gpiod_is_gpiochip_device(link));
+ ret = unlink(link);
+ g_assert_cmpint(ret, ==, 0);
+}
+
+GPIOD_TEST_CASE(is_gpiochip_link_good)
+{
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new(NULL);
+ g_autofree gchar *link = NULL;
+ gint ret;
+
+ link = g_strdup_printf("/tmp/gpiod-test-link.%u", getpid());
+ ret = symlink(g_gpiosim_chip_get_dev_path(sim), link);
+ g_assert_cmpint(ret, ==, 0);
+ gpiod_test_return_if_failed();
+
+ g_assert_true(gpiod_is_gpiochip_device(link));
+ ret = unlink(link);
+ g_assert_cmpint(ret, ==, 0);
+}
+
+GPIOD_TEST_CASE(version_string)
+{
+ static const gchar *const pattern = "^[0-9][1-9]?\\.[0-9][1-9]?([\\.0-9]?|\\-devel)$";
+
+ g_autoptr(GError) err = NULL;
g_autoptr(GRegex) regex = NULL;
- GError *err = NULL;
+ g_autoptr(GMatchInfo) match = NULL;
+ g_autofree gchar *res = NULL;
const gchar *ver;
+ gboolean ret;
ver = gpiod_version_string();
g_assert_nonnull(ver);
gpiod_test_return_if_failed();
- g_assert_cmpuint(strlen(ver), >, 0);
- regex = g_regex_new("^[0-9]+\\.[0-9]+[0-9a-zA-Z\\.-]*$", 0, 0, &err);
- g_assert_null(err);
+ regex = g_regex_new(pattern, 0, 0, &err);
+ g_assert_nonnull(regex);
+ g_assert_no_error(err);
gpiod_test_return_if_failed();
- g_assert_true(g_regex_match(regex, ver, 0, NULL));
+
+ ret = g_regex_match(regex, ver, 0, &match);
+ g_assert_true(ret);
+ gpiod_test_return_if_failed();
+
+ g_assert_true(g_match_info_matches(match));
+ res = g_match_info_fetch(match, 0);
+ g_assert_nonnull(res);
+ g_assert_cmpstr(res, ==, ver);
+ g_match_info_next(match, &err);
+ g_assert_no_error(err);
+ g_assert_false(g_match_info_matches(match));
}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+
+#include <glib.h>
+#include <gpiod.h>
+
+#include "gpiod-test.h"
+#include "gpiod-test-helpers.h"
+
+#define GPIOD_TEST_GROUP "request-config"
+
+GPIOD_TEST_CASE(default_config)
+{
+ g_autoptr(struct_gpiod_request_config) config = NULL;
+
+ config = gpiod_test_create_request_config_or_fail();
+
+ g_assert_null(gpiod_request_config_get_consumer(config));
+ g_assert_cmpuint(gpiod_request_config_get_event_buffer_size(config),
+ ==, 0);
+}
+
+GPIOD_TEST_CASE(set_consumer)
+{
+ g_autoptr(struct_gpiod_request_config) config = NULL;
+
+ config = gpiod_test_create_request_config_or_fail();
+
+ gpiod_request_config_set_consumer(config, "foobar");
+ g_assert_cmpstr(gpiod_request_config_get_consumer(config),
+ ==, "foobar");
+}
+
+GPIOD_TEST_CASE(set_event_buffer_size)
+{
+ g_autoptr(struct_gpiod_request_config) config = NULL;
+
+ config = gpiod_test_create_request_config_or_fail();
+
+ gpiod_request_config_set_event_buffer_size(config, 128);
+ g_assert_cmpuint(gpiod_request_config_get_event_buffer_size(config),
+ ==, 128);
+}
run_tool gpioget "$(gpiosim_chip_name sim0)" 0 1 2 3 4
test "$status" -eq "1"
- output_regex_match ".*unable to retrieve GPIO lines from chip"
+ output_regex_match ".*unable to request lines.*"
}
@test "gpioget: same line twice" {
run_tool gpioget "$(gpiosim_chip_name sim0)" 0 0
test "$status" -eq "1"
- output_regex_match ".*unable to request lines.*"
+ output_regex_match ".*offsets must be unique"
}
@test "gpioget: invalid bias" {
run_tool gpioset "$(gpiosim_chip_name sim0)" 0=1 1=1 2=1 3=1 4=1 5=1
test "$status" -eq "1"
- output_regex_match ".*unable to retrieve GPIO lines from chip"
+ output_regex_match ".*unable to request lines.*"
}
@test "gpioset: use --sec without --mode=time" {
run_tool gpioset "$(gpiosim_chip_name sim0)" 0=1 0=1
test "$status" -eq "1"
- output_regex_match ".*unable to request lines.*"
+ output_regex_match ".*offsets must be unique"
}
#
run_tool gpiomon "$(gpiosim_chip_name sim0)" 0 0
test "$status" -eq "1"
- output_regex_match ".*unable to request GPIO lines for events"
+ output_regex_match ".*offsets must be unique"
}
@test "gpiomon: no arguments" {
run_tool gpiomon "$(gpiosim_chip_name sim0)" 5
test "$status" -eq "1"
- output_regex_match ".*unable to retrieve GPIO lines from chip"
+ output_regex_match ".*unable to request lines"
}
@test "gpiomon: invalid bias" {
#include <getopt.h>
#include <gpiod.h>
#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
#include "tools-common.h"
{
int optc, opti, num_chips, i;
struct gpiod_chip *chip;
+ struct gpiod_chip_info *info;
struct dirent **entries;
for (;;) {
for (i = 0; i < num_chips; i++) {
chip = chip_open_by_name(entries[i]->d_name);
- if (!chip) {
- if (errno == EACCES)
- printf("%s Permission denied\n",
- entries[i]->d_name);
- else
- die_perror("unable to open %s",
- entries[i]->d_name);
- }
+ if (!chip)
+ die_perror("unable to open %s", entries[i]->d_name);
+
+ info = gpiod_chip_get_info(chip);
+ if (!info)
+ die_perror("unable to get info for %s", entries[i]->d_name);
+
- printf("%s [%s] (%u lines)\n",
- gpiod_chip_get_name(chip),
- gpiod_chip_get_label(chip),
- gpiod_chip_get_num_lines(chip));
+ printf("%s [%s] (%zu lines)\n",
+ gpiod_chip_info_get_name(info),
+ gpiod_chip_info_get_label(info),
+ gpiod_chip_info_get_num_lines(info));
- gpiod_chip_unref(chip);
+ gpiod_chip_info_free(info);
+ gpiod_chip_close(chip);
free(entries[i]);
}
#include <getopt.h>
#include <gpiod.h>
#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
#include "tools-common.h"
{
int i, num_chips, optc, opti, offset;
struct gpiod_chip *chip;
+ struct gpiod_chip_info *info;
struct dirent **entries;
for (;;) {
die_perror("unable to open %s", entries[i]->d_name);
}
- offset = gpiod_chip_find_line(chip, argv[0]);
+ offset = gpiod_chip_get_line_offset_from_name(chip, argv[0]);
if (offset >= 0) {
+ info = gpiod_chip_get_info(chip);
+ if (!info)
+ die_perror("unable to get info for %s", entries[i]->d_name);
+
printf("%s %u\n",
- gpiod_chip_get_name(chip), offset);
- gpiod_chip_unref(chip);
+ gpiod_chip_info_get_name(info), offset);
+ gpiod_chip_info_free(info);
+ gpiod_chip_close(chip);
return EXIT_SUCCESS;
}
}
#include <gpiod.h>
#include <limits.h>
#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
#include "tools-common.h"
int main(int argc, char **argv)
{
- int request_type = GPIOD_LINE_REQUEST_DIRECTION_INPUT;
- struct gpiod_line_request_config config;
- int *values, optc, opti, rv, flags = 0;
- unsigned int *offsets, i, num_lines;
- struct gpiod_line_bulk *lines;
+ int direction = GPIOD_LINE_DIRECTION_INPUT;
+ int optc, opti, bias = 0, ret, *values;
+ struct gpiod_line_settings *settings;
+ struct gpiod_request_config *req_cfg;
+ struct gpiod_line_request *request;
+ struct gpiod_line_config *line_cfg;
struct gpiod_chip *chip;
+ bool active_low = false;
+ unsigned int *offsets;
+ size_t i, num_lines;
char *device, *end;
for (;;) {
print_version();
return EXIT_SUCCESS;
case 'l':
- flags |= GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW;
+ active_low = true;
break;
case 'n':
- request_type = GPIOD_LINE_REQUEST_DIRECTION_AS_IS;
+ direction = GPIOD_LINE_DIRECTION_AS_IS;
break;
case 'B':
- flags |= bias_flags(optarg);
+ bias = parse_bias(optarg);
break;
case '?':
die("try %s --help", get_progname());
device = argv[0];
num_lines = argc - 1;
- values = malloc(sizeof(*values) * num_lines);
- offsets = malloc(sizeof(*offsets) * num_lines);
- if (!values || !offsets)
+ offsets = calloc(num_lines, sizeof(*offsets));
+ values = calloc(num_lines, sizeof(*values));
+ if (!offsets || ! values)
die("out of memory");
for (i = 0; i < num_lines; i++) {
die("invalid GPIO offset: %s", argv[i + 1]);
}
+ if (has_duplicate_offsets(num_lines, offsets))
+ die("offsets must be unique");
+
chip = chip_open_lookup(device);
if (!chip)
die_perror("unable to open %s", device);
- lines = gpiod_chip_get_lines(chip, offsets, num_lines);
- if (!lines)
- die_perror("unable to retrieve GPIO lines from chip");
+ settings = gpiod_line_settings_new();
+ if (!settings)
+ die_perror("unable to allocate line settings");
+
+ gpiod_line_settings_set_direction(settings, direction);
+
+ if (bias)
+ gpiod_line_settings_set_bias(settings, bias);
+
+ if (active_low)
+ gpiod_line_settings_set_active_low(settings, active_low);
- memset(&config, 0, sizeof(config));
+ req_cfg = gpiod_request_config_new();
+ if (!req_cfg)
+ die_perror("unable to allocate the request config structure");
- config.consumer = "gpioget";
- config.request_type = request_type;
- config.flags = flags;
+ gpiod_request_config_set_consumer(req_cfg, "gpioget");
- rv = gpiod_line_request_bulk(lines, &config, NULL);
- if (rv)
+ line_cfg = gpiod_line_config_new();
+ if (!line_cfg)
+ die_perror("unable to allocate the line config structure");
+
+ ret = gpiod_line_config_add_line_settings(line_cfg, offsets,
+ num_lines, settings);
+ if (ret)
+ die_perror("unable to add line settings");
+
+ request = gpiod_chip_request_lines(chip, req_cfg, line_cfg);
+ if (!request)
die_perror("unable to request lines");
- rv = gpiod_line_get_value_bulk(lines, values);
- if (rv < 0)
- die_perror("error reading GPIO values");
+ ret = gpiod_line_request_get_values(request, values);
+ if (ret)
+ die_perror("unable to read GPIO line values");
for (i = 0; i < num_lines; i++) {
printf("%d", values[i]);
}
printf("\n");
- gpiod_line_release_bulk(lines);
- gpiod_chip_unref(chip);
- gpiod_line_bulk_free(lines);
- free(values);
+ gpiod_line_request_release(request);
+ gpiod_request_config_free(req_cfg);
+ gpiod_line_config_free(line_cfg);
+ gpiod_line_settings_free(settings);
+ gpiod_chip_close(chip);
free(offsets);
+ free(values);
return EXIT_SUCCESS;
}
#include <gpiod.h>
#include <stdarg.h>
#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
#include "tools-common.h"
-typedef bool (*is_set_func)(struct gpiod_line *);
+typedef bool (*is_set_func)(struct gpiod_line_info *);
struct flag {
const char *name;
is_set_func is_set;
};
-static bool line_bias_is_pullup(struct gpiod_line *line)
+static bool line_bias_is_pullup(struct gpiod_line_info *info)
{
- return gpiod_line_bias(line) == GPIOD_LINE_BIAS_PULL_UP;
+ return gpiod_line_info_get_bias(info) == GPIOD_LINE_BIAS_PULL_UP;
}
-static bool line_bias_is_pulldown(struct gpiod_line *line)
+static bool line_bias_is_pulldown(struct gpiod_line_info *info)
{
- return gpiod_line_bias(line) == GPIOD_LINE_BIAS_PULL_DOWN;
+ return gpiod_line_info_get_bias(info) == GPIOD_LINE_BIAS_PULL_DOWN;
}
-static bool line_bias_is_disabled(struct gpiod_line *line)
+static bool line_bias_is_disabled(struct gpiod_line_info *info)
{
- return gpiod_line_bias(line) == GPIOD_LINE_BIAS_DISABLED;
+ return gpiod_line_info_get_bias(info) == GPIOD_LINE_BIAS_DISABLED;
}
-static bool line_drive_is_open_drain(struct gpiod_line *line)
+static bool line_drive_is_open_drain(struct gpiod_line_info *info)
{
- return gpiod_line_drive(line) == GPIOD_LINE_DRIVE_OPEN_DRAIN;
+ return gpiod_line_info_get_drive(info) == GPIOD_LINE_DRIVE_OPEN_DRAIN;
}
-static bool line_drive_is_open_source(struct gpiod_line *line)
+static bool line_drive_is_open_source(struct gpiod_line_info *info)
{
- return gpiod_line_drive(line) == GPIOD_LINE_DRIVE_OPEN_SOURCE;
+ return gpiod_line_info_get_drive(info) == GPIOD_LINE_DRIVE_OPEN_SOURCE;
}
static const struct flag flags[] = {
{
.name = "used",
- .is_set = gpiod_line_is_used,
+ .is_set = gpiod_line_info_is_used,
},
{
.name = "open-drain",
static void list_lines(struct gpiod_chip *chip)
{
bool flag_printed, of, active_low;
+ struct gpiod_chip_info *chip_info;
+ struct gpiod_line_info *info;
const char *name, *consumer;
- struct gpiod_line *line;
- unsigned int i, offset;
+ size_t i, offset, num_lines;
int direction;
- printf("%s - %u lines:\n",
- gpiod_chip_get_name(chip), gpiod_chip_get_num_lines(chip));
+ chip_info = gpiod_chip_get_info(chip);
+ if (!chip_info)
+ die_perror("unable to retrieve the chip info from chip");
- for (offset = 0; offset < gpiod_chip_get_num_lines(chip); offset++) {
- line = gpiod_chip_get_line(chip, offset);
- if (!line)
- die_perror("unable to retrieve the line object from chip");
+ num_lines = gpiod_chip_info_get_num_lines(chip_info);
+ printf("%s - %zu lines:\n",
+ gpiod_chip_info_get_name(chip_info), num_lines);
- name = gpiod_line_name(line);
- consumer = gpiod_line_consumer(line);
- direction = gpiod_line_direction(line);
- active_low = gpiod_line_is_active_low(line);
+ for (offset = 0; offset < num_lines; offset++) {
+ info = gpiod_chip_get_line_info(chip, offset);
+ if (!info)
+ die_perror("unable to retrieve the line info from chip");
+ name = gpiod_line_info_get_name(info);
+ consumer = gpiod_line_info_get_consumer(info);
+ direction = gpiod_line_info_get_direction(info);
+ active_low = gpiod_line_info_is_active_low(info);
of = false;
printf("\tline ");
- prinfo(&of, 3, "%u", offset);
+ prinfo(&of, 3, "%zu", offset);
printf(": ");
name ? prinfo(&of, 12, "\"%s\"", name)
: prinfo(&of, 12, "unnamed");
printf(" ");
- if (!gpiod_line_is_used(line))
+ if (!gpiod_line_info_is_used(info))
prinfo(&of, 12, "unused");
else
consumer ? prinfo(&of, 12, "\"%s\"", consumer)
flag_printed = false;
for (i = 0; i < ARRAY_SIZE(flags); i++) {
- if (flags[i].is_set(line)) {
+ if (flags[i].is_set(info)) {
if (flag_printed)
printf(" ");
else
printf("]");
printf("\n");
+
+ gpiod_line_info_free(info);
}
+ gpiod_chip_info_free(chip_info);
}
int main(int argc, char **argv)
for (i = 0; i < num_chips; i++) {
chip = chip_open_by_name(entries[i]->d_name);
- if (!chip) {
- if (errno == EACCES)
- printf("%s Permission denied\n",
- entries[i]->d_name);
- else
- die_perror("unable to open %s",
- entries[i]->d_name);
- }
+ if (!chip)
+ die_perror("unable to open %s",
+ entries[i]->d_name);
list_lines(chip);
- gpiod_chip_unref(chip);
+ gpiod_chip_close(chip);
free(entries[i]);
}
free(entries);
list_lines(chip);
- gpiod_chip_unref(chip);
+ gpiod_chip_close(chip);
}
}
#include <errno.h>
#include <getopt.h>
#include <gpiod.h>
+#include <inttypes.h>
#include <limits.h>
#include <poll.h>
#include <signal.h>
#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "tools-common.h"
+#define EVENT_BUF_SIZE 32
+
static const struct option longopts[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, 'v' },
char *fmt;
};
-static void event_print_custom(unsigned int offset,
- const struct timespec *ts,
- int event_type,
- struct mon_ctx *ctx)
+static void event_print_custom(unsigned int offset, uint64_t timeout,
+ int event_type, struct mon_ctx *ctx)
{
char *prev, *curr, fmt;
printf("%u", offset);
break;
case 'e':
- if (event_type == GPIOD_LINE_EVENT_RISING_EDGE)
+ if (event_type == GPIOD_EDGE_EVENT_RISING_EDGE)
fputc('1', stdout);
else
fputc('0', stdout);
break;
case 's':
- printf("%ld", ts->tv_sec);
+ printf("%"PRIu64, timeout / 1000000000);
break;
case 'n':
- printf("%ld", ts->tv_nsec);
+ printf("%"PRIu64, timeout % 1000000000);
break;
case '%':
fputc('%', stdout);
}
static void event_print_human_readable(unsigned int offset,
- const struct timespec *ts,
- int event_type)
+ uint64_t timeout, int event_type)
{
char *evname;
- if (event_type == GPIOD_LINE_EVENT_RISING_EDGE)
+ if (event_type == GPIOD_EDGE_EVENT_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);
+ printf("event: %s offset: %u timestamp: [%8"PRIu64".%09"PRIu64"]\n",
+ evname, offset, timeout / 1000000000, timeout % 1000000000);
}
static void handle_event(unsigned int line_offset, unsigned int event_type,
- struct timespec *timestamp, struct mon_ctx *ctx)
+ uint64_t timestamp, struct mon_ctx *ctx)
{
if (!ctx->silent) {
if (ctx->fmt)
int main(int argc, char **argv)
{
- unsigned int offsets[64], num_lines = 0, offset,
- events_wanted = 0, events_done = 0, x;
- bool watch_rising = false, watch_falling = false;
- int flags = 0;
- struct timespec timeout = { 10, 0 };
- int optc, opti, rv, i, y, event_type;
- struct mon_ctx ctx;
+ bool watch_rising = false, watch_falling = false, active_low = false;
+ size_t num_lines = 0, events_wanted = 0, events_done = 0;
+ struct gpiod_edge_event_buffer *event_buffer;
+ int optc, opti, ret, i, edge, bias = 0;
+ uint64_t timeout = 10 * 1000000000LLU;
+ struct gpiod_line_settings *settings;
+ struct gpiod_request_config *req_cfg;
+ struct gpiod_line_request *request;
+ struct gpiod_line_config *line_cfg;
+ unsigned int offsets[64], offset;
+ struct gpiod_edge_event *event;
struct gpiod_chip *chip;
- struct gpiod_line_bulk *lines, *evlines;
+ struct mon_ctx ctx;
char *end;
- struct gpiod_line_request_config config;
- struct gpiod_line *line;
- struct gpiod_line_event events[16];
/*
* FIXME: use signalfd once the API has been converted to using a single file
print_version();
return EXIT_SUCCESS;
case 'l':
- flags |= GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW;
+ active_low = true;
break;
case 'B':
- flags |= bias_flags(optarg);
+ bias = parse_bias(optarg);
break;
case 'n':
events_wanted = strtoul(optarg, &end, 10);
argv += optind;
if (watch_rising && !watch_falling)
- event_type = GPIOD_LINE_REQUEST_EVENT_RISING_EDGE;
+ edge = GPIOD_LINE_EDGE_RISING;
else if (watch_falling && !watch_rising)
- event_type = GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE;
+ edge = GPIOD_LINE_EDGE_FALLING;
else
- event_type = GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES;
+ edge = GPIOD_LINE_EDGE_BOTH;
if (argc < 1)
die("gpiochip must be specified");
num_lines++;
}
+ if (has_duplicate_offsets(num_lines, offsets))
+ die("offsets must be unique");
+
chip = chip_open_lookup(argv[0]);
if (!chip)
die_perror("unable to open %s", argv[0]);
- lines = gpiod_chip_get_lines(chip, offsets, num_lines);
- if (!lines)
- die_perror("unable to retrieve GPIO lines from chip");
+ settings = gpiod_line_settings_new();
+ if (!settings)
+ die_perror("unable to allocate line settings");
+
+ if (bias)
+ gpiod_line_settings_set_bias(settings, bias);
+ if (active_low)
+ gpiod_line_settings_set_active_low(settings, active_low);
+ gpiod_line_settings_set_edge_detection(settings, edge);
+
+ req_cfg = gpiod_request_config_new();
+ if (!req_cfg)
+ die_perror("unable to allocate the request config structure");
+
+ gpiod_request_config_set_consumer(req_cfg, "gpiomon");
- memset(&config, 0, sizeof(config));
+ line_cfg = gpiod_line_config_new();
+ if (!line_cfg)
+ die_perror("unable to allocate the line config structure");
- config.consumer = "gpiomon";
- config.request_type = event_type;
- config.flags = flags;
+ ret = gpiod_line_config_add_line_settings(line_cfg, offsets,
+ num_lines, settings);
+ if (ret)
+ die_perror("unable to add line settings");
- rv = gpiod_line_request_bulk(lines, &config, NULL);
- if (rv)
- die_perror("unable to request GPIO lines for events");
+ request = gpiod_chip_request_lines(chip, req_cfg, line_cfg);
+ if (!request)
+ die_perror("unable to request lines");
- evlines = gpiod_line_bulk_new(num_lines);
- if (!evlines)
- die("out of memory");
+ event_buffer = gpiod_edge_event_buffer_new(EVENT_BUF_SIZE);
+ if (!event_buffer)
+ die_perror("unable to allocate the line event buffer");
for (;;) {
- gpiod_line_bulk_reset(evlines);
- rv = gpiod_line_event_wait_bulk(lines, &timeout, evlines);
- if (rv < 0)
+ ret = gpiod_line_request_wait_edge_event(request, timeout);
+ if (ret < 0)
die_perror("error waiting for events");
- if (rv == 0)
+ if (ret == 0)
continue;
- num_lines = gpiod_line_bulk_num_lines(evlines);
+ ret = gpiod_line_request_read_edge_event(request, event_buffer,
+ EVENT_BUF_SIZE);
+ if (ret < 0)
+ die_perror("error reading line events");
- for (x = 0; x < num_lines; x++) {
- line = gpiod_line_bulk_get_line(evlines, x);
+ for (i = 0; i < ret; i++) {
+ event = gpiod_edge_event_buffer_get_event(event_buffer,
+ i);
+ if (!event)
+ die_perror("unable to retrieve the event from the buffer");
- rv = gpiod_line_event_read_multiple(line, events,
- ARRAY_SIZE(events));
- if (rv < 0)
- die_perror("error reading line events");
+ handle_event(gpiod_edge_event_get_line_offset(event),
+ gpiod_edge_event_get_event_type(event),
+ gpiod_edge_event_get_timestamp_ns(event),
+ &ctx);
- for (y = 0; y < rv; y++) {
- handle_event(gpiod_line_offset(line),
- events[y].event_type,
- &events[y].ts, &ctx);
- events_done++;
+ events_done++;
- if (events_wanted &&
- events_done >= events_wanted)
- goto done;
- }
+ if (events_wanted && events_done >= events_wanted)
+ goto done;
}
}
done:
- gpiod_line_release_bulk(lines);
- gpiod_line_bulk_free(lines);
- gpiod_line_bulk_free(evlines);
- gpiod_chip_unref(chip);
+ gpiod_edge_event_buffer_free(event_buffer);
+ gpiod_line_request_release(request);
+ gpiod_request_config_free(req_cfg);
+ gpiod_line_config_free(line_cfg);
+ gpiod_line_settings_free(settings);
+ gpiod_chip_close(chip);
return EXIT_SUCCESS;
}
#include <limits.h>
#include <poll.h>
#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <unistd.h>
static const struct mode_mapping *parse_mode(const char *mode)
{
- unsigned int i;
+ size_t i;
for (i = 0; i < ARRAY_SIZE(modes); i++)
if (strcmp(mode, modes[i].name) == 0)
return NULL;
}
-static int drive_flags(const char *option)
+static int parse_drive(const char *option)
{
if (strcmp(option, "open-drain") == 0)
- return GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN;
+ return GPIOD_LINE_DRIVE_OPEN_DRAIN;
if (strcmp(option, "open-source") == 0)
- return GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE;
+ return GPIOD_LINE_DRIVE_OPEN_SOURCE;
if (strcmp(option, "push-pull") != 0)
die("invalid drive: %s", option);
return 0;
int main(int argc, char **argv)
{
const struct mode_mapping *mode = &modes[MODE_EXIT];
- struct gpiod_line_request_config config;
- int *values, rv, optc, opti, flags = 0;
- unsigned int *offsets, num_lines, i;
- struct gpiod_line_bulk *lines;
+ int ret, optc, opti, bias = 0, drive = 0, *values;
+ struct gpiod_line_settings *settings;
+ struct gpiod_request_config *req_cfg;
+ struct gpiod_line_request *request;
+ struct gpiod_line_config *line_cfg;
struct callback_data cbdata;
struct gpiod_chip *chip;
+ bool active_low = false;
+ unsigned int *offsets;
+ size_t i, num_lines;
char *device, *end;
memset(&cbdata, 0, sizeof(cbdata));
print_version();
return EXIT_SUCCESS;
case 'l':
- flags |= GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW;
+ active_low = true;
break;
case 'B':
- flags |= bias_flags(optarg);
+ bias = parse_bias(optarg);
break;
case 'D':
- flags |= drive_flags(optarg);
+ drive = parse_drive(optarg);
break;
case 'm':
mode = parse_mode(optarg);
num_lines = argc - 1;
- offsets = malloc(sizeof(*offsets) * num_lines);
- values = malloc(sizeof(*values) * num_lines);
- if (!values || !offsets)
+ offsets = calloc(num_lines, sizeof(*offsets));
+ values = calloc(num_lines, sizeof(*values));
+ if (!offsets)
die("out of memory");
for (i = 0; i < num_lines; i++) {
- rv = sscanf(argv[i + 1], "%u=%d", &offsets[i], &values[i]);
- if (rv != 2)
+ ret = sscanf(argv[i + 1], "%u=%d", &offsets[i], &values[i]);
+ if (ret != 2)
die("invalid offset<->value mapping: %s", argv[i + 1]);
if (values[i] != 0 && values[i] != 1)
die("invalid offset: %s", argv[i + 1]);
}
+ if (has_duplicate_offsets(num_lines, offsets))
+ die("offsets must be unique");
+
chip = chip_open_lookup(device);
if (!chip)
die_perror("unable to open %s", device);
- lines = gpiod_chip_get_lines(chip, offsets, num_lines);
- if (!lines)
- die_perror("unable to retrieve GPIO lines from chip");
+ settings = gpiod_line_settings_new();
+ if (!settings)
+ die_perror("unable to allocate line settings");
+
+ if (bias)
+ gpiod_line_settings_set_bias(settings, bias);
+ if (drive)
+ gpiod_line_settings_set_drive(settings, drive);
+ if (active_low)
+ gpiod_line_settings_set_active_low(settings, active_low);
+ gpiod_line_settings_set_direction(settings,
+ GPIOD_LINE_DIRECTION_OUTPUT);
+
+ req_cfg = gpiod_request_config_new();
+ if (!req_cfg)
+ die_perror("unable to allocate the request config structure");
+
+ gpiod_request_config_set_consumer(req_cfg, "gpioset");
- memset(&config, 0, sizeof(config));
+ line_cfg = gpiod_line_config_new();
+ if (!line_cfg)
+ die_perror("unable to allocate the line config structure");
- config.consumer = "gpioset";
- config.request_type = GPIOD_LINE_REQUEST_DIRECTION_OUTPUT;
- config.flags = flags;
+ for (i = 0; i < num_lines; i++) {
+ gpiod_line_settings_set_output_value(settings, values[i]);
+
+ ret = gpiod_line_config_add_line_settings(line_cfg, &offsets[i],
+ 1, settings);
+ if (ret)
+ die_perror("unable to add line settings");
+ }
- rv = gpiod_line_request_bulk(lines, &config, values);
- if (rv)
+ request = gpiod_chip_request_lines(chip, req_cfg, line_cfg);
+ if (!request)
die_perror("unable to request lines");
if (mode->callback)
mode->callback(&cbdata);
- gpiod_line_release_bulk(lines);
- gpiod_chip_unref(chip);
- gpiod_line_bulk_free(lines);
+ gpiod_line_request_release(request);
+ gpiod_request_config_free(req_cfg);
+ gpiod_line_config_free(line_cfg);
+ gpiod_line_settings_free(settings);
+ gpiod_chip_close(chip);
free(offsets);
- free(values);
return EXIT_SUCCESS;
}
printf("There is NO WARRANTY, to the extent permitted by law.\n");
}
-int bias_flags(const char *option)
+int parse_bias(const char *option)
{
if (strcmp(option, "pull-down") == 0)
- return GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN;
+ return GPIOD_LINE_BIAS_PULL_DOWN;
if (strcmp(option, "pull-up") == 0)
- return GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP;
+ return GPIOD_LINE_BIAS_PULL_UP;
if (strcmp(option, "disable") == 0)
- return GPIOD_LINE_REQUEST_FLAG_BIAS_DISABLED;
+ return GPIOD_LINE_BIAS_DISABLED;
if (strcmp(option, "as-is") != 0)
die("invalid bias: %s", option);
return 0;
return chip;
}
+
+bool has_duplicate_offsets(size_t num_offsets, unsigned int *offsets)
+{
+ size_t i, j;
+
+ for (i = 0; i < num_offsets; i++) {
+ for (j = i + 1; j < num_offsets; j++)
+ if (offsets[i] == offsets[j])
+ return true;
+ }
+
+ return false;
+}
void die(const char *fmt, ...) NORETURN PRINTF(1, 2);
void die_perror(const char *fmt, ...) NORETURN PRINTF(1, 2);
void print_version(void);
-int bias_flags(const char *option);
+int parse_bias(const char *option);
void print_bias_help(void);
int make_signalfd(void);
int chip_dir_filter(const struct dirent *entry);
struct gpiod_chip *chip_open_by_name(const char *name);
struct gpiod_chip *chip_open_lookup(const char *device);
+bool has_duplicate_offsets(size_t num_offsets, unsigned int *offsets);
#endif /* __GPIOD_TOOLS_COMMON_H__ */