Skip to contentSkip to conversation

dbus: add GLib-based DBus daemon and command-line client

PATCH Superseded Bartosz Golaszewski 3 participants 29 messages Apr 12, 2024 – Jun 28, 2024

Subsystems (2)

v1 of 5 Superseded Latest is v5 →

lore
Share & tools
lore
#0 lore raw

cover dbus: add GLib-based DBus daemon and command-line client

From: Bartosz Golaszewski <[email protected]>

This has been in the works for a long time but I'm finally ready to share it with the world. This introduces the DBus API definition and its implementation in the form of a GPIO manager daemon and a companion command-line client as well as GLib bindings to libgpiod which form the base on which the former are built.

Please note that while the functionality is complete, the dbus part still requires some work (bug fixing, corner-cases, general ironing out of all the wrinkles, a lot of code could be refactored too). This is why the series is marked as RFC/RFT. I want to get it out for reviews and first tests before I leave for EOSS and later on vacation. I am aware of some instabilities so don't be surprised by an occasional segfault.

While I split the GLib and DBus code into several commits for easier review, I intend to apply all changes to bindings/glib/ and dbus/ as two big commits in the end as otherwise the split commits are not buildable until all of them are applied.

The main point of interest is the DBus interface definition XML at dbus/lib/io.gpiod1.xml as it is what defines the actual DBus API. Everything else can be considered as implementation details as it's easier to change later than the API that's supposed to be stable once released.

The first two patches expose the test infrastructure we use for the core library and tools to the GLib bindings and dbus code. Next we add the GLib bindings themselves. Not much to discuss here, they cover the entire libgpiod API but wrap it in GObject abstractions and plug into the GLib event loop.

Finally we add the DBus code that's split into the daemon and command-line client. I added some examples to the README and documented the behavior in the help text of the programs as well as documented the interface file with XML comments that gdbus-codegen can parse and use to generate docbook output.

For DBus, most of the testing happens in the command-line client bash tests. It has a very good coverage of the daemon's code and also allows to run the daemon through valgrind and verify there are no memory leaks and invalid accesses. I still intend to extend the C test-suite for DBus with some corner cases but didn't not have enough time for it.

Bartosz Golaszewski (18):
tests: split out reusable test code into a local static library
tests: split out the common test code for bash scripts
bindings: glib: add build files
bindings: glib: add public headers
bindings: glib: add core code
bindings: glib: add examples
bindings: glib: add tests
README: document GLib bindings
dbus: add build files
dbus: add the API definitions
dbus: add a wrapper around the gdbus-codegen generated header
dbus: add data files
dbus: add gpio-manager code
dbus: add tests
dbus: add a command-line client
dbus: client: add tests
README: document the DBus API
TODO: drop the DBus daemon from the list

 .gitignore                                    |    2 +
 Doxyfile.in                                   |    4 +-
 Makefile.am                                   |    7 +
 README                                        |   73 +-
 TODO                                          |   17 -
 bindings/Makefile.am                          |    7 +
 bindings/glib/Makefile.am                     |   78 +
 bindings/glib/chip-info.c                     |  118 ++
 bindings/glib/chip.c                          |  396 +++++
 bindings/glib/edge-event.c                    |  158 ++
 bindings/glib/error.c                         |   67 +
 bindings/glib/examples/.gitignore             |   14 +
 bindings/glib/examples/Makefile.am            |   22 +
 .../glib/examples/find_line_by_name_glib.c    |   72 +
 bindings/glib/examples/get_chip_info_glib.c   |   39 +
 bindings/glib/examples/get_line_info_glib.c   |   79 +
 bindings/glib/examples/get_line_value_glib.c  |   67 +
 .../examples/get_multiple_line_values_glib.c  |   72 +
 .../reconfigure_input_to_output_glib.c        |  103 ++
 .../glib/examples/toggle_line_value_glib.c    |   99 ++
 .../toggle_multiple_line_values_glib.c        |  132 ++
 bindings/glib/examples/watch_line_info_glib.c |   63 +
 .../glib/examples/watch_line_value_glib.c     |   91 ++
 .../watch_multiple_edge_rising_glib.c         |   95 ++
 bindings/glib/generated-enums.c.template      |   43 +
 bindings/glib/generated-enums.h.template      |   30 +
 bindings/glib/gpiod-glib.h                    |   33 +
 bindings/glib/gpiod-glib.pc.in                |   14 +
 bindings/glib/gpiod-glib/Makefile.am          |   18 +
 bindings/glib/gpiod-glib/chip-info.h          |   84 +
 bindings/glib/gpiod-glib/chip.h               |  164 ++
 bindings/glib/gpiod-glib/edge-event.h         |  114 ++
 bindings/glib/gpiod-glib/error.h              |   67 +
 bindings/glib/gpiod-glib/info-event.h         |   97 ++
 bindings/glib/gpiod-glib/line-config.h        |  116 ++
 bindings/glib/gpiod-glib/line-info.h          |  171 ++
 bindings/glib/gpiod-glib/line-request.h       |  182 +++
 bindings/glib/gpiod-glib/line-settings.h      |  202 +++
 bindings/glib/gpiod-glib/line.h               |  114 ++
 bindings/glib/gpiod-glib/misc.h               |   51 +
 bindings/glib/gpiod-glib/request-config.h     |  107 ++
 bindings/glib/info-event.c                    |  150 ++
 bindings/glib/internal.c                      |  334 ++++
 bindings/glib/internal.h                      |   54 +
 bindings/glib/line-config.c                   |  186 +++
 bindings/glib/line-info.c                     |  274 ++++
 bindings/glib/line-request.c                  |  434 +++++
 bindings/glib/line-settings.c                 |  359 ++++
 bindings/glib/misc.c                          |   17 +
 bindings/glib/request-config.c                |  155 ++
 bindings/glib/tests/.gitignore                |    4 +
 bindings/glib/tests/Makefile.am               |   29 +
 bindings/glib/tests/helpers.c                 |   12 +
 bindings/glib/tests/helpers.h                 |  139 ++
 bindings/glib/tests/tests-chip-info.c         |   55 +
 bindings/glib/tests/tests-chip.c              |  183 +++
 bindings/glib/tests/tests-edge-event.c        |  226 +++
 bindings/glib/tests/tests-info-event.c        |  321 ++++
 bindings/glib/tests/tests-line-config.c       |  186 +++
 bindings/glib/tests/tests-line-info.c         |   98 ++
 bindings/glib/tests/tests-line-request.c      |  704 ++++++++
 bindings/glib/tests/tests-line-settings.c     |  252 +++
 bindings/glib/tests/tests-misc.c              |   88 +
 bindings/glib/tests/tests-request-config.c    |   58 +
 configure.ac                                  |   72 +
 dbus/Makefile.am                              |   10 +
 dbus/client/.gitignore                        |    4 +
 dbus/client/Makefile.am                       |   30 +
 dbus/client/common.c                          |  644 ++++++++
 dbus/client/common.h                          |  203 +++
 dbus/client/detect.c                          |   53 +
 dbus/client/find.c                            |   66 +
 dbus/client/get.c                             |  210 +++
 dbus/client/gpiocli-test.bash                 | 1439 +++++++++++++++++
 dbus/client/gpiocli.c                         |  174 ++
 dbus/client/info.c                            |  184 +++
 dbus/client/monitor.c                         |  191 +++
 dbus/client/notify.c                          |  295 ++++
 dbus/client/reconfigure.c                     |   74 +
 dbus/client/release.c                         |   62 +
 dbus/client/request.c                         |  249 +++
 dbus/client/requests.c                        |   71 +
 dbus/client/set.c                             |  170 ++
 dbus/client/wait.c                            |  188 +++
 dbus/data/Makefile.am                         |   13 +
 dbus/data/gpio-manager.service                |   14 +
 dbus/data/io.gpiod1.conf                      |   29 +
 dbus/lib/Makefile.am                          |   28 +
 dbus/lib/gpiodbus.h                           |    9 +
 dbus/lib/io.gpiod1.xml                        |  318 ++++
 dbus/manager/.gitignore                       |    4 +
 dbus/manager/Makefile.am                      |   20 +
 dbus/manager/daemon.c                         |  821 ++++++++++
 dbus/manager/daemon.h                         |   22 +
 dbus/manager/gpio-manager.c                   |  167 ++
 dbus/manager/helpers.c                        |  420 +++++
 dbus/manager/helpers.h                        |   24 +
 dbus/tests/.gitignore                         |    4 +
 dbus/tests/Makefile.am                        |   25 +
 dbus/tests/daemon-process.c                   |  129 ++
 dbus/tests/daemon-process.h                   |   20 +
 dbus/tests/helpers.c                          |  107 ++
 dbus/tests/helpers.h                          |  112 ++
 dbus/tests/tests-chip.c                       |  133 ++
 dbus/tests/tests-line.c                       |  213 +++
 dbus/tests/tests-request.c                    |  116 ++
 tests/Makefile.am                             |   14 +-
 tests/bash/Makefile.am                        |    4 +
 tests/bash/gpiod-bash-test-helper.inc         |  328 ++++
 tests/gpiod-test-helpers.c                    |   41 -
 tests/gpiosim-glib/Makefile.am                |   13 +
 .../gpiosim-glib.c}                           |   30 +-
 .../gpiosim-glib.h}                           |   14 +
 tests/harness/Makefile.am                     |   12 +
 tests/harness/gpiod-test-common.h             |   23 +
 tests/{ => harness}/gpiod-test.c              |    0
 tests/{ => harness}/gpiod-test.h              |    0
 tests/{gpiod-test-helpers.h => helpers.h}     |   36 +-
 tests/tests-chip-info.c                       |    7 +-
 tests/tests-chip.c                            |   15 +-
 tests/tests-edge-event.c                      |    7 +-
 tests/tests-info-event.c                      |    7 +-
 tests/tests-line-config.c                     |    7 +-
 tests/tests-line-info.c                       |   11 +-
 tests/tests-line-request.c                    |    7 +-
 tests/tests-line-settings.c                   |    5 +-
 tests/tests-misc.c                            |    7 +-
 tests/tests-request-config.c                  |    5 +-
 tools/gpio-tools-test.bash                    |  559 ++-----
 129 files changed, 15510 insertions(+), 579 deletions(-)
 create mode 100644 bindings/glib/Makefile.am
 create mode 100644 bindings/glib/chip-info.c
 create mode 100644 bindings/glib/chip.c
 create mode 100644 bindings/glib/edge-event.c
 create mode 100644 bindings/glib/error.c
 create mode 100644 bindings/glib/examples/.gitignore
 create mode 100644 bindings/glib/examples/Makefile.am
 create mode 100644 bindings/glib/examples/find_line_by_name_glib.c
 create mode 100644 bindings/glib/examples/get_chip_info_glib.c
 create mode 100644 bindings/glib/examples/get_line_info_glib.c
 create mode 100644 bindings/glib/examples/get_line_value_glib.c
 create mode 100644 bindings/glib/examples/get_multiple_line_values_glib.c
 create mode 100644 bindings/glib/examples/reconfigure_input_to_output_glib.c
 create mode 100644 bindings/glib/examples/toggle_line_value_glib.c
 create mode 100644 bindings/glib/examples/toggle_multiple_line_values_glib.c
 create mode 100644 bindings/glib/examples/watch_line_info_glib.c
 create mode 100644 bindings/glib/examples/watch_line_value_glib.c
 create mode 100644 bindings/glib/examples/watch_multiple_edge_rising_glib.c
 create mode 100644 bindings/glib/generated-enums.c.template
 create mode 100644 bindings/glib/generated-enums.h.template
 create mode 100644 bindings/glib/gpiod-glib.h
 create mode 100644 bindings/glib/gpiod-glib.pc.in
 create mode 100644 bindings/glib/gpiod-glib/Makefile.am
 create mode 100644 bindings/glib/gpiod-glib/chip-info.h
 create mode 100644 bindings/glib/gpiod-glib/chip.h
 create mode 100644 bindings/glib/gpiod-glib/edge-event.h
 create mode 100644 bindings/glib/gpiod-glib/error.h
 create mode 100644 bindings/glib/gpiod-glib/info-event.h
 create mode 100644 bindings/glib/gpiod-glib/line-config.h
 create mode 100644 bindings/glib/gpiod-glib/line-info.h
 create mode 100644 bindings/glib/gpiod-glib/line-request.h
 create mode 100644 bindings/glib/gpiod-glib/line-settings.h
 create mode 100644 bindings/glib/gpiod-glib/line.h
 create mode 100644 bindings/glib/gpiod-glib/misc.h
 create mode 100644 bindings/glib/gpiod-glib/request-config.h
 create mode 100644 bindings/glib/info-event.c
 create mode 100644 bindings/glib/internal.c
 create mode 100644 bindings/glib/internal.h
 create mode 100644 bindings/glib/line-config.c
 create mode 100644 bindings/glib/line-info.c
 create mode 100644 bindings/glib/line-request.c
 create mode 100644 bindings/glib/line-settings.c
 create mode 100644 bindings/glib/misc.c
 create mode 100644 bindings/glib/request-config.c
 create mode 100644 bindings/glib/tests/.gitignore
 create mode 100644 bindings/glib/tests/Makefile.am
 create mode 100644 bindings/glib/tests/helpers.c
 create mode 100644 bindings/glib/tests/helpers.h
 create mode 100644 bindings/glib/tests/tests-chip-info.c
 create mode 100644 bindings/glib/tests/tests-chip.c
 create mode 100644 bindings/glib/tests/tests-edge-event.c
 create mode 100644 bindings/glib/tests/tests-info-event.c
 create mode 100644 bindings/glib/tests/tests-line-config.c
 create mode 100644 bindings/glib/tests/tests-line-info.c
 create mode 100644 bindings/glib/tests/tests-line-request.c
 create mode 100644 bindings/glib/tests/tests-line-settings.c
 create mode 100644 bindings/glib/tests/tests-misc.c
 create mode 100644 bindings/glib/tests/tests-request-config.c
 create mode 100644 dbus/Makefile.am
 create mode 100644 dbus/client/.gitignore
 create mode 100644 dbus/client/Makefile.am
 create mode 100644 dbus/client/common.c
 create mode 100644 dbus/client/common.h
 create mode 100644 dbus/client/detect.c
 create mode 100644 dbus/client/find.c
 create mode 100644 dbus/client/get.c
 create mode 100755 dbus/client/gpiocli-test.bash
 create mode 100644 dbus/client/gpiocli.c
 create mode 100644 dbus/client/info.c
 create mode 100644 dbus/client/monitor.c
 create mode 100644 dbus/client/notify.c
 create mode 100644 dbus/client/reconfigure.c
 create mode 100644 dbus/client/release.c
 create mode 100644 dbus/client/request.c
 create mode 100644 dbus/client/requests.c
 create mode 100644 dbus/client/set.c
 create mode 100644 dbus/client/wait.c
 create mode 100644 dbus/data/Makefile.am
 create mode 100644 dbus/data/gpio-manager.service
 create mode 100644 dbus/data/io.gpiod1.conf
 create mode 100644 dbus/lib/Makefile.am
 create mode 100644 dbus/lib/gpiodbus.h
 create mode 100644 dbus/lib/io.gpiod1.xml
 create mode 100644 dbus/manager/.gitignore
 create mode 100644 dbus/manager/Makefile.am
 create mode 100644 dbus/manager/daemon.c
 create mode 100644 dbus/manager/daemon.h
 create mode 100644 dbus/manager/gpio-manager.c
 create mode 100644 dbus/manager/helpers.c
 create mode 100644 dbus/manager/helpers.h
 create mode 100644 dbus/tests/.gitignore
 create mode 100644 dbus/tests/Makefile.am
 create mode 100644 dbus/tests/daemon-process.c
 create mode 100644 dbus/tests/daemon-process.h
 create mode 100644 dbus/tests/helpers.c
 create mode 100644 dbus/tests/helpers.h
 create mode 100644 dbus/tests/tests-chip.c
 create mode 100644 dbus/tests/tests-line.c
 create mode 100644 dbus/tests/tests-request.c
 create mode 100644 tests/bash/Makefile.am
 create mode 100644 tests/bash/gpiod-bash-test-helper.inc
 delete mode 100644 tests/gpiod-test-helpers.c
 create mode 100644 tests/gpiosim-glib/Makefile.am
 rename tests/{gpiod-test-sim.c => gpiosim-glib/gpiosim-glib.c} (93%)
 rename tests/{gpiod-test-sim.h => gpiosim-glib/gpiosim-glib.h} (86%)
 create mode 100644 tests/harness/Makefile.am
 create mode 100644 tests/harness/gpiod-test-common.h
 rename tests/{ => harness}/gpiod-test.c (100%)
 rename tests/{ => harness}/gpiod-test.h (100%)
 rename tests/{gpiod-test-helpers.h => helpers.h} (87%)
Signature · 1 line
2.40.1
#1 lore raw

PATCH 1/18 tests: split out reusable test code into a local static library

From: Bartosz Golaszewski <[email protected]>

In order to allow the upcoming GLib and DBus bindings to reuse the test code, let's put all common elements into reusable libtool objects and export the relevant symbols in internal headers.

---
 configure.ac                                  |  2 +
 tests/Makefile.am                             | 14 +++----
 tests/gpiod-test-helpers.c                    | 41 -------------------
 tests/gpiosim-glib/Makefile.am                | 13 ++++++
 .../gpiosim-glib.c}                           | 30 +++++++++++++-
 .../gpiosim-glib.h}                           | 14 +++++++
 tests/harness/Makefile.am                     | 12 ++++++
 tests/harness/gpiod-test-common.h             | 23 +++++++++++
 tests/{ => harness}/gpiod-test.c              |  0
 tests/{ => harness}/gpiod-test.h              |  0
 tests/{gpiod-test-helpers.h => helpers.h}     | 36 ++--------------
 tests/tests-chip-info.c                       |  7 ++--
 tests/tests-chip.c                            | 15 +++----
 tests/tests-edge-event.c                      |  7 ++--
 tests/tests-info-event.c                      |  7 ++--
 tests/tests-line-config.c                     |  7 ++--
 tests/tests-line-info.c                       | 11 ++---
 tests/tests-line-request.c                    |  7 ++--
 tests/tests-line-settings.c                   |  5 ++-
 tests/tests-misc.c                            |  7 ++--
 tests/tests-request-config.c                  |  5 ++-
 21 files changed, 146 insertions(+), 117 deletions(-)
 delete mode 100644 tests/gpiod-test-helpers.c
 create mode 100644 tests/gpiosim-glib/Makefile.am
 rename tests/{gpiod-test-sim.c => gpiosim-glib/gpiosim-glib.c} (93%)
 rename tests/{gpiod-test-sim.h => gpiosim-glib/gpiosim-glib.h} (86%)
 create mode 100644 tests/harness/Makefile.am
 create mode 100644 tests/harness/gpiod-test-common.h
 rename tests/{ => harness}/gpiod-test.c (100%)
 rename tests/{ => harness}/gpiod-test.h (100%)
 rename tests/{gpiod-test-helpers.h => helpers.h} (87%)
configure.ac +2 -0
index 3b5bbf2..b711501 100644
@@ -272,6 +272,8 @@ AC_CONFIG_FILES([Makefile
 		 tools/Makefile
 		 tests/Makefile
 		 tests/gpiosim/Makefile
+		 tests/gpiosim-glib/Makefile
+		 tests/harness/Makefile
 		 bindings/cxx/libgpiodcxx.pc
 		 bindings/Makefile
 		 bindings/cxx/Makefile
tests/Makefile.am +6 -8
index 0680d5e..13d7c5d 100644
@@ -1,25 +1,23 @@
 # SPDX-License-Identifier: GPL-2.0-or-later
 # SPDX-FileCopyrightText: 2017-2022 Bartosz Golaszewski <[email protected]>
 
-SUBDIRS = gpiosim
+SUBDIRS = gpiosim gpiosim-glib harness
 
-AM_CFLAGS = -I$(top_srcdir)/include/ -I$(top_srcdir)/tests/gpiosim/
+AM_CFLAGS = -I$(top_srcdir)/include/ -I$(top_srcdir)/tests/gpiosim-glib/
+AM_CFLAGS += -I$(top_srcdir)/tests/harness/
 AM_CFLAGS += -include $(top_builddir)/config.h
 AM_CFLAGS += -Wall -Wextra -g -std=gnu89 $(GLIB_CFLAGS) $(GIO_CFLAGS)
 AM_CFLAGS += -DG_LOG_DOMAIN=\"gpiod-test\"
 LDADD = $(top_builddir)/lib/libgpiod.la
 LDADD += $(top_builddir)/tests/gpiosim/libgpiosim.la
+LDADD += $(top_builddir)/tests/gpiosim-glib/libgpiosim-glib.la
+LDADD += $(top_builddir)/tests/harness/libgpiod-test-harness.la
 LDADD += $(GLIB_LIBS) $(GIO_LIBS)
 
 noinst_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 \
+	helpers.h \
 	tests-chip.c \
 	tests-chip-info.c \
 	tests-edge-event.c \
tests/gpiod-test-helpers.c deleted +0 -41

41 lines not on this page · raw message

tests/gpiosim-glib/Makefile.am new file +13 -0
new file mode 100644
index 0000000..1c01629
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]>
+
+noinst_LTLIBRARIES = libgpiosim-glib.la
+libgpiosim_glib_la_SOURCES = \
+	gpiosim-glib.c \
+	gpiosim-glib.h
+
+AM_CFLAGS = -I$(top_srcdir)/tests/gpiosim/
+AM_CFLAGS += -include $(top_builddir)/config.h
+AM_CFLAGS += -Wall -Wextra -g -std=gnu89 $(GLIB_CFLAGS) $(GIO_CFLAGS)
+AM_CFLAGS += -DG_LOG_DOMAIN=\"gpiosim-glib\"
+libgpiosim_glib_la_LDFLAGS = -lgpiosim
tests/gpiosim-glib/gpiosim-glib.c renamed from tests/gpiod-test-sim.c +29 -1
similarity index 93%
rename from tests/gpiod-test-sim.c
rename to tests/gpiosim-glib/gpiosim-glib.c
index ac6c71a..4eaeace 100644
@@ -6,7 +6,7 @@
 #include <stdlib.h>
 #include <unistd.h>
 
-#include "gpiod-test-sim.h"
+#include "gpiosim-glib.h"
 
 G_DEFINE_QUARK(g-gpiosim-error, g_gpiosim_error);
 
@@ -462,3 +462,31 @@ void g_gpiosim_chip_set_pull(GPIOSimChip *chip, guint offset, GPIOSimPull pull)
 		g_critical("Unable to set the pull setting for simulated line: %s",
 			    g_strerror(errno));
 }
+
+GVariant *g_gpiosim_package_line_names(const GPIOSimLineName *names)
+{
+	g_autoptr(GVariantBuilder) builder = NULL;
+	const GPIOSimLineName *name;
+
+	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);
+
+	return g_variant_ref_sink(g_variant_new("a(us)", builder));
+}
+
+GVariant *g_gpiosim_package_hogs(const GPIOSimHog *hogs)
+{
+	g_autoptr(GVariantBuilder) builder = NULL;
+	const GPIOSimHog *hog;
+
+	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);
+
+	return g_variant_ref_sink(g_variant_new("a(usi)", builder));
+}
tests/gpiosim-glib/gpiosim-glib.h renamed from tests/gpiod-test-sim.h +14 -0
similarity index 86%
rename from tests/gpiod-test-sim.h
rename to tests/gpiosim-glib/gpiosim-glib.h
index f6a4bf0..fa76736 100644
@@ -74,6 +74,20 @@ void g_gpiosim_chip_set_pull(GPIOSimChip *self, guint offset, GPIOSimPull pull);
 		_val; \
 	})
 
+typedef struct {
+	guint offset;
+	const gchar *name;
+} GPIOSimLineName;
+
+typedef struct {
+	guint offset;
+	const gchar *name;
+	GPIOSimDirection direction;
+} GPIOSimHog;
+
+GVariant *g_gpiosim_package_line_names(const GPIOSimLineName *names);
+GVariant *g_gpiosim_package_hogs(const GPIOSimHog *hogs);
+
 G_END_DECLS
 
 #endif /* __GPIOD_TEST_SIM_H__ */
tests/harness/Makefile.am new file +12 -0
new file mode 100644
index 0000000..185c00f
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]>
+
+noinst_LTLIBRARIES = libgpiod-test-harness.la
+libgpiod_test_harness_la_SOURCES = \
+	gpiod-test.c \
+	gpiod-test.h \
+	gpiod-test-common.h
+
+AM_CFLAGS = -include $(top_builddir)/config.h
+AM_CFLAGS += -Wall -Wextra -g -std=gnu89 $(GLIB_CFLAGS)
+AM_CFLAGS += -DG_LOG_DOMAIN=\"gpiod-test\"
tests/harness/gpiod-test-common.h new file +23 -0
new file mode 100644
index 0000000..7aaec05
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]> */
+
+#ifndef __GPIOD_TEST_COMMON_H__
+#define __GPIOD_TEST_COMMON_H__
+
+#include <glib.h>
+
+#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)
+
+#endif /* __GPIOD_TEST_COMMON_H__ */
tests/harness/gpiod-test.c renamed from tests/gpiod-test.c +0 -0
similarity index 100%
rename from tests/gpiod-test.c
rename to tests/harness/gpiod-test.c
tests/harness/gpiod-test.h renamed from tests/gpiod-test.h +0 -0
similarity index 100%
rename from tests/gpiod-test.h
rename to tests/harness/gpiod-test.h
tests/helpers.h renamed from tests/gpiod-test-helpers.h +3 -33
similarity index 87%
rename from tests/gpiod-test-helpers.h
rename to tests/helpers.h
index 41791a3..ecb7baf 100644
@@ -1,14 +1,12 @@
 /* SPDX-License-Identifier: GPL-2.0-or-later */
-/* SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]> */
+/* SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]> */
 
 #ifndef __GPIOD_TEST_HELPERS_H__
 #define __GPIOD_TEST_HELPERS_H__
 
-#include <errno.h>
 #include <glib.h>
 #include <gpiod.h>
-
-#include "gpiod-test-sim.h"
+#include <gpiod-test-common.h>
 
 /*
  * These typedefs are needed to make g_autoptr work - it doesn't accept
@@ -49,20 +47,6 @@ 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); \
@@ -184,20 +168,6 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(struct_gpiod_edge_event_buffer,
 	} while (0)
 
 #define gpiod_test_expect_errno(_expected) \
-	g_assert_cmpint(_expected, ==, errno)
-
-typedef struct {
-	guint offset;
-	const gchar *name;
-} GPIOSimLineName;
-
-typedef struct {
-	guint offset;
-	const gchar *name;
-	GPIOSimDirection direction;
-} GPIOSimHog;
-
-GVariant *gpiod_test_package_line_names(const GPIOSimLineName *names);
-GVariant *gpiod_test_package_hogs(const GPIOSimHog *hogs);
+	g_assert_cmpint((_expected), ==, errno)
 
 #endif /* __GPIOD_TEST_HELPERS_H__ */
tests/tests-chip-info.c +4 -3
index db76385..7b2e857 100644
@@ -4,10 +4,11 @@
 #include <errno.h>
 #include <glib.h>
 #include <gpiod.h>
+#include <gpiod-test.h>
+#include <gpiod-test-common.h>
+#include <gpiosim-glib.h>
 
-#include "gpiod-test.h"
-#include "gpiod-test-helpers.h"
-#include "gpiod-test-sim.h"
+#include "helpers.h"
 
 #define GPIOD_TEST_GROUP "chip-info"
 
tests/tests-chip.c +8 -7
index 815b4c7..13e3f61 100644
@@ -4,10 +4,11 @@
 #include <errno.h>
 #include <glib.h>
 #include <gpiod.h>
+#include <gpiod-test.h>
+#include <gpiod-test-common.h>
+#include <gpiosim-glib.h>
 
-#include "gpiod-test.h"
-#include "gpiod-test-helpers.h"
-#include "gpiod-test-sim.h"
+#include "helpers.h"
 
 #define GPIOD_TEST_GROUP "chip"
 
@@ -89,7 +90,7 @@ GPIOD_TEST_CASE(find_line_bad)
 
 	g_autoptr(GPIOSimChip) sim = NULL;
 	g_autoptr(struct_gpiod_chip) chip = NULL;
-	g_autoptr(GVariant) vnames = gpiod_test_package_line_names(names);
+	g_autoptr(GVariant) vnames = g_gpiosim_package_line_names(names);
 
 	sim = g_gpiosim_chip_new(
 			"num-lines", 8,
@@ -116,7 +117,7 @@ GPIOD_TEST_CASE(find_line_good)
 
 	g_autoptr(GPIOSimChip) sim = NULL;
 	g_autoptr(struct_gpiod_chip) chip = NULL;
-	g_autoptr(GVariant) vnames = gpiod_test_package_line_names(names);
+	g_autoptr(GVariant) vnames = g_gpiosim_package_line_names(names);
 
 	sim = g_gpiosim_chip_new(
 			"num-lines", 8,
@@ -142,7 +143,7 @@ GPIOD_TEST_CASE(find_line_duplicate)
 
 	g_autoptr(GPIOSimChip) sim = NULL;
 	g_autoptr(struct_gpiod_chip) chip = NULL;
-	g_autoptr(GVariant) vnames = gpiod_test_package_line_names(names);
+	g_autoptr(GVariant) vnames = g_gpiosim_package_line_names(names);
 
 	sim = g_gpiosim_chip_new(
 			"num-lines", 8,
@@ -165,7 +166,7 @@ GPIOD_TEST_CASE(find_line_non_standard_names)
 		{ }
 	};
 
-	g_autoptr(GVariant) vnames = gpiod_test_package_line_names(names);
+	g_autoptr(GVariant) vnames = g_gpiosim_package_line_names(names);
 	g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8,
 							"line-names", vnames,
 							NULL);
tests/tests-edge-event.c +4 -3
index b744ca5..6389455 100644
@@ -3,11 +3,12 @@
 
 #include <glib.h>
 #include <gpiod.h>
+#include <gpiod-test.h>
+#include <gpiod-test-common.h>
+#include <gpiosim-glib.h>
 #include <poll.h>
 
-#include "gpiod-test.h"
-#include "gpiod-test-helpers.h"
-#include "gpiod-test-sim.h"
+#include "helpers.h"
 
 #define GPIOD_TEST_GROUP "edge-event"
 
tests/tests-info-event.c +4 -3
index cbd9e9e..e014500 100644
@@ -3,11 +3,12 @@
 
 #include <glib.h>
 #include <gpiod.h>
+#include <gpiod-test.h>
+#include <gpiod-test-common.h>
+#include <gpiosim-glib.h>
 #include <poll.h>
 
-#include "gpiod-test.h"
-#include "gpiod-test-helpers.h"
-#include "gpiod-test-sim.h"
+#include "helpers.h"
 
 #define GPIOD_TEST_GROUP "info-event"
 
tests/tests-line-config.c +4 -3
index 469500b..b61a445 100644
@@ -4,10 +4,11 @@
 #include <errno.h>
 #include <glib.h>
 #include <gpiod.h>
+#include <gpiod-test.h>
+#include <gpiod-test-common.h>
+#include <gpiosim-glib.h>
 
-#include "gpiod-test.h"
-#include "gpiod-test-helpers.h"
-#include "gpiod-test-sim.h"
+#include "helpers.h"
 
 #define GPIOD_TEST_GROUP "line-config"
 
tests/tests-line-info.c +6 -5
index cf2c650..92cd7e0 100644
@@ -4,10 +4,11 @@
 #include <errno.h>
 #include <glib.h>
 #include <gpiod.h>
+#include <gpiod-test.h>
+#include <gpiod-test-common.h>
+#include <gpiosim-glib.h>
 
-#include "gpiod-test.h"
-#include "gpiod-test-helpers.h"
-#include "gpiod-test-sim.h"
+#include "helpers.h"
 
 #define GPIOD_TEST_GROUP "line-info"
 
@@ -64,8 +65,8 @@ GPIOD_TEST_CASE(line_info_basic_properties)
 	g_autoptr(struct_gpiod_chip) chip = NULL;
 	g_autoptr(struct_gpiod_line_info) info4 = NULL;
 	g_autoptr(struct_gpiod_line_info) info6 = NULL;
-	g_autoptr(GVariant) vnames = gpiod_test_package_line_names(names);
-	g_autoptr(GVariant) vhogs = gpiod_test_package_hogs(hogs);
+	g_autoptr(GVariant) vnames = g_gpiosim_package_line_names(names);
+	g_autoptr(GVariant) vhogs = g_gpiosim_package_hogs(hogs);
 
 	sim = g_gpiosim_chip_new(
 			"num-lines", 8,
tests/tests-line-request.c +4 -3
index 7bba078..dd4e9a8 100644
@@ -3,10 +3,11 @@
 
 #include <glib.h>
 #include <gpiod.h>
+#include <gpiod-test.h>
+#include <gpiod-test-common.h>
+#include <gpiosim-glib.h>
 
-#include "gpiod-test.h"
-#include "gpiod-test-helpers.h"
-#include "gpiod-test-sim.h"
+#include "helpers.h"
 
 #define GPIOD_TEST_GROUP "line-request"
 
tests/tests-line-settings.c +3 -2
index b86fd26..18fde50 100644
@@ -4,9 +4,10 @@
 #include <errno.h>
 #include <glib.h>
 #include <gpiod.h>
+#include <gpiod-test.h>
+#include <gpiod-test-common.h>
 
-#include "gpiod-test.h"
-#include "gpiod-test-helpers.h"
+#include "helpers.h"
 
 #define GPIOD_TEST_GROUP "line-settings"
 
tests/tests-misc.c +4 -3
index 240dd02..9d4f3de 100644
@@ -4,11 +4,12 @@
 #include <errno.h>
 #include <glib.h>
 #include <gpiod.h>
+#include <gpiod-test.h>
+#include <gpiod-test-common.h>
+#include <gpiosim-glib.h>
 #include <unistd.h>
 
-#include "gpiod-test.h"
-#include "gpiod-test-helpers.h"
-#include "gpiod-test-sim.h"
+#include "helpers.h"
 
 #define GPIOD_TEST_GROUP "misc"
 
tests/tests-request-config.c +3 -2
index d3c679a..a38befd 100644
@@ -3,9 +3,10 @@
 
 #include <glib.h>
 #include <gpiod.h>
+#include <gpiod-test.h>
+#include <gpiod-test-common.h>
 
-#include "gpiod-test.h"
-#include "gpiod-test-helpers.h"
+#include "helpers.h"
 
 #define GPIOD_TEST_GROUP "request-config"
 
Signature · 1 line
2.40.1
#2 lore raw

PATCH 2/18 tests: split out the common test code for bash scripts

In reply to Bartosz Golaszewski

From: Bartosz Golaszewski <[email protected]>

In order to allow the upcoming DBus command-line client tests to reuse the existing bash test harness, let's put the common code into an importable file and rename run_tool to run_prog to reflect that it now can run any program.

---
 configure.ac                          |   1 +
 tests/Makefile.am                     |   2 +-
 tests/bash/Makefile.am                |   4 +
 tests/bash/gpiod-bash-test-helper.inc | 328 +++++++++++++++
 tools/gpio-tools-test.bash            | 559 ++++++--------------------
 5 files changed, 452 insertions(+), 442 deletions(-)
 create mode 100644 tests/bash/Makefile.am
 create mode 100644 tests/bash/gpiod-bash-test-helper.inc
configure.ac +1 -0
index b711501..3595071 100644
@@ -274,6 +274,7 @@ AC_CONFIG_FILES([Makefile
 		 tests/gpiosim/Makefile
 		 tests/gpiosim-glib/Makefile
 		 tests/harness/Makefile
+		 tests/bash/Makefile
 		 bindings/cxx/libgpiodcxx.pc
 		 bindings/Makefile
 		 bindings/cxx/Makefile
tests/Makefile.am +1 -1
index 13d7c5d..0ccf921 100644
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0-or-later
 # SPDX-FileCopyrightText: 2017-2022 Bartosz Golaszewski <[email protected]>
 
-SUBDIRS = gpiosim gpiosim-glib harness
+SUBDIRS = gpiosim gpiosim-glib harness bash
 
 AM_CFLAGS = -I$(top_srcdir)/include/ -I$(top_srcdir)/tests/gpiosim-glib/
 AM_CFLAGS += -I$(top_srcdir)/tests/harness/
tests/bash/Makefile.am new file +4 -0
new file mode 100644
index 0000000..5766593
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2023 Bartosz Golaszewski <[email protected]>
+
+EXTRA_DIST = gpiod-bash-test-helper.inc
tests/bash/gpiod-bash-test-helper.inc new file +328 -0

328 lines not on this page · raw message

tools/gpio-tools-test.bash +118 -441

1,240 lines not on this page · raw message

Signature · 1 line
2.40.1
#3 lore raw

Re: [libgpiod][RFC/RFT 02/18] tests: split out the common test code for bash scripts

Quoted 9 lines · Bartosz Golaszewski

From: Bartosz Golaszewski <[email protected]>

In order to allow the upcoming DBus command-line client tests to reuse the existing bash test harness, let's put the common code into an importable file and rename run_tool to run_prog to reflect that it now can run any program.

 tests/bash/Makefile.am                |   4 +
 tests/bash/gpiod-bash-test-helper.inc | 328 +++++++++++++++

Is the 'bash' a new folder name?

If so, it might be problematic for make as it may recognize it as a bash command. I would rather name it 'scripts' or 'shell' or alike.

...

Quoted 1 line

+++ b/tests/bash/gpiod-bash-test-helper.inc

And why not a simple shell? Do you use bashisms?

...

Quoted 1 line

+# Run the command in $* and fail the test if the command succeeds.

"$*" in most cases is simply wrong. "$@" is better.

https://unix.stackexchange.com/questions/294052/how-to-properly-pass-set-of-parameters-with-spaces-into-shell-function

...

tests/bash/gpiod-bash-test-helper.inc line 48

Quoted 2 lines
tests/bash/gpiod-bash-test-helper.inc new file +2 -0
+num_lines_is() {
+	[ "$1" -eq "0" ] || [ -z "$output" ] && return 0

-o inside [] will do better than ||

Quoted 3 lines
tests/bash/gpiod-bash-test-helper.inc new file +3 -0
+	local NUM_LINES=$(echo "$output" | wc -l)
+	assertEquals " number of lines:" "$1" "$NUM_LINES"
+}

...

tests/bash/gpiod-bash-test-helper.inc line 71

Quoted 1 line
tests/bash/gpiod-bash-test-helper.inc new file +1 -0
+	for ARG in $*

This may be wrong.

Quoted 3 lines
tests/bash/gpiod-bash-test-helper.inc new file +2 -0
+	do
 
+	done

...

tests/bash/gpiod-bash-test-helper.inc line 112

Quoted 5 lines
tests/bash/gpiod-bash-test-helper.inc new file +5 -0
+gpiosim_chip_symlink_cleanup() {
+	if [ -n "$GPIOSIM_CHIP_LINK" ]
+	then
+		rm "$GPIOSIM_CHIP_LINK"
+	fi

If you use already || or && you may continue doing that, this will be one liner with it.

	[ -n "$GPIOSIM_CHIP_LINK" ] && rm "$GPIOSIM_CHIP_LINK"
Quoted 2 lines
(quoted diff) +2 -0
+	unset GPIOSIM_CHIP_LINK
+}

...

tests/bash/gpiod-bash-test-helper.inc line 142

Quoted 1 line
tests/bash/gpiod-bash-test-helper.inc new file +1 -0
+	for i in {1..30}; do

$(seq 1 30) or IIRC $(seq 30)

Less bashisms are better.

Quoted 3 lines
tests/bash/gpiod-bash-test-helper.inc new file +3 -0
+		[ "$(<$PORT)" = "$EXPECTED" ] && return
+		sleep 0.01
+	done

...

tests/bash/gpiod-bash-test-helper.inc line 162

Quoted 3 lines
tests/bash/gpiod-bash-test-helper.inc new file +3 -0
+		if [ "$?" = "0" ]
+		then
+			for LINE in $(find $BANKPATH/ | grep -E "line[0-9]+$")

What's the point in -E ?

Yet another Useless use of grep.

IIRC how find works. You may filter names there (and with regex IIRC again).

Review

Quoted 2 lines
(quoted diff) +2 -0
+			do
+				test -e $LINE/hog && rmdir $LINE/hog

Why not [ -e ... ] && ?

Quoted 3 lines
tests/bash/gpiod-bash-test-helper.inc new file +3 -0
+				rmdir $LINE
+			done
+		fi

...

tests/bash/gpiod-bash-test-helper.inc line 205

Quoted 5 lines
tests/bash/gpiod-bash-test-helper.inc new file +5 -0
+dut_read_redirect() {
+	output=$(<$SHUNIT_TMPDIR/$DUT_OUTPUT)
+        local ORIG_IFS="$IFS"
+        IFS=$'\n' lines=($output)
+        IFS="$ORIG_IFS"

TABs vs. spaces indentation issues?

Quoted 1 line

+}

...

tests/bash/gpiod-bash-test-helper.inc line 211

Quoted 4 lines
tests/bash/gpiod-bash-test-helper.inc new file +4 -0
+dut_read() {
+	local LINE
+	lines=()
+	while read -t 0.2 -u ${COPROC[0]} LINE;

What is ; for as you are using three-lines form?

tests/bash/gpiod-bash-test-helper.inc line 218

Quoted 7 lines
tests/bash/gpiod-bash-test-helper.inc new file +7 -0
+	do
+		if [ -n "$DUT_FIRST_CHAR" ]
+		then
+			LINE=${DUT_FIRST_CHAR}${LINE}
+			unset DUT_FIRST_CHAR
+		fi
+		lines+=("$LINE")

Don't remember the syntax, but something like this

lines=$((LINE + $lines))

is better than bashisms.

Quoted 3 lines
tests/bash/gpiod-bash-test-helper.inc new file +3 -0
+	done
+	output="${lines[@]}"
+}

...

tests/bash/gpiod-bash-test-helper.inc line 242

Quoted 1 line
tests/bash/gpiod-bash-test-helper.inc new file +1 -0
+	read -t 0.2 -u ${COPROC[0]} LINE || (echo Timeout && false)

Wouldn't () fork a shell?
(The () vs. {} discussion, don't remember by heart though.)

...

tests/bash/gpiod-bash-test-helper.inc line 253

Quoted 2 lines
tests/bash/gpiod-bash-test-helper.inc new file +2 -0
+dut_write() {
+	echo $* >&${COPROC[1]}

Oh.

Quoted 1 line

+}

...

tests/bash/gpiod-bash-test-helper.inc line 290

Quoted 4 lines
tests/bash/gpiod-bash-test-helper.inc new file +4 -0
+# Must be done after we sources shunit2 as we need SHUNIT_VERSION to be set.
+oneTimeSetUp() {
+	test "$SHUNIT_VERSION" = "$MIN_SHUNIT_VERSION" && return 0
+	local FIRST=$(printf "$SHUNIT_VERSION\n$MIN_SHUNIT_VERSION\n" | sort -Vr | head -1)

I believe you can do it with a simple test rather than involving sort and head and printf. And test most likely will be builtin.

Quoted 3 lines
tests/bash/gpiod-bash-test-helper.inc new file +3 -0
+	test "$FIRST" = "$MIN_SHUNIT_VERSION" && \
+		die "minimum shunit version required is $MIN_SHUNIT_VERSION (current version is $SHUNIT_VERSION"
+}

...

tests/bash/gpiod-bash-test-helper.inc line 299

Quoted 1 line
tests/bash/gpiod-bash-test-helper.inc new file +1 -0
+	SORTED=$(printf "$REQUIRED\n$CURRENT" | sort -V | head -n 1)

Ditto.

tests/bash/gpiod-bash-test-helper.inc line 301

Quoted 1 line
tests/bash/gpiod-bash-test-helper.inc new file +1 -0
+	if [ "$SORTED" != "$REQUIRED" ]

Dup? Seems to me you can run a single test to get the answer.

Review

Quoted 2 lines
(quoted diff) +2 -0
+	then
+		die "linux kernel version must be at least: v$REQUIRED - got: v$CURRENT"

Linux

Review

Quoted 1 line
(quoted diff) +1 -0
+	fi

...

Okay, it seems you moved existing code... That code needs more love towards becoming a nicely formatted shell.

Signature · 2 lines
With Best Regards,
Andy Shevchenko
#4 lore raw

Re: [libgpiod][RFC/RFT 02/18] tests: split out the common test code for bash scripts

Quoted 9 lines · Andy Shevchenko
Quoted 6 lines · Bartosz Golaszewski

From: Bartosz Golaszewski <[email protected]>

In order to allow the upcoming DBus command-line client tests to reuse the existing bash test harness, let's put the common code into an importable file and rename run_tool to run_prog to reflect that it now can run any program.

Quoted 8 lines
Quoted 2 lines
 tests/bash/Makefile.am                |   4 +
 tests/bash/gpiod-bash-test-helper.inc | 328 +++++++++++++++

Is the 'bash' a new folder name?

If so, it might be problematic for make as it may recognize it as a bash command. I would rather name it 'scripts' or 'shell' or alike.

Good point, 'scripts' works fine.

For the rest: it's out of scope for this series - you reviewed code that has existed for a while - and needs to be addressed separately.

Quoted 6 lines

...

Quoted 1 line

+++ b/tests/bash/gpiod-bash-test-helper.inc

And why not a simple shell? Do you use bashisms?

Yes, quite a lot actually.

Quoted 8 lines

...

Quoted 1 line

+# Run the command in $* and fail the test if the command succeeds.

"$*" in most cases is simply wrong. "$@" is better.

https://unix.stackexchange.com/questions/294052/how-to-properly-pass-set-of-parameters-with-spaces-into-shell-function

I'll change this.

Quoted 37 lines

...

tests/bash/gpiod-bash-test-helper.inc line 48

Quoted 2 lines
tests/bash/gpiod-bash-test-helper.inc new file +2 -0
+num_lines_is() {
+     [ "$1" -eq "0" ] || [ -z "$output" ] && return 0

-o inside [] will do better than ||

Quoted 3 lines
tests/bash/gpiod-bash-test-helper.inc new file +3 -0
+     local NUM_LINES=$(echo "$output" | wc -l)
+     assertEquals " number of lines:" "$1" "$NUM_LINES"
+}

...

Quoted 1 line

+ for ARG in $*

This may be wrong.

Quoted 3 lines
tests/bash/gpiod-bash-test-helper.inc new file +2 -0
+     do
 
+     done

...

tests/bash/gpiod-bash-test-helper.inc line 112

Quoted 5 lines
tests/bash/gpiod-bash-test-helper.inc new file +5 -0
+gpiosim_chip_symlink_cleanup() {
+     if [ -n "$GPIOSIM_CHIP_LINK" ]
+     then
+             rm "$GPIOSIM_CHIP_LINK"
+     fi

If you use already || or && you may continue doing that, this will be one liner with it.

        [ -n "$GPIOSIM_CHIP_LINK" ] && rm "$GPIOSIM_CHIP_LINK"

Review

Quoted 2 lines
(quoted diff) +2 -0
+     unset GPIOSIM_CHIP_LINK
+}
Quoted 8 lines

...

Quoted 1 line

+ for i in {1..30}; do

$(seq 1 30) or IIRC $(seq 30)

Less bashisms are better.

Disagree. We know we'll keep on using bash here in tests. No issue with using it here.

Quoted 6 lines
Quoted 3 lines
tests/bash/gpiod-bash-test-helper.inc new file +3 -0
+             [ "$(<$PORT)" = "$EXPECTED" ] && return
+             sleep 0.01
+     done

...

Quoted 8 lines

tests/bash/gpiod-bash-test-helper.inc line 162

Quoted 3 lines
tests/bash/gpiod-bash-test-helper.inc new file +3 -0
+             if [ "$?" = "0" ]
+             then
+                     for LINE in $(find $BANKPATH/ | grep -E "line[0-9]+$")

What's the point in -E ?

Yet another Useless use of grep.

Right, we only use grep in one place. I think we used to depend on it more in the past. I'll drop it.

Quoted 13 lines

IIRC how find works. You may filter names there (and with regex IIRC again).

Review

Quoted 2 lines
(quoted diff) +2 -0
+                     do
+                             test -e $LINE/hog && rmdir $LINE/hog

Why not [ -e ... ] && ?

Quoted 3 lines
tests/bash/gpiod-bash-test-helper.inc new file +3 -0
+                             rmdir $LINE
+                     done
+             fi

...

Quoted 8 lines

tests/bash/gpiod-bash-test-helper.inc line 205

Quoted 5 lines
tests/bash/gpiod-bash-test-helper.inc new file +5 -0
+dut_read_redirect() {
+     output=$(<$SHUNIT_TMPDIR/$DUT_OUTPUT)
+        local ORIG_IFS="$IFS"
+        IFS=$'\n' lines=($output)
+        IFS="$ORIG_IFS"

TABs vs. spaces indentation issues?

Indeed and not only here.

Quoted 76 lines
Quoted 1 line

+}

...

tests/bash/gpiod-bash-test-helper.inc line 211

Quoted 4 lines
tests/bash/gpiod-bash-test-helper.inc new file +4 -0
+dut_read() {
+     local LINE
+     lines=()
+     while read -t 0.2 -u ${COPROC[0]} LINE;

What is ; for as you are using three-lines form?

tests/bash/gpiod-bash-test-helper.inc line 218

Quoted 7 lines
tests/bash/gpiod-bash-test-helper.inc new file +7 -0
+     do
+             if [ -n "$DUT_FIRST_CHAR" ]
+             then
+                     LINE=${DUT_FIRST_CHAR}${LINE}
+                     unset DUT_FIRST_CHAR
+             fi
+             lines+=("$LINE")

Don't remember the syntax, but something like this

lines=$((LINE + $lines))

is better than bashisms.

Quoted 3 lines
tests/bash/gpiod-bash-test-helper.inc new file +3 -0
+     done
+     output="${lines[@]}"
+}

...

Quoted 1 line

+ read -t 0.2 -u ${COPROC[0]} LINE || (echo Timeout && false)

Wouldn't () fork a shell?
(The () vs. {} discussion, don't remember by heart though.)

...

tests/bash/gpiod-bash-test-helper.inc line 253

Quoted 2 lines
tests/bash/gpiod-bash-test-helper.inc new file +2 -0
+dut_write() {
+     echo $* >&${COPROC[1]}

Oh.

Quoted 1 line

+}

...

tests/bash/gpiod-bash-test-helper.inc line 290

Quoted 4 lines
tests/bash/gpiod-bash-test-helper.inc new file +4 -0
+# Must be done after we sources shunit2 as we need SHUNIT_VERSION to be set.
+oneTimeSetUp() {
+     test "$SHUNIT_VERSION" = "$MIN_SHUNIT_VERSION" && return 0
+     local FIRST=$(printf "$SHUNIT_VERSION\n$MIN_SHUNIT_VERSION\n" | sort -Vr | head -1)

I believe you can do it with a simple test rather than involving sort and head and printf. And test most likely will be builtin.

Quoted 3 lines
tests/bash/gpiod-bash-test-helper.inc new file +3 -0
+     test "$FIRST" = "$MIN_SHUNIT_VERSION" && \
+             die "minimum shunit version required is $MIN_SHUNIT_VERSION (current version is $SHUNIT_VERSION"
+}

...

Quoted 1 line

+ SORTED=$(printf "$REQUIRED\n$CURRENT" | sort -V | head -n 1)

Ditto.

Quoted 1 line

+ if [ "$SORTED" != "$REQUIRED" ]

Dup? Seems to me you can run a single test to get the answer.

Review

Quoted 2 lines
(quoted diff) +2 -0
+     then
+             die "linux kernel version must be at least: v$REQUIRED - got: v$CURRENT"

Linux

Quoted 1 line

+ fi

Quoted 10 lines

...

Okay, it seems you moved existing code... That code needs more love towards becoming a nicely formatted shell.

--
With Best Regards,
Andy Shevchenko

I will send a series addressing some of the issues. Others I choose to ignore for now as they are a matter of personal taste and Kent is the author of most of the bash code here.

Bart

#5 lore raw

PATCH 3/18 bindings: glib: add build files

In reply to Bartosz Golaszewski

From: Bartosz Golaszewski <[email protected]>

Add the directory structure and build files as well as changes to .gitignore and Doxygen.in for GLib bindings.

---
 .gitignore                           |  2 +
 Doxyfile.in                          |  4 +-
 bindings/Makefile.am                 |  7 +++
 bindings/glib/Makefile.am            | 78 ++++++++++++++++++++++++++++
 bindings/glib/examples/.gitignore    | 14 +++++
 bindings/glib/examples/Makefile.am   | 22 ++++++++
 bindings/glib/gpiod-glib.pc.in       | 14 +++++
 bindings/glib/gpiod-glib/Makefile.am | 18 +++++++
 bindings/glib/tests/.gitignore       |  4 ++
 bindings/glib/tests/Makefile.am      | 29 +++++++++++
 configure.ac                         | 24 +++++++++
 11 files changed, 215 insertions(+), 1 deletion(-)
 create mode 100644 bindings/glib/Makefile.am
 create mode 100644 bindings/glib/examples/.gitignore
 create mode 100644 bindings/glib/examples/Makefile.am
 create mode 100644 bindings/glib/gpiod-glib.pc.in
 create mode 100644 bindings/glib/gpiod-glib/Makefile.am
 create mode 100644 bindings/glib/tests/.gitignore
 create mode 100644 bindings/glib/tests/Makefile.am
.gitignore +2 -0
index cf66e97..c3a29d8 100644
@@ -4,6 +4,8 @@
 *.o
 *.lo
 *.la
+generated-*.c
+generated-*.h
 doc
 *.pc
 *.tar.gz
Doxyfile.in +3 -1
index 9c85e21..548a0da 100644
@@ -46,7 +46,9 @@ WARN_FORMAT            =
 WARN_LOGFILE           =
 INPUT                  = @top_srcdir@/include/gpiod.h \
                          @top_srcdir@/bindings/cxx/gpiod.hpp \
-                         @top_srcdir@/bindings/cxx/gpiodcxx/
+                         @top_srcdir@/bindings/cxx/gpiodcxx/ \
+                         @top_srcdir@/bindings/glib/gpiod-glib.h \
+                         @top_srcdir@/bindings/glib/gpiod-glib/
 SOURCE_BROWSER         = YES
 INLINE_SOURCES         = NO
 REFERENCED_BY_RELATION = YES
bindings/Makefile.am +7 -0
index 004ae23..a177187 100644
@@ -1,5 +1,6 @@
 # SPDX-License-Identifier: GPL-2.0-or-later
 # SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <[email protected]>
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]>
 
 SUBDIRS = .
 
@@ -20,3 +21,9 @@ if WITH_BINDINGS_RUST
 SUBDIRS += rust
 
 endif
+
+if WITH_BINDINGS_GLIB
+
+SUBDIRS += glib
+
+endif
bindings/glib/Makefile.am new file +78 -0

78 lines not on this page · raw message

bindings/glib/examples/.gitignore new file +14 -0
new file mode 100644
index 0000000..c2415ae
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: CC0-1.0
+# SPDX-FileCopyrightText: 2023 Bartosz Golaszewski <[email protected]>
+
+find_line_by_name_glib
+get_chip_info_glib
+get_line_info_glib
+get_line_value_glib
+get_multiple_line_values_glib
+reconfigure_input_to_output_glib
+toggle_line_value_glib
+toggle_multiple_line_values_glib
+watch_line_info_glib
+watch_line_value_glib
+watch_multiple_edge_rising_glib
bindings/glib/examples/Makefile.am new file +22 -0
new file mode 100644
index 0000000..fb4e5b1
@@ -0,0 +1,22 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022-2023 Bartosz Golaszewski <[email protected]>
+
+bin_PROGRAMS = \
+	find_line_by_name_glib \
+	get_chip_info_glib \
+	get_line_info_glib \
+	get_line_value_glib \
+	get_multiple_line_values_glib \
+	reconfigure_input_to_output_glib \
+	toggle_line_value_glib \
+	toggle_multiple_line_values_glib \
+	watch_line_info_glib \
+	watch_line_value_glib \
+	watch_multiple_edge_rising_glib
+
+AM_CFLAGS = -I$(top_srcdir)/bindings/glib/
+AM_CFLAGS += -include $(top_builddir)/config.h
+AM_CFLAGS += -Wall -Wextra -g -std=gnu89 $(GLIB_CFLAGS) $(GOBJECT_CFLAGS)
+AM_CFLAGS += -DG_LOG_DOMAIN=\"gpiotools-glib\"
+LDADD = $(top_builddir)/bindings/glib/libgpiod-glib.la
+LDADD += $(GLIB_LIBS) $(GOBJECT_LIBS)
bindings/glib/gpiod-glib.pc.in new file +14 -0
new file mode 100644
index 0000000..5da56da
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022-2023 Bartosz Golaszewski <[email protected]>
+
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: gpiod-glib
+Description: GObject bindings for libgpiod
+URL: @PACKAGE_URL@
+Version: @PACKAGE_VERSION@
+Libs: -L${libdir} -lgpiod-glib
+Cflags: -I${includedir}
bindings/glib/gpiod-glib/Makefile.am new file +18 -0
new file mode 100644
index 0000000..3d47772
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022-2023 Bartosz Golaszewski <[email protected]>
+
+otherincludedir = $(includedir)/gpiod-glib
+otherinclude_HEADERS = \
+	chip.h \
+	chip-info.h \
+	edge-event.h \
+	error.h \
+	generated-enums.h \
+	info-event.h \
+	line.h \
+	line-config.h \
+	line-info.h \
+	line-request.h \
+	line-settings.h \
+	misc.h \
+	request-config.h
bindings/glib/tests/.gitignore new file +4 -0
new file mode 100644
index 0000000..8eb499f
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: CC0-1.0
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]>
+
+gpiod-glib-test
bindings/glib/tests/Makefile.am new file +29 -0
new file mode 100644
index 0000000..a90587a
@@ -0,0 +1,29 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022-2023 Bartosz Golaszewski <[email protected]>
+
+noinst_PROGRAMS = gpiod-glib-test
+gpiod_glib_test_SOURCES = \
+	helpers.c \
+	helpers.h \
+	tests-chip.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
+
+AM_CFLAGS = -I$(top_srcdir)/bindings/glib/
+AM_CFLAGS += -I$(top_srcdir)/tests/gpiosim-glib/
+AM_CFLAGS += -I$(top_srcdir)/tests/harness/
+AM_CFLAGS += -include $(top_builddir)/config.h
+AM_CFLAGS += -Wall -Wextra -g -std=gnu89 $(GLIB_CFLAGS) $(GIO_CFLAGS)
+AM_CFLAGS += -DG_LOG_DOMAIN=\"gpiod-glib-test\"
+LDADD = $(top_builddir)/bindings/glib/libgpiod-glib.la
+LDADD += $(top_builddir)/tests/gpiosim/libgpiosim.la
+LDADD += $(top_builddir)/tests/gpiosim-glib/libgpiosim-glib.la
+LDADD += $(top_builddir)/tests/harness/libgpiod-test-harness.la
+LDADD += $(GLIB_LIBS) $(GIO_LIBS)
configure.ac +24 -0
index 3595071..fa486f4 100644
@@ -31,6 +31,8 @@ AC_SUBST(ABI_CXX_VERSION, [3.0.1])
 # ABI version for libgpiosim (we need this since it can be installed if we
 # enable tests).
 AC_SUBST(ABI_GPIOSIM_VERSION, [1.1.0])
+# ... and another one for GLib bindings:
+AC_SUBST(ABI_GLIB_VERSION, [1.0.0])
 
 AC_CONFIG_AUX_DIR([autostuff])
 AC_CONFIG_MACRO_DIRS([m4])
@@ -245,6 +247,23 @@ then
 	fi
 fi
 
+AC_ARG_ENABLE([bindings-glib],
+	[AS_HELP_STRING([--enable-bindings-glib],[enable GLib 2.0 bindings [default=no]])],
+	[if test "x$enableval" = xyes; then with_bindings_glib=true; fi],
+	[with_bindings_glib=false])
+AM_CONDITIONAL([WITH_BINDINGS_GLIB], [test "x$with_bindings_glib" = xtrue])
+
+if test "x$with_bindings_glib" = xtrue
+then
+	PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.54])
+	PKG_CHECK_MODULES([GOBJECT], [gobject-2.0 >= 2.54])
+	PKG_CHECK_MODULES([GIO], [gio-2.0 >= 2.54])
+	PKG_CHECK_MODULES([GIO_UNIX], [gio-unix-2.0 >= 2.54])
+	PKG_PROG_PKG_CONFIG([0.28])
+	PKG_CHECK_VAR([GLIB_MKENUMS], [glib-2.0], [glib_mkenums], [],
+		AC_MSG_ERROR([glib-mkenums not found - needed to build GLib bindings]))
+fi
+
 AC_CHECK_PROG([has_doxygen], [doxygen], [true], [false])
 AM_CONDITIONAL([HAS_DOXYGEN], [test "x$has_doxygen" = xtrue])
 if test "x$has_doxygen" = xfalse
@@ -281,6 +300,11 @@ AC_CONFIG_FILES([Makefile
 		 bindings/cxx/gpiodcxx/Makefile
 		 bindings/cxx/examples/Makefile
 		 bindings/cxx/tests/Makefile
+		 bindings/glib/gpiod-glib.pc
+		 bindings/glib/Makefile
+		 bindings/glib/examples/Makefile
+		 bindings/glib/gpiod-glib/Makefile
+		 bindings/glib/tests/Makefile
 		 bindings/python/Makefile
 		 bindings/python/gpiod/Makefile
 		 bindings/python/gpiod/ext/Makefile
Signature · 1 line
2.40.1
#6 lore raw

PATCH 4/18 bindings: glib: add public headers

In reply to Bartosz Golaszewski

From: Bartosz Golaszewski <[email protected]>

Add the public headers for GLib bindings. They contain the entire API together with Doxygen comments.

---
 bindings/glib/generated-enums.h.template  |  30 ++++
 bindings/glib/gpiod-glib.h                |  33 ++++
 bindings/glib/gpiod-glib/chip-info.h      |  84 +++++++++
 bindings/glib/gpiod-glib/chip.h           | 164 ++++++++++++++++++
 bindings/glib/gpiod-glib/edge-event.h     | 114 ++++++++++++
 bindings/glib/gpiod-glib/error.h          |  67 +++++++
 bindings/glib/gpiod-glib/info-event.h     |  97 +++++++++++
 bindings/glib/gpiod-glib/line-config.h    | 116 +++++++++++++
 bindings/glib/gpiod-glib/line-info.h      | 171 ++++++++++++++++++
 bindings/glib/gpiod-glib/line-request.h   | 182 +++++++++++++++++++
 bindings/glib/gpiod-glib/line-settings.h  | 202 ++++++++++++++++++++++
 bindings/glib/gpiod-glib/line.h           | 114 ++++++++++++
 bindings/glib/gpiod-glib/misc.h           |  51 ++++++
 bindings/glib/gpiod-glib/request-config.h | 107 ++++++++++++
 14 files changed, 1532 insertions(+)
 create mode 100644 bindings/glib/generated-enums.h.template
 create mode 100644 bindings/glib/gpiod-glib.h
 create mode 100644 bindings/glib/gpiod-glib/chip-info.h
 create mode 100644 bindings/glib/gpiod-glib/chip.h
 create mode 100644 bindings/glib/gpiod-glib/edge-event.h
 create mode 100644 bindings/glib/gpiod-glib/error.h
 create mode 100644 bindings/glib/gpiod-glib/info-event.h
 create mode 100644 bindings/glib/gpiod-glib/line-config.h
 create mode 100644 bindings/glib/gpiod-glib/line-info.h
 create mode 100644 bindings/glib/gpiod-glib/line-request.h
 create mode 100644 bindings/glib/gpiod-glib/line-settings.h
 create mode 100644 bindings/glib/gpiod-glib/line.h
 create mode 100644 bindings/glib/gpiod-glib/misc.h
 create mode 100644 bindings/glib/gpiod-glib/request-config.h
bindings/glib/generated-enums.h.template new file +30 -0
new file mode 100644
index 0000000..53ea247
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2023 Bartosz Golaszewski <[email protected]>
+
+/*** BEGIN file-header ***/
+
+#ifndef __GPIOD_GLIB_GENERATED_ENUMS_H__
+#define __GPIOD_GLIB_GENERATED_ENUMS_H__
+
+#if !defined (__GPIOD_GLIB_INSIDE__)
+#error "Only <gpiod-glib.h> can be included directly."
+#endif
+
+G_BEGIN_DECLS
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+
+/*** END file-production ***/
+
+/*** BEGIN value-header ***/
+GType @enum_name@_get_type(void) G_GNUC_CONST;
+#define @ENUMPREFIX@_@ENUMSHORT@_TYPE (@enum_name@_get_type())
+/*** END value-header ***/
+
+/*** BEGIN file-tail ***/
+
+G_END_DECLS
+
+#endif /* __GPIOD_GLIB_GENERATED_ENUMS_H__ */
+/*** END file-tail ***/
bindings/glib/gpiod-glib.h new file +33 -0
new file mode 100644
index 0000000..c45064c
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/* SPDX-FileCopyrightText: 2022-2023 Bartosz Golaszewski <[email protected]> */
+
+/**
+ * @file gpiod-glib.h
+ */
+
+#ifndef __GPIOD_GLIB_H__
+#define __GPIOD_GLIB_H__
+
+/**
+ * @defgroup gpiod_glib GLib bindings
+ *
+ * GLib bindings for libgpiod representing all data structures using the
+ * GObject type system.
+ */
+
+#define __GPIOD_GLIB_INSIDE__
+#include "gpiod-glib/chip.h"
+#include "gpiod-glib/chip-info.h"
+#include "gpiod-glib/edge-event.h"
+#include "gpiod-glib/error.h"
+#include "gpiod-glib/generated-enums.h"
+#include "gpiod-glib/info-event.h"
+#include "gpiod-glib/line-config.h"
+#include "gpiod-glib/line-info.h"
+#include "gpiod-glib/line-request.h"
+#include "gpiod-glib/line-settings.h"
+#include "gpiod-glib/misc.h"
+#include "gpiod-glib/request-config.h"
+#undef __GPIOD_GLIB_INSIDE__
+
+#endif /* __GPIOD_GLIB_H__ */
bindings/glib/gpiod-glib/chip-info.h new file +84 -0

84 lines not on this page · raw message

bindings/glib/gpiod-glib/chip.h new file +164 -0

164 lines not on this page · raw message

bindings/glib/gpiod-glib/edge-event.h new file +114 -0

114 lines not on this page · raw message

bindings/glib/gpiod-glib/error.h new file +67 -0

67 lines not on this page · raw message

bindings/glib/gpiod-glib/info-event.h new file +97 -0

97 lines not on this page · raw message

bindings/glib/gpiod-glib/line-config.h new file +116 -0

116 lines not on this page · raw message

bindings/glib/gpiod-glib/line-info.h new file +171 -0

171 lines not on this page · raw message

bindings/glib/gpiod-glib/line-request.h new file +182 -0

182 lines not on this page · raw message

bindings/glib/gpiod-glib/line-settings.h new file +202 -0

202 lines not on this page · raw message

bindings/glib/gpiod-glib/line.h new file +114 -0

114 lines not on this page · raw message

bindings/glib/gpiod-glib/misc.h new file +51 -0

51 lines not on this page · raw message

bindings/glib/gpiod-glib/request-config.h new file +107 -0

107 lines not on this page · raw message

Signature · 1 line
2.40.1
#7 lore raw

PATCH 5/18 bindings: glib: add core code

In reply to Bartosz Golaszewski

From: Bartosz Golaszewski <[email protected]>

Add the files implementing the public API of the GLib bindings to libgpiod.

---
 bindings/glib/chip-info.c                | 118 ++++++
 bindings/glib/chip.c                     | 396 +++++++++++++++++++++
 bindings/glib/edge-event.c               | 158 +++++++++
 bindings/glib/error.c                    |  67 ++++
 bindings/glib/generated-enums.c.template |  43 +++
 bindings/glib/info-event.c               | 150 ++++++++
 bindings/glib/internal.c                 | 334 +++++++++++++++++
 bindings/glib/internal.h                 |  54 +++
 bindings/glib/line-config.c              | 186 ++++++++++
 bindings/glib/line-info.c                | 274 ++++++++++++++
 bindings/glib/line-request.c             | 434 +++++++++++++++++++++++
 bindings/glib/line-settings.c            | 359 +++++++++++++++++++
 bindings/glib/misc.c                     |  17 +
 bindings/glib/request-config.c           | 155 ++++++++
 14 files changed, 2745 insertions(+)
 create mode 100644 bindings/glib/chip-info.c
 create mode 100644 bindings/glib/chip.c
 create mode 100644 bindings/glib/edge-event.c
 create mode 100644 bindings/glib/error.c
 create mode 100644 bindings/glib/generated-enums.c.template
 create mode 100644 bindings/glib/info-event.c
 create mode 100644 bindings/glib/internal.c
 create mode 100644 bindings/glib/internal.h
 create mode 100644 bindings/glib/line-config.c
 create mode 100644 bindings/glib/line-info.c
 create mode 100644 bindings/glib/line-request.c
 create mode 100644 bindings/glib/line-settings.c
 create mode 100644 bindings/glib/misc.c
 create mode 100644 bindings/glib/request-config.c
bindings/glib/chip-info.c new file +118 -0
new file mode 100644
index 0000000..a2038c6
@@ -0,0 +1,118 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2022-2023 Bartosz Golaszewski <[email protected]>
+
+#include <gio/gio.h>
+#include <gpiod.h>
+#include <gpiod-glib.h>
+
+#include "internal.h"
+
+struct _GPIODChipInfo {
+	GObject parent_instance;
+	struct gpiod_chip_info *handle;
+};
+
+enum {
+	G_GPIOD_CHIP_INFO_PROP_HANDLE = 1,
+	G_GPIOD_CHIP_INFO_PROP_NAME,
+	G_GPIOD_CHIP_INFO_PROP_LABEL,
+	G_GPIOD_CHIP_INFO_PROP_NUM_LINES,
+};
+
+G_DEFINE_TYPE(GPIODChipInfo, g_gpiod_chip_info, G_TYPE_OBJECT);
+
+static void g_gpiod_chip_info_get_property(GObject *obj, guint prop_id,
+					   GValue *val, GParamSpec *pspec)
+{
+	GPIODChipInfo *self = G_GPIOD_CHIP_INFO_OBJ(obj);
+
+	switch (prop_id) {
+	case G_GPIOD_CHIP_INFO_PROP_NAME:
+		g_value_set_static_string(val,
+			gpiod_chip_info_get_name(self->handle));
+		break;
+	case G_GPIOD_CHIP_INFO_PROP_LABEL:
+		g_value_set_static_string(val,
+			gpiod_chip_info_get_label(self->handle));
+		break;
+	case G_GPIOD_CHIP_INFO_PROP_NUM_LINES:
+		g_value_set_uint(val,
+			gpiod_chip_info_get_num_lines(self->handle));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
+	}
+}
+
+static void g_gpiod_chip_info_set_property(GObject *obj, guint prop_id,
+					   const GValue *val, GParamSpec *pspec)
+{
+	GPIODChipInfo *self = G_GPIOD_CHIP_INFO_OBJ(obj);
+
+	switch (prop_id) {
+	case G_GPIOD_CHIP_INFO_PROP_HANDLE:
+		self->handle = g_value_get_pointer(val);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
+	}
+}
+
+static void g_gpiod_chip_info_finalize(GObject *obj)
+{
+	GPIODChipInfo *self = G_GPIOD_CHIP_INFO_OBJ(obj);
+
+	g_clear_pointer(&self->handle, gpiod_chip_info_free);
+
+	G_OBJECT_CLASS(g_gpiod_chip_info_parent_class)->finalize(obj);
+}
+
+static void g_gpiod_chip_info_class_init(GPIODChipInfoClass *chip_info_class)
+{
+	GObjectClass *class = G_OBJECT_CLASS(chip_info_class);
+
+	class->set_property = g_gpiod_chip_info_set_property;
+	class->get_property = g_gpiod_chip_info_get_property;
+	class->finalize = g_gpiod_chip_info_finalize;
+
+	g_object_class_install_property(class, G_GPIOD_CHIP_INFO_PROP_HANDLE,
+		g_param_spec_pointer("handle", "Handle",
+			"GPIO Chip information object.",
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
+
+	g_object_class_install_property(class, G_GPIOD_CHIP_INFO_PROP_NAME,
+		g_param_spec_string("name", "Name",
+			"Name of this GPIO chip device.", NULL,
+			G_PARAM_READABLE));
+
+	g_object_class_install_property(class, G_GPIOD_CHIP_INFO_PROP_LABEL,
+		g_param_spec_string("label", "Label",
+			"Label of this GPIO chip device.", NULL,
+			G_PARAM_READABLE));
+
+	g_object_class_install_property(class, G_GPIOD_CHIP_INFO_PROP_NUM_LINES,
+		g_param_spec_uint("num-lines", "NumLines",
+			"Number of GPIO lines exposed by this chip.",
+			1, G_MAXUINT, 1,
+			G_PARAM_READABLE));
+}
+
+static void g_gpiod_chip_info_init(GPIODChipInfo *self)
+{
+	self->handle = NULL;
+}
+
+const gchar *g_gpiod_chip_info_get_name(GPIODChipInfo *self)
+{
+	return g_gpiod_get_prop_string(G_OBJECT(self), "name");
+}
+
+const gchar *g_gpiod_chip_info_get_label(GPIODChipInfo *self)
+{
+	return g_gpiod_get_prop_string(G_OBJECT(self), "label");
+}
+
+guint g_gpiod_chip_info_get_num_lines(GPIODChipInfo *self)
+{
+	return g_gpiod_get_prop_uint(G_OBJECT(self), "num-lines");
+}
bindings/glib/chip.c new file +396 -0

396 lines not on this page · raw message

bindings/glib/edge-event.c new file +158 -0

158 lines not on this page · raw message

bindings/glib/error.c new file +67 -0

67 lines not on this page · raw message

bindings/glib/generated-enums.c.template new file +43 -0

43 lines not on this page · raw message

bindings/glib/info-event.c new file +150 -0

150 lines not on this page · raw message

bindings/glib/internal.c new file +334 -0

334 lines not on this page · raw message

bindings/glib/internal.h new file +54 -0

54 lines not on this page · raw message

bindings/glib/line-config.c new file +186 -0

186 lines not on this page · raw message

bindings/glib/line-info.c new file +274 -0

274 lines not on this page · raw message

bindings/glib/line-request.c new file +434 -0

434 lines not on this page · raw message

bindings/glib/line-settings.c new file +359 -0

359 lines not on this page · raw message

bindings/glib/misc.c new file +17 -0
new file mode 100644
index 0000000..139efed
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2022-2023 Bartosz Golaszewski <[email protected]>
+
+#include <gpiod.h>
+#include <gpiod-glib.h>
+
+gboolean g_gpiod_is_gpiochip_device(const gchar *path)
+{
+	g_assert(path);
+
+	return gpiod_is_gpiochip_device(path);
+}
+
+const gchar *g_gpiod_api_version(void)
+{
+	return gpiod_api_version();
+}
bindings/glib/request-config.c new file +155 -0

155 lines not on this page · raw message

Signature · 1 line
2.40.1
#8 lore raw

PATCH 6/18 bindings: glib: add examples

In reply to Bartosz Golaszewski

From: Bartosz Golaszewski <[email protected]>

Add example programs showcasing the usage of GLib bindings to libgpiod.

---
 .../glib/examples/find_line_by_name_glib.c    |  72 ++++++++++
 bindings/glib/examples/get_chip_info_glib.c   |  39 ++++++
 bindings/glib/examples/get_line_info_glib.c   |  79 +++++++++++
 bindings/glib/examples/get_line_value_glib.c  |  67 +++++++++
 .../examples/get_multiple_line_values_glib.c  |  72 ++++++++++
 .../reconfigure_input_to_output_glib.c        | 103 ++++++++++++++
 .../glib/examples/toggle_line_value_glib.c    |  99 +++++++++++++
 .../toggle_multiple_line_values_glib.c        | 132 ++++++++++++++++++
 bindings/glib/examples/watch_line_info_glib.c |  63 +++++++++
 .../glib/examples/watch_line_value_glib.c     |  91 ++++++++++++
 .../watch_multiple_edge_rising_glib.c         |  95 +++++++++++++
 11 files changed, 912 insertions(+)
 create mode 100644 bindings/glib/examples/find_line_by_name_glib.c
 create mode 100644 bindings/glib/examples/get_chip_info_glib.c
 create mode 100644 bindings/glib/examples/get_line_info_glib.c
 create mode 100644 bindings/glib/examples/get_line_value_glib.c
 create mode 100644 bindings/glib/examples/get_multiple_line_values_glib.c
 create mode 100644 bindings/glib/examples/reconfigure_input_to_output_glib.c
 create mode 100644 bindings/glib/examples/toggle_line_value_glib.c
 create mode 100644 bindings/glib/examples/toggle_multiple_line_values_glib.c
 create mode 100644 bindings/glib/examples/watch_line_info_glib.c
 create mode 100644 bindings/glib/examples/watch_line_value_glib.c
 create mode 100644 bindings/glib/examples/watch_multiple_edge_rising_glib.c
bindings/glib/examples/find_line_by_name_glib.c new file +72 -0
new file mode 100644
index 0000000..935f229
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2023 Bartosz Golaszewski <[email protected]>
+
+/* Minimal example of finding a line with the given name. */
+
+#include <glib.h>
+#include <gpiod-glib.h>
+#include <stdlib.h>
+
+int main(void)
+{
+	/* Example configuration - customize to suit your situation. */
+	static const gchar *const line_name = "GPIO0";
+
+	g_autoptr(GPIODChipInfo) info = NULL;
+	g_autoptr(GError) err = NULL;
+	g_autoptr(GDir) dir = NULL;
+	const gchar *filename;
+	gboolean ret;
+	guint offset;
+
+	dir = g_dir_open("/dev", 0, &err);
+	if (err) {
+		g_printerr("Unable to open /dev: %s\n", err->message);
+		return EXIT_FAILURE;
+	}
+
+	/*
+	 * Names are not guaranteed unique, so this finds the first line with
+	 * the given name.
+	 */
+	while ((filename = g_dir_read_name(dir))) {
+		g_autoptr(GPIODChip) chip = NULL;
+		g_autofree gchar *path = NULL;
+
+		path = g_strdup_printf("/dev/%s", filename);
+		if (!g_gpiod_is_gpiochip_device(path))
+			continue;
+
+		chip = g_gpiod_chip_new(path, &err);
+		if (err) {
+			g_printerr("Failed to open the GPIO chip at '%s': %s\n",
+				   path, err->message);
+			return EXIT_FAILURE;
+		}
+
+		ret = g_gpiod_chip_get_line_offset_from_name(chip, line_name,
+							     &offset, &err);
+		if (ret) {
+			info = g_gpiod_chip_get_info(chip, &err);
+			if (!info) {
+				g_printerr("Failed to get chip info: %s\n",
+					   err->message);
+				return EXIT_FAILURE;
+			}
+
+			g_print("%s %u\n",
+				g_gpiod_chip_info_get_name(info),
+				offset);
+
+			return EXIT_SUCCESS;
+		} else if (!ret && err) {
+			g_printerr("Failed to map the line name '%s' to offset: %s\n",
+				   line_name, err->message);
+			return EXIT_FAILURE;
+		}
+	}
+
+	g_print("line '%s' not found\n", line_name);
+
+	return EXIT_SUCCESS;
+}
bindings/glib/examples/get_chip_info_glib.c new file +39 -0
new file mode 100644
index 0000000..1f1811a
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2023 Bartosz Golaszewski <[email protected]>
+
+/* Minimal example of reading the info for a chip. */
+
+#include <glib.h>
+#include <gpiod-glib.h>
+#include <stdlib.h>
+
+int main(void)
+{
+	/* Example configuration - customize to suit your situation. */
+	static const gchar *const chip_path = "/dev/gpiochip0";
+
+	g_autoptr(GPIODChipInfo) info = NULL;
+	g_autoptr(GPIODChip) chip = NULL;
+	g_autoptr(GError) err = NULL;
+
+	chip = g_gpiod_chip_new(chip_path, &err);
+	if (err) {
+		g_printerr("Failed to open the GPIO chip at '%s': %s\n",
+			   chip_path, err->message);
+		return EXIT_FAILURE;
+	}
+
+	info = g_gpiod_chip_get_info(chip, &err);
+	if (err) {
+		g_printerr("Failed to retrieve GPIO chip info: %s\n",
+			   err->message);
+		return EXIT_FAILURE;
+	}
+
+	g_print("%s [%s] (%u lines)\n",
+		g_gpiod_chip_info_get_name(info),
+		g_gpiod_chip_info_get_label(info),
+		g_gpiod_chip_info_get_num_lines(info));
+
+	return EXIT_SUCCESS;
+}
bindings/glib/examples/get_line_info_glib.c new file +79 -0

79 lines not on this page · raw message

bindings/glib/examples/get_line_value_glib.c new file +67 -0

67 lines not on this page · raw message

bindings/glib/examples/get_multiple_line_values_glib.c new file +72 -0

72 lines not on this page · raw message

bindings/glib/examples/reconfigure_input_to_output_glib.c new file +103 -0

103 lines not on this page · raw message

bindings/glib/examples/toggle_line_value_glib.c new file +99 -0

99 lines not on this page · raw message

bindings/glib/examples/toggle_multiple_line_values_glib.c new file +132 -0

132 lines not on this page · raw message

bindings/glib/examples/watch_line_info_glib.c new file +63 -0

63 lines not on this page · raw message

bindings/glib/examples/watch_line_value_glib.c new file +91 -0

91 lines not on this page · raw message

bindings/glib/examples/watch_multiple_edge_rising_glib.c new file +95 -0

95 lines not on this page · raw message

Signature · 1 line
2.40.1
#9 lore raw

Re: [libgpiod][RFC/RFT 06/18] bindings: glib: add examples

Quoted 10 lines · Bartosz Golaszewski

From: Bartosz Golaszewski <[email protected]>

Add example programs showcasing the usage of GLib bindings to libgpiod.

bindings/glib/examples/find_line_by_name_glib.c new file +6 -0
+		ret = g_gpiod_chip_get_line_offset_from_name(chip, line_name,
+							     &offset, &err);
+		if (ret) {
+			info = g_gpiod_chip_get_info(chip, &err);
+			if (!info) {
+				g_printerr("Failed to get chip info: %s\n",

bindings/glib/examples/find_line_by_name_glib.c line 62

Quoted 10 lines
bindings/glib/examples/find_line_by_name_glib.c new file +10 -0
+					   err->message);
+				return EXIT_FAILURE;
+			}
+
+			g_print("%s %u\n",
+				g_gpiod_chip_info_get_name(info),
+				offset);
+
+			return EXIT_SUCCESS;
+		} else if (!ret && err) {

Besides redundant 'else' the !ret is also redundant.

Quoted 5 lines
bindings/glib/examples/find_line_by_name_glib.c new file +5 -0
+			g_printerr("Failed to map the line name '%s' to offset: %s\n",
+				   line_name, err->message);
+			return EXIT_FAILURE;
+		}
+	}

...

bindings/glib/examples/get_line_info_glib.c line 74

Quoted 2 lines
bindings/glib/examples/get_line_info_glib.c new file +2 -0
+		direction == G_GPIOD_LINE_DIRECTION_INPUT ?
+					"input" : "output",

One line?

...

bindings/glib/examples/get_line_value_glib.c line 37

Quoted 3 lines
bindings/glib/examples/get_line_value_glib.c new file +3 -0
+	settings = g_gpiod_line_settings_new(
+			"direction", G_GPIOD_LINE_DIRECTION_INPUT,
+			NULL);

Personally I do not like the open parenthesis style...

I don't even know why you have done this way with having a plenty of room in the previous line at least for the first parameter.

...

bindings/glib/examples/get_multiple_line_values_glib.c line 52

Quoted 2 lines
bindings/glib/examples/get_multiple_line_values_glib.c new file +2 -0
+	req_cfg = g_gpiod_request_config_new(
+			"consumer", "get-multiple-line-values", NULL);

Ditto. And so on across the code of the entire series...

...

bindings/glib/examples/toggle_line_value_glib.c line 28

Quoted 3 lines
bindings/glib/examples/toggle_line_value_glib.c new file +3 -0
+	ret = g_gpiod_line_request_set_value(data->request, data->line_offset,
+					     data->value, &err);
+	if (!ret) {

ret == 0 equals error?!

bindings/glib/examples/toggle_line_value_glib.c line 30

Quoted 2 lines
bindings/glib/examples/toggle_line_value_glib.c new file +2 -0
+		g_printerr("failed to set line value: %s\n", err->message);
+		exit(EXIT_FAILURE);

Don't you have something like err->code to propagate?

Quoted 1 line
(quoted diff) +1 -0
+	}
Signature · 2 lines
With Best Regards,
Andy Shevchenko
#10 lore raw

Re: [libgpiod][RFC/RFT 06/18] bindings: glib: add examples

Quoted 16 lines · Andy Shevchenko
Quoted 14 lines · Bartosz Golaszewski

From: Bartosz Golaszewski <[email protected]>

Add example programs showcasing the usage of GLib bindings to libgpiod.

bindings/glib/examples/find_line_by_name_glib.c new file +10 -0
+             ret = g_gpiod_chip_get_line_offset_from_name(chip, line_name,
+                                                          &offset, &err);
+             if (ret) {
+                     info = g_gpiod_chip_get_info(chip, &err);
+                     if (!info) {
+                             g_printerr("Failed to get chip info: %s\n",
+                                        err->message);
+                             return EXIT_FAILURE;
+                     }
+
Quoted 9 lines

bindings/glib/examples/find_line_by_name_glib.c line 62

Quoted 6 lines
bindings/glib/examples/find_line_by_name_glib.c new file +6 -0
+                     g_print("%s %u\n",
+                             g_gpiod_chip_info_get_name(info),
+                             offset);
+
+                     return EXIT_SUCCESS;
+             } else if (!ret && err) {

Besides redundant 'else' the !ret is also redundant.

Indeed but isn't it premature nitpicking in the grand scheme of things? :)

Quoted 24 lines
Quoted 5 lines
bindings/glib/examples/find_line_by_name_glib.c new file +5 -0
+                     g_printerr("Failed to map the line name '%s' to offset: %s\n",
+                                line_name, err->message);
+                     return EXIT_FAILURE;
+             }
+     }

...

bindings/glib/examples/get_line_info_glib.c line 74

Quoted 2 lines
bindings/glib/examples/get_line_info_glib.c new file +2 -0
+             direction == G_GPIOD_LINE_DIRECTION_INPUT ?
+                                     "input" : "output",

One line?

...

bindings/glib/examples/get_line_value_glib.c line 37

Quoted 3 lines
bindings/glib/examples/get_line_value_glib.c new file +3 -0
+     settings = g_gpiod_line_settings_new(
+                     "direction", G_GPIOD_LINE_DIRECTION_INPUT,
+                     NULL);

Personally I do not like the open parenthesis style...

I don't even know why you have done this way with having a plenty of room in the previous line at least for the first parameter.

Quoted 7 lines

...

bindings/glib/examples/get_multiple_line_values_glib.c line 52

Quoted 2 lines
bindings/glib/examples/get_multiple_line_values_glib.c new file +2 -0
+     req_cfg = g_gpiod_request_config_new(
+                     "consumer", "get-multiple-line-values", NULL);

Ditto. And so on across the code of the entire series...

Personal taste.

Quoted 8 lines

...

bindings/glib/examples/toggle_line_value_glib.c line 28

Quoted 3 lines
bindings/glib/examples/toggle_line_value_glib.c new file +3 -0
+     ret = g_gpiod_line_request_set_value(data->request, data->line_offset,
+                                          data->value, &err);
+     if (!ret) {

ret == 0 equals error?!

It returns gboolean where false means error. This is a pattern in GLib.

Quoted 5 lines

bindings/glib/examples/toggle_line_value_glib.c line 30

Quoted 2 lines
bindings/glib/examples/toggle_line_value_glib.c new file +2 -0
+             g_printerr("failed to set line value: %s\n", err->message);
+             exit(EXIT_FAILURE);

Don't you have something like err->code to propagate?

What for? err->message is the human-readable string of the error.

Bart

#11 lore raw

Re: [libgpiod][RFC/RFT 06/18] bindings: glib: add examples

Quoted 4 lines · Bartosz Golaszewski
Quoted 2 lines · Andy Shevchenko
Quoted 1 line · Bartosz Golaszewski

From: Bartosz Golaszewski <[email protected]>

...

Quoted 7 lines
Quoted 5 lines

bindings/glib/examples/toggle_line_value_glib.c line 28

Quoted 3 lines
bindings/glib/examples/toggle_line_value_glib.c new file +3 -0
+     ret = g_gpiod_line_request_set_value(data->request, data->line_offset,
+                                          data->value, &err);
+     if (!ret) {

ret == 0 equals error?!

It returns gboolean where false means error. This is a pattern in GLib.

For booleans we have a pattern like

	if (bool_func(...))

to imply it.

	ret = int_func(...);
	if (ret) // if (!ret)

kinda implies int. Does Glib has that type of pattern?

Quoted 6 lines
Quoted 4 lines

bindings/glib/examples/toggle_line_value_glib.c line 30

Quoted 2 lines
bindings/glib/examples/toggle_line_value_glib.c new file +2 -0
+             g_printerr("failed to set line value: %s\n", err->message);
+             exit(EXIT_FAILURE);

Don't you have something like err->code to propagate?

What for? err->message is the human-readable string of the error.

If somebody wants to parse this with a script?

Signature · 2 lines
With Best Regards,
Andy Shevchenko
#12 lore raw

Re: [libgpiod][RFC/RFT 06/18] bindings: glib: add examples

Quoted 28 lines · Andy Shevchenko
Quoted 4 lines · Bartosz Golaszewski
Quoted 2 lines · Andy Shevchenko
Quoted 1 line · Bartosz Golaszewski

From: Bartosz Golaszewski <[email protected]>

...

Quoted 7 lines
Quoted 5 lines

bindings/glib/examples/toggle_line_value_glib.c line 28

Quoted 3 lines
bindings/glib/examples/toggle_line_value_glib.c new file +3 -0
+     ret = g_gpiod_line_request_set_value(data->request, data->line_offset,
+                                          data->value, &err);
+     if (!ret) {

ret == 0 equals error?!

It returns gboolean where false means error. This is a pattern in GLib.

For booleans we have a pattern like

        if (bool_func(...))

to imply it.

        ret = int_func(...);
        if (ret) // if (!ret)

kinda implies int. Does Glib has that type of pattern?

Quoted 9 lines
Quoted 6 lines
Quoted 4 lines

bindings/glib/examples/toggle_line_value_glib.c line 30

Quoted 2 lines
bindings/glib/examples/toggle_line_value_glib.c new file +2 -0
+             g_printerr("failed to set line value: %s\n", err->message);
+             exit(EXIT_FAILURE);

Don't you have something like err->code to propagate?

What for? err->message is the human-readable string of the error.

If somebody wants to parse this with a script?

Andy: these are just examples. The user of the library is free to print the GError contents however they wish. The main point of interest in this huge series is the DBus API because it will have to be carved in stone, anything else is just implementation details.

I am always grateful for your thorough reviews but I don't want to bikeshed over example code at this point, let's use that energy where it's needed right now and revisit the implementation at a later time.

Bart

#13 lore raw

PATCH 7/18 bindings: glib: add tests

In reply to Bartosz Golaszewski

From: Bartosz Golaszewski <[email protected]>

Add a comprehensive set of test-cases for GLib bindings to libgpiod reusing the core test framework based around the gpio-sim module.

---
 bindings/glib/tests/helpers.c              |  12 +
 bindings/glib/tests/helpers.h              | 139 ++++
 bindings/glib/tests/tests-chip-info.c      |  55 ++
 bindings/glib/tests/tests-chip.c           | 183 ++++++
 bindings/glib/tests/tests-edge-event.c     | 226 +++++++
 bindings/glib/tests/tests-info-event.c     | 321 ++++++++++
 bindings/glib/tests/tests-line-config.c    | 186 ++++++
 bindings/glib/tests/tests-line-info.c      |  98 +++
 bindings/glib/tests/tests-line-request.c   | 704 +++++++++++++++++++++
 bindings/glib/tests/tests-line-settings.c  | 252 ++++++++
 bindings/glib/tests/tests-misc.c           |  88 +++
 bindings/glib/tests/tests-request-config.c |  58 ++
 12 files changed, 2322 insertions(+)
 create mode 100644 bindings/glib/tests/helpers.c
 create mode 100644 bindings/glib/tests/helpers.h
 create mode 100644 bindings/glib/tests/tests-chip-info.c
 create mode 100644 bindings/glib/tests/tests-chip.c
 create mode 100644 bindings/glib/tests/tests-edge-event.c
 create mode 100644 bindings/glib/tests/tests-info-event.c
 create mode 100644 bindings/glib/tests/tests-line-config.c
 create mode 100644 bindings/glib/tests/tests-line-info.c
 create mode 100644 bindings/glib/tests/tests-line-request.c
 create mode 100644 bindings/glib/tests/tests-line-settings.c
 create mode 100644 bindings/glib/tests/tests-misc.c
 create mode 100644 bindings/glib/tests/tests-request-config.c
bindings/glib/tests/helpers.c new file +12 -0
new file mode 100644
index 0000000..d77f2b8
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2023 Bartosz Golaszewski <[email protected]>
+
+#include "helpers.h"
+
+GArray *gpiod_glib_test_array_from_const(gconstpointer data, gsize len,
+					 gsize elem_size)
+{
+	GArray *arr = g_array_new(FALSE, TRUE, elem_size);
+
+	return g_array_append_vals(arr, data, len);
+}
bindings/glib/tests/helpers.h new file +139 -0

139 lines not on this page · raw message

bindings/glib/tests/tests-chip-info.c new file +55 -0

55 lines not on this page · raw message

bindings/glib/tests/tests-chip.c new file +183 -0

183 lines not on this page · raw message

bindings/glib/tests/tests-edge-event.c new file +226 -0

226 lines not on this page · raw message

bindings/glib/tests/tests-info-event.c new file +321 -0

321 lines not on this page · raw message

bindings/glib/tests/tests-line-config.c new file +186 -0

186 lines not on this page · raw message

bindings/glib/tests/tests-line-info.c new file +98 -0

98 lines not on this page · raw message

bindings/glib/tests/tests-line-request.c new file +704 -0

704 lines not on this page · raw message

bindings/glib/tests/tests-line-settings.c new file +252 -0

252 lines not on this page · raw message

bindings/glib/tests/tests-misc.c new file +88 -0

88 lines not on this page · raw message

bindings/glib/tests/tests-request-config.c new file +58 -0

58 lines not on this page · raw message

Signature · 1 line
2.40.1
#14 lore raw

PATCH 8/18 README: document GLib bindings

In reply to Bartosz Golaszewski

From: Bartosz Golaszewski <[email protected]>

Add information on the requirements of and the configure build switch for GLib bindings to libgpiod.

---
 README | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)
README +6 -3
index ec60ea1..8e1e1c7 100644
@@ -206,13 +206,16 @@ Examples:
 BINDINGS
 --------
 
-High-level, object-oriented bindings for C++, python3 and Rust are provided.
-They can be enabled by passing --enable-bindings-cxx, --enable-bindings-python
-and --enable-bindings-rust arguments respectively to configure.
+High-level, object-oriented bindings for C++, GLib, python3 and Rust are
+provided. They can be enabled by passing --enable-bindings-cxx,
+--enable-bindings-glib, --enable-bindings-python and --enable-bindings-rust
+arguments respectively to configure.
 
 C++ bindings require C++11 support and autoconf-archive collection if building
 from git.
 
+GLib bindings requires GLib (as well as GObject, GIO and GIO-Unix) v2.54.
+
 Python bindings require python3 support and libpython development files. Please
 refer to bindings/python/README.md for more information.
 
Signature · 1 line
2.40.1
#15 lore raw

PATCH 9/18 dbus: add build files

In reply to Bartosz Golaszewski

From: Bartosz Golaszewski <[email protected]>

Add the directory structure and build files as well as changes to .gitignore for the DBus API.

---
 Makefile.am              |  7 +++++++
 configure.ac             | 45 ++++++++++++++++++++++++++++++++++++++++
 dbus/Makefile.am         | 10 +++++++++
 dbus/client/.gitignore   |  4 ++++
 dbus/client/Makefile.am  | 30 +++++++++++++++++++++++++++
 dbus/data/Makefile.am    | 13 ++++++++++++
 dbus/lib/Makefile.am     | 28 +++++++++++++++++++++++++
 dbus/manager/.gitignore  |  4 ++++
 dbus/manager/Makefile.am | 20 ++++++++++++++++++
 dbus/tests/.gitignore    |  4 ++++
 dbus/tests/Makefile.am   | 25 ++++++++++++++++++++++
 11 files changed, 190 insertions(+)
 create mode 100644 dbus/Makefile.am
 create mode 100644 dbus/client/.gitignore
 create mode 100644 dbus/client/Makefile.am
 create mode 100644 dbus/data/Makefile.am
 create mode 100644 dbus/lib/Makefile.am
 create mode 100644 dbus/manager/.gitignore
 create mode 100644 dbus/manager/Makefile.am
 create mode 100644 dbus/tests/.gitignore
 create mode 100644 dbus/tests/Makefile.am
Makefile.am +7 -0
index 941d7e8..cdb900a 100644
@@ -1,5 +1,6 @@
 # SPDX-License-Identifier: GPL-2.0-or-later
 # SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <[email protected]>
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]>
 
 ACLOCAL_AMFLAGS = -I m4
 AUTOMAKE_OPTIONS = foreign
@@ -38,6 +39,12 @@ endif
 # libgpiosim to be already present.
 SUBDIRS += bindings
 
+if WITH_DBUS
+
+SUBDIRS += dbus
+
+endif
+
 if HAS_DOXYGEN
 
 doc: Doxyfile
configure.ac +45 -0

57 lines not on this page · raw message

dbus/Makefile.am new file +10 -0
new file mode 100644
index 0000000..7868a96
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022-2023 Bartosz Golaszewski <[email protected]>
+
+SUBDIRS = data lib manager client
+
+if WITH_TESTS
+
+SUBDIRS += tests
+
+endif
dbus/client/.gitignore new file +4 -0
new file mode 100644
index 0000000..08ec6c8
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: CC0-1.0
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]>
+
+gpiocli
dbus/client/Makefile.am new file +30 -0
new file mode 100644
index 0000000..1bef1bf
@@ -0,0 +1,30 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022-2023 Bartosz Golaszewski <[email protected]>
+
+AM_CFLAGS = -include $(top_builddir)/config.h -I$(top_srcdir)/dbus/lib/
+AM_CFLAGS += -Wall -Wextra -g
+AM_CFLAGS += $(GLIB_CFLAGS) $(GIO_CFLAGS) $(GIO_UNIX_CFLAGS)
+AM_CFLAGS += -DG_LOG_DOMAIN=\"gpiocli\"
+AM_LDFLAGS = $(GLIB_LIBS) $(GIO_LIBS) $(GIO_UNIX_LIBS)
+LDADD = $(top_builddir)/dbus/lib/libgpiodbus.la
+
+bin_PROGRAMS = gpiocli
+
+gpiocli_SOURCES = \
+	common.c \
+	common.h \
+	detect.c \
+	find.c \
+	get.c \
+	gpiocli.c \
+	info.c \
+	monitor.c \
+	notify.c \
+	reconfigure.c \
+	release.c \
+	request.c \
+	requests.c \
+	set.c \
+	wait.c
+
+noinst_SCRIPTS = gpiocli-test.bash
dbus/data/Makefile.am new file +13 -0
new file mode 100644
index 0000000..3d5c2e1
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022-2023 Bartosz Golaszewski <[email protected]>
+
+EXTRA_DIST = gpio-manager.service
+
+dbusdir = $(sysconfdir)/dbus-1/system.d/
+dbus_DATA = io.gpiod1.conf
+
+if WITH_SYSTEMD
+
+systemdsystemunit_DATA = gpio-manager.service
+
+endif
dbus/lib/Makefile.am new file +28 -0
new file mode 100644
index 0000000..c7cbc13
@@ -0,0 +1,28 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022-2023 Bartosz Golaszewski <[email protected]>
+
+libgpiodbus_la_CFLAGS = -include $(top_builddir)/config.h -Wall -Wextra -g
+libgpiodbus_la_CFLAGS += $(GLIB_CFLAGS) $(GIO_CFLAGS)
+libgpiodbus_la_CFLAGS += -DG_LOG_DOMAIN=\"gpiodbus\"
+libgpiodbus_la_LDFLAGS = -version-info 1
+
+generated-gpiodbus.h generated-gpiodbus.c: io.gpiod1.xml
+	$(AM_V_GEN)gdbus-codegen \
+		--interface-prefix io.gpiod1 \
+		--c-namespace GPIODBus \
+		--generate-c-code generated-gpiodbus \
+		--c-generate-object-manager \
+		--c-generate-autocleanup=all \
+		$(srcdir)/io.gpiod1.xml
+
+lib_LTLIBRARIES = libgpiodbus.la
+include_HEADERS = \
+	generated-gpiodbus.h \
+	gpiodbus.h
+libgpiodbus_la_SOURCES = generated-gpiodbus.c
+
+BUILT_SOURCES = generated-gpiodbus.c generated-gpiodbus.h
+CLEANFILES = $(BUILT_SOURCES)
+
+dbusdir = $(datadir)/dbus-1/interfaces
+dbus_DATA = io.gpiod1.xml
dbus/manager/.gitignore new file +4 -0
new file mode 100644
index 0000000..5507c6d
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: CC0-1.0
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]>
+
+gpio-manager
dbus/manager/Makefile.am new file +20 -0
new file mode 100644
index 0000000..dbf8599
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022-2023 Bartosz Golaszewski <[email protected]>
+
+AM_CFLAGS = -I$(top_srcdir)/bindings/glib/ -include $(top_builddir)/config.h
+AM_CFLAGS += -Wall -Wextra -g -I$(top_srcdir)/dbus/lib/
+AM_CFLAGS += $(GLIB_CFLAGS) $(GIO_CFLAGS) $(GIO_UNIX_CFLAGS) $(GUDEV_CFLAGS)
+AM_CFLAGS += -DG_LOG_DOMAIN=\"gpio-manager\"
+AM_CFLAGS += $(PROFILING_CFLAGS)
+AM_LDFLAGS = $(GLIB_LIBS) $(GIO_LIBS) $(GIO_UNIX_LIBS) $(GUDEV_LIBS)
+AM_LDFLAGS += $(PROFILING_LDFLAGS)
+LDADD = $(top_builddir)/bindings/glib/libgpiod-glib.la
+LDADD += $(top_builddir)/dbus/lib/libgpiodbus.la
+
+bin_PROGRAMS = gpio-manager
+gpio_manager_SOURCES = \
+	daemon.c \
+	daemon.h \
+	helpers.c \
+	helpers.h \
+	gpio-manager.c
dbus/tests/.gitignore new file +4 -0
new file mode 100644
index 0000000..19f64af
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: CC0-1.0
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]>
+
+gpiodbus-test
dbus/tests/Makefile.am new file +25 -0
new file mode 100644
index 0000000..ef9a7b7
@@ -0,0 +1,25 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022-2023 Bartosz Golaszewski <[email protected]>
+
+noinst_PROGRAMS = gpiodbus-test
+gpiodbus_test_SOURCES = \
+	daemon-process.c \
+	daemon-process.h \
+	helpers.c \
+	helpers.h \
+	tests-chip.c \
+	tests-line.c \
+	tests-request.c
+
+AM_CFLAGS = -I$(top_srcdir)/tests/gpiosim-glib/
+AM_CFLAGS += -I$(top_srcdir)/dbus/lib/
+AM_CFLAGS += -I$(top_srcdir)/tests/harness/
+AM_CFLAGS += -include $(top_builddir)/config.h
+AM_CFLAGS += -Wall -Wextra -g -std=gnu89
+AM_CFLAGS += $(GLIB_CFLAGS) $(GIO_CFLAGS)
+AM_CFLAGS += -DG_LOG_DOMAIN=\"gpiodbus-test\"
+LDADD = $(top_builddir)/tests/gpiosim/libgpiosim.la
+LDADD += $(top_builddir)/tests/gpiosim-glib/libgpiosim-glib.la
+LDADD += $(top_builddir)/tests/harness/libgpiod-test-harness.la
+LDADD += $(top_builddir)/dbus/lib/libgpiodbus.la
+LDADD += $(GLIB_LIBS) $(GIO_LIBS)
Signature · 1 line
2.40.1
#16 lore raw

PATCH 10/18 dbus: add the API definitions

In reply to Bartosz Golaszewski

From: Bartosz Golaszewski <[email protected]>

Add the XML file defining all the interfaces for the io.gpiod1 service.

---
 dbus/lib/io.gpiod1.xml | 318 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 318 insertions(+)
 create mode 100644 dbus/lib/io.gpiod1.xml
dbus/lib/io.gpiod1.xml new file +318 -0
new file mode 100644
index 0000000..ca95302
@@ -0,0 +1,318 @@
+<!-- SPDX-License-Identifier: CC-BY-SA-4.0 -->
+<!-- SPDX-FileCopyrightText: 2022-2024 Bartosz Golaszewski <[email protected]> -->
+
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+
+<node>
+
+  <!--
+    io.gpiod1.Chip:
+    @short_description: Represents a single GPIO chip in the system.
+  -->
+  <interface name='io.gpiod1.Chip'>
+
+    <!--
+      Name:
+
+      Name of the chip as represented in the kernel.
+    -->
+    <property name='Name' type='s' access='read'/>
+
+    <!--
+      Label:
+
+      Label of the chip as represented in the kernel.
+    -->
+    <property name='Label' type='s' access='read'/>
+
+    <!--
+      NumLines:
+
+      Number of GPIO lines exposed by this chip.
+    -->
+    <property name='NumLines' type='u' access='read'/>
+
+    <!--
+      Path:
+
+      Filesystem path used to open this chip.
+    -->
+    <property name='Path' type='s' access='read'/>
+
+    <!--
+      RequestLines:
+      @line_config: Line configuration. See below for details.
+      @request_config: Request configuration. See below for details.
+      @request_path: Object path pointing to the newly added request.
+
+      Requests a set of lines and makes it possible for the users of this API
+      to manipulate them depending on the line configuration.
+
+      Line configuration is a tuple of two arrays. The first one contains
+      mappings of arrays of line offsets to sets of line settings. The second
+      contains the list of default output values which are only used in output
+      mode.
+
+      Available line config options:
+
+        "direction" => String representing the line direction. Accepts the
+                       following values: "input", "output".
+        "edge" => String representing the edge detection setting. Accepts the
+                  following values: "falling", "rising", "both".
+        "active-low" => Boolean representing the active-low setting.
+        "drive" => String representing the drive settings. Accepts the
+                   following values: "push-pull", "open-drain", "open-source".
+        "bias" => String representing the internal bias settings. Accepts the
+                  following values: "disabled", "pull-up", "pull-down", "as-is".
+        "debounce-period" => Debounce period in milliseconds represented as a
+                             signed, 64-bit integer.
+        "event-clock" => String representing the clock used to timestamp edge
+                         events. Accepts the following values: "monotonic",
+                         "realtime", "hte".
+
+      Output values are applied to the lines in the order they appear in the
+      settings mappings.
+
+      Example variant that allows to request lines at offsets 1, 5 and 11 in
+      output, push-pull and active-low modes and specifies the output values
+      as active (as visualized with g_variant_print()):
+
+        // Line config tuple
+        (
+          // Array of line settings mappings
+          [
+            // Single mapping tuple
+            (
+              // Offsets to map
+              [1, 5, 11],
+              // Line settings dict
+              {
+                'direction': <'output'>,
+                'drive': <'push-pull'>,
+                'active-low': <true>
+              }
+            )
+          ],
+          // Output values
+          [1, 1, 1]
+        )
+
+      Request configuration is a hashmap mapping names of the available config
+      options to their values wrapped in a variant.
+
+      Available request config options:
+
+        "consumer" => Consumer name as a string
+        "event-buffer-size" => Requested size of the in-kernel edge event
+                               buffer as an unsigned 32-bit integer.
+
+      The object path to the new request is returned on success. The user
+      should wait for it to appear before trying to use the requested lines in
+      any way.
+    -->
+    <method name='RequestLines'>
+      <arg name='line_config' direction='in' type='(a(aua{sv})ai)'/>
+      <arg name='request_config' direction='in' type='a{sv}'/>
+      <arg name='request_path' direction='out' type='o'/>
+    </method>
+
+  </interface>
+
+  <!--
+    io.gpiod1.Line:
+    @short_description: Represents a single GPIO line on a chip.
+  -->
+  <interface name='io.gpiod1.Line'>
+
+    <!--
+      Offset:
+
+      Uniquely identifies the line on the chip.
+    -->
+    <property name='Offset' type='u' access='read'/>
+
+    <!--
+      Name:
+
+      Name of the GPIO line as represented in the kernel.
+    -->
+    <property name='Name' type='s' access='read'/>
+
+    <!--
+      Used:
+
+      True if line is busy.
+
+      Line can be used by gpio-manager, another user-space process, a kernel
+      driver or is hogged. The exact reason a line is busy cannot be determined
+      from user-space unless it's known to be managed by gpio-manager (see:
+      the Managed property of this interface).
+    -->
+    <property name='Used' type='b' access='read'/>
+
+    <!--
+      Consumer:
+
+      Name of the consumer of the line.
+    -->
+    <property name='Consumer' type='s' access='read'/>
+
+    <!--
+      Direction:
+
+      Direction of the line. Returns "input" or "output".
+    -->
+    <property name='Direction' type='s' access='read'/>
+
+    <!--
+      EdgeDetection:
+
+      Edge detection settings of the line. Returns: "none", "falling",
+      "rising" or "both".
+    -->
+    <property name='EdgeDetection' type='s' access='read'/>
+
+    <!--
+      Bias:
+
+      Bias setting of the line. Returns: "unknown", "disabled, "pull-up" or
+      "pull-down".
+    -->
+    <property name='Bias' type='s' access='read'/>
+
+    <!--
+      Drive:
+
+      Drive setting of the line. Returns "push-pull", "open-source" or
+      "open-drain".
+    -->
+    <property name='Drive' type='s' access='read'/>
+
+    <!--
+      ActiveLow:
+
+      True if the line is active-low. False for active-high.
+    -->
+    <property name='ActiveLow' type='b' access='read'/>
+
+    <!--
+      Debounced:
+
+      True if line is being debounced on interrupts. Can only be true with
+      edge-detection enabled.
+    -->
+    <property name='Debounced' type='b' access='read'/>
+
+    <!--
+      DebouncePeriodUs:
+
+      Debounce period in milliseconds. 0 if the line is not debounced. Can
+      only be non-zero with edge-detection enabled.
+    -->
+    <property name='DebouncePeriodUs' type='t' access='read'/>
+
+    <!--
+      EventClock:
+
+      System clock used to timestamp edge events on this line. Returns:
+      "monotonic", "realtime" or "hte".
+    -->
+    <property name='EventClock' type='s' access='read'/>
+
+    <!--
+      Managed:
+
+      True if the line is managed by gpio-manager.
+    -->
+    <property name='Managed' type='b' access='read'/>
+
+    <!--
+      RequestPath:
+
+      If this line is managed by gpio-manager then this property will contain
+      the DBus object path pointing to the managing request object.
+    -->
+    <property name='RequestPath' type='o' access='read'/>
+
+    <!--
+      EdgeEvent:
+      @event_data: Contains the edge (1 for rising, 0 for falling), timestamp
+                   in milliseconds and the global & line-local sequence numbers.
+
+      If the line is managed by the gpio-manager and is requested with edge
+      detection enabled then this signal will be emitted for every edge event
+      registered on this line.
+    -->
+    <signal name='EdgeEvent'>
+      <arg name='event_data' type='(ittt)'/>
+    </signal>
+
+  </interface>
+
+  <!--
+    io.gpiod1.Request:
+    @short_description: Represents a set of requested GPIO lines.
+  -->
+  <interface name='io.gpiod1.Request'>
+
+    <!--
+      ChipPath:
+
+      DBus object path pointing to the chip exposing the lines held by this
+      request.
+    -->
+    <property name='ChipPath' type='o' access='read'/>
+
+    <!--
+      LinePaths:
+
+      Array of DBus object paths pointing to the lines held by this request.
+    -->
+    <property name='LinePaths' type='ao' access='read'/>
+
+    <!--
+      Release:
+
+      Release the requested lines. After this method returns successfully, the
+      request object on which it was called will be destroyed.
+    -->
+    <method name='Release'/>
+
+    <!--
+      ReconfigureLines:
+      @line_config: Line configuration. Refer to the RequestLines method of
+                    the io.gpiod1.Chip interface for details.
+
+      Change the configuration of lines held by this request object without
+      releasing them.
+    -->
+    <method name='ReconfigureLines'>
+      <arg name='line_config' direction='in' type='(a(aua{sv})ai)'/>
+    </method>
+
+    <!--
+      GetValues:
+      @offsets: Array of line offsets within the request to read values for.
+      @values: Array of values in the order lines were specified in @offsets.
+
+      Read the values for a set of lines held by the request.
+    -->
+    <method name='GetValues'>
+      <arg name='offsets' direction='in' type='au'/>
+      <arg name='values' direction='out' type='ai'/>
+    </method>
+
+    <!--
+      SetValues:
+      @values: Array of mappings from line offsets to desired output values.
+
+      Set the values for a set of lines held by the request.
+    -->
+    <method name='SetValues'>
+      <arg name='values' direction='in' type='a{ui}'/>
+    </method>
+
+  </interface>
+
+</node>
Signature · 1 line
2.40.1
#17 lore raw

PATCH 11/18 dbus: add a wrapper around the gdbus-codegen generated header

In reply to Bartosz Golaszewski

From: Bartosz Golaszewski <[email protected]>

Add a header that will be included by all the code using the automatically generated GLib layer for the DBus interface.

---
 dbus/lib/gpiodbus.h | 9 +++++++++
 1 file changed, 9 insertions(+)
 create mode 100644 dbus/lib/gpiodbus.h
dbus/lib/gpiodbus.h new file +9 -0
new file mode 100644
index 0000000..69362f0
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* SPDX-FileCopyrightText: 2023 Bartosz Golaszewski <[email protected]> */
+
+#ifndef __GPIODBUS_H__
+#define __GPIODBUS_H__
+
+#include "generated-gpiodbus.h"
+
+#endif /* __GPIODBUS_H__ */
Signature · 1 line
2.40.1
#18 lore raw

PATCH 12/18 dbus: add data files

In reply to Bartosz Golaszewski

From: Bartosz Golaszewski <[email protected]>

Add the default service configuration file for the DBus GPIO API and a systemd unit file that allows to start up the gpio-manager.

---
 dbus/data/gpio-manager.service | 14 ++++++++++++++
 dbus/data/io.gpiod1.conf       | 29 +++++++++++++++++++++++++++++
 2 files changed, 43 insertions(+)
 create mode 100644 dbus/data/gpio-manager.service
 create mode 100644 dbus/data/io.gpiod1.conf
dbus/data/gpio-manager.service new file +14 -0
new file mode 100644
index 0000000..f84f8cc
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: CC0-1.0
+# SPDX-FileCopyrightText: 2023 Bartosz Golaszewski <[email protected]>
+
+[Unit]
+Description=Centralized GPIO manager daemon
+
+[Service]
+Type=dbus
+BusName=io.gpiod1
+ExecStart=/usr/bin/gpio-manager
+Restart=always
+
+[Install]
+WantedBy=multi-user.target
dbus/data/io.gpiod1.conf new file +29 -0
new file mode 100644
index 0000000..339a382
@@ -0,0 +1,29 @@
+<!-- SPDX-License-Identifier: CC-BY-SA-4.0.txt -->
+<!-- SPDX-FileCopyrightText: 2022-2024 Bartosz Golaszewski <[email protected]> -->
+
+<!-- This configuration file specifies the required security policies
+     for the gpio-dbus daemon to work. -->
+
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+
+<busconfig>
+
+  <policy context="default">
+    <allow send_destination="io.gpiod1"
+           send_interface="org.freedesktop.DBus.Peer"
+           send_member="Ping"/>
+    <allow send_destination="io.gpiod1"
+           send_interface="org.freedesktop.DBus.Introspectable"/>
+    <allow send_destination="io.gpiod1"
+           send_interface="org.freedesktop.DBus.Properties"/>
+    <allow send_destination="io.gpiod1"
+           send_interface="org.freedesktop.DBus.ObjectManager"/>
+  </policy>
+
+  <policy user="root">
+    <allow own="io.gpiod1"/>
+    <allow send_destination="io.gpiod1"/>
+  </policy>
+
+</busconfig>
Signature · 1 line
2.40.1
#19 lore raw

PATCH 13/18 dbus: add gpio-manager code

In reply to Bartosz Golaszewski

From: Bartosz Golaszewski <[email protected]>

Add the code for the gpio-manager - the actual implementation of the DBus API defined in io.gpiod1.xml.

---
 dbus/manager/daemon.c       | 821 ++++++++++++++++++++++++++++++++++++
 dbus/manager/daemon.h       |  22 +
 dbus/manager/gpio-manager.c | 167 ++++++++
 dbus/manager/helpers.c      | 420 ++++++++++++++++++
 dbus/manager/helpers.h      |  24 ++
 5 files changed, 1454 insertions(+)
 create mode 100644 dbus/manager/daemon.c
 create mode 100644 dbus/manager/daemon.h
 create mode 100644 dbus/manager/gpio-manager.c
 create mode 100644 dbus/manager/helpers.c
 create mode 100644 dbus/manager/helpers.h
dbus/manager/daemon.c new file +821 -0
new file mode 100644
index 0000000..59bf1ee
@@ -0,0 +1,821 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2022-2024 Bartosz Golaszewski <[email protected]>
+
+#include <gpiod-glib.h>
+#include <gpiodbus.h>
+#include <gudev/gudev.h>
+
+#include "daemon.h"
+#include "helpers.h"
+
+struct _GPIODBusDaemon {
+	GObject parent;
+	GDBusConnection *con;
+	GUdevClient *udev;
+	GDBusObjectManagerServer *chip_manager;
+	GDBusObjectManagerServer *request_manager;
+	GHashTable *chips;
+	GHashTable *requests;
+	GTree *req_id_root;
+};
+
+G_DEFINE_TYPE(GPIODBusDaemon, gpiodbus_daemon, G_TYPE_OBJECT);
+
+typedef struct {
+	GPIODChip *chip;
+	GPIODBusChip *dbus_chip;
+	GPIODBusDaemon *daemon;
+	GDBusObjectManagerServer *line_manager;
+	GHashTable *lines;
+} GPIODBusDaemonChipData;
+
+typedef struct {
+	GPIODLineRequest *request;
+	GPIODBusRequest *dbus_request;
+	gint id;
+	GPIODBusDaemonChipData *chip_data;
+} GPIODBusDaemonRequestData;
+
+typedef struct {
+	GPIODBusLine *dbus_line;
+	GPIODBusDaemonChipData *chip_data;
+	GPIODBusDaemonRequestData *req_data;
+} GPIODBusDaemonLineData;
+
+static const gchar* const gpiodbus_daemon_udev_subsystems[] = { "gpio", NULL };
+
+static void gpiodbus_daemon_dispose(GObject *obj)
+{
+	GPIODBusDaemon *self = GPIODBUS_DAEMON(obj);
+
+	g_debug("disposing of the GPIO daemon");
+
+	g_clear_pointer(&self->chips, g_hash_table_unref);
+	/*
+	 * REVISIT: Do we even need to unref the request hash table here at
+	 * all? All requests should have been freed when removing their parent
+	 * chips.
+	 */
+	g_clear_pointer(&self->requests, g_hash_table_unref);
+	g_clear_pointer(&self->req_id_root, g_tree_destroy);
+	g_clear_object(&self->con);
+
+	G_OBJECT_CLASS(gpiodbus_daemon_parent_class)->dispose(obj);
+}
+
+static void gpiodbus_daemon_finalize(GObject *obj)
+{
+	GPIODBusDaemon *self = GPIODBUS_DAEMON(obj);
+
+	g_debug("finalizing GPIO daemon");
+
+	g_clear_object(&self->request_manager);
+	g_clear_object(&self->chip_manager);
+	g_clear_object(&self->udev);
+
+	G_OBJECT_CLASS(gpiodbus_daemon_parent_class)->finalize(obj);
+}
+
+static void gpiodbus_daemon_class_init(GPIODBusDaemonClass *daemon_class)
+{
+	GObjectClass *class = G_OBJECT_CLASS(daemon_class);
+
+	class->dispose = gpiodbus_daemon_dispose;
+	class->finalize = gpiodbus_daemon_finalize;
+}
+
+static gboolean
+gpiodbus_remove_request_if_chip_matches(gpointer key G_GNUC_UNUSED,
+					gpointer value, gpointer user_data)
+{
+	GPIODBusDaemonChipData *chip_data = user_data;
+	GPIODBusDaemonRequestData *req_data = value;
+
+	return req_data->chip_data == chip_data;
+}
+
+static void gpiodbus_daemon_chip_data_free(gpointer data)
+{
+	GPIODBusDaemonChipData *chip_data = data;
+	const gchar *obj_path;
+
+	obj_path = g_dbus_interface_skeleton_get_object_path(
+			G_DBUS_INTERFACE_SKELETON(chip_data->dbus_chip));
+
+	g_debug("unexporting object for GPIO chip: '%s'", obj_path);
+
+	g_hash_table_foreach_remove(chip_data->daemon->requests,
+				    gpiodbus_remove_request_if_chip_matches,
+				    chip_data);
+
+	g_dbus_object_manager_server_unexport(chip_data->daemon->chip_manager,
+					      obj_path);
+
+	g_hash_table_unref(chip_data->lines);
+	g_object_unref(chip_data->line_manager);
+	g_object_unref(chip_data->chip);
+	g_object_unref(chip_data->dbus_chip);
+	g_free(chip_data);
+}
+
+static void gpiodbus_daemon_line_data_free(gpointer data)
+{
+	GPIODBusDaemonLineData *line_data = data;
+	const gchar *obj_path;
+
+	obj_path = g_dbus_interface_skeleton_get_object_path(
+			G_DBUS_INTERFACE_SKELETON(line_data->dbus_line));
+
+	g_debug("unexporting object for GPIO line: '%s'",
+		obj_path);
+
+	g_dbus_object_manager_server_unexport(
+				line_data->chip_data->line_manager, obj_path);
+
+	g_object_unref(line_data->dbus_line);
+	g_free(line_data);
+}
+
+static void gpiodbus_lines_set_managed(GPIODBusDaemonRequestData *req_data,
+				       gboolean managed)
+{
+	g_autoptr(GDBusObject) obj = NULL;
+	const gchar *const *line_paths;
+	GPIODBusLine *line;
+	const gchar *path;
+	guint i;
+
+	line_paths = gpiodbus_request_get_line_paths(req_data->dbus_request);
+
+	for (path = line_paths[0], i = 0; path; path = line_paths[++i]) {
+		obj = g_dbus_object_manager_get_object(
+			G_DBUS_OBJECT_MANAGER(
+				req_data->chip_data->line_manager), path);
+		line = gpiodbus_object_peek_line(GPIODBUS_OBJECT(obj));
+
+		g_debug("Setting line %u on chip object '%s' to '%s'",
+			gpiodbus_line_get_offset(line),
+			g_dbus_interface_skeleton_get_object_path(
+				G_DBUS_INTERFACE_SKELETON(
+					req_data->chip_data->dbus_chip)),
+			managed ? "managed" : "unmanaged");
+
+		gpiodbus_line_set_managed(line, managed);
+		gpiodbus_line_set_request_path(line,
+			managed ? g_dbus_interface_skeleton_get_object_path(
+				G_DBUS_INTERFACE_SKELETON(
+					req_data->dbus_request)) : NULL);
+		g_dbus_interface_skeleton_flush(
+					G_DBUS_INTERFACE_SKELETON(line));
+	}
+}
+
+static void gpiodbus_daemon_request_data_free(gpointer data)
+{
+	GPIODBusDaemonRequestData *req_data = data;
+	const gchar *obj_path;
+
+	obj_path = g_dbus_interface_skeleton_get_object_path(
+			G_DBUS_INTERFACE_SKELETON(req_data->dbus_request));
+
+	g_debug("unexporting object for GPIO request: '%s'", obj_path);
+
+	g_dbus_object_manager_server_unexport(
+		req_data->chip_data->daemon->request_manager, obj_path);
+
+	gpiodbus_lines_set_managed(req_data, FALSE);
+	gpiodbus_id_free(req_data->chip_data->daemon->req_id_root,
+			 req_data->id);
+	g_object_unref(req_data->request);
+	g_object_unref(req_data->dbus_request);
+	g_free(req_data);
+}
+
+static void gpiodbus_daemon_init(GPIODBusDaemon *self)
+{
+	g_debug("initializing GPIO DBus daemon");
+
+	self->con = NULL;
+	self->udev = g_udev_client_new(gpiodbus_daemon_udev_subsystems);
+	self->chip_manager =
+			g_dbus_object_manager_server_new("/io/gpiod1/chips");
+	self->request_manager =
+			g_dbus_object_manager_server_new("/io/gpiod1/requests");
+	self->chips = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
+					    gpiodbus_daemon_chip_data_free);
+	self->requests = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
+					gpiodbus_daemon_request_data_free);
+	self->req_id_root = g_tree_new_full(gpiodbus_id_cmp, NULL,
+					    g_free, NULL);
+}
+
+GPIODBusDaemon *gpiodbus_daemon_new(void)
+{
+	return GPIODBUS_DAEMON(g_object_new(GPIODBUS_DAEMON_TYPE, NULL));
+}
+
+static void gpiodbus_daemon_on_info_event(GPIODChip *chip G_GNUC_UNUSED,
+					  GPIODInfoEvent *event,
+					  gpointer data)
+{
+	GPIODBusDaemonChipData *chip_data = data;
+	g_autoptr(GPIODLineInfo) info = NULL;
+	GPIODBusDaemonLineData *line_data;
+	guint offset;
+
+	info = g_gpiod_info_event_get_line_info(event);
+	offset = g_gpiod_line_info_get_offset(info);
+
+	g_debug("line info event received for offset %u on chip '%s'",
+		offset,
+		g_dbus_interface_skeleton_get_object_path(
+			G_DBUS_INTERFACE_SKELETON(chip_data->dbus_chip)));
+
+	line_data = g_hash_table_lookup(chip_data->lines,
+					GINT_TO_POINTER(offset));
+	if (!line_data)
+		g_error("failed to retrieve line data - programming bug?");
+
+	gpiodbus_line_set_props(line_data->dbus_line, info);
+}
+
+static void gpiodbus_daemon_export_line(GPIODBusDaemon *self,
+					GPIODBusDaemonChipData *chip_data,
+					GPIODLineInfo *info)
+{
+	g_autofree GPIODBusDaemonLineData *line_data = NULL;
+	g_autoptr(GPIODBusObjectSkeleton) skeleton = NULL;
+	g_autoptr(GPIODBusLine) dbus_line = NULL;
+	g_autofree gchar *obj_path = NULL;
+	const gchar *obj_prefix;
+	guint line_offset;
+	gboolean ret;
+
+	obj_prefix = g_dbus_object_manager_get_object_path(
+				G_DBUS_OBJECT_MANAGER(chip_data->line_manager));
+	line_offset = g_gpiod_line_info_get_offset(info);
+	dbus_line = gpiodbus_line_skeleton_new();
+	obj_path = g_strdup_printf("%s/line%u", obj_prefix, line_offset);
+
+	gpiodbus_line_set_props(dbus_line, info);
+
+	skeleton = gpiodbus_object_skeleton_new(obj_path);
+	gpiodbus_object_skeleton_set_line(skeleton, GPIODBUS_LINE(dbus_line));
+
+	g_debug("exporting object for GPIO line: '%s'", obj_path);
+
+	g_dbus_object_manager_server_export(chip_data->line_manager,
+					    G_DBUS_OBJECT_SKELETON(skeleton));
+	g_dbus_object_manager_server_set_connection(chip_data->line_manager,
+						    self->con);
+
+	line_data = g_malloc0(sizeof(*line_data));
+	line_data->dbus_line = g_steal_pointer(&dbus_line);
+	line_data->chip_data = chip_data;
+
+	ret = g_hash_table_insert(chip_data->lines,
+				  GUINT_TO_POINTER(line_offset),
+				  g_steal_pointer(&line_data));
+	/* It's a programming bug if the line is already in the hashmap. */
+	g_assert(ret);
+}
+
+static gboolean gpiodbus_daemon_export_lines(GPIODBusDaemon *self,
+					     GPIODBusDaemonChipData *chip_data)
+{
+	g_autoptr(GPIODChipInfo) chip_info = NULL;
+	GPIODChip *chip = chip_data->chip;
+	g_autoptr(GError) err = NULL;
+	guint i, num_lines;
+	gint j;
+
+	chip_info = g_gpiod_chip_get_info(chip, &err);
+	if (!chip_info) {
+		g_critical("failed to read chip info: %s", err->message);
+		return FALSE;
+	}
+
+	num_lines = g_gpiod_chip_info_get_num_lines(chip_info);
+
+	g_signal_connect(chip, "info-event",
+			 G_CALLBACK(gpiodbus_daemon_on_info_event), chip_data);
+
+	for (i = 0; i < num_lines; i++) {
+		g_autoptr(GPIODLineInfo) linfo = NULL;
+
+		linfo = g_gpiod_chip_watch_line_info(chip, i, &err);
+		if (!linfo) {
+			g_critical("failed to setup a line-info watch: %s",
+				   err->message);
+			for (j = i; j >= 0; j--)
+				g_gpiod_chip_unwatch_line_info(chip, i, NULL);
+			return FALSE;
+		}
+
+		gpiodbus_daemon_export_line(self, chip_data, linfo);
+	}
+
+	return TRUE;
+}
+
+static gboolean
+gpiodbus_daemon_handle_release_lines(GPIODBusRequest *request,
+				     GDBusMethodInvocation *invocation,
+				     gpointer user_data)
+{
+	GPIODBusDaemonRequestData *req_data = user_data;
+	g_autofree gchar *obj_path = NULL;
+	gboolean ret;
+
+	obj_path = g_strdup(g_dbus_interface_skeleton_get_object_path(
+					G_DBUS_INTERFACE_SKELETON(request)));
+
+	g_debug("release call received on request '%s'", obj_path);
+
+	ret = g_hash_table_remove(req_data->chip_data->daemon->requests,
+				  obj_path);
+	/* It's a programming bug if the request was not in the hashmap. */
+	if (!ret)
+		g_warning("request '%s' is not registered - logic error?",
+			  obj_path);
+
+	g_dbus_method_invocation_return_value(invocation, NULL);
+
+	return G_SOURCE_CONTINUE;
+}
+
+static gboolean
+gpiodbus_daemon_handle_reconfigure_lines(GPIODBusRequest *request,
+					 GDBusMethodInvocation *invocation,
+					 GVariant *arg_line_cfg,
+					 gpointer user_data)
+{
+	GPIODBusDaemonRequestData *req_data = user_data;
+	g_autoptr(GPIODLineConfig) line_cfg = NULL;
+	g_autofree gchar *line_cfg_str = NULL;
+	g_autoptr(GError) err = NULL;
+	const gchar *obj_path;
+	gboolean ret;
+
+	obj_path = g_dbus_interface_skeleton_get_object_path(
+					G_DBUS_INTERFACE_SKELETON(request));
+	line_cfg_str = g_variant_print(arg_line_cfg, FALSE);
+
+	g_debug("reconfigure call received on request '%s', line config: %s",
+		obj_path, line_cfg_str);
+
+	line_cfg = gpiodbus_line_config_from_variant(arg_line_cfg);
+	if (!line_cfg) {
+		g_critical("failed to convert method call arguments '%s' to line config",
+			   line_cfg_str);
+		g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR,
+						      G_DBUS_ERROR_INVALID_ARGS,
+						      "Invalid line configuration");
+		goto out;
+	}
+
+	ret = g_gpiod_line_request_reconfigure_lines(req_data->request,
+						     line_cfg, &err);
+	if (!ret) {
+		g_critical("failed to reconfigure GPIO lines on request '%s': %s",
+			   obj_path, err->message);
+		g_dbus_method_invocation_return_dbus_error(invocation,
+						"io.gpiod1.ReconfigureFailed",
+						err->message);
+		goto out;
+	}
+
+	g_dbus_method_invocation_return_value(invocation, NULL);
+
+out:
+	return G_SOURCE_CONTINUE;
+}
+
+static gboolean
+gpiodbus_daemon_handle_get_values(GPIODBusRequest *request,
+				  GDBusMethodInvocation *invocation,
+				  GVariant *arg_offsets, gpointer user_data)
+{
+	GPIODBusDaemonRequestData *req_data = user_data;
+	g_autoptr(GVariant) out_values = NULL;
+	g_autofree gchar *offsets_str = NULL;
+	g_autoptr(GVariant) response = NULL;
+	g_autoptr(GArray) offsets = NULL;
+	g_autoptr(GArray) values = NULL;
+	g_autoptr(GError) err = NULL;
+	GVariantBuilder builder;
+	const gchar *obj_path;
+	GVariantIter iter;
+	gsize num_offsets;
+	guint offset, i;
+	gboolean ret;
+
+	obj_path = g_dbus_interface_skeleton_get_object_path(
+					G_DBUS_INTERFACE_SKELETON(request));
+	offsets_str = g_variant_print(arg_offsets, FALSE);
+	num_offsets = g_variant_n_children(arg_offsets);
+
+	g_debug("get-values call received on request '%s' for offsets: %s",
+		obj_path, offsets_str);
+
+	if (num_offsets == 0) {
+		ret = g_gpiod_line_request_get_values(req_data->request,
+						      &values, &err);
+	} else {
+		offsets = g_array_sized_new(FALSE, TRUE, sizeof(offset),
+					    num_offsets);
+		g_variant_iter_init(&iter, arg_offsets);
+		while (g_variant_iter_next(&iter, "u", &offset))
+			g_array_append_val(offsets, offset);
+
+		ret = g_gpiod_line_request_get_values_subset(req_data->request,
+							     offsets, &values,
+							     &err);
+	}
+	if (!ret) {
+		g_critical("failed to get GPIO line values on request '%s': %s",
+			   obj_path, err->message);
+		g_dbus_method_invocation_return_dbus_error(invocation,
+						"io.gpiod1.GetValuesFailed",
+						err->message);
+		goto out;
+	}
+
+	g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
+	for (i = 0; i < values->len; i++)
+		g_variant_builder_add(&builder, "i",
+				      g_array_index(values, gint, i));
+	out_values = g_variant_ref_sink(g_variant_builder_end(&builder));
+
+	g_variant_builder_init(&builder, G_VARIANT_TYPE_TUPLE);
+	g_variant_builder_add_value(&builder, out_values);
+	response = g_variant_ref_sink(g_variant_builder_end(&builder));
+
+	g_dbus_method_invocation_return_value(invocation, response);
+
+out:
+	return G_SOURCE_CONTINUE;
+}
+
+static gboolean
+gpiodbus_daemon_handle_set_values(GPIODBusRequest *request,
+				  GDBusMethodInvocation *invocation,
+				  GVariant *arg_values, gpointer user_data)
+{
+	GPIODBusDaemonRequestData *req_data = user_data;
+	g_autofree gchar *values_str = NULL;
+	g_autoptr(GArray) offsets = NULL;
+	g_autoptr(GArray) values = NULL;
+	g_autoptr(GError) err = NULL;
+	const gchar *obj_path;
+	GVariantIter iter;
+	gsize num_values;
+	guint offset;
+	gboolean ret;
+	gint value;
+
+	obj_path = g_dbus_interface_skeleton_get_object_path(
+					G_DBUS_INTERFACE_SKELETON(request));
+	values_str = g_variant_print(arg_values, FALSE);
+	num_values = g_variant_n_children(arg_values);
+
+	g_debug("set-values call received on request '%s': %s",
+		obj_path, values_str);
+
+	if (num_values == 0) {
+		g_critical("Client passed no offset to value mappings");
+		g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR,
+						      G_DBUS_ERROR_INVALID_ARGS,
+						      "No offset <-> value mappings specified");
+		goto out;
+	}
+
+	offsets = g_array_sized_new(FALSE, TRUE, sizeof(offset), num_values);
+	values = g_array_sized_new(FALSE, TRUE, sizeof(value), num_values);
+
+	g_variant_iter_init(&iter, arg_values);
+	while (g_variant_iter_next(&iter, "{ui}", &offset, &value)) {
+		g_array_append_val(offsets, offset);
+		g_array_append_val(values, value);
+	}
+
+	ret = g_gpiod_line_request_set_values_subset(req_data->request, offsets,
+						     values, &err);
+	if (!ret) {
+		g_critical("failed to set GPIO line values on request '%s': %s",
+			   obj_path, err->message);
+		g_dbus_method_invocation_return_dbus_error(invocation,
+						"io.gpiod1.SetValuesFailed",
+						err->message);
+		goto out;
+	}
+
+	g_dbus_method_invocation_return_value(invocation, NULL);
+
+out:
+	return G_SOURCE_CONTINUE;
+}
+
+static void
+gpiodbus_daemon_on_edge_event(GPIODLineRequest *request G_GNUC_UNUSED,
+			      GPIODEdgeEvent *event, gpointer user_data)
+{
+	GPIODBusDaemonRequestData *req_data = user_data;
+	GPIODBusDaemonLineData *line_data;
+	gulong line_seqno, global_seqno;
+	GPIODEdgeEventType edge;
+	guint64 timestamp;
+	guint offset;
+	gint val;
+
+	edge = g_gpiod_edge_event_get_event_type(event);
+	offset = g_gpiod_edge_event_get_line_offset(event);
+	timestamp = g_gpiod_edge_event_get_timestamp_ns(event);
+	global_seqno = g_gpiod_edge_event_get_global_seqno(event);
+	line_seqno = g_gpiod_edge_event_get_line_seqno(event);
+
+	val = edge == G_GPIOD_EDGE_EVENT_RISING_EDGE ? 1 : 0;
+
+	g_debug("%s edge event received for offset %u on request '%s'",
+		val ? "rising" : "falling", offset,
+		g_dbus_interface_skeleton_get_object_path(
+			G_DBUS_INTERFACE_SKELETON(req_data->dbus_request)));
+
+	line_data = g_hash_table_lookup(req_data->chip_data->lines,
+					GINT_TO_POINTER(offset));
+	if (!line_data)
+		g_error("failed to retrieve line data - programming bug?");
+
+	gpiodbus_line_emit_edge_event(line_data->dbus_line,
+				      g_variant_new("(ittt)", val, timestamp,
+						    global_seqno, line_seqno));
+}
+
+static void
+gpiodbus_daemon_export_request(GPIODBusDaemon *self, GPIODLineRequest *request,
+			       GPIODBusDaemonChipData *chip_data, gint id)
+{
+	g_autofree GPIODBusDaemonRequestData *req_data = NULL;
+	g_autoptr(GPIODBusObjectSkeleton) skeleton = NULL;
+	g_autoptr(GPIODBusRequest) dbus_req = NULL;
+	g_autofree gchar *obj_path = NULL;
+	gboolean ret;
+
+	dbus_req = gpiodbus_request_skeleton_new();
+	obj_path = g_strdup_printf("/io/gpiod1/requests/request%d", id);
+
+	gpiodbus_request_set_props(dbus_req, request, chip_data->dbus_chip,
+				G_DBUS_OBJECT_MANAGER(chip_data->line_manager));
+
+	skeleton = gpiodbus_object_skeleton_new(obj_path);
+	gpiodbus_object_skeleton_set_request(skeleton,
+					     GPIODBUS_REQUEST(dbus_req));
+
+	g_debug("exporting object for GPIO request: '%s'", obj_path);
+
+	g_dbus_object_manager_server_export(self->request_manager,
+					    G_DBUS_OBJECT_SKELETON(skeleton));
+
+	req_data = g_malloc0(sizeof(*req_data));
+	req_data->chip_data = chip_data;
+	req_data->dbus_request = g_steal_pointer(&dbus_req);
+	req_data->id = id;
+	req_data->request = g_object_ref(request);
+
+	g_signal_connect(req_data->dbus_request, "handle-release",
+			 G_CALLBACK(gpiodbus_daemon_handle_release_lines),
+			 req_data);
+	g_signal_connect(req_data->dbus_request, "handle-reconfigure-lines",
+			 G_CALLBACK(gpiodbus_daemon_handle_reconfigure_lines),
+			 req_data);
+	g_signal_connect(req_data->dbus_request, "handle-get-values",
+			 G_CALLBACK(gpiodbus_daemon_handle_get_values),
+			 req_data);
+	g_signal_connect(req_data->dbus_request, "handle-set-values",
+			 G_CALLBACK(gpiodbus_daemon_handle_set_values),
+			 req_data);
+	g_signal_connect(req_data->request, "edge-event",
+			 G_CALLBACK(gpiodbus_daemon_on_edge_event), req_data);
+
+	gpiodbus_lines_set_managed(req_data, TRUE);
+
+	ret = g_hash_table_insert(self->requests, g_steal_pointer(&obj_path),
+				  g_steal_pointer(&req_data));
+	/* It's a programming bug if the request is already in the hashmap. */
+	g_assert(ret);
+}
+
+static gboolean
+gpiodbus_daemon_handle_request_lines(GPIODBusChip *chip,
+				     GDBusMethodInvocation *invocation,
+				     GVariant *arg_line_cfg,
+				     GVariant *arg_req_cfg,
+				     gpointer user_data)
+{
+	GPIODBusDaemonChipData *chip_data = user_data;
+	g_autoptr(GPIODRequestConfig) req_cfg = NULL;
+	g_autoptr(GPIODLineRequest) request = NULL;
+	g_autoptr(GPIODLineConfig) line_cfg = NULL;
+	g_autofree gchar *line_cfg_str = NULL;
+	g_autofree gchar *req_cfg_str = NULL;
+	g_autofree gchar *response = NULL;
+	g_autoptr(GError) err = NULL;
+	const gchar *obj_path;
+	guint id;
+
+	obj_path = g_dbus_interface_skeleton_get_object_path(
+			G_DBUS_INTERFACE_SKELETON(chip));
+	line_cfg_str = g_variant_print(arg_line_cfg, FALSE);
+	req_cfg_str = g_variant_print(arg_req_cfg, FALSE);
+
+	g_debug("line request received on chip '%s', line config: %s, request_config: %s",
+		obj_path, line_cfg_str, req_cfg_str);
+
+	line_cfg = gpiodbus_line_config_from_variant(arg_line_cfg);
+	if (!line_cfg) {
+		g_critical("failed to convert method call arguments '%s' to line config",
+			   line_cfg_str);
+		g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR,
+						      G_DBUS_ERROR_INVALID_ARGS,
+						      "Invalid line configuration");
+		goto out;
+	}
+
+	req_cfg = gpiodbus_request_config_from_variant(arg_req_cfg);
+	if (!req_cfg) {
+		g_critical("failed to convert method call arguments '%s' to request config",
+			   req_cfg_str);
+		g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR,
+						      G_DBUS_ERROR_INVALID_ARGS,
+						      "Invalid request configuration");
+		goto out;
+	}
+
+	request = g_gpiod_chip_request_lines(chip_data->chip, req_cfg, line_cfg,
+					     &err);
+	if (err) {
+		g_critical("failed to request GPIO lines on chip '%s': %s",
+			   obj_path, err->message);
+		g_dbus_method_invocation_return_dbus_error(invocation,
+				"io.gpiod1.RequestFailed", err->message);
+		goto out;
+	}
+
+	g_debug("line request succeeded on chip '%s'", obj_path);
+
+	id = gpiodbus_id_alloc(chip_data->daemon->req_id_root);
+	gpiodbus_daemon_export_request(chip_data->daemon, request,
+				       chip_data, id);
+
+	response = g_strdup_printf("/io/gpiod1/requests/request%d", id);
+	g_dbus_method_invocation_return_value(invocation,
+					      g_variant_new("(o)", response));
+
+out:
+	return G_SOURCE_CONTINUE;
+}
+
+static void gpiodbus_daemon_export_chip(GPIODBusDaemon *self, GUdevDevice *dev)
+{
+	g_autofree GPIODBusDaemonChipData *chip_data = NULL;
+	g_autoptr(GDBusObjectManagerServer) manager = NULL;
+	g_autoptr(GPIODBusObjectSkeleton) skeleton = NULL;
+	const gchar *devname, *devpath, *obj_prefix;
+	g_autoptr(GPIODBusChip) dbus_chip = NULL;
+	g_autoptr(GHashTable) lines = NULL;
+	g_autofree gchar *obj_path = NULL;
+	g_autoptr(GPIODChip) chip = NULL;
+	g_autoptr(GError) err = NULL;
+	gboolean ret;
+
+	devname = g_udev_device_get_name(dev);
+	devpath = g_udev_device_get_device_file(dev);
+	obj_prefix = g_dbus_object_manager_get_object_path(
+				G_DBUS_OBJECT_MANAGER(self->chip_manager));
+
+	chip = g_gpiod_chip_new(devpath, &err);
+	if (!chip) {
+		g_critical("failed to open GPIO chip %s: %s",
+			   devpath, err->message);
+		return;
+	}
+
+	dbus_chip = gpiodbus_chip_skeleton_new();
+	obj_path = g_strdup_printf("%s/%s", obj_prefix, devname);
+
+	ret = gpiodbus_chip_set_props(dbus_chip, chip, &err);
+	if (!ret) {
+		g_critical("failed to set chip properties: %s", err->message);
+		return;
+	}
+
+	skeleton = gpiodbus_object_skeleton_new(obj_path);
+	gpiodbus_object_skeleton_set_chip(skeleton, GPIODBUS_CHIP(dbus_chip));
+
+	g_debug("exporting object for GPIO chip: '%s'", obj_path);
+
+	g_dbus_object_manager_server_export(self->chip_manager,
+					    G_DBUS_OBJECT_SKELETON(skeleton));
+
+	lines = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL,
+				      gpiodbus_daemon_line_data_free);
+	manager = g_dbus_object_manager_server_new(obj_path);
+
+	chip_data = g_malloc0(sizeof(*chip_data));
+	chip_data->daemon = self;
+	chip_data->chip = g_steal_pointer(&chip);
+	chip_data->dbus_chip = g_steal_pointer(&dbus_chip);
+	chip_data->lines = g_steal_pointer(&lines);
+	chip_data->line_manager = g_steal_pointer(&manager);
+
+	ret = gpiodbus_daemon_export_lines(self, chip_data);
+	if (!ret) {
+		g_dbus_object_manager_server_unexport(self->chip_manager,
+						      obj_path);
+		return;
+	}
+
+	g_signal_connect(chip_data->dbus_chip, "handle-request-lines",
+			 G_CALLBACK(gpiodbus_daemon_handle_request_lines),
+			 chip_data);
+
+	ret = g_hash_table_insert(self->chips, g_strdup(devname),
+				  g_steal_pointer(&chip_data));
+	/* It's a programming bug if the chip is already in the hashmap. */
+	g_assert(ret);
+}
+
+static void gpiodbus_daemon_unexport_chip(GPIODBusDaemon *self,
+					  GUdevDevice *dev)
+{
+	const gchar *name = g_udev_device_get_name(dev);
+	gboolean ret;
+
+	ret = g_hash_table_remove(self->chips, name);
+	/* It's a programming bug if the chip was not in the hashmap. */
+	if (!ret)
+		g_warning("chip '%s' is not registered - exporting failed?",
+			  name);
+}
+
+/*
+ * We can get two uevents per action per gpiochip. One is for the new-style
+ * character device, the other for legacy sysfs devices. We are only concerned
+ * with the former, which we can tell from the latter by the presence of
+ * the device file.
+ */
+static gboolean gpiodbus_daemon_is_gpiochip_device(GUdevDevice *dev)
+{
+	return g_udev_device_get_device_file(dev) != NULL;
+}
+
+static void gpiodbus_daemon_on_uevent(GUdevClient *udev G_GNUC_UNUSED,
+				      const gchar *action, GUdevDevice *dev,
+				      gpointer data)
+{
+	GPIODBusDaemon *self = data;
+
+	if (!gpiodbus_daemon_is_gpiochip_device(dev))
+		return;
+
+	g_debug("uevent: %s action on %s device",
+		action, g_udev_device_get_name(dev));
+
+	if (g_strcmp0(action, "bind") == 0)
+		gpiodbus_daemon_export_chip(self, dev);
+	else if (g_strcmp0(action, "unbind") == 0)
+		gpiodbus_daemon_unexport_chip(self, dev);
+}
+
+static void gpiodbus_daemon_process_chip_dev(gpointer data, gpointer user_data)
+{
+	GPIODBusDaemon *daemon = user_data;
+	GUdevDevice *dev = data;
+
+	if (gpiodbus_daemon_is_gpiochip_device(dev))
+		gpiodbus_daemon_export_chip(daemon, dev);
+}
+
+void gpiodbus_daemon_start(GPIODBusDaemon *self, GDBusConnection *con)
+{
+	g_autolist(GUdevDevice) devs = NULL;
+
+	g_assert(self);
+	g_assert(!self->con); /* Don't allow to call this twice. */
+
+	self->con = g_object_ref(con);
+
+	/* Subscribe for GPIO uevents. */
+	g_signal_connect(self->udev, "uevent",
+			 G_CALLBACK(gpiodbus_daemon_on_uevent), self);
+
+	devs = g_udev_client_query_by_subsystem(self->udev, "gpio");
+	g_list_foreach(devs, gpiodbus_daemon_process_chip_dev, self);
+
+	g_dbus_object_manager_server_set_connection(self->chip_manager,
+						    self->con);
+	g_dbus_object_manager_server_set_connection(self->request_manager,
+						    self->con);
+
+	g_debug("GPIO daemon now listening");
+}
dbus/manager/daemon.h new file +22 -0
new file mode 100644
index 0000000..7674892
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* SPDX-FileCopyrightText: 2022-2023 Bartosz Golaszewski <[email protected]> */
+
+#ifndef __GPIODBUS_DAEMON_H__
+#define __GPIODBUS_DAEMON_H__
+
+#include <gio/gio.h>
+#include <glib.h>
+#include <glib-object.h>
+
+G_DECLARE_FINAL_TYPE(GPIODBusDaemon, gpiodbus_daemon,
+		     GPIODBUS, DAEMON, GObject);
+
+#define GPIODBUS_DAEMON_TYPE (gpiodbus_daemon_get_type())
+#define GPIODBUS_DAEMON(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST((obj), \
+	 GPIODBUS_DAEMON_TYPE, GPIODBusDaemon))
+
+GPIODBusDaemon *gpiodbus_daemon_new(void);
+void gpiodbus_daemon_start(GPIODBusDaemon *daemon, GDBusConnection *con);
+
+#endif /* __GPIODBUS_DAEMON_H__ */
dbus/manager/gpio-manager.c new file +167 -0

167 lines not on this page · raw message

dbus/manager/helpers.c new file +420 -0

420 lines not on this page · raw message

dbus/manager/helpers.h new file +24 -0
new file mode 100644
index 0000000..03b3240
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* SPDX-FileCopyrightText: 2023 Bartosz Golaszewski <[email protected]> */
+
+#ifndef __GPIODBUS_HELPERS_H__
+#define __GPIODBUS_HELPERS_H__
+
+#include <gio/gio.h>
+#include <gpiod-glib.h>
+#include <glib.h>
+#include <gpiodbus.h>
+
+gint gpiodbus_id_cmp(gconstpointer a, gconstpointer b, gpointer user_data);
+gint gpiodbus_id_alloc(GTree *id_root);
+void gpiodbus_id_free(GTree *id_root, gint id);
+gboolean
+gpiodbus_chip_set_props(GPIODBusChip *skeleton, GPIODChip *chip, GError **err);
+void gpiodbus_line_set_props(GPIODBusLine *skeleton, GPIODLineInfo *info);
+void gpiodbus_request_set_props(GPIODBusRequest *skeleton,
+				GPIODLineRequest *request, GPIODBusChip *chip,
+				GDBusObjectManager *line_manager);
+GPIODLineConfig *gpiodbus_line_config_from_variant(GVariant *variant);
+GPIODRequestConfig *gpiodbus_request_config_from_variant(GVariant *variant);
+
+#endif /* __GPIODBUS_HELPERS_H__ */
Signature · 1 line
2.40.1
#20 lore raw

PATCH 14/18 dbus: add tests

In reply to Bartosz Golaszewski

From: Bartosz Golaszewski <[email protected]>

Add a set of test-cases verifying the functionality of the gpio-manager daemon.

---
 dbus/tests/daemon-process.c | 129 ++++++++++++++++++++++
 dbus/tests/daemon-process.h |  20 ++++
 dbus/tests/helpers.c        | 107 ++++++++++++++++++
 dbus/tests/helpers.h        | 112 +++++++++++++++++++
 dbus/tests/tests-chip.c     | 133 ++++++++++++++++++++++
 dbus/tests/tests-line.c     | 213 ++++++++++++++++++++++++++++++++++++
 dbus/tests/tests-request.c  | 116 ++++++++++++++++++++
 7 files changed, 830 insertions(+)
 create mode 100644 dbus/tests/daemon-process.c
 create mode 100644 dbus/tests/daemon-process.h
 create mode 100644 dbus/tests/helpers.c
 create mode 100644 dbus/tests/helpers.h
 create mode 100644 dbus/tests/tests-chip.c
 create mode 100644 dbus/tests/tests-line.c
 create mode 100644 dbus/tests/tests-request.c
dbus/tests/daemon-process.c new file +129 -0
new file mode 100644
index 0000000..9eec71b
@@ -0,0 +1,129 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2022-2023 Bartosz Golaszewski <[email protected]>
+
+#include <gio/gio.h>
+#include <signal.h>
+
+#include "daemon-process.h"
+
+struct _GPIODBusDaemonProcess {
+	GObject parent_instance;
+	GSubprocess *proc;
+};
+
+G_DEFINE_TYPE(GPIODBusDaemonProcess, gpiodbus_daemon_process, G_TYPE_OBJECT);
+
+static gboolean on_timeout(gpointer data G_GNUC_UNUSED)
+{
+	g_error("timeout reached waiting for the daemon name to appear on the system bus");
+
+	return G_SOURCE_REMOVE;
+}
+
+static void on_name_appeared(GDBusConnection *con G_GNUC_UNUSED,
+			     const gchar *name G_GNUC_UNUSED,
+			     const gchar *name_owner G_GNUC_UNUSED,
+			     gpointer data)
+{
+	gboolean *name_state = data;
+
+	*name_state = TRUE;
+}
+
+static void gpiodbus_daemon_process_constructed(GObject *obj)
+{
+	GPIODBusDaemonProcess *self = GPIODBUS_DAEMON_PROCESS_OBJ(obj);
+	const gchar *path = g_getenv("GPIODBUS_TEST_DAEMON_PATH");
+	g_autoptr(GDBusConnection) con = NULL;
+	g_autofree gchar *addr = NULL;
+	g_autoptr(GError) err = NULL;
+	gboolean name_state = FALSE;
+	guint watch_id, timeout_id;
+
+	if (!path)
+		g_error("GPIODBUS_TEST_DAEMON_PATH environment variable must be set");
+
+	addr = g_dbus_address_get_for_bus_sync(G_BUS_TYPE_SYSTEM, NULL, &err);
+	if (!addr)
+		g_error("failed to get an address for system bus: %s",
+			err->message);
+
+	con = g_dbus_connection_new_for_address_sync(addr,
+			G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
+			G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
+			NULL, NULL, &err);
+	if (!con)
+		g_error("failed to get a dbus connection: %s", err->message);
+
+	watch_id = g_bus_watch_name_on_connection(con, "io.gpiod1",
+						  G_BUS_NAME_WATCHER_FLAGS_NONE,
+						  on_name_appeared, NULL,
+						  &name_state, NULL);
+
+	self->proc = g_subprocess_new(G_SUBPROCESS_FLAGS_STDOUT_SILENCE |
+				      G_SUBPROCESS_FLAGS_STDERR_SILENCE,
+				      &err, path, NULL);
+	if (!self->proc)
+		g_error("failed to launch the gpio-manager process: %s",
+			err->message);
+
+	timeout_id = g_timeout_add_seconds(5, on_timeout, NULL);
+
+	while (!name_state)
+		g_main_context_iteration(NULL, TRUE);
+
+	g_bus_unwatch_name(watch_id);
+	g_source_remove(timeout_id);
+
+	G_OBJECT_CLASS(gpiodbus_daemon_process_parent_class)->constructed(obj);
+}
+
+static void gpiodbus_daemon_process_kill(GSubprocess *proc)
+{
+	g_autoptr(GError) err = NULL;
+	gint status;
+
+	g_subprocess_send_signal(proc, SIGTERM);
+	g_subprocess_wait(proc, NULL, &err);
+	if (err)
+		g_error("failed to collect the exit status of gpio-manager: %s",
+			err->message);
+
+	if (!g_subprocess_get_if_exited(proc))
+		g_error("dbus-manager process did not exit normally");
+
+	status = g_subprocess_get_exit_status(proc);
+	if (status != 0)
+		g_error("dbus-manager process exited with a non-zero status: %d",
+			status);
+
+	g_object_unref(proc);
+}
+
+static void gpiodbus_daemon_process_dispose(GObject *obj)
+{
+	GPIODBusDaemonProcess *self = GPIODBUS_DAEMON_PROCESS_OBJ(obj);
+
+	g_clear_pointer(&self->proc, gpiodbus_daemon_process_kill);
+
+	G_OBJECT_CLASS(gpiodbus_daemon_process_parent_class)->dispose(obj);
+}
+
+static void
+gpiodbus_daemon_process_class_init(GPIODBusDaemonProcessClass *proc_class)
+{
+	GObjectClass *class = G_OBJECT_CLASS(proc_class);
+
+	class->constructed = gpiodbus_daemon_process_constructed;
+	class->dispose = gpiodbus_daemon_process_dispose;
+}
+
+static void gpiodbus_daemon_process_init(GPIODBusDaemonProcess *self)
+{
+	self->proc = NULL;
+}
+
+GPIODBusDaemonProcess *gpiodbus_daemon_process_new(void)
+{
+	return g_object_new(GPIODBUS_DAEMON_PROCESS_TYPE, NULL);
+}
dbus/tests/daemon-process.h new file +20 -0
new file mode 100644
index 0000000..24d22a8
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* SPDX-FileCopyrightText: 2022-2023 Bartosz Golaszewski <[email protected]> */
+
+#ifndef __GPIODBUS_TEST_DAEMON_PROCESS_H__
+#define __GPIODBUS_TEST_DAEMON_PROCESS_H__
+
+#include <glib.h>
+
+G_DECLARE_FINAL_TYPE(GPIODBusDaemonProcess, gpiodbus_daemon_process,
+		     GPIODBUS, DAEMON_PROCESS, GObject);
+
+#define GPIODBUS_DAEMON_PROCESS_TYPE (gpiodbus_daemon_process_get_type())
+#define GPIODBUS_DAEMON_PROCESS_OBJ(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST(obj, \
+	 GPIODBUS_DAEMON_PROCESS_TYPE, \
+	 GPIODBusDaemonProcess))
+
+GPIODBusDaemonProcess *gpiodbus_daemon_process_new(void);
+
+#endif /* __GPIODBUS_TEST_DAEMON_PROCESS_H__ */
dbus/tests/helpers.c new file +107 -0

107 lines not on this page · raw message

dbus/tests/helpers.h new file +112 -0

112 lines not on this page · raw message

dbus/tests/tests-chip.c new file +133 -0

133 lines not on this page · raw message

dbus/tests/tests-line.c new file +213 -0

213 lines not on this page · raw message

dbus/tests/tests-request.c new file +116 -0

116 lines not on this page · raw message

Signature · 1 line
2.40.1
#21 lore raw

PATCH 15/18 dbus: add a command-line client

In reply to Bartosz Golaszewski

From: Bartosz Golaszewski <[email protected]>

Add code for gpiocli - the command-line client for controlling GPIOs using the gpio-manager daemon.

---
 dbus/client/common.c      | 644 ++++++++++++++++++++++++++++++++++++++
 dbus/client/common.h      | 203 ++++++++++++
 dbus/client/detect.c      |  53 ++++
 dbus/client/find.c        |  66 ++++
 dbus/client/get.c         | 210 +++++++++++++
 dbus/client/gpiocli.c     | 174 ++++++++++
 dbus/client/info.c        | 184 +++++++++++
 dbus/client/monitor.c     | 191 +++++++++++
 dbus/client/notify.c      | 295 +++++++++++++++++
 dbus/client/reconfigure.c |  74 +++++
 dbus/client/release.c     |  62 ++++
 dbus/client/request.c     | 249 +++++++++++++++
 dbus/client/requests.c    |  71 +++++
 dbus/client/set.c         | 170 ++++++++++
 dbus/client/wait.c        | 188 +++++++++++
 15 files changed, 2834 insertions(+)
 create mode 100644 dbus/client/common.c
 create mode 100644 dbus/client/common.h
 create mode 100644 dbus/client/detect.c
 create mode 100644 dbus/client/find.c
 create mode 100644 dbus/client/get.c
 create mode 100644 dbus/client/gpiocli.c
 create mode 100644 dbus/client/info.c
 create mode 100644 dbus/client/monitor.c
 create mode 100644 dbus/client/notify.c
 create mode 100644 dbus/client/reconfigure.c
 create mode 100644 dbus/client/release.c
 create mode 100644 dbus/client/request.c
 create mode 100644 dbus/client/requests.c
 create mode 100644 dbus/client/set.c
 create mode 100644 dbus/client/wait.c
dbus/client/common.c new file +644 -0
new file mode 100644
index 0000000..280c465
@@ -0,0 +1,644 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2022-2023 Bartosz Golaszewski <[email protected]>
+
+#include <glib/gprintf.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "common.h"
+
+static void print_err_msg(GError *err, const gchar *fmt, va_list va)
+{
+	g_printerr("%s: ", g_get_prgname());
+	g_vfprintf(stderr, fmt, va);
+	if (err)
+		g_printerr(": %s", err->message);
+	g_printerr("\n");
+}
+
+void die(const gchar *fmt, ...)
+{
+	va_list va;
+
+	va_start(va, fmt);
+	print_err_msg(NULL, fmt, va);
+	va_end(va);
+
+	exit(EXIT_FAILURE);
+}
+
+void die_gerror(GError *err, const gchar *fmt, ...)
+{
+	va_list va;
+
+	va_start(va, fmt);
+	print_err_msg(err, fmt, va);
+	va_end(va);
+
+	exit(EXIT_FAILURE);
+}
+
+void die_parsing_opts(const char *fmt, ...)
+{
+	va_list va;
+
+	va_start(va, fmt);
+	print_err_msg(NULL, fmt, va);
+	va_end(va);
+	g_printerr("\nSee %s --help\n", g_get_prgname());
+
+	exit(EXIT_FAILURE);
+}
+
+void parse_options(const GOptionEntry *opts, const gchar *summary,
+		   const gchar *description, int *argc, char ***argv)
+{
+	g_autoptr(GOptionContext) ctx = NULL;
+	g_autoptr(GError) err = NULL;
+	gboolean ret;
+
+	ctx = g_option_context_new(NULL);
+	g_option_context_set_summary(ctx, summary);
+	g_option_context_set_description(ctx, description);
+	g_option_context_add_main_entries(ctx, opts, NULL);
+	g_option_context_set_strict_posix(ctx, TRUE);
+
+	ret = g_option_context_parse(ctx, argc, argv, &err);
+	if (!ret) {
+		g_printerr("%s: Option parsing failed: %s\nSee %s --help\n",
+			   g_get_prgname(), err->message, g_get_prgname());
+		exit(EXIT_FAILURE);
+	}
+}
+
+void check_manager(void)
+{
+	g_autoptr(GDBusProxy) proxy = NULL;
+	g_autoptr(GVariant) result = NULL;
+	g_autoptr(GError) err = NULL;
+
+	proxy = g_dbus_proxy_new_for_bus_sync(
+			G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, NULL,
+			"io.gpiod1", "/io/gpiod1", "org.freedesktop.DBus.Peer",
+			NULL, &err);
+	if (!proxy)
+		die_gerror(err, "Unable to create a proxy to '/io/gpiod1'");
+
+	result = g_dbus_proxy_call_sync(proxy, "Ping", NULL,
+					G_DBUS_CALL_FLAGS_NONE, -1, NULL,
+					&err);
+	if (!result) {
+		if (err->domain == G_DBUS_ERROR) {
+			switch (err->code) {
+			case G_DBUS_ERROR_ACCESS_DENIED:
+				die("Access to gpio-manager denied, check your permissions");
+			case G_DBUS_ERROR_SERVICE_UNKNOWN:
+				die("gpio-manager not running");
+			}
+		}
+
+		die_gerror(err, "Failed trying to contect the gpio manager");
+	}
+}
+
+gboolean quit_main_loop_on_signal(gpointer user_data)
+{
+	GMainLoop *loop = user_data;
+
+	g_main_loop_quit(loop);
+
+	return G_SOURCE_REMOVE;
+}
+
+void die_on_name_vanished(GDBusConnection *con G_GNUC_UNUSED,
+			  const gchar *name G_GNUC_UNUSED,
+			  gpointer user_data G_GNUC_UNUSED)
+{
+	die("gpio-manager exited unexpectedly");
+}
+
+GList *strv_to_gstring_list(GStrv lines)
+{
+	gsize llen = g_strv_length(lines);
+	GList *list = NULL;
+	guint i;
+
+	for (i = 0; i < llen; i++)
+		list = g_list_append(list, g_string_new(lines[i]));
+
+	return list;
+}
+
+gint output_value_from_str(const gchar *value_str)
+{
+	if ((g_strcmp0(value_str, "active") == 0) ||
+	    (g_strcmp0(value_str, "1") == 0))
+		return 1;
+	else if ((g_strcmp0(value_str, "inactive") == 0) ||
+		 (g_strcmp0(value_str, "0") == 0))
+		return 0;
+
+	die_parsing_opts("invalid output value: '%s'", value_str);
+}
+
+static gboolean str_is_all_digits(const gchar *str)
+{
+	for (; *str; str++) {
+		if (!g_ascii_isdigit(*str))
+			return FALSE;
+	}
+
+	return TRUE;
+}
+
+static gint compare_objs_by_path(GDBusObject *a, GDBusObject *b)
+{
+	return strverscmp(g_dbus_object_get_object_path(a),
+			  g_dbus_object_get_object_path(b));
+}
+
+GDBusObjectManager *get_object_manager_client(const gchar *obj_path)
+{
+	g_autoptr(GDBusObjectManager) manager = NULL;
+	g_autoptr(GError) err = NULL;
+
+	manager = gpiodbus_object_manager_client_new_for_bus_sync(
+				G_BUS_TYPE_SYSTEM,
+				G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
+				"io.gpiod1", obj_path, NULL, &err);
+	if (!manager)
+		die_gerror(err,
+			   "failed to create the object manager client for %s",
+			   obj_path);
+
+	return g_object_ref(manager);
+}
+
+static gchar *make_chip_obj_path(const gchar *chip)
+{
+	return g_strdup_printf(
+		str_is_all_digits(chip) ?
+			"/io/gpiod1/chips/gpiochip%s" :
+			"/io/gpiod1/chips/%s",
+		chip);
+}
+
+GPIODBusObject *get_chip_obj_by_path(const gchar *obj_path)
+{
+	g_autoptr(GDBusObjectManager) manager = NULL;
+	g_autoptr(GPIODBusObject) chip_obj = NULL;
+
+	manager = get_object_manager_client("/io/gpiod1/chips");
+
+	chip_obj = GPIODBUS_OBJECT(g_dbus_object_manager_get_object(manager,
+								    obj_path));
+	if (!chip_obj)
+		die("No such chip object: '%s'", obj_path);
+
+	return g_object_ref(chip_obj);
+}
+
+GPIODBusObject *get_chip_obj(const gchar *chip_name)
+{
+	return get_chip_obj_by_path(make_chip_obj_path(chip_name));
+}
+
+GList *get_chip_objs(GStrv chip_names)
+{
+	g_autoptr(GDBusObjectManager) manager = NULL;
+	GList *objs = NULL;
+	gint i;
+
+	manager = get_object_manager_client("/io/gpiod1/chips");
+
+	if (!chip_names)
+		return g_list_sort(g_dbus_object_manager_get_objects(manager),
+				   (GCompareFunc)compare_objs_by_path);
+
+	for (i = 0; chip_names[i]; i++) {
+		g_autofree gchar *obj_path = make_chip_obj_path(chip_names[i]);
+		g_autoptr(GPIODBusObject) obj = NULL;
+
+		obj = GPIODBUS_OBJECT(
+			g_dbus_object_manager_get_object(manager, obj_path));
+		if (!obj)
+			die("No such chip: '%s'", chip_names[i]);
+
+		objs = g_list_insert_sorted(objs, g_object_ref(obj),
+					    (GCompareFunc)compare_objs_by_path);
+	}
+
+	return objs;
+}
+
+gchar *make_request_obj_path(const gchar *request)
+{
+	return g_strdup_printf(
+		str_is_all_digits(request) ?
+			"/io/gpiod1/requests/request%s" :
+			"/io/gpiod1/requests/%s",
+		request);
+}
+
+GPIODBusObject *get_request_obj(const gchar *request_name)
+{
+	g_autoptr(GDBusObjectManager) manager = NULL;
+	g_autoptr(GPIODBusObject) req_obj = NULL;
+	g_autofree gchar *obj_path = NULL;
+
+	manager = get_object_manager_client("/io/gpiod1/requests");
+	obj_path = make_request_obj_path(request_name);
+
+	req_obj = GPIODBUS_OBJECT(g_dbus_object_manager_get_object(manager,
+								   obj_path));
+	if (!req_obj)
+		die("No such request: '%s'", request_name);
+
+	return g_object_ref(req_obj);
+}
+
+GList *get_request_objs(void)
+{
+	g_autoptr(GDBusObjectManager) manager = NULL;
+	GList *objs = NULL;
+
+	manager = get_object_manager_client("/io/gpiod1/requests");
+	objs = g_dbus_object_manager_get_objects(manager);
+
+	return g_list_sort(objs, (GCompareFunc)compare_objs_by_path);
+}
+
+GArray *get_request_offsets(GPIODBusRequest *request)
+{
+	const gchar *chip_path, *line_path, *const *line_paths;
+	g_autoptr(GDBusObjectManager) manager = NULL;
+	g_autoptr(GArray) offsets = NULL;
+	GPIODBusLine *line;
+	guint i, offset;
+
+	chip_path = gpiodbus_request_get_chip_path(request);
+	line_paths = gpiodbus_request_get_line_paths(request);
+	offsets = g_array_new(FALSE, TRUE, sizeof(guint));
+	manager = get_object_manager_client(chip_path);
+
+	for (i = 0, line_path = line_paths[i];
+	     line_path;
+	     line_path = line_paths[++i]) {
+		g_autoptr(GDBusObject) line_obj = NULL;
+
+		line_obj = g_dbus_object_manager_get_object(manager, line_path);
+		line = gpiodbus_object_peek_line(GPIODBUS_OBJECT(line_obj));
+		offset = gpiodbus_line_get_offset(line);
+		g_array_append_val(offsets, offset);
+	}
+
+	return g_array_ref(offsets);
+}
+
+gboolean get_line_obj_by_name(const gchar *name, GPIODBusObject **line_obj,
+			      GPIODBusObject **chip_obj)
+{
+	g_autolist(GPIODBusObject) chip_objs = NULL;
+	GList *pos;
+
+	if (str_is_all_digits(name))
+		die("Refusing to use line offsets if chip is not specified");
+
+	chip_objs = get_chip_objs(NULL);
+
+	for (pos = g_list_first(chip_objs); pos; pos = g_list_next(pos)) {
+		*line_obj = get_line_obj_by_name_for_chip(pos->data, name);
+		if (*line_obj) {
+			if (chip_obj)
+				*chip_obj = g_object_ref(pos->data);
+			return TRUE;
+		}
+	}
+
+	return FALSE;
+}
+
+GPIODBusObject *
+get_line_obj_by_name_for_chip(GPIODBusObject *chip_obj, const gchar *line_name)
+{
+	g_autoptr(GDBusObjectManager) manager = NULL;
+	g_autolist(GPIODBusObject) line_objs = NULL;
+	const gchar *chip_path;
+	GPIODBusLine *line;
+	guint64 offset;
+	GList *pos;
+
+	chip_path = g_dbus_object_get_object_path(G_DBUS_OBJECT(chip_obj));
+	manager = get_object_manager_client(chip_path);
+	line_objs = g_dbus_object_manager_get_objects(manager);
+
+	for (pos = g_list_first(line_objs); pos; pos = g_list_next(pos)) {
+		line = gpiodbus_object_peek_line(pos->data);
+
+		if (g_strcmp0(gpiodbus_line_get_name(line), line_name) == 0)
+			return g_object_ref(pos->data);
+
+		if (str_is_all_digits(line_name)) {
+			offset = g_ascii_strtoull(line_name, NULL, 10);
+			if (offset == gpiodbus_line_get_offset(line))
+				return g_object_ref(pos->data);
+		}
+	}
+
+	return NULL;
+}
+
+GList *get_all_line_objs_for_chip(GPIODBusObject *chip_obj)
+{
+	g_autoptr(GDBusObjectManager) manager = NULL;
+	const gchar *chip_path;
+
+	chip_path = g_dbus_object_get_object_path(G_DBUS_OBJECT(chip_obj));
+	manager = get_object_manager_client(chip_path);
+
+	return g_list_sort(g_dbus_object_manager_get_objects(manager),
+			   (GCompareFunc)compare_objs_by_path);
+}
+
+static gchar *sanitize_str(const gchar *str)
+{
+	if (!strlen(str))
+		return NULL;
+
+	return g_strdup(str);
+}
+
+static const gchar *sanitize_direction(const gchar *direction)
+{
+	if ((g_strcmp0(direction, "input") == 0) ||
+	    (g_strcmp0(direction, "output") == 0))
+		return direction;
+
+	die("invalid direction value received from manager: '%s'", direction);
+}
+
+static const gchar *sanitize_drive(const gchar *drive)
+{
+	if ((g_strcmp0(drive, "push-pull") == 0) ||
+	    (g_strcmp0(drive, "open-source") == 0) ||
+	    (g_strcmp0(drive, "open-drain") == 0))
+		return drive;
+
+	die("invalid drive value received from manager: '%s'", drive);
+}
+
+static const gchar *sanitize_bias(const gchar *bias)
+{
+	if ((g_strcmp0(bias, "pull-up") == 0) ||
+	    (g_strcmp0(bias, "pull-down") == 0) ||
+	    (g_strcmp0(bias, "disabled") == 0))
+		return bias;
+
+	if (g_strcmp0(bias, "unknown") == 0)
+		return NULL;
+
+	die("invalid bias value received from manager: '%s'", bias);
+}
+
+static const gchar *sanitize_edge(const gchar *edge)
+{
+	if ((g_strcmp0(edge, "rising") == 0) ||
+	    (g_strcmp0(edge, "falling") == 0) ||
+	    (g_strcmp0(edge, "both") == 0))
+		return edge;
+
+	if (g_strcmp0(edge, "none") == 0)
+		return NULL;
+
+	die("invalid edge value received from manager: '%s'", edge);
+}
+
+static const gchar *sanitize_clock(const gchar *event_clock)
+{
+	if ((g_strcmp0(event_clock, "monotonic") == 0) ||
+	    (g_strcmp0(event_clock, "realtime") == 0) ||
+	    (g_strcmp0(event_clock, "hte") == 0))
+		return event_clock;
+
+	die("invalid clock value received from manager: '%s'", event_clock);
+}
+
+gchar *sanitize_object_path(const gchar *path)
+{
+	if (g_strcmp0(path, "/") == 0)
+		return g_strdup("N/A");
+
+	return g_path_get_basename(path);
+}
+
+LineProperties *get_line_properties(GPIODBusLine *line)
+{
+	LineProperties *props;
+
+	props = g_malloc0(sizeof(*props));
+	props->name = sanitize_str(gpiodbus_line_get_name(line));
+	props->offset = gpiodbus_line_get_offset(line);
+	props->used = gpiodbus_line_get_used(line);
+	props->consumer = sanitize_str(gpiodbus_line_get_consumer(line));
+	props->direction = sanitize_direction(
+				gpiodbus_line_get_direction(line));
+	props->drive = sanitize_drive(gpiodbus_line_get_drive(line));
+	props->bias = sanitize_bias(gpiodbus_line_get_bias(line));
+	props->active_low = gpiodbus_line_get_active_low(line);
+	props->edge = sanitize_edge(gpiodbus_line_get_edge_detection(line));
+	props->debounced = gpiodbus_line_get_debounced(line);
+	props->debounce_period = gpiodbus_line_get_debounce_period_us(line);
+	props->event_clock = sanitize_clock(
+				gpiodbus_line_get_event_clock(line));
+	props->managed = gpiodbus_line_get_managed(line);
+	props->request_name = sanitize_object_path(
+			gpiodbus_line_get_request_path(line));
+
+	return props;
+}
+
+void free_line_properties(LineProperties *props)
+{
+	g_free(props->name);
+	g_free(props->consumer);
+	g_free(props->request_name);
+	g_free(props);
+}
+
+void validate_line_config_opts(LineConfigOpts *opts)
+{
+	gint counter;
+
+	if (opts->input && opts->output)
+		die_parsing_opts("--input and --output are mutually exclusive");
+
+	if (opts->both_edges)
+		opts->rising_edge = opts->falling_edge = TRUE;
+
+	if (!opts->input && (opts->rising_edge || opts->falling_edge))
+		die_parsing_opts("monitoring edges is only possible in input mode");
+
+	counter = 0;
+	if (opts->push_pull)
+		counter++;
+	if (opts->open_drain)
+		counter++;
+	if (opts->open_source)
+		counter++;
+
+	if (counter > 1)
+		die_parsing_opts("--push-pull, --open-drain and --open-source are mutually exclusive");
+
+	if (!opts->output && (counter > 0))
+		die_parsing_opts("--push-pull, --open-drain and --open-source are only available in output mode");
+
+	counter = 0;
+	if (opts->pull_up)
+		counter++;
+	if (opts->pull_down)
+		counter++;
+	if (opts->bias_disabled)
+		counter++;
+
+	if (counter > 1)
+		die_parsing_opts("--pull-up, --pull-down and --bias-disabled are mutually exclusive");
+
+	counter = 0;
+	if (opts->clock_monotonic)
+		counter++;
+	if (opts->clock_realtime)
+		counter++;
+	if (opts->clock_hte)
+		counter++;
+
+	if (counter > 1)
+		die_parsing_opts("--clock-monotonic, --clock-realtime and --clock-hte are mutually exclusive");
+
+	if (counter > 0 && (!opts->rising_edge && !opts->falling_edge))
+		die_parsing_opts("--clock-monotonic, --clock-realtime and --clock-hte can only be used with edge detection enabled");
+
+	if (opts->debounce_period && (!opts->rising_edge && !opts->falling_edge))
+		die_parsing_opts("--debounce-period can only be used with edge-detection enabled");
+}
+
+GVariant *make_line_config(GArray *offsets, LineConfigOpts *opts)
+{
+	const char *direction, *edge = NULL, *bias = NULL, *drive = NULL,
+		   *clock = NULL;
+	g_autoptr(GVariant) output_values = NULL;
+	g_autoptr(GVariant) line_settings = NULL;
+	g_autoptr(GVariant) line_offsets = NULL;
+	g_autoptr(GVariant) line_configs = NULL;
+	g_autoptr(GVariant) line_config = NULL;
+	GVariantBuilder builder;
+	guint i;
+
+	g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
+	for (i = 0; i < offsets->len; i++)
+		g_variant_builder_add_value(&builder,
+			g_variant_new_uint32(g_array_index(offsets, guint, i)));
+	line_offsets = g_variant_builder_end(&builder);
+
+	g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
+
+	if (opts->input)
+		direction = "input";
+	else if (opts->output)
+		direction = "output";
+	else
+		direction = "as-is";
+
+	if (direction)
+		g_variant_builder_add_value(&builder,
+			g_variant_new("{sv}", "direction",
+				      g_variant_new_string(direction)));
+
+	if (opts->rising_edge && opts->falling_edge)
+		edge = "both";
+	else if (opts->falling_edge)
+		edge = "falling";
+	else if (opts->rising_edge)
+		edge = "rising";
+
+	if (edge)
+		g_variant_builder_add_value(&builder,
+			g_variant_new("{sv}", "edge",
+				      g_variant_new_string(edge)));
+
+	if (opts->pull_up)
+		bias = "pull-up";
+	else if (opts->pull_down)
+		bias = "pull-down";
+	else if (opts->bias_disabled)
+		bias = "disabled";
+
+	if (bias)
+		g_variant_builder_add_value(&builder,
+			g_variant_new("{sv}", "bias",
+				      g_variant_new_string(bias)));
+
+	if (opts->push_pull)
+		drive = "push-pull";
+	else if (opts->open_drain)
+		drive = "open-drain";
+	else if (opts->open_source)
+		drive = "open-source";
+
+	if (drive)
+		g_variant_builder_add_value(&builder,
+			g_variant_new("{sv}", "drive",
+				      g_variant_new_string(drive)));
+
+	if (opts->active_low)
+		g_variant_builder_add_value(&builder,
+			g_variant_new("{sv}", "active-low",
+				      g_variant_new_boolean(TRUE)));
+
+	if (opts->debounce_period)
+		g_variant_builder_add_value(&builder,
+			g_variant_new("{sv}", "debounce-period",
+				g_variant_new_int64(opts->debounce_period)));
+
+	if (opts->clock_monotonic)
+		clock = "monotonic";
+	else if (opts->clock_realtime)
+		clock = "realtime";
+	else if (opts->clock_hte)
+		clock = "hte";
+
+	if (clock)
+		g_variant_builder_add_value(&builder,
+			g_variant_new("{sv}", "event-clock",
+				      g_variant_new_string(clock)));
+
+	line_settings = g_variant_builder_end(&builder);
+
+	g_variant_builder_init(&builder, G_VARIANT_TYPE_TUPLE);
+	g_variant_builder_add_value(&builder, g_variant_ref(line_offsets));
+	g_variant_builder_add_value(&builder, g_variant_ref(line_settings));
+	line_config = g_variant_builder_end(&builder);
+
+	g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
+	g_variant_builder_add_value(&builder, g_variant_ref(line_config));
+	line_configs = g_variant_builder_end(&builder);
+
+	if (opts->output_values) {
+		g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
+		for (i = 0; i < opts->output_values->len; i++) {
+			g_variant_builder_add(&builder, "i",
+					g_array_index(opts->output_values,
+						      gint, i));
+		}
+		output_values = g_variant_builder_end(&builder);
+	} else {
+		output_values = g_variant_new("ai", opts->output_values);
+	}
+
+	g_variant_builder_init(&builder, G_VARIANT_TYPE_TUPLE);
+	g_variant_builder_add_value(&builder, g_variant_ref(line_configs));
+	g_variant_builder_add_value(&builder, g_variant_ref(output_values));
+
+	return g_variant_ref_sink(g_variant_builder_end(&builder));
+}
dbus/client/common.h new file +203 -0

203 lines not on this page · raw message

dbus/client/detect.c new file +53 -0

53 lines not on this page · raw message

dbus/client/find.c new file +66 -0

66 lines not on this page · raw message

dbus/client/get.c new file +210 -0

210 lines not on this page · raw message

dbus/client/gpiocli.c new file +174 -0

174 lines not on this page · raw message

dbus/client/info.c new file +184 -0

184 lines not on this page · raw message

dbus/client/monitor.c new file +191 -0

191 lines not on this page · raw message

dbus/client/notify.c new file +295 -0

295 lines not on this page · raw message

dbus/client/reconfigure.c new file +74 -0

74 lines not on this page · raw message

dbus/client/release.c new file +62 -0

62 lines not on this page · raw message

dbus/client/request.c new file +249 -0

249 lines not on this page · raw message

dbus/client/requests.c new file +71 -0

71 lines not on this page · raw message

dbus/client/set.c new file +170 -0

170 lines not on this page · raw message

dbus/client/wait.c new file +188 -0

188 lines not on this page · raw message

Signature · 1 line
2.40.1
#22 lore raw

PATCH 16/18 dbus: client: add tests

In reply to Bartosz Golaszewski

From: Bartosz Golaszewski <[email protected]>

Add a set of bash tests of the command-line DBus client.

---
 dbus/client/gpiocli-test.bash | 1439 +++++++++++++++++++++++++++++++++
 1 file changed, 1439 insertions(+)
 create mode 100755 dbus/client/gpiocli-test.bash
dbus/client/gpiocli-test.bash new file +1439 -0
new file mode 100755
index 0000000..9bad8f2
@@ -0,0 +1,1439 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2023-2024 Bartosz Golaszewski <[email protected]>
+
+#
+# Test cases for gpiocli utility. This test-suite assumes that gpio-manager
+# is already running.
+#
+
+SOURCE_DIR="$(dirname ${BASH_SOURCE[0]})"
+
+wait_for_sim() {
+	COUNTER=100
+
+	while true
+	do
+		gdbus call --system --dest io.gpiod1 \
+			--object-path /io/gpiod1/chips/$1 \
+			--method org.freedesktop.DBus.Peer.Ping > /dev/null 2>&1 && break
+		sleep 0.01
+		COUNTER=$(expr $COUNTER - 1)
+		if [ $COUNTER -eq 0 ]
+		then
+			fail "error waiting for the GPIO sim chip to be exported on the bus"
+			return 1
+		fi
+	done
+}
+
+# Create a simulated GPIO chip and wait until it's exported by the gpio-manager.
+gpiosim_chip_dbus() {
+	gpiosim_chip $*
+	wait_for_sim ${GPIOSIM_CHIP_NAME[$1]}
+}
+
+gpiodbus_release_request() {
+	run_prog gpiocli release $1
+	status_is 0
+}
+
+gpiodbus_check_request() {
+	run_prog gpiocli requests
+	status_is 0
+	num_lines_is 1
+	output_regex_match $1
+}
+
+#
+# gpiocli wait test cases
+#
+
+test_wait_for_manager() {
+	run_prog gpiocli wait
+	status_is 0
+	num_lines_is 0
+}
+
+test_wait_for_chip() {
+	dut_run gpiocli wait --chip=foobar
+	sleep 0.01
+
+	gpiosim_chip_dbus sim0 label=foobar
+	dut_flush
+	dut_read
+
+	status_is 0
+	num_lines_is 0
+}
+
+test_wait_timeout() {
+	run_prog gpiocli wait --chip=foobar --timeout=100ms
+	status_is 1
+	num_lines_is 1
+	output_regex_match ".*: wait timed out!"
+}
+
+#
+# gpiocli detect test cases
+#
+
+test_detect_all_chips() {
+	gpiosim_chip_dbus sim0 num_lines=4
+	gpiosim_chip_dbus sim1 num_lines=8
+	gpiosim_chip_dbus sim2 num_lines=16
+
+	local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+	local sim1=${GPIOSIM_CHIP_NAME[sim1]}
+	local sim2=${GPIOSIM_CHIP_NAME[sim2]}
+	local sim0dev=${GPIOSIM_DEV_NAME[sim0]}
+	local sim1dev=${GPIOSIM_DEV_NAME[sim1]}
+	local sim2dev=${GPIOSIM_DEV_NAME[sim2]}
+
+	run_prog gpiocli detect
+
+	status_is 0
+	output_contains_line "$sim0 [${sim0dev}-node0] (4 lines)"
+	output_contains_line "$sim1 [${sim1dev}-node0] (8 lines)"
+	output_contains_line "$sim2 [${sim2dev}-node0] (16 lines)"
+
+	# ignoring symlinks
+	local initial_output=$output
+	gpiosim_chip_symlink sim1 /dev
+
+	run_prog gpiocli detect
+
+	status_is 0
+	output_is "$initial_output"
+}
+
+test_detect_one_chip() {
+	gpiosim_chip_dbus sim0 num_lines=4
+	gpiosim_chip_dbus sim1 num_lines=8
+	gpiosim_chip_dbus sim2 num_lines=16
+
+	local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+	local sim2=${GPIOSIM_CHIP_NAME[sim2]}
+	local sim0dev=${GPIOSIM_DEV_NAME[sim0]}
+	local sim2dev=${GPIOSIM_DEV_NAME[sim2]}
+
+	# by name
+	run_prog gpiocli detect $sim0
+
+	status_is 0
+	output_contains_line "$sim0 [${sim0dev}-node0] (4 lines)"
+	num_lines_is 1
+
+	# by number
+	run_prog gpiocli detect $(gpiosim_chip_number sim2)
+
+	status_is 0
+	output_contains_line "$sim2 [${sim2dev}-node0] (16 lines)"
+	num_lines_is 1
+}
+
+test_detect_multiple_chips() {
+	gpiosim_chip_dbus sim0 num_lines=4
+	gpiosim_chip_dbus sim1 num_lines=8
+	gpiosim_chip_dbus sim2 num_lines=16
+
+	local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+	local sim1=${GPIOSIM_CHIP_NAME[sim1]}
+	local sim2=${GPIOSIM_CHIP_NAME[sim2]}
+	local sim0dev=${GPIOSIM_DEV_NAME[sim0]}
+	local sim1dev=${GPIOSIM_DEV_NAME[sim1]}
+	local sim2dev=${GPIOSIM_DEV_NAME[sim2]}
+
+	run_prog gpiocli detect $sim0 $sim1 $sim2
+
+	status_is 0
+	output_contains_line "$sim0 [${sim0dev}-node0] (4 lines)"
+	output_contains_line "$sim1 [${sim1dev}-node0] (8 lines)"
+	output_contains_line "$sim2 [${sim2dev}-node0] (16 lines)"
+	num_lines_is 3
+}
+
+test_detect_with_nonexistent_chip() {
+	run_prog gpiocli detect nonexistent_chip
+
+	status_is 1
+	output_regex_match ".*: No such chip: 'nonexistent_chip'"
+}
+
+#
+# gpiocli info test cases
+#
+
+test_info_all_chips() {
+	gpiosim_chip_dbus sim0 num_lines=4
+	gpiosim_chip_dbus sim1 num_lines=8
+
+	run_prog gpiocli info
+
+	status_is 0
+	output_contains_line "${GPIOSIM_CHIP_NAME[sim0]} - 4 lines:"
+	output_contains_line "${GPIOSIM_CHIP_NAME[sim1]} - 8 lines:"
+	output_regex_match "\\s+line\\s+0:\\s+unnamed\\s+\[input\]"
+	output_regex_match "\\s+line\\s+7:\\s+unnamed\\s+\[input\]"
+}
+
+test_info_one_chip() {
+	gpiosim_chip_dbus sim0 num_lines=4
+	gpiosim_chip_dbus sim1 num_lines=8
+	gpiosim_chip_dbus sim2 num_lines=12
+
+	local sim1=${GPIOSIM_CHIP_NAME[sim1]}
+
+	# by name
+	run_prog gpiocli info -c $sim1
+
+	status_is 0
+	output_contains_line "$sim1 - 8 lines:"
+	output_regex_match "\\s+line\\s+2:\\s+unnamed\\s+\[input\]"
+	num_lines_is 9
+
+	# by number
+	run_prog gpiocli info -c $(gpiosim_chip_number sim1)
+
+	status_is 0
+	output_contains_line "$sim1 - 8 lines:"
+	output_regex_match "\\s+line\\s+2:\\s+unnamed\\s+\[input\]"
+	num_lines_is 9
+}
+
+test_info_one_line_by_name() {
+	gpiosim_chip_dbus sim0 num_lines=8 line_name=3:foo line_name=5:bar
+	gpiosim_chip_dbus sim1 num_lines=8 line_name=2:baz
+
+	local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+	run_prog gpiocli info bar
+
+	status_is 0
+	output_regex_match "$sim0\\s+5:\\s+\"bar\"\\s+\[input\]"
+	num_lines_is 1
+}
+
+test_info_one_line_by_chip_and_offset() {
+	gpiosim_chip_dbus sim0 num_lines=8
+	gpiosim_chip_dbus sim1 num_lines=8
+
+	local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+	run_prog gpiocli info -c $sim0 3
+
+	status_is 0
+	output_regex_match "$sim0\\s+3:\\s+unnamed\\s+\[input\]"
+	num_lines_is 1
+}
+
+test_info_two_lines_by_chip_offset_and_name() {
+	gpiosim_chip_dbus sim0 num_lines=8 line_name=3:foo line_name=5:bar
+	gpiosim_chip_dbus sim1 num_lines=8 line_name=2:baz
+
+	local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+	run_prog gpiocli info -c $sim0 3 bar
+
+	status_is 0
+	output_regex_match "$sim0\\s+3:\\s+\"foo\"\\s+\[input\]"
+	output_regex_match "$sim0\\s+5:\\s+\"bar\"\\s+\[input\]"
+	num_lines_is 2
+}
+
+test_info_two_lines() {
+	gpiosim_chip_dbus sim0 num_lines=8 line_name=3:foo line_name=5:bar
+	gpiosim_chip_dbus sim1 num_lines=8 line_name=2:baz
+
+	local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+	local sim1=${GPIOSIM_CHIP_NAME[sim1]}
+
+	run_prog gpiocli info bar baz
+
+	status_is 0
+	output_regex_match "$sim0\\s+5:\\s+\"bar\"\\s+\[input\]"
+	output_regex_match "$sim1\\s+2:\\s+\"baz\"\\s+\[input\]"
+	num_lines_is 2
+}
+
+test_info_repeating_lines() {
+	gpiosim_chip_dbus sim0 num_lines=8 line_name=3:foo line_name=5:bar
+	gpiosim_chip_dbus sim1 num_lines=8 line_name=2:baz
+
+	local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+	local sim1=${GPIOSIM_CHIP_NAME[sim1]}
+
+	run_prog gpiocli info baz bar baz
+
+	status_is 0
+	output_regex_match "$sim1\\s+2:\\s+\"baz\"\\s+\[input\]"
+	output_regex_match "$sim0\\s+5:\\s+\"bar\"\\s+\[input\]"
+	output_regex_match "$sim1\\s+2:\\s+\"baz\"\\s+\[input\]"
+	num_lines_is 3
+}
+
+#
+# gpiocli find test cases
+#
+
+test_map_existing_line() {
+	gpiosim_chip_dbus sim0 num_lines=4 line_name=3:baz
+	gpiosim_chip_dbus sim1 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	run_prog gpiocli find bar
+
+	status_is 0
+	num_lines_is 1
+	output_is "${GPIOSIM_CHIP_NAME[sim1]} 5"
+}
+
+test_map_nonexistent_line() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	run_prog gpiocli find foobar
+
+	status_is 1
+	num_lines_is 1
+	output_regex_match ".*: line 'foobar' not found"
+}
+
+#
+# gpiocli request test cases
+#
+
+test_request_invalid_arguments() {
+	gpiosim_chip_dbus sim0 num_lines=8 line_name=3:foo
+
+	run_prog gpiocli request --input --output foo
+	status_is 1
+	output_regex_match ".*: --input and --output are mutually exclusive"
+
+	run_prog gpiocli request --output --both-edges foo
+	status_is 1
+	output_regex_match ".*: monitoring edges is only possible in input mode"
+
+	run_prog gpiocli request --output --open-source --open-drain foo
+	status_is 1
+	output_regex_match ".*: --push-pull, --open-drain and --open-source are mutually exclusive"
+
+	run_prog gpiocli request --input --open-source foo
+	status_is 1
+	output_regex_match ".*: --push-pull, --open-drain and --open-source are only available in output mode"
+
+	run_prog gpiocli request --input --bias-disabled --pull-down foo
+	status_is 1
+	output_regex_match ".*: --pull-up, --pull-down and --bias-disabled are mutually exclusive"
+
+	run_prog gpiocli request --input --debounce-period=3000 foo
+	status_is 1
+	output_regex_match ".*: --debounce-period can only be used with edge-detection enabled"
+
+	run_prog gpiocli request --input --clock-monotonic foo
+	status_is 1
+	output_regex_match ".*: --clock-monotonic, --clock-realtime and --clock-hte can only be used with edge detection enabled"
+
+	run_prog gpiocli request --input --clock-monotonic --clock-realtime foo
+	status_is 1
+	output_regex_match ".*: --clock-monotonic, --clock-realtime and --clock-hte are mutually exclusive"
+}
+
+test_request_one_line_by_name() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	run_prog gpiocli request --input bar
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	gpiodbus_check_request "$request\\s+\(${GPIOSIM_CHIP_NAME[sim0]}\)\\s+Offsets:\\s+\[5\]"
+	gpiodbus_release_request $request
+}
+
+test_request_one_line_by_chip_and_offset() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+	run_prog gpiocli request --input --chip=$sim0 4
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	gpiodbus_check_request "$request\\s+\(${GPIOSIM_CHIP_NAME[sim0]}\)\\s+Offsets:\\s+\[4\]"
+	gpiodbus_release_request $request
+}
+
+test_request_from_different_chips() {
+	gpiosim_chip_dbus sim0 num_lines=8 line_name=1:foo line_name=5:bar
+	gpiosim_chip_dbus sim1 num_lines=4 line_name=1:xyz
+
+	run_prog gpiocli request --input foo xyz
+	status_is 1
+	output_regex_match ".*: all requested lines must belong to the same chip"
+}
+
+test_multiple_requests() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+	run_prog gpiocli request --input --chip=$sim0 0
+	status_is 0
+	num_lines_is 1
+	local request0=$output
+
+	run_prog gpiocli request --output --chip=$sim0 1 2
+	status_is 0
+	num_lines_is 1
+	local request1=$output
+
+	run_prog gpiocli request --chip=$sim0 5
+	status_is 0
+	num_lines_is 1
+	local request2=$output
+
+	run_prog gpiocli requests
+	status_is 0
+	num_lines_is 3
+	output_contains_line "$request0 ($sim0) Offsets: [0]"
+	output_contains_line "$request1 ($sim0) Offsets: [1, 2]"
+	output_contains_line "$request2 ($sim0) Offsets: [5]"
+
+	gpiodbus_release_request $request0
+	gpiodbus_release_request $request1
+	gpiodbus_release_request $request2
+}
+
+test_request_multiple_lines_by_names() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	run_prog gpiocli request --input foo xyz bar
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	gpiodbus_check_request "$request\\s+\(${GPIOSIM_CHIP_NAME[sim0]}\)\\s+Offsets:\\s+\[1, 11, 5\]"
+	gpiodbus_release_request $request
+}
+
+test_request_multiple_lines_by_chip_number_by_name_and_offset() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+	run_prog gpiocli request --input -c $(gpiosim_chip_number sim0) xyz 0 foo 15
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	gpiodbus_check_request "$request\\s+\(${GPIOSIM_CHIP_NAME[sim0]}\)\\s+Offsets:\\s+\[11, 0, 1, 15\]"
+	gpiodbus_release_request $request
+}
+
+test_request_with_consumer_name() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+	run_prog gpiocli request --input --consumer='foobar' foo
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	run_prog gpiocli info foo
+	status_is 0
+	num_lines_is 1
+	output_regex_match "gpiochip1\\s+1:\\s+\"foo\"\\s+\[used,consumer=\"foobar\",managed=\"request0\",input\]"
+
+	gpiodbus_release_request $request
+}
+
+test_request_with_consumer_name_with_whitespaces() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+	run_prog gpiocli request --input --consumer='foo bar' foo
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	run_prog gpiocli info foo
+	status_is 0
+	num_lines_is 1
+	output_regex_match "gpiochip1\\s+1:\\s+\"foo\"\\s+\[used,consumer=\"foo bar\",managed=\"request0\",input\]"
+
+	gpiodbus_release_request $request
+}
+
+test_request_active_low() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+	run_prog gpiocli request --input --active-low foo
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	run_prog gpiocli info foo
+	status_is 0
+	num_lines_is 1
+	output_regex_match "$sim0\\s+1:\\s+\"foo\"\\s+\[used,consumer=\"gpio-manager\",managed=\"$request\",active-low,input\]"
+
+	run_prog gpiocli get foo
+	status_is 0
+	num_lines_is 1
+	output_is "\"foo\"=active"
+
+	gpiodbus_release_request $request
+}
+
+test_request_pull_up() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+	run_prog gpiocli request --input --pull-up foo
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	run_prog gpiocli info foo
+	status_is 0
+	num_lines_is 1
+	output_regex_match "$sim0\\s+1:\\s+\"foo\"\\s+\[used,consumer=\"gpio-manager\",managed=\"$request\",bias=pull-up,input\]"
+
+	run_prog gpiocli get foo
+	status_is 0
+	num_lines_is 1
+	output_is "\"foo\"=active"
+
+	gpiodbus_release_request $request
+}
+
+test_request_pull_down() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+	run_prog gpiocli request --input --pull-down foo
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	run_prog gpiocli info foo
+	status_is 0
+	num_lines_is 1
+	output_regex_match "$sim0\\s+1:\\s+\"foo\"\\s+\[used,consumer=\"gpio-manager\",managed=\"$request\",bias=pull-down,input\]"
+
+	run_prog gpiocli get foo
+	status_is 0
+	num_lines_is 1
+	output_is "\"foo\"=inactive"
+
+	gpiodbus_release_request $request
+}
+
+test_request_pull_bias_disabled() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+	run_prog gpiocli request --input --bias-disabled foo
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	run_prog gpiocli info foo
+	status_is 0
+	num_lines_is 1
+	output_regex_match "$sim0\\s+1:\\s+\"foo\"\\s+\[used,consumer=\"gpio-manager\",managed=\"$request\",bias=disabled,input\]"
+
+	run_prog gpiocli get foo
+	status_is 0
+	num_lines_is 1
+	output_is "\"foo\"=inactive"
+
+	gpiodbus_release_request $request
+}
+
+test_request_drive_push_pull() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+	run_prog gpiocli request --output --push-pull foo
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	run_prog gpiocli info foo
+	status_is 0
+	num_lines_is 1
+	output_regex_match "$sim0\\s+1:\\s+\"foo\"\\s+\[used,consumer=\"gpio-manager\",managed=\"$request\",output,push-pull\]"
+
+	gpiodbus_release_request $request
+}
+
+test_request_drive_open_drain() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+	run_prog gpiocli request --output --open-drain foo
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	run_prog gpiocli info foo
+	status_is 0
+	num_lines_is 1
+	output_regex_match "$sim0\\s+1:\\s+\"foo\"\\s+\[used,consumer=\"gpio-manager\",managed=\"$request\",output,open-drain\]"
+
+	gpiodbus_release_request $request
+}
+
+test_request_drive_open_source() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+	run_prog gpiocli request --output --open-source foo
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	run_prog gpiocli info foo
+	status_is 0
+	num_lines_is 1
+	output_regex_match "$sim0\\s+1:\\s+\"foo\"\\s+\[used,consumer=\"gpio-manager\",managed=\"$request\",output,open-source\]"
+
+	gpiodbus_release_request $request
+}
+
+test_request_edge_falling() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+	run_prog gpiocli request --input --falling-edge foo
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	run_prog gpiocli info foo
+	status_is 0
+	num_lines_is 1
+	output_regex_match "$sim0\\s+1:\\s+\"foo\"\\s+\[used,consumer=\"gpio-manager\",managed=\"$request\",edges=falling,event-clock=monotonic,input\]"
+
+	gpiodbus_release_request $request
+}
+
+test_request_edge_rising() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+	run_prog gpiocli request --input --rising-edge foo
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	run_prog gpiocli info foo
+	status_is 0
+	num_lines_is 1
+	output_regex_match "$sim0\\s+1:\\s+\"foo\"\\s+\[used,consumer=\"gpio-manager\",managed=\"$request\",edges=rising,event-clock=monotonic,input\]"
+
+	gpiodbus_release_request $request
+}
+
+test_request_edge_both() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+	run_prog gpiocli request --input --both-edges foo
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	run_prog gpiocli info foo
+	status_is 0
+	num_lines_is 1
+	output_regex_match "$sim0\\s+1:\\s+\"foo\"\\s+\[used,consumer=\"gpio-manager\",managed=\"$request\",edges=both,event-clock=monotonic,input\]"
+
+	gpiodbus_release_request $request
+}
+
+test_request_edge_falling_and_rising() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+	run_prog gpiocli request --input --falling-edge --rising-edge foo
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	run_prog gpiocli info foo
+	status_is 0
+	num_lines_is 1
+	output_regex_match "$sim0\\s+1:\\s+\"foo\"\\s+\[used,consumer=\"gpio-manager\",managed=\"$request\",edges=both,event-clock=monotonic,input\]"
+
+	gpiodbus_release_request $request
+}
+
+test_request_edge_with_debounce_period() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+	run_prog gpiocli request --input --falling-edge --rising-edge --debounce-period=4000 foo
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	run_prog gpiocli info foo
+	status_is 0
+	num_lines_is 1
+	output_regex_match "$sim0\\s+1:\\s+\"foo\"\\s+\[used,consumer=\"gpio-manager\",managed=\"$request\",edges=both,event-clock=monotonic,debounce-period=4000,input\]"
+
+	gpiodbus_release_request $request
+}
+
+test_request_edge_with_realtime_clock() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+	run_prog gpiocli request --input --falling-edge --rising-edge --clock-realtime foo
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	run_prog gpiocli info foo
+	status_is 0
+	num_lines_is 1
+	output_regex_match "$sim0\\s+1:\\s+\"foo\"\\s+\[used,consumer=\"gpio-manager\",managed=\"$request\",edges=both,event-clock=realtime,input\]"
+
+	gpiodbus_release_request $request
+}
+
+test_request_with_output_values() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz line_name=9:abc
+
+	run_prog gpiocli request --output foo=active bar=inactive xyz=1
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	run_prog gpiocli get -r $request
+	status_is 0
+	num_lines_is 1
+	output_regex_match "\"1\"=active \"5\"=inactive \"11\"=active"
+
+	gpiodbus_release_request $request
+}
+
+test_request_output_values_input_mode() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	run_prog gpiocli request --input foo=active bar=inactive xyz=1
+	status_is 1
+	num_lines_is 3
+	output_regex_match ".*: Output values can only be set in output mode"
+}
+
+test_request_output_values_invalid_format() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	run_prog gpiocli request --output foo=active bar=foobar xyz=1
+	status_is 1
+	num_lines_is 3
+	output_regex_match ".*: invalid output value: 'foobar'"
+}
+
+#
+# gpiocli reconfigure test cases
+#
+
+test_reconfigure_from_output_to_input() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	run_prog gpiocli request --output foo=active bar=inactive xyz=1
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	run_prog gpiocli info foo
+	status_is 0
+	num_lines_is 1
+	output_regex_match "$sim0\\s+1:\\s+\"foo\"\\s+\[used,consumer=\"gpio-manager\",managed=\"$request\",output,push-pull\]"
+
+	run_prog gpiocli reconfigure --input $request
+	status_is 0
+	num_lines_is 1
+
+	run_prog gpiocli info foo
+	status_is 0
+	num_lines_is 1
+	output_regex_match "$sim0\\s+1:\\s+\"foo\"\\s+\[used,consumer=\"gpio-manager\",managed=\"$request\",input\]"
+
+	gpiodbus_release_request $request
+}
+
+test_reconfigure_from_input_to_output_with_values() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	run_prog gpiocli request --input foo bar xyz
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	run_prog gpiocli info foo
+	status_is 0
+	num_lines_is 1
+	output_regex_match "$sim0\\s+1:\\s+\"foo\"\\s+\[used,consumer=\"gpio-manager\",managed=\"$request\",input\]"
+
+	run_prog gpiocli reconfigure --output $request 1 0 active
+	status_is 0
+	num_lines_is 1
+
+	run_prog gpiocli info foo
+	status_is 0
+	num_lines_is 1
+	output_regex_match "$sim0\\s+1:\\s+\"foo\"\\s+\[used,consumer=\"gpio-manager\",managed=\"$request\",output,push-pull\]"
+
+	run_prog gpiocli get foo bar xyz
+	status_is 0
+	num_lines_is 1
+	output_is "\"foo\"=active \"bar\"=inactive \"xyz\"=active"
+
+	gpiodbus_release_request $request
+}
+
+test_reconfigure_fails_with_wrong_number_of_output_values() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	run_prog gpiocli request --input foo bar xyz
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	run_prog gpiocli info foo
+	status_is 0
+	num_lines_is 1
+	output_regex_match "$sim0\\s+1:\\s+\"foo\"\\s+\[used,consumer=\"gpio-manager\",managed=\"$request\",input\]"
+
+	run_prog gpiocli reconfigure --output $request 1 0
+	status_is 1
+	num_lines_is 3
+	output_regex_match ".*: The number of output values must correspond to the number of lines in the request"
+
+	run_prog gpiocli reconfigure --output $request 1 0 1 0
+	status_is 1
+	num_lines_is 3
+
+	gpiodbus_release_request $request
+}
+
+#
+# gpiocli release test cases
+#
+
+test_release_nonexistent_request() {
+	run_prog gpiocli release request0
+	status_is 1
+	output_regex_match ".*: No such request: 'request0'"
+}
+
+#
+# gpiocli get test cases
+#
+
+test_get_value_for_unmanaged_line() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	run_prog gpiocli get foo
+	status_is 1
+	num_lines_is 1
+	output_regex_match ".*: Line 'foo' not managed by gpio-manager, must be requested first"
+}
+
+test_get_one_value_by_name() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	run_prog gpiocli request --input foo bar
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	gpiosim_set_pull sim0 1 pull-up
+
+	run_prog gpiocli get foo
+	status_is 0
+	num_lines_is 1
+	output_is "\"foo\"=active"
+
+	gpiodbus_release_request $request
+}
+
+test_get_multiple_values_by_names() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	run_prog gpiocli request --input foo xyz bar
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	gpiosim_set_pull sim0 1 pull-up
+	gpiosim_set_pull sim0 5 pull-up
+	gpiosim_set_pull sim0 11 pull-down
+
+	run_prog gpiocli get xyz bar foo
+	status_is 0
+	num_lines_is 1
+	output_is "\"xyz\"=inactive \"bar\"=active \"foo\"=active"
+
+	gpiodbus_release_request $request
+}
+
+test_get_one_value_by_request_and_offset() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	run_prog gpiocli request --input xyz foo
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	gpiosim_set_pull sim0 1 pull-up
+
+	run_prog gpiocli get --request=$request 1
+	status_is 0
+	num_lines_is 1
+	output_is "\"1\"=active"
+
+	gpiodbus_release_request $request
+}
+
+test_get_multiple_values_by_request_and_offsets() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	run_prog gpiocli request --input foo bar xyz
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	gpiosim_set_pull sim0 1 pull-up
+	gpiosim_set_pull sim0 5 pull-down
+	gpiosim_set_pull sim0 11 pull-up
+
+	run_prog gpiocli get --request=$request 11 1 5
+	status_is 0
+	num_lines_is 1
+	output_is "\"11\"=active \"1\"=active \"5\"=inactive"
+
+	gpiodbus_release_request $request
+}
+
+test_get_multiple_values_by_request_names_and_offsets() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	run_prog gpiocli request --input foo bar xyz
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	gpiosim_set_pull sim0 1 pull-up
+	gpiosim_set_pull sim0 5 pull-down
+	gpiosim_set_pull sim0 11 pull-up
+
+	run_prog gpiocli get --request=$request xyz 1 5
+	status_is 0
+	num_lines_is 1
+	output_is "\"xyz\"=active \"1\"=active \"5\"=inactive"
+
+	gpiodbus_release_request $request
+}
+
+test_get_all_values_for_request() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	run_prog gpiocli request --input foo bar xyz
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	gpiosim_set_pull sim0 1 pull-up
+	gpiosim_set_pull sim0 5 pull-down
+	gpiosim_set_pull sim0 11 pull-up
+
+	run_prog gpiocli get --request=$request
+	status_is 0
+	num_lines_is 1
+	output_is "\"1\"=active \"5\"=inactive \"11\"=active"
+
+	gpiodbus_release_request $request
+}
+
+test_get_unquoted_output() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	run_prog gpiocli request --input foo bar xyz
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	gpiosim_set_pull sim0 1 pull-up
+	gpiosim_set_pull sim0 5 pull-down
+	gpiosim_set_pull sim0 11 pull-up
+
+	run_prog gpiocli get --unquoted --request=$request xyz 1 5
+	status_is 0
+	num_lines_is 1
+	output_is "xyz=active 1=active 5=inactive"
+
+	gpiodbus_release_request $request
+}
+
+test_get_numeric_output() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	run_prog gpiocli request --input foo bar xyz
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	gpiosim_set_pull sim0 1 pull-up
+	gpiosim_set_pull sim0 5 pull-down
+	gpiosim_set_pull sim0 11 pull-up
+
+	run_prog gpiocli get --numeric --request=$request xyz 1 5
+	status_is 0
+	num_lines_is 1
+	output_is "\"xyz\"=1 \"1\"=1 \"5\"=0"
+
+	gpiodbus_release_request $request
+}
+
+#
+# gpiocli set test cases
+#
+
+test_set_value_for_unmanaged_line() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	run_prog gpiocli set foo=active
+	status_is 1
+	num_lines_is 1
+	output_regex_match ".*: Line 'foo' not managed by gpio-manager, must be requested first"
+}
+
+test_set_one_value_with_invalid_arguments() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	run_prog gpiocli request --output foo bar xyz
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	run_prog gpiocli set bar=wrong
+	status_is 1
+	num_lines_is 3
+	output_regex_match ".*: invalid output value: 'wrong'"
+
+	run_prog gpiocli set bar=
+	status_is 1
+	num_lines_is 3
+	output_regex_match ".*: invalid output value: ''"
+
+	run_prog gpiocli set bar
+	status_is 1
+	num_lines_is 3
+	output_regex_match ".*: line must have a single value assigned"
+
+	gpiodbus_release_request $request
+}
+
+test_set_one_value_by_name() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	run_prog gpiocli request --output foo bar xyz
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	run_prog gpiocli get --request=$request
+	status_is 0
+	num_lines_is 1
+	output_is "\"1\"=inactive \"5\"=inactive \"11\"=inactive"
+
+	run_prog gpiocli set bar=active
+	status_is 0
+
+	run_prog gpiocli get --request=$request
+	status_is 0
+	num_lines_is 1
+	output_is "\"1\"=inactive \"5\"=active \"11\"=inactive"
+
+	gpiodbus_release_request $request
+}
+
+test_set_multiple_values_by_names() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	run_prog gpiocli request --output foo bar xyz
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	run_prog gpiocli get --request=$request
+	status_is 0
+	num_lines_is 1
+	output_is "\"1\"=inactive \"5\"=inactive \"11\"=inactive"
+
+	run_prog gpiocli set bar=active foo=active xyz=0
+	status_is 0
+
+	run_prog gpiocli get --request=$request
+	status_is 0
+	num_lines_is 1
+	output_is "\"1\"=active \"5\"=active \"11\"=inactive"
+
+	gpiodbus_release_request $request
+}
+
+test_set_one_value_by_request_and_offset() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	run_prog gpiocli request --output foo bar xyz
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	run_prog gpiocli get --request=$request
+	status_is 0
+	num_lines_is 1
+	output_is "\"1\"=inactive \"5\"=inactive \"11\"=inactive"
+
+	run_prog gpiocli set -r $request 5=1
+	status_is 0
+
+	run_prog gpiocli get --request=$request
+	status_is 0
+	num_lines_is 1
+	output_is "\"1\"=inactive \"5\"=active \"11\"=inactive"
+
+	gpiodbus_release_request $request
+}
+
+test_set_multiple_values_by_request_and_offsets() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	run_prog gpiocli request --output foo bar xyz
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	run_prog gpiocli get --request=$request
+	status_is 0
+	num_lines_is 1
+	output_is "\"1\"=inactive \"5\"=inactive \"11\"=inactive"
+
+	run_prog gpiocli set --request=$request 11=active 5=1 1=0
+	status_is 0
+
+	run_prog gpiocli get --request=$request
+	status_is 0
+	num_lines_is 1
+	output_is "\"1\"=inactive \"5\"=active \"11\"=active"
+
+	gpiodbus_release_request $request
+}
+
+test_set_multiple_values_by_request_names_and_offsets() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	run_prog gpiocli request --output foo bar xyz
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	run_prog gpiocli get --request=$request
+	status_is 0
+	num_lines_is 1
+	output_is "\"1\"=inactive \"5\"=inactive \"11\"=inactive"
+
+	run_prog gpiocli set --request=$request xyz=active 5=1 foo=0
+	status_is 0
+
+	run_prog gpiocli get --request=$request
+	status_is 0
+	num_lines_is 1
+	output_is "\"1\"=inactive \"5\"=active \"11\"=active"
+
+	gpiodbus_release_request $request
+}
+
+#
+# gpiocli notify test cases
+#
+
+test_notify_print_initial_info_by_name() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+	run_prog gpiocli request --input --both-edges --clock-realtime --debounce-period=5000 foo bar xyz
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	dut_run gpiocli notify foo
+	dut_read
+
+	output_is "$sim0 - 1 (\"foo\"): [input,used,consumer=\"gpio-manager\",both-edges,realtime-clockdebounced,debounce-period=5000,managed,request=\"request0\"]"
+
+	gpiodbus_release_request $request
+}
+
+test_notify_print_initial_info_by_chip_and_offset() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+	run_prog gpiocli request --output --open-drain --active-low foo bar xyz
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	dut_run gpiocli notify --chip=$sim0 5
+	dut_read
+
+	output_is "$sim0 - 5 (\"bar\"): [output,used,consumer=\"gpio-manager\",open-drain,active-low,managed,request=\"request0\"]"
+
+	gpiodbus_release_request $request
+}
+
+test_notify_print_initial_info_by_chip_name_and_offset_for_multiple_lines() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+	run_prog gpiocli request --output --open-drain --active-low foo bar xyz
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	dut_run gpiocli notify --chip=$sim0 5 foo 11
+
+	dut_read
+	output_regex_match ".*$sim0 - 5 \(\"bar\"\): \[output,used,consumer=\"gpio-manager\",open-drain,active-low,managed,request=\"request0\"\].*"
+	output_regex_match ".*$sim0 - 1 \(\"foo\"\): \[output,used,consumer=\"gpio-manager\",open-drain,active-low,managed,request=\"request0\"\].*"
+	output_regex_match ".*$sim0 - 11 \(\"xyz\"\): \[output,used,consumer=\"gpio-manager\",open-drain,active-low,managed,request=\"request0\"\].*"
+
+	gpiodbus_release_request $request
+}
+
+test_notify_request_event() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+	dut_run gpiocli notify foo bar
+	dut_flush
+
+	run_prog gpiocli request --output --open-drain --active-low foo bar
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	dut_read
+	output_regex_match "$sim0 - 1 \(\"foo\"\): \[active-low=>True\]"
+	output_regex_match "$sim0 - 1 \(\"foo\"\): \[drive=>open-drain\]"
+	output_regex_match "$sim0 - 1 \(\"foo\"\): \[direction=>output\]"
+	output_regex_match "$sim0 - 1 \(\"foo\"\): \[consumer=>\"gpio-manager\"\]"
+	output_regex_match "$sim0 - 1 \(\"foo\"\): \[used=>True\]"
+	output_regex_match "$sim0 - 1 \(\"foo\"\): \[request=>request0\]"
+	output_regex_match "$sim0 - 1 \(\"foo\"\): \[managed=>True\]"
+	output_regex_match "$sim0 - 5 \(\"bar\"\): \[request=>request0\]"
+	output_regex_match "$sim0 - 5 \(\"bar\"\): \[managed=>True\]"
+	output_regex_match "$sim0 - 5 \(\"bar\"\): \[active-low=>True\]"
+	output_regex_match "$sim0 - 5 \(\"bar\"\): \[drive=>open-drain\]"
+	output_regex_match "$sim0 - 5 \(\"bar\"\): \[direction=>output\]"
+	output_regex_match "$sim0 - 5 \(\"bar\"\): \[consumer=>\"gpio-manager\"\]"
+	output_regex_match "$sim0 - 5 \(\"bar\"\): \[used=>True\]"
+
+	gpiodbus_release_request $request
+}
+
+test_notify_release_event() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+	dut_run gpiocli notify foo bar
+
+	run_prog gpiocli request --output --open-drain --active-low foo bar
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	dut_flush
+
+	gpiodbus_release_request $request
+
+	dut_read
+	output_regex_match "$sim0 - 1 \(\"foo\"\): \[active-low=>False\]"
+	output_regex_match "$sim0 - 1 \(\"foo\"\): \[drive=>push-pull\]"
+	output_regex_match "$sim0 - 1 \(\"foo\"\): \[consumer=>\"unused\"\]"
+	output_regex_match "$sim0 - 1 \(\"foo\"\): \[used=>False\]"
+	output_regex_match "$sim0 - 1 \(\"foo\"\): \[request=>N/A\]"
+	output_regex_match "$sim0 - 1 \(\"foo\"\): \[managed=>False\]"
+	output_regex_match "$sim0 - 5 \(\"bar\"\): \[request=>N/A\]"
+	output_regex_match "$sim0 - 5 \(\"bar\"\): \[managed=>False\]"
+	output_regex_match "$sim0 - 5 \(\"bar\"\): \[active-low=>False\]"
+	output_regex_match "$sim0 - 5 \(\"bar\"\): \[drive=>push-pull\]"
+	output_regex_match "$sim0 - 5 \(\"bar\"\): \[consumer=>\"unused\"\]"
+	output_regex_match "$sim0 - 5 \(\"bar\"\): \[used=>False\]"
+}
+
+test_notify_reconfigure_event() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+	dut_run gpiocli notify foo bar
+
+	run_prog gpiocli request --output --open-drain --active-low foo bar
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	dut_flush
+
+	run_prog gpiocli reconfigure --input --pull-up --rising-edge $request
+	status_is 0
+
+	dut_read
+	output_regex_match "$sim0 - 1 \(\"foo\"\): \[active-low=>False\]"
+	output_regex_match "$sim0 - 1 \(\"foo\"\): \[drive=>push-pull\]"
+	output_regex_match "$sim0 - 1 \(\"foo\"\): \[bias=>pull-up\]"
+	output_regex_match "$sim0 - 1 \(\"foo\"\): \[edge=>rising\]"
+	output_regex_match "$sim0 - 1 \(\"foo\"\): \[direction=>input\]"
+	output_regex_match "$sim0 - 5 \(\"bar\"\): \[active-low=>False\]"
+	output_regex_match "$sim0 - 5 \(\"bar\"\): \[drive=>push-pull\]"
+	output_regex_match "$sim0 - 5 \(\"bar\"\): \[bias=>pull-up\]"
+	output_regex_match "$sim0 - 5 \(\"bar\"\): \[edge=>rising\]"
+	output_regex_match "$sim0 - 5 \(\"bar\"\): \[direction=>input\]"
+
+	gpiodbus_release_request $request
+}
+
+#
+# gpiocli monitor test cases
+#
+
+test_monitor_unmanaged_line() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+	run_prog gpiocli monitor foo
+
+	output_regex_match ".*: Line must be managed by gpio-manager in order to be monitored"
+}
+
+test_monitor_one_line_by_name() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+	run_prog gpiocli request --input --both-edges foo
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	dut_run gpiocli monitor foo
+
+	gpiosim_set_pull sim0 1 pull-up
+
+	dut_read
+	output_regex_match "[0-9]+ rising\\s+\"foo\""
+
+	gpiodbus_release_request $request
+}
+
+test_monitor_multiple_lines_by_name() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+	run_prog gpiocli request --input --both-edges foo bar xyz
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	dut_run gpiocli monitor foo xyz
+
+	gpiosim_set_pull sim0 1 pull-up
+	gpiosim_set_pull sim0 5 pull-up # This should be ignored
+	gpiosim_set_pull sim0 11 pull-up
+	gpiosim_set_pull sim0 1 pull-down
+
+	dut_read
+	output_regex_match "[0-9]+ rising\\s+\"foo\""
+	output_regex_match "[0-9]+ rising\\s+\"xyz\""
+	output_regex_match "[0-9]+ falling\\s+\"foo\""
+
+	gpiodbus_release_request $request
+}
+
+test_monitor_one_line_by_request_and_offset() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+	run_prog gpiocli request --input --both-edges foo
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	dut_run gpiocli monitor --request=$request 1
+
+	gpiosim_set_pull sim0 1 pull-up
+
+	dut_read
+	output_regex_match "[0-9]+ rising\\s+\"foo\""
+
+	gpiodbus_release_request $request
+}
+
+test_monitor_all_lines_on_request() {
+	gpiosim_chip_dbus sim0 num_lines=16 line_name=1:foo line_name=5:bar line_name=11:xyz
+
+	local sim0=${GPIOSIM_CHIP_NAME[sim0]}
+
+	run_prog gpiocli request --input --both-edges --chip=$sim0 foo bar xyz 4
+	status_is 0
+	num_lines_is 1
+	local request=$output
+
+	dut_run gpiocli monitor -r $request
+
+	gpiosim_set_pull sim0 1 pull-up
+	gpiosim_set_pull sim0 4 pull-up
+	gpiosim_set_pull sim0 1 pull-down
+
+	dut_read
+	output_regex_match "[0-9]+ rising\\s+\"foo\""
+	output_regex_match "[0-9]+ rising\\s+4"
+	output_regex_match "[0-9]+ falling\\s+\"foo\""
+
+	gpiodbus_release_request $request
+}
+
+. gpiod-bash-test-helper.inc
+check_prog gdbus
+. shunit2
Signature · 1 line
2.40.1
#23 lore raw

PATCH 17/18 README: document the DBus API

In reply to Bartosz Golaszewski

From: Bartosz Golaszewski <[email protected]>

Add information on the DBus API as well as gpio-manager and gpiocli to the README file.

---
 README | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 64 insertions(+)
README +64 -0
index 8e1e1c7..54f2df5 100644
@@ -224,6 +224,70 @@ C library using make, they will be automatically configured to build against the
 build results of the C library. Please refer to bindings/rust/libgpiod/README.md
 for more information.
 
+DBUS
+----
+
+A commonly requested feature for the GPIO character device was state persistence
+after releasing the lines (as a kernel feature) or providing a central authority
+(in user-space) that would be in charge of keeping the lines requested and in a
+certain state (similarily to how the sysfs ABI works). DBus API has been
+provided to address this requirement. We define an interface covering the
+majority of the GPIO chardev's functionality and implement it from both the
+server and client sides in the form of the gpio-manager daemon and the gpiocli
+command-line utility for talking to the manager.
+
+DBus support can be built by passing --enable-dbus to configure. The daemon
+is bundled with a systemd unit file and an example configuration file for the
+io.gpiod1 interface that allows all users to access basic information about the
+GPIOs in the system but only root to request lines or change their values.
+
+With the manager running the user can run gpiocli to control GPIOs by asking
+gpio-manager to act on their behalf:
+
+    # Detect chips in the system.
+    $ gpiocli detect
+    gpiochip0 [INT34C6:00] (463 lines)
+
+    # Request a set of lines. Note that gpiocli exits immediately but the
+    # state of the lines is retained because it's the gpio-manager that
+    # requested them.
+    $ gpiocli request --output foo=active
+    request0
+
+    # Previous invocation printed out the name of the request by which the
+    # caller can refer to it later. All active requests can also be inspected
+    # at any time.
+    $ gpiocli requests
+    request0 (gpiochip1) Offsets: [5]
+
+    # We can print the information about the requested line using the
+    # information above.
+    $ gpiocli info --chip=gpiochip1 5
+    gpiochip1   5:	"foo"		[used,consumer="gpiocli request",managed="request0",output,push-pull]
+
+    # We can now change the value of the line.
+    $ gpiocli set foo=inactive
+
+    # And read it.
+    $ gpiocli get foo
+    "foo"=inactive
+
+    # We can even reconfigure it to input and enable edge-detection.
+    $ gpiocli reconfigure --input --both-edges request0
+
+    # And wait for edge events.
+    $ gpiocli monitor cos
+    21763952894920 rising  "foo"
+
+    # And finally release the request.
+    $ gpiocli release request0
+
+For more information please refer to the output of gpiocli --help as well as
+gpiocli <command> --help which prints detailed info on every available command.
+
+Of course - this being DBus - users can talk to gpio-manager using any DBus
+library available and are not limited to the provided client.
+
 TESTING
 -------
 
Signature · 1 line
2.40.1
#24 lore raw

PATCH 18/18 TODO: drop the DBus daemon from the list

In reply to Bartosz Golaszewski

From: Bartosz Golaszewski <[email protected]>

We now have a full DBus API implementation. Drop this item from the list.

---
 TODO | 17 -----------------
 1 file changed, 17 deletions(-)
TODO +0 -17
index 79a6246..5092f3f 100644
@@ -11,23 +11,6 @@ serve as the starting point.
 
 ==========
 
-* implement dbus API for controlling GPIOs
-
-A common complaint from users about gpioset is that the state of a line is not
-retained once the program exits. While this is precisely the way linux
-character devices work, it's understandable that most users will want some
-centralized way of controlling GPIOs - similar to how sysfs worked.
-
-One of the possible solutions is a DBus API. We need a daemon exposing chips
-and lines as dbus objects and allowing to control and inspect lines using
-dbus methods and monitor them using signals.
-
-As of writing of this document some of the work has already been done and the
-skeleton of the dbus daemon written in C using GLib has already been developed
-and is partially functional.
-
-----------
-
 * implement a simple daemon for controlling GPIOs in C together with a client
   program
 
Signature · 1 line
2.40.1
#25 lore raw
In reply to Bartosz Golaszewski
Quoted 35 lines · Bartosz Golaszewski

From: Bartosz Golaszewski <[email protected]>

This has been in the works for a long time but I'm finally ready to share it with the world. This introduces the DBus API definition and its implementation in the form of a GPIO manager daemon and a companion command-line client as well as GLib bindings to libgpiod which form the base on which the former are built.

Please note that while the functionality is complete, the dbus part still requires some work (bug fixing, corner-cases, general ironing out of all the wrinkles, a lot of code could be refactored too). This is why the series is marked as RFC/RFT. I want to get it out for reviews and first tests before I leave for EOSS and later on vacation. I am aware of some instabilities so don't be surprised by an occasional segfault.

While I split the GLib and DBus code into several commits for easier review, I intend to apply all changes to bindings/glib/ and dbus/ as two big commits in the end as otherwise the split commits are not buildable until all of them are applied.

The main point of interest is the DBus interface definition XML at dbus/lib/io.gpiod1.xml as it is what defines the actual DBus API. Everything else can be considered as implementation details as it's easier to change later than the API that's supposed to be stable once released.

The first two patches expose the test infrastructure we use for the core library and tools to the GLib bindings and dbus code. Next we add the GLib bindings themselves. Not much to discuss here, they cover the entire libgpiod API but wrap it in GObject abstractions and plug into the GLib event loop.

Finally we add the DBus code that's split into the daemon and command-line client. I added some examples to the README and documented the behavior in the help text of the programs as well as documented the interface file with XML comments that gdbus-codegen can parse and use to generate docbook output.

Quoted 5 lines

For DBus, most of the testing happens in the command-line client bash tests. It has a very good coverage of the daemon's code and also allows to run the daemon through valgrind and verify there are no memory leaks and invalid accesses. I still intend to extend the C test-suite for DBus with some corner cases but didn't not have enough time for it.

I might have time to briefly look at this code, but I can't test right away as I have no setup that uses D-Bus, all what I use is a simplest Buildroot + Busybox.

Signature · 2 lines
With Best Regards,
Andy Shevchenko
#26 lore raw
Quoted 10 lines · Andy Shevchenko
Quoted 7 lines · Bartosz Golaszewski

From: Bartosz Golaszewski <[email protected]>

For DBus, most of the testing happens in the command-line client bash tests. It has a very good coverage of the daemon's code and also allows to run the daemon through valgrind and verify there are no memory leaks and invalid accesses. I still intend to extend the C test-suite for DBus with some corner cases but didn't not have enough time for it.

Quoted 4 lines

I might have time to briefly look at this code, but I can't test right away as I have no setup that uses D-Bus, all what I use is a simplest Buildroot + Busybox.

It's my long-term plan to also have some hand-written, binary protocol and a simpler daemon that implements it. But it's going to be a couple years most likely before it happens.

For now: this is aimed at people who use proper distros which typically include systemd and dbus.

Bart

#27 lore raw
In reply to Bartosz Golaszewski

Hello Bartosz!

Quoted 5 lines · Bartosz Golaszewski

This has been in the works for a long time but I'm finally ready to share it with the world. This introduces the DBus API definition and its implementation in the form of a GPIO manager daemon and a companion command-line client as well as GLib bindings to libgpiod which form the base on which the former are built.

Thanks a lot for your efforts!

Quoted 4 lines

Finally we add the DBus code that's split into the daemon and command-line client. I added some examples to the README and documented the behavior in the help text of the programs as well as documented the interface file with XML comments that gdbus-codegen can parse and use to generate docbook output.

To me it looks like the long time anticipated replacement of [persistent] SYSFS userspace API finally landed and one can start thinking about phasing out the API declared obsolete in Linux v4.8 ;-)

I've compiled gpio-manager and gpiocli for TI am625 (arm64) and tested on some HW one may compare to Beagle Play.

To me it looks solid, no problems whatsoever noticed up to now, even though some non-obvious limitations do exist
("gpiocli request: all requested lines must belong to the same chip"), but this probably can either be justified in documentation or improved even after merging.

Will be happy to provide Tested-by: as soon as you send the series for merging!

Signature · 3 lines
Alexander Sverdlin
Siemens AG
www.siemens.com
#28 lore raw
Quoted 12 lines · Alexander

Hello Bartosz!

Quoted 5 lines · Bartosz Golaszewski

This has been in the works for a long time but I'm finally ready to share it with the world. This introduces the DBus API definition and its implementation in the form of a GPIO manager daemon and a companion command-line client as well as GLib bindings to libgpiod which form the base on which the former are built.

Thanks a lot for your efforts!

Quoted 9 lines
Quoted 4 lines

Finally we add the DBus code that's split into the daemon and command-line client. I added some examples to the README and documented the behavior in the help text of the programs as well as documented the interface file with XML comments that gdbus-codegen can parse and use to generate docbook output.

To me it looks like the long time anticipated replacement of [persistent] SYSFS userspace API finally landed and one can start thinking about phasing out the API declared obsolete in Linux v4.8 ;-)

That's precisely the goal.

Quoted 9 lines

I've compiled gpio-manager and gpiocli for TI am625 (arm64) and tested on some HW one may compare to Beagle Play.

To me it looks solid, no problems whatsoever noticed up to now, even though some non-obvious limitations do exist
("gpiocli request: all requested lines must belong to the same chip"), but this probably can either be justified in documentation or improved even after merging.

Awesome, thank you!

Quoted 2 lines

Will be happy to provide Tested-by: as soon as you send the series for merging!

Thanks, please take a look at v2 as well[1]!

Bart

[1] https://lore.kernel.org/linux-gpio/[email protected]/