From patchwork Wed Aug 7 09:10:32 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Bartosz Golaszewski X-Patchwork-Id: 818165 Received: from mail-wm1-f42.google.com (mail-wm1-f42.google.com [209.85.128.42]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9B1891C9DE2 for ; Wed, 7 Aug 2024 09:10:47 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.42 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723021850; cv=none; b=Ezcouh7hK4LVS82bTxGu/7ZNobNzUCv2z38BNyHsQs9+IjAJyjkJf3Hi1ca5dLnpC8TizxjHyvhH9etUoGxP+aSFhRDSLjU1j03NZDKmSIb/B7v92xhdoK8WfpC9n+Z7yyOySkQ5vQKws5/EP/EUI/4MbaPHBvf9uM7pODObtq4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723021850; c=relaxed/simple; bh=MSozu9z5WN0+sALAmnVaDjERWL6k0HXNzEEoXj0vn2s=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=ALujrWmBzhxukRI/qs91vWZ8+vBD8i5igPJ7wrz1cZ0oL8UgI6az16xPty2V3L2qMowEU1QeOO2EuHEuc5Pk6LKeNPyvabWYfJx0Tt3apEq/FVp0gcEDbsDjzA40yXsFRluvNHttXChuN2Mm1VmadcnWPwf59zB7/FIE/325AfA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=bgdev.pl; spf=none smtp.mailfrom=bgdev.pl; dkim=pass (2048-bit key) header.d=bgdev-pl.20230601.gappssmtp.com header.i=@bgdev-pl.20230601.gappssmtp.com header.b=zdWuuIUn; arc=none smtp.client-ip=209.85.128.42 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=bgdev.pl Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=bgdev.pl Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=bgdev-pl.20230601.gappssmtp.com header.i=@bgdev-pl.20230601.gappssmtp.com header.b="zdWuuIUn" Received: by mail-wm1-f42.google.com with SMTP id 5b1f17b1804b1-428f5c0833bso3798405e9.0 for ; Wed, 07 Aug 2024 02:10:47 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bgdev-pl.20230601.gappssmtp.com; s=20230601; t=1723021846; x=1723626646; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=BirZvIipRdEW7YR8FNm+vf9VSBri/u/DJc7N5uiCqcs=; b=zdWuuIUnHJhfqtN/io7Sd3PLpoY4J7bgbY0Mu07WIcqq0OJb5MaNhEQ28GLNjJ9zk9 brdBDJBDA/davUx62IUu/6fs2rB9QJR5mt8wPsrkkCEwedjD2oRO2FKZZVoWonrgsmj2 tlJ4rNZ8VSM3JY0b0w3zmASZo4Y8lAKHOm73iznTlliHwwf9U0AQ63y+6/YqFaqTrm+o As0wqwXPvX2YeNVpFmM/MePH+nlaqn2lWCIh37AwtcJ3ZV54UIXQY1pjn8kQuVPaaiMA JgTKKC1a+KRqWCH9Xu78+e5t8z8MmGyS+4PnBwILkOsJVf4gG5YLoP5E/ec9Z8Qffpxb agwQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1723021846; x=1723626646; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=BirZvIipRdEW7YR8FNm+vf9VSBri/u/DJc7N5uiCqcs=; b=f2tyntXHIQHqLAk3cYRiwpQ4yezQWK6GKQ6SD+hrLujiDd8wSG7whv13tZYtwwWY14 b1nFRRp0WBrKmPTxry17uhKi9p1ayjLhqFaHWU04XoyLjnuIaD3J+gCfNZzk2rvv/o63 6Ea3z9iOjdzqa+GZc6CWg+DQFbTZGGvLtIBoEpmY3PUa87ArS5EbULVlMQKBhrURaJK/ uMfIvQZxzwGRwJb9Fy46GL7DP+fGAthvPsiY4WNewXELLsHRZ7+p951A54xRVjVuX5Xs DYjQhtjwnSmQpFhm4RTMWN4ORD2hdRIMfyEFp4LC8iLE20nQMMYUxzCfg2Bc2cptLDUm N6yA== X-Gm-Message-State: AOJu0Yw/wgFdgBUEjyMFCc4huDD4ZzWrTBw8erwEawICjBLFjn5x5fDv vy3/vY8KGcHWK73wzLRm60PuPsrdrDZVa/P818Gjt4Knp4a7LHufpsL+bVbqdNE= X-Google-Smtp-Source: AGHT+IEst8AJKb6lkcriNC2vHYe8aImHX770h/Jv5TxmDwmYLDy/+HfsYVPiBUpDPm2xy6iKo2b7Kw== X-Received: by 2002:a05:600c:19c7:b0:426:6fb1:6b64 with SMTP id 5b1f17b1804b1-429050c2b96mr11129195e9.7.1723021845438; Wed, 07 Aug 2024 02:10:45 -0700 (PDT) Received: from [127.0.1.1] ([2a01:cb1d:dc:7e00:a3e7:be71:fe4f:c360]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-429059cd2b3sm18936355e9.44.2024.08.07.02.10.44 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 07 Aug 2024 02:10:45 -0700 (PDT) From: Bartosz Golaszewski Date: Wed, 07 Aug 2024 11:10:32 +0200 Subject: [PATCH libgpiod v4 01/18] tests: split out reusable test code into a local static library Precedence: bulk X-Mailing-List: linux-gpio@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20240807-dbus-v4-1-64ea80169e51@linaro.org> References: <20240807-dbus-v4-0-64ea80169e51@linaro.org> In-Reply-To: <20240807-dbus-v4-0-64ea80169e51@linaro.org> To: Linus Walleij , Kent Gibson , Erik Schilling , Phil Howard , Andy Shevchenko , Viresh Kumar , Dan Carpenter , Philip Withnall Cc: linux-gpio@vger.kernel.org, Bartosz Golaszewski , Alexander Sverdlin X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=18656; i=bartosz.golaszewski@linaro.org; h=from:subject:message-id; bh=szXE5TlFuxBsWS+vMeZOwJRtXfQ9fBEF96xW6pIVXxo=; b=owEBbQKS/ZANAwAKARGnLqAUcddyAcsmYgBmszoPSe+aTerEGMLoUZVp8p6DHxylzehDaOfhM /QVlYZRbO6JAjMEAAEKAB0WIQQWnetsC8PEYBPSx58Rpy6gFHHXcgUCZrM6DwAKCRARpy6gFHHX co5kEAC270dxvb2JS0efIdYFXTe1JliuedXAdH8VWW0E0SVq1wI+r7AxCI3jHEw5Fd1lJdEzxeT ZlcmqDMYJnRYRnvnbEsVv1TWsBeJw2f0Wkkn7hi80BYy+Mh/9neSS/2raD2jx6qWDQHCPEpG9mm ulDIxStYjoDawkxL545otlbvtcGnXrmCoidwPTyPceF4lke2P3YiAU+QTaIZle/lvrrT7xWumJh zPPIBj+5o7pNnEid++uNMjnI68Msp1phW3Mp6WWDiZIyCUbtElct0ytDDeTD9/rPDOPiuFhaFoE ucaiJxgHgd+R87MzeVliqitc6A662udH7BqyFG4hHEwV6tvWTYhdTy9iuVizj9xyBJFRSwOdUFd PdpRWMd3ck3VMk9HcCDK7eLoZr7OnywRu/ZxNIpLoK1kPNCh+0jRVy2kSr8xLdIFMfUO7JhNuYW HYjSYjgto2/+LLpyVHeChQszWhWo5RHEafqrscV14cSLIvK7IblfmG2a8+pIYIU8vBunTH+cWRp 0pbrcGezaD1B91KREq28phy3Z3aF8GRCFllHo711EtGlBP9zGYRTZgR5RKcayXrgHZ5cs0daXCq BQ6Gd7UDbWfDySSov8LnCjRZ2nosgvAOtsLUGLm4fenGeA2dZCKBfdOCL0wKWnTRIKIhOQF42PJ RTKSd3eLGGxNu3g== X-Developer-Key: i=bartosz.golaszewski@linaro.org; a=openpgp; fpr=169DEB6C0BC3C46013D2C79F11A72EA01471D772 From: Bartosz Golaszewski 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. Tested-by: Alexander Sverdlin Signed-off-by: Bartosz Golaszewski --- 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-kernel-uapi.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 +-- 22 files changed, 150 insertions(+), 120 deletions(-) diff --git a/configure.ac b/configure.ac index b86eee0..d1f49ac 100644 --- a/configure.ac +++ b/configure.ac @@ -275,6 +275,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 diff --git a/tests/Makefile.am b/tests/Makefile.am index a5e1fe0..c89fd8d 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,25 +1,23 @@ # SPDX-License-Identifier: GPL-2.0-or-later # SPDX-FileCopyrightText: 2017-2022 Bartosz Golaszewski -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 \ diff --git a/tests/gpiod-test-helpers.c b/tests/gpiod-test-helpers.c deleted file mode 100644 index 7e5b396..0000000 --- a/tests/gpiod-test-helpers.c +++ /dev/null @@ -1,41 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* SPDX-FileCopyrightText: 2017-2022 Bartosz Golaszewski */ - -/* - * Testing framework for the core library. - * - * This file contains functions and definitions extending the GLib unit testing - * framework with functionalities necessary to test the libgpiod core C API as - * well as the kernel-to-user-space interface. - */ - -#include "gpiod-test-helpers.h" - -GVariant * -gpiod_test_package_line_names(const 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 *gpiod_test_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)); -} diff --git a/tests/gpiosim-glib/Makefile.am b/tests/gpiosim-glib/Makefile.am new file mode 100644 index 0000000..1c01629 --- /dev/null +++ b/tests/gpiosim-glib/Makefile.am @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski + +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 diff --git a/tests/gpiod-test-sim.c b/tests/gpiosim-glib/gpiosim-glib.c similarity index 93% rename from tests/gpiod-test-sim.c rename to tests/gpiosim-glib/gpiosim-glib.c index ac6c71a..4eaeace 100644 --- a/tests/gpiod-test-sim.c +++ b/tests/gpiosim-glib/gpiosim-glib.c @@ -6,7 +6,7 @@ #include #include -#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)); +} diff --git a/tests/gpiod-test-sim.h b/tests/gpiosim-glib/gpiosim-glib.h similarity index 86% rename from tests/gpiod-test-sim.h rename to tests/gpiosim-glib/gpiosim-glib.h index f6a4bf0..fa76736 100644 --- a/tests/gpiod-test-sim.h +++ b/tests/gpiosim-glib/gpiosim-glib.h @@ -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__ */ diff --git a/tests/harness/Makefile.am b/tests/harness/Makefile.am new file mode 100644 index 0000000..185c00f --- /dev/null +++ b/tests/harness/Makefile.am @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski + +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\" diff --git a/tests/harness/gpiod-test-common.h b/tests/harness/gpiod-test-common.h new file mode 100644 index 0000000..7aaec05 --- /dev/null +++ b/tests/harness/gpiod-test-common.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* SPDX-FileCopyrightText: 2022 Bartosz Golaszewski */ + +#ifndef __GPIOD_TEST_COMMON_H__ +#define __GPIOD_TEST_COMMON_H__ + +#include + +#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__ */ diff --git a/tests/gpiod-test.c b/tests/harness/gpiod-test.c similarity index 100% rename from tests/gpiod-test.c rename to tests/harness/gpiod-test.c diff --git a/tests/gpiod-test.h b/tests/harness/gpiod-test.h similarity index 100% rename from tests/gpiod-test.h rename to tests/harness/gpiod-test.h diff --git a/tests/gpiod-test-helpers.h b/tests/helpers.h similarity index 87% rename from tests/gpiod-test-helpers.h rename to tests/helpers.h index 41791a3..ecb7baf 100644 --- a/tests/gpiod-test-helpers.h +++ b/tests/helpers.h @@ -1,14 +1,12 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -/* SPDX-FileCopyrightText: 2022 Bartosz Golaszewski */ +/* SPDX-FileCopyrightText: 2022 Bartosz Golaszewski */ #ifndef __GPIOD_TEST_HELPERS_H__ #define __GPIOD_TEST_HELPERS_H__ -#include #include #include - -#include "gpiod-test-sim.h" +#include /* * 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__ */ diff --git a/tests/tests-chip-info.c b/tests/tests-chip-info.c index db76385..7b2e857 100644 --- a/tests/tests-chip-info.c +++ b/tests/tests-chip-info.c @@ -4,10 +4,11 @@ #include #include #include +#include +#include +#include -#include "gpiod-test.h" -#include "gpiod-test-helpers.h" -#include "gpiod-test-sim.h" +#include "helpers.h" #define GPIOD_TEST_GROUP "chip-info" diff --git a/tests/tests-chip.c b/tests/tests-chip.c index 815b4c7..13e3f61 100644 --- a/tests/tests-chip.c +++ b/tests/tests-chip.c @@ -4,10 +4,11 @@ #include #include #include +#include +#include +#include -#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); diff --git a/tests/tests-edge-event.c b/tests/tests-edge-event.c index b744ca5..6389455 100644 --- a/tests/tests-edge-event.c +++ b/tests/tests-edge-event.c @@ -3,11 +3,12 @@ #include #include +#include +#include +#include #include -#include "gpiod-test.h" -#include "gpiod-test-helpers.h" -#include "gpiod-test-sim.h" +#include "helpers.h" #define GPIOD_TEST_GROUP "edge-event" diff --git a/tests/tests-info-event.c b/tests/tests-info-event.c index cbd9e9e..e014500 100644 --- a/tests/tests-info-event.c +++ b/tests/tests-info-event.c @@ -3,11 +3,12 @@ #include #include +#include +#include +#include #include -#include "gpiod-test.h" -#include "gpiod-test-helpers.h" -#include "gpiod-test-sim.h" +#include "helpers.h" #define GPIOD_TEST_GROUP "info-event" diff --git a/tests/tests-kernel-uapi.c b/tests/tests-kernel-uapi.c index e54cfcc..ff220fc 100644 --- a/tests/tests-kernel-uapi.c +++ b/tests/tests-kernel-uapi.c @@ -4,10 +4,11 @@ #include #include #include +#include +#include +#include -#include "gpiod-test.h" -#include "gpiod-test-helpers.h" -#include "gpiod-test-sim.h" +#include "helpers.h" #define GPIOD_TEST_GROUP "kernel-uapi" diff --git a/tests/tests-line-config.c b/tests/tests-line-config.c index 469500b..b61a445 100644 --- a/tests/tests-line-config.c +++ b/tests/tests-line-config.c @@ -4,10 +4,11 @@ #include #include #include +#include +#include +#include -#include "gpiod-test.h" -#include "gpiod-test-helpers.h" -#include "gpiod-test-sim.h" +#include "helpers.h" #define GPIOD_TEST_GROUP "line-config" diff --git a/tests/tests-line-info.c b/tests/tests-line-info.c index cf2c650..92cd7e0 100644 --- a/tests/tests-line-info.c +++ b/tests/tests-line-info.c @@ -4,10 +4,11 @@ #include #include #include +#include +#include +#include -#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, diff --git a/tests/tests-line-request.c b/tests/tests-line-request.c index 7bba078..dd4e9a8 100644 --- a/tests/tests-line-request.c +++ b/tests/tests-line-request.c @@ -3,10 +3,11 @@ #include #include +#include +#include +#include -#include "gpiod-test.h" -#include "gpiod-test-helpers.h" -#include "gpiod-test-sim.h" +#include "helpers.h" #define GPIOD_TEST_GROUP "line-request" diff --git a/tests/tests-line-settings.c b/tests/tests-line-settings.c index b86fd26..18fde50 100644 --- a/tests/tests-line-settings.c +++ b/tests/tests-line-settings.c @@ -4,9 +4,10 @@ #include #include #include +#include +#include -#include "gpiod-test.h" -#include "gpiod-test-helpers.h" +#include "helpers.h" #define GPIOD_TEST_GROUP "line-settings" diff --git a/tests/tests-misc.c b/tests/tests-misc.c index 240dd02..9d4f3de 100644 --- a/tests/tests-misc.c +++ b/tests/tests-misc.c @@ -4,11 +4,12 @@ #include #include #include +#include +#include +#include #include -#include "gpiod-test.h" -#include "gpiod-test-helpers.h" -#include "gpiod-test-sim.h" +#include "helpers.h" #define GPIOD_TEST_GROUP "misc" diff --git a/tests/tests-request-config.c b/tests/tests-request-config.c index d3c679a..a38befd 100644 --- a/tests/tests-request-config.c +++ b/tests/tests-request-config.c @@ -3,9 +3,10 @@ #include #include +#include +#include -#include "gpiod-test.h" -#include "gpiod-test-helpers.h" +#include "helpers.h" #define GPIOD_TEST_GROUP "request-config" From patchwork Wed Aug 7 09:10:33 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Bartosz Golaszewski X-Patchwork-Id: 818164 Received: from mail-wm1-f45.google.com (mail-wm1-f45.google.com [209.85.128.45]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A219D1C9EC8 for ; Wed, 7 Aug 2024 09:10:48 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.45 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723021852; cv=none; b=d1EXvdNuw+QxRmrUa9QSU6S8WDwFqqCKbbqUtEID+zUte/oDkoQwQs1jpPie+y/UrfhbOvfsjmv25qtZ/46nc4Xa8Lk1/y04Mar4dCCs+F/HAI05aPWbMz2yg8yAnwV9gSh0ecG09FNND9Rvg39wsg7PsOOSjdjbacpRYDMUNuM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723021852; c=relaxed/simple; bh=nuYuXPICAiX5DjdCrsPuHLtvJWGJy9lcKRFlneaaScc=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=rsaDgJBZ8aIp6Ai8t+kHU07rtan6lgclsAZyQMIxxczeUt4OuyxZJ/mT36dcij44D0EhucF7OU6IFfQqcux2apnRWFJ7HozsrI1lu1ra8p7M2wd2gPDa3u/6NHcjZT/xLlc+VF9zrDEZOlslNFu6W1gM7kmoT5hsV3A1ofNx/0Y= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=bgdev.pl; spf=none smtp.mailfrom=bgdev.pl; dkim=pass (2048-bit key) header.d=bgdev-pl.20230601.gappssmtp.com header.i=@bgdev-pl.20230601.gappssmtp.com header.b=tKbUCtPT; arc=none smtp.client-ip=209.85.128.45 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=bgdev.pl Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=bgdev.pl Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=bgdev-pl.20230601.gappssmtp.com header.i=@bgdev-pl.20230601.gappssmtp.com header.b="tKbUCtPT" Received: by mail-wm1-f45.google.com with SMTP id 5b1f17b1804b1-4280ee5f1e3so10518105e9.0 for ; Wed, 07 Aug 2024 02:10:48 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bgdev-pl.20230601.gappssmtp.com; s=20230601; t=1723021847; x=1723626647; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=ck4vtCeoXKfTeXkBP6cL+9zhWfqNxXZ5wT4S1VHaruU=; b=tKbUCtPT3Ze79gIxT2x98yGC9bfKQAP/Tgs7XAIIcONwvtdTDLSc2U9RWq8cKc6MSD +zWrJgmOO2ErD1jCDAupD/86KAyD0u0u/5IHCgoVzaGFBS7h7XwIRFALm/s3fMiKEaCL twMExIvKUuoOVW/uRqebTysrAJX3EbL1GW+ABc+6oDPjzLfHx3kVOowsVLmz78sKT6+r VLJMuXvSr7l0ZLUu1ZjnRcIKb7uFIhS15aq6WsJFJeJccZaZ9ZbYCJtysVgS9/YwxISf ftKlXUuAjV4l4RNeZVdl67VAcLn7U2A9WDhIlQmuFYJqWm6vIsU1+sn17JuLvlFTnYpo geHQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1723021847; x=1723626647; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=ck4vtCeoXKfTeXkBP6cL+9zhWfqNxXZ5wT4S1VHaruU=; b=UK3el+kgaOiCU3HywfBqgfJfmD32i9dJTDhZ3lYN0kaMXUsbqq+Vfc7MsDN8a3kiLn aH0BTDioO+Vvor83LnmPDR9a8/oCVoh2W8Qbo6jd1L/fPwjSVxxpkQqQADsNRmgbtZy/ 09f0/b+91tp0EN4tzsl4Y7RpD12vN3LOs7iB/lRa1I5xT/beLEj+8W5ajItbgxRqOmID yuZH3c+jKkkOD7qX3Nq/KU6xzuFgQ0glWTm0gdSe/0u8ggBsbzLqAfe0bCeyQFmP3W2W 14mBDjm8c7NcSpcUlG4cdrXWv1jy1dGdiUakyOI/gOMKwrWB30QYGQf4k2TsY7e4fvSY bs2g== X-Gm-Message-State: AOJu0Yy6XC44PhxwHtYEDy43aMGdaQbHUudvP/d4cqWVyujfET9dgh8i ZdUvsoLmn31/cj9BsZI8YDPQwhY87fwVYC99bVeUXg6X1tGXLMgfGQhCzUAW6Ls= X-Google-Smtp-Source: AGHT+IHhi/jSJZxFZ1vIcjTyNh5DFUS6bxYmUdz3dpB9rhCYA5l7ZQ2HEqLUzsBt7gNoZ9anlgR+TQ== X-Received: by 2002:a05:600c:1f86:b0:428:1e8c:ff75 with SMTP id 5b1f17b1804b1-428e6b93fd6mr106020895e9.35.1723021846447; Wed, 07 Aug 2024 02:10:46 -0700 (PDT) Received: from [127.0.1.1] ([2a01:cb1d:dc:7e00:a3e7:be71:fe4f:c360]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-429059cd2b3sm18936355e9.44.2024.08.07.02.10.45 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 07 Aug 2024 02:10:46 -0700 (PDT) From: Bartosz Golaszewski Date: Wed, 07 Aug 2024 11:10:33 +0200 Subject: [PATCH libgpiod v4 02/18] tests: split out the common test code for bash scripts Precedence: bulk X-Mailing-List: linux-gpio@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20240807-dbus-v4-2-64ea80169e51@linaro.org> References: <20240807-dbus-v4-0-64ea80169e51@linaro.org> In-Reply-To: <20240807-dbus-v4-0-64ea80169e51@linaro.org> To: Linus Walleij , Kent Gibson , Erik Schilling , Phil Howard , Andy Shevchenko , Viresh Kumar , Dan Carpenter , Philip Withnall Cc: linux-gpio@vger.kernel.org, Bartosz Golaszewski , Alexander Sverdlin X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=49483; i=bartosz.golaszewski@linaro.org; h=from:subject:message-id; bh=bCQc+qxQiVzA3UYaXHuonfN4zQifl9K9zF9gmCmVPb0=; b=owEBbQKS/ZANAwAKARGnLqAUcddyAcsmYgBmszoPrpAUpCWiynXfUbwS/lK6rlpE5CQBoHIfk 0OcqTXMjLSJAjMEAAEKAB0WIQQWnetsC8PEYBPSx58Rpy6gFHHXcgUCZrM6DwAKCRARpy6gFHHX csncD/sESBoTMVN3ScNvbj1MxpNpVcGx0e4BVReXAS2ZAtxsPu9i/WHZbkaexlb/9RS1d8hAlVv X9ZdIrReqyz2Xq+U9xWuEty+HtwuNVbGqS5jbMiTYEn7xiiB9wQml+auU3e8FD4xOcmKY3PeYcM TWGCUAfdP1cdesE+EI0KB0HIqJCsTZ8M8wiOjl1n02p9y24vKuZtqf1j2YbjDkSfnRtUyQ78+F8 FU60lGQarShLQu0bWiz7aHUYztaDgUCMwpOlC3VRzuJ58XMJMH0ukUsuLIO8cpsRXPQoHPgiCuR GD2FuRtU/YRQI2nZq2vj/svjt9FtZHRLNeJSoOZHeGKa34P+wKu9Eqd7ORdiSvBIGVGmqP065Vl YgUP7Q9sryI32bQGdd+JbUPyNLye0xa4zezcc9YAbnObY4Wq0Sw/s/Y6xWlS2P6KUxJKmK0SFEW n5heC01nUh+9zNmvspIsqPx7cOd3goztpIR7cnyZZeZBwXAbJIFwzZ7FK5xlMi3WsB3v27ALl+C NGoChTJHpl32/OEWHrBG3o1aOgvYtZSefxGYlaq4x5SVTE0FcXPnSyXjq6NqA+CnN/V4PZ7Rxzj S+TRX6Utbq7Q9IlGN6wNsaBv74ran9TvzPBf5XFDds9b97JzYmXFcYbkgXl7sPdCBL4P7GCZ/n8 N0KrRcs/NOB9+Ww== X-Developer-Key: i=bartosz.golaszewski@linaro.org; a=openpgp; fpr=169DEB6C0BC3C46013D2C79F11A72EA01471D772 From: Bartosz Golaszewski 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. Tested-by: Alexander Sverdlin Signed-off-by: Bartosz Golaszewski --- configure.ac | 1 + tests/Makefile.am | 2 +- tests/scripts/Makefile.am | 4 + tests/scripts/gpiod-bash-test-helper.inc | 330 ++++++++++++++++++ tools/gpio-tools-test.bash | 566 +++++++------------------------ 5 files changed, 458 insertions(+), 445 deletions(-) diff --git a/configure.ac b/configure.ac index d1f49ac..93d9d75 100644 --- a/configure.ac +++ b/configure.ac @@ -277,6 +277,7 @@ AC_CONFIG_FILES([Makefile tests/gpiosim/Makefile tests/gpiosim-glib/Makefile tests/harness/Makefile + tests/scripts/Makefile bindings/cxx/libgpiodcxx.pc bindings/Makefile bindings/cxx/Makefile diff --git a/tests/Makefile.am b/tests/Makefile.am index c89fd8d..7049d21 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-or-later # SPDX-FileCopyrightText: 2017-2022 Bartosz Golaszewski -SUBDIRS = gpiosim gpiosim-glib harness +SUBDIRS = gpiosim gpiosim-glib harness scripts AM_CFLAGS = -I$(top_srcdir)/include/ -I$(top_srcdir)/tests/gpiosim-glib/ AM_CFLAGS += -I$(top_srcdir)/tests/harness/ diff --git a/tests/scripts/Makefile.am b/tests/scripts/Makefile.am new file mode 100644 index 0000000..5766593 --- /dev/null +++ b/tests/scripts/Makefile.am @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# SPDX-FileCopyrightText: 2023 Bartosz Golaszewski + +EXTRA_DIST = gpiod-bash-test-helper.inc diff --git a/tests/scripts/gpiod-bash-test-helper.inc b/tests/scripts/gpiod-bash-test-helper.inc new file mode 100644 index 0000000..d0f8a6d --- /dev/null +++ b/tests/scripts/gpiod-bash-test-helper.inc @@ -0,0 +1,330 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski +# SPDX-FileCopyrightText: 2022 Kent Gibson +# SPDX-FileCopyrightText: 2023 Bartosz Golaszewski + +# Simple test harness for the gpio-tools. + +# Where output from the dut is stored (must be used together +# with SHUNIT_TMPDIR). +DUT_OUTPUT=gpio-tools-test-output + +# Save the PID of coprocess - otherwise we won't be able to wait for it +# once it exits as the COPROC_PID will be cleared. +DUT_PID="" + +# mappings from local name to system chip name, path, dev name +declare -A GPIOSIM_CHIP_NAME +declare -A GPIOSIM_CHIP_PATH +declare -A GPIOSIM_DEV_NAME +GPIOSIM_CONFIGFS="/sys/kernel/config/gpio-sim" +GPIOSIM_SYSFS="/sys/devices/platform/" +GPIOSIM_APP_NAME="gpio-tools-test" + +MIN_KERNEL_VERSION="5.17.4" +MIN_SHUNIT_VERSION="2.1.8" + +# Run the command in $@ and fail the test if the command succeeds. +assert_fail() { + "$@" || return 0 + fail " '$*': command did not fail as expected" +} + +# Check if the string in $2 matches against the pattern in $1. +regex_matches() { + [[ $2 =~ $1 ]] + assertEquals " '$2' did not match '$1':" "0" "$?" +} + +output_contains_line() { + assertContains "$1" "$output" +} + +output_is() { + assertEquals " output:" "$1" "$output" +} + +num_lines_is() { + [ "$1" -eq "0" ] || [ -z "$output" ] && return 0 + local NUM_LINES + NUM_LINES=$(echo "$output" | wc -l) + assertEquals " number of lines:" "$1" "$NUM_LINES" +} + +status_is() { + assertEquals " status:" "$1" "$status" +} + +# Same as above but match against the regex pattern in $1. +output_regex_match() { + [[ "$output" =~ $1 ]] + assertEquals " '$output' did not match '$1'" "0" "$?" +} + +gpiosim_chip() { + local VAR=$1 + local NAME=${GPIOSIM_APP_NAME}-$$-${VAR} + local DEVPATH=$GPIOSIM_CONFIGFS/$NAME + local BANKPATH=$DEVPATH/bank0 + + mkdir -p "$BANKPATH" + + for ARG in "$@" + do + local KEY VAL + KEY=$(echo "$ARG" | cut -d"=" -f1) + VAL=$(echo "$ARG" | cut -d"=" -f2) + + if [ "$KEY" = "num_lines" ] + then + echo "$VAL" > "$BANKPATH/num_lines" + elif [ "$KEY" = "line_name" ] + then + local OFFSET LINENAME + OFFSET=$(echo "$VAL" | cut -d":" -f1) + LINENAME=$(echo "$VAL" | cut -d":" -f2) + local LINEPATH=$BANKPATH/line$OFFSET + + mkdir -p "$LINEPATH" + echo "$LINENAME" > "$LINEPATH/name" + fi + done + + echo 1 > "$DEVPATH/live" + + local CHIP_NAME + CHIP_NAME=$(<"$BANKPATH/chip_name") + GPIOSIM_CHIP_NAME[$1]=$CHIP_NAME + GPIOSIM_CHIP_PATH[$1]="/dev/$CHIP_NAME" + GPIOSIM_DEV_NAME[$1]=$(<"$DEVPATH/dev_name") +} + +gpiosim_chip_number() { + local NAME=${GPIOSIM_CHIP_NAME[$1]} + echo "${NAME#gpiochip}" +} + +gpiosim_chip_symlink() { + GPIOSIM_CHIP_LINK="$2/${GPIOSIM_APP_NAME}-$$-lnk" + ln -s "${GPIOSIM_CHIP_PATH[$1]}" "$GPIOSIM_CHIP_LINK" +} + +gpiosim_chip_symlink_cleanup() { + if [ -n "$GPIOSIM_CHIP_LINK" ] + then + rm "$GPIOSIM_CHIP_LINK" + fi + unset GPIOSIM_CHIP_LINK +} + +gpiosim_set_pull() { + local OFFSET=$2 + local PULL=$3 + local DEVNAME=${GPIOSIM_DEV_NAME[$1]} + local CHIPNAME=${GPIOSIM_CHIP_NAME[$1]} + + echo "$PULL" > "$GPIOSIM_SYSFS/$DEVNAME/$CHIPNAME/sim_gpio$OFFSET/pull" +} + +gpiosim_check_value() { + local OFFSET=$2 + local EXPECTED=$3 + local DEVNAME=${GPIOSIM_DEV_NAME[$1]} + local CHIPNAME=${GPIOSIM_CHIP_NAME[$1]} + + VAL=$(<"$GPIOSIM_SYSFS/$DEVNAME/$CHIPNAME/sim_gpio$OFFSET/value") + [ "$VAL" = "$EXPECTED" ] +} + +gpiosim_wait_value() { + local OFFSET=$2 + local EXPECTED=$3 + local DEVNAME=${GPIOSIM_DEV_NAME[$1]} + local CHIPNAME=${GPIOSIM_CHIP_NAME[$1]} + local PORT=$GPIOSIM_SYSFS/$DEVNAME/$CHIPNAME/sim_gpio$OFFSET/value + + for _i in {1..30}; do + [ "$(<"$PORT")" = "$EXPECTED" ] && return + sleep 0.01 + done + return 1 +} + +gpiosim_cleanup() { + for CHIP in "${!GPIOSIM_CHIP_NAME[@]}" + do + local NAME=${GPIOSIM_APP_NAME}-$$-$CHIP + + local DEVPATH=$GPIOSIM_CONFIGFS/$NAME + + echo 0 > "$DEVPATH/live" + find "$DEVPATH" -type d -name hog -exec rmdir '{}' '+' + find "$DEVPATH" -type d -name "line*" -exec rmdir '{}' '+' + find "$DEVPATH" -type d -name "bank*" -exec rmdir '{}' '+' + rmdir "$DEVPATH" + done + + gpiosim_chip_symlink_cleanup + + GPIOSIM_CHIP_NAME=() + GPIOSIM_CHIP_PATH=() + GPIOSIM_DEV_NAME=() +} + +run_prog() { + # Executables to test are expected to be in the same directory as the + # testing script. + cmd=$1 + shift + output=$(timeout 10s "$SOURCE_DIR/$cmd" "$@" 2>&1) + status=$? +} + +dut_run() { + cmd=$1 + shift + coproc timeout 10s "$SOURCE_DIR/$cmd" "$@" 2>&1 + DUT_PID=$COPROC_PID + read -r -t1 -n1 -u "${COPROC[0]}" DUT_FIRST_CHAR +} + +dut_run_redirect() { + cmd=$1 + shift + coproc timeout 10s "$SOURCE_DIR/$cmd" "$@" > "$SHUNIT_TMPDIR/$DUT_OUTPUT" 2>&1 + DUT_PID=$COPROC_PID + # give the process time to spin up + # FIXME - find a better solution + sleep 0.2 +} + +dut_read_redirect() { + output=$(<"$SHUNIT_TMPDIR/$DUT_OUTPUT") + local ORIG_IFS="$IFS" + IFS=$'\n' mapfile -t lines <<< "$output" + IFS="$ORIG_IFS" +} + +dut_read() { + local LINE + lines=() + while read -r -t 0.2 -u "${COPROC[0]}" LINE + do + if [ -n "$DUT_FIRST_CHAR" ] + then + LINE=${DUT_FIRST_CHAR}${LINE} + unset DUT_FIRST_CHAR + fi + lines+=("$LINE") + done + output="${lines[*]}" +} + +dut_readable() { + read -t 0 -u "${COPROC[0]}" LINE +} + +dut_flush() { + local _JUNK + lines=() + output= + unset DUT_FIRST_CHAR + while read -t 0 -u "${COPROC[0]}" _JUNK + do + read -r -t 0.1 -u "${COPROC[0]}" _JUNK || true + done +} + +# check the next line of output matches the regex +dut_regex_match() { + PATTERN=$1 + + read -r -t 0.2 -u "${COPROC[0]}" LINE || (echo Timeout && false) + if [ -n "$DUT_FIRST_CHAR" ] + then + LINE=${DUT_FIRST_CHAR}${LINE} + unset DUT_FIRST_CHAR + fi + [[ $LINE =~ $PATTERN ]] + assertEquals "'$LINE' did not match '$PATTERN'" "0" "$?" +} + +dut_write() { + echo "$@" >&"${COPROC[1]}" +} + +dut_kill() { + kill "$@" "$DUT_PID" +} + +dut_wait() { + wait "$DUT_PID" + export status=$? + unset DUT_PID +} + +dut_cleanup() { + if [ -n "$DUT_PID" ] + then + kill -SIGTERM "$DUT_PID" 2> /dev/null + wait "$DUT_PID" || false + fi + rm -f "$SHUNIT_TMPDIR/$DUT_OUTPUT" +} + +tearDown() { + dut_cleanup + gpiosim_cleanup +} + +request_release_line() { + "$SOURCE_DIR/gpioget" -c "$@" >/dev/null +} + +die() { + echo "$@" 1>&2 + exit 1 +} + +# 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 + FIRST=$(printf "%s\n%s\n" "$SHUNIT_VERSION" "$MIN_SHUNIT_VERSION" | sort -Vr | head -1) + test "$FIRST" = "$MIN_SHUNIT_VERSION" && \ + die "minimum shunit version required is $MIN_SHUNIT_VERSION (current version is $SHUNIT_VERSION" +} + +check_kernel() { + local REQUIRED=$1 + local CURRENT + CURRENT=$(uname -r) + + SORTED=$(printf "%s\n%s" "$REQUIRED" "$CURRENT" | sort -V | head -n 1) + + if [ "$SORTED" != "$REQUIRED" ] + then + die "linux kernel version must be at least: v$REQUIRED - got: v$CURRENT" + fi +} + +check_prog() { + local PROG=$1 + + if ! which "$PROG" > /dev/null + then + die "$PROG not found - needed to run the tests" + fi +} + +# Check all required non-coreutils tools +check_prog shunit2 +check_prog modprobe +check_prog timeout + +# Check if we're running a kernel at the required version or later +check_kernel $MIN_KERNEL_VERSION + +modprobe gpio-sim || die "unable to load the gpio-sim module" +mountpoint /sys/kernel/config/ > /dev/null 2> /dev/null || \ + die "configfs not mounted at /sys/kernel/config/" diff --git a/tools/gpio-tools-test.bash b/tools/gpio-tools-test.bash index 3b93388..359960a 100755 --- a/tools/gpio-tools-test.bash +++ b/tools/gpio-tools-test.bash @@ -4,285 +4,8 @@ # SPDX-FileCopyrightText: 2022 Kent Gibson # SPDX-FileCopyrightText: 2023 Bartosz Golaszewski -# Simple test harness for the gpio-tools. - -# Where output from the dut is stored (must be used together -# with SHUNIT_TMPDIR). -DUT_OUTPUT=gpio-tools-test-output - -# Save the PID of coprocess - otherwise we won't be able to wait for it -# once it exits as the COPROC_PID will be cleared. -DUT_PID="" - -SOURCE_DIR=$(dirname "${BASH_SOURCE[0]}") - -# mappings from local name to system chip name, path, dev name -declare -A GPIOSIM_CHIP_NAME -declare -A GPIOSIM_CHIP_PATH -declare -A GPIOSIM_DEV_NAME -GPIOSIM_CONFIGFS="/sys/kernel/config/gpio-sim" -GPIOSIM_SYSFS="/sys/devices/platform/" -GPIOSIM_APP_NAME="gpio-tools-test" - -MIN_KERNEL_VERSION="5.17.4" -MIN_SHUNIT_VERSION="2.1.8" - -# Run the command in $@ and fail the test if the command succeeds. -assert_fail() { - "$@" || return 0 - fail " '$*': command did not fail as expected" -} - -# Check if the string in $2 matches against the pattern in $1. -regex_matches() { - [[ $2 =~ $1 ]] - assertEquals " '$2' did not match '$1':" "0" "$?" -} - -output_contains_line() { - assertContains "$1" "$output" -} - -output_is() { - assertEquals " output:" "$1" "$output" -} - -num_lines_is() { - [ "$1" -eq "0" ] || [ -z "$output" ] && return 0 - local NUM_LINES - NUM_LINES=$(echo "$output" | wc -l) - assertEquals " number of lines:" "$1" "$NUM_LINES" -} - -status_is() { - assertEquals " status:" "$1" "$status" -} - -# Same as above but match against the regex pattern in $1. -output_regex_match() { - [[ "$output" =~ $1 ]] - assertEquals " '$output' did not match '$1'" "0" "$?" -} - -gpiosim_chip() { - local VAR=$1 - local NAME=${GPIOSIM_APP_NAME}-$$-${VAR} - local DEVPATH=$GPIOSIM_CONFIGFS/$NAME - local BANKPATH=$DEVPATH/bank0 - - mkdir -p "$BANKPATH" - - for ARG in "$@" - do - local KEY VAL - KEY=$(echo "$ARG" | cut -d"=" -f1) - VAL=$(echo "$ARG" | cut -d"=" -f2) - - if [ "$KEY" = "num_lines" ] - then - echo "$VAL" > "$BANKPATH/num_lines" - elif [ "$KEY" = "line_name" ] - then - local OFFSET LINENAME - OFFSET=$(echo "$VAL" | cut -d":" -f1) - LINENAME=$(echo "$VAL" | cut -d":" -f2) - local LINEPATH=$BANKPATH/line$OFFSET - - mkdir -p "$LINEPATH" - echo "$LINENAME" > "$LINEPATH/name" - fi - done - - echo 1 > "$DEVPATH/live" - - local CHIP_NAME - CHIP_NAME=$(<"$BANKPATH/chip_name") - GPIOSIM_CHIP_NAME[$1]=$CHIP_NAME - GPIOSIM_CHIP_PATH[$1]="/dev/$CHIP_NAME" - GPIOSIM_DEV_NAME[$1]=$(<"$DEVPATH/dev_name") -} - -gpiosim_chip_number() { - local NAME=${GPIOSIM_CHIP_NAME[$1]} - echo "${NAME#gpiochip}" -} - -gpiosim_chip_symlink() { - GPIOSIM_CHIP_LINK="$2/${GPIOSIM_APP_NAME}-$$-lnk" - ln -s "${GPIOSIM_CHIP_PATH[$1]}" "$GPIOSIM_CHIP_LINK" -} - -gpiosim_chip_symlink_cleanup() { - if [ -n "$GPIOSIM_CHIP_LINK" ] - then - rm "$GPIOSIM_CHIP_LINK" - fi - unset GPIOSIM_CHIP_LINK -} - -gpiosim_set_pull() { - local OFFSET=$2 - local PULL=$3 - local DEVNAME=${GPIOSIM_DEV_NAME[$1]} - local CHIPNAME=${GPIOSIM_CHIP_NAME[$1]} - - echo "$PULL" > "$GPIOSIM_SYSFS/$DEVNAME/$CHIPNAME/sim_gpio$OFFSET/pull" -} - -gpiosim_check_value() { - local OFFSET=$2 - local EXPECTED=$3 - local DEVNAME=${GPIOSIM_DEV_NAME[$1]} - local CHIPNAME=${GPIOSIM_CHIP_NAME[$1]} - - VAL=$(<"$GPIOSIM_SYSFS/$DEVNAME/$CHIPNAME/sim_gpio$OFFSET/value") - [ "$VAL" = "$EXPECTED" ] -} - -gpiosim_wait_value() { - local OFFSET=$2 - local EXPECTED=$3 - local DEVNAME=${GPIOSIM_DEV_NAME[$1]} - local CHIPNAME=${GPIOSIM_CHIP_NAME[$1]} - local PORT=$GPIOSIM_SYSFS/$DEVNAME/$CHIPNAME/sim_gpio$OFFSET/value - - for _i in {1..30}; do - [ "$(<"$PORT")" = "$EXPECTED" ] && return - sleep 0.01 - done - return 1 -} - -gpiosim_cleanup() { - for CHIP in "${!GPIOSIM_CHIP_NAME[@]}" - do - local NAME=${GPIOSIM_APP_NAME}-$$-$CHIP - - local DEVPATH=$GPIOSIM_CONFIGFS/$NAME - - echo 0 > "$DEVPATH/live" - find "$DEVPATH" -type d -name hog -exec rmdir '{}' '+' - find "$DEVPATH" -type d -name "line*" -exec rmdir '{}' '+' - find "$DEVPATH" -type d -name "bank*" -exec rmdir '{}' '+' - rmdir "$DEVPATH" - done - - gpiosim_chip_symlink_cleanup - - GPIOSIM_CHIP_NAME=() - GPIOSIM_CHIP_PATH=() - GPIOSIM_DEV_NAME=() -} - -run_tool() { - # Executables to test are expected to be in the same directory as the - # testing script. - cmd=$1 - shift - output=$(timeout 10s "$SOURCE_DIR/$cmd" "$@" 2>&1) - status=$? -} - -dut_run() { - cmd=$1 - shift - coproc timeout 10s "$SOURCE_DIR/$cmd" "$@" 2>&1 - DUT_PID=$COPROC_PID - read -r -t1 -n1 -u "${COPROC[0]}" DUT_FIRST_CHAR -} - -dut_run_redirect() { - cmd=$1 - shift - coproc timeout 10s "$SOURCE_DIR/$cmd" "$@" > "$SHUNIT_TMPDIR/$DUT_OUTPUT" 2>&1 - DUT_PID=$COPROC_PID - # give the process time to spin up - # FIXME - find a better solution - sleep 0.2 -} - -dut_read_redirect() { - output=$(<"$SHUNIT_TMPDIR/$DUT_OUTPUT") - local ORIG_IFS="$IFS" - IFS=$'\n' mapfile -t lines <<< "$output" - IFS="$ORIG_IFS" -} - -dut_read() { - local LINE - lines=() - while read -r -t 0.2 -u "${COPROC[0]}" LINE - do - if [ -n "$DUT_FIRST_CHAR" ] - then - LINE=${DUT_FIRST_CHAR}${LINE} - unset DUT_FIRST_CHAR - fi - lines+=("$LINE") - done - output="${lines[*]}" -} - -dut_readable() { - read -t 0 -u "${COPROC[0]}" LINE -} - -dut_flush() { - local _JUNK - lines=() - output= - unset DUT_FIRST_CHAR - while read -t 0 -u "${COPROC[0]}" _JUNK - do - read -r -t 0.1 -u "${COPROC[0]}" _JUNK || true - done -} - -# check the next line of output matches the regex -dut_regex_match() { - PATTERN=$1 - - read -r -t 0.2 -u "${COPROC[0]}" LINE || (echo Timeout && false) - if [ -n "$DUT_FIRST_CHAR" ] - then - LINE=${DUT_FIRST_CHAR}${LINE} - unset DUT_FIRST_CHAR - fi - [[ $LINE =~ $PATTERN ]] - assertEquals "'$LINE' did not match '$PATTERN'" "0" "$?" -} - -dut_write() { - echo "$@" >&"${COPROC[1]}" -} - -dut_kill() { - kill "$@" "$DUT_PID" -} - -dut_wait() { - wait "$DUT_PID" - export status=$? - unset DUT_PID -} - -dut_cleanup() { - if [ -n "$DUT_PID" ] - then - kill -SIGTERM "$DUT_PID" 2> /dev/null - wait "$DUT_PID" || false - fi - rm -f "$SHUNIT_TMPDIR/$DUT_OUTPUT" -} - -tearDown() { - dut_cleanup - gpiosim_cleanup -} - -request_release_line() { - "$SOURCE_DIR/gpioget" -c "$@" >/dev/null -} +export SOURCE_DIR +SOURCE_DIR="$(dirname "${BASH_SOURCE[0]}")" # # gpiodetect test cases @@ -300,7 +23,7 @@ test_gpiodetect_all_chips() { local sim1dev=${GPIOSIM_DEV_NAME[sim1]} local sim2dev=${GPIOSIM_DEV_NAME[sim2]} - run_tool gpiodetect + run_prog gpiodetect output_regex_match "$sim0 \[${sim0dev}[-:]node0\] \(4 lines\)" output_regex_match "$sim1 \[${sim1dev}[-:]node0\] \(8 lines\)" @@ -311,7 +34,7 @@ test_gpiodetect_all_chips() { local initial_output=$output gpiosim_chip_symlink sim1 /dev - run_tool gpiodetect + run_prog gpiodetect output_is "$initial_output" status_is 0 @@ -330,21 +53,21 @@ test_gpiodetect_a_chip() { local sim2dev=${GPIOSIM_DEV_NAME[sim2]} # by name - run_tool gpiodetect "$sim0" + run_prog gpiodetect "$sim0" output_regex_match "$sim0 \[${sim0dev}[-:]node0\] \(4 lines\)" num_lines_is 1 status_is 0 # by path - run_tool gpiodetect "${GPIOSIM_CHIP_PATH[sim1]}" + run_prog gpiodetect "${GPIOSIM_CHIP_PATH[sim1]}" output_regex_match "$sim1 \[${sim1dev}[-:]node0\] \(8 lines\)" num_lines_is 1 status_is 0 # by number - run_tool gpiodetect "$(gpiosim_chip_number sim2)" + run_prog gpiodetect "$(gpiosim_chip_number sim2)" output_regex_match "$sim2 \[${sim2dev}[-:]node0\] \(16 lines\)" num_lines_is 1 @@ -352,7 +75,7 @@ test_gpiodetect_a_chip() { # by symlink gpiosim_chip_symlink sim2 . - run_tool gpiodetect "$GPIOSIM_CHIP_LINK" + run_prog gpiodetect "$GPIOSIM_CHIP_LINK" output_regex_match "$sim2 \[${sim2dev}[-:]node0\] \(16 lines\)" num_lines_is 1 @@ -371,7 +94,7 @@ test_gpiodetect_multiple_chips() { local sim1dev=${GPIOSIM_DEV_NAME[sim1]} local sim2dev=${GPIOSIM_DEV_NAME[sim2]} - run_tool gpiodetect "$sim0" "$sim1" "$sim2" + run_prog gpiodetect "$sim0" "$sim1" "$sim2" output_regex_match "$sim0 \[${sim0dev}[-:]node0\] \(4 lines\)" output_regex_match "$sim1 \[${sim1dev}[-:]node0\] \(8 lines\)" @@ -381,7 +104,7 @@ test_gpiodetect_multiple_chips() { } test_gpiodetect_with_nonexistent_chip() { - run_tool gpiodetect nonexistent-chip + run_prog gpiodetect nonexistent-chip status_is 1 output_regex_match \ @@ -396,7 +119,7 @@ test_gpioinfo_all_chips() { gpiosim_chip sim0 num_lines=4 gpiosim_chip sim1 num_lines=8 - run_tool gpioinfo + run_prog gpioinfo output_contains_line "${GPIOSIM_CHIP_NAME[sim0]} - 4 lines:" output_contains_line "${GPIOSIM_CHIP_NAME[sim1]} - 8 lines:" @@ -408,7 +131,7 @@ test_gpioinfo_all_chips() { local initial_output=$output gpiosim_chip_symlink sim1 /dev - run_tool gpioinfo + run_prog gpioinfo output_is "$initial_output" status_is 0 @@ -420,7 +143,7 @@ test_gpioinfo_all_chips_with_some_used_lines() { dut_run gpioset --banner --active-low foo=1 baz=0 - run_tool gpioinfo + run_prog gpioinfo output_contains_line "${GPIOSIM_CHIP_NAME[sim0]} - 4 lines:" output_contains_line "${GPIOSIM_CHIP_NAME[sim1]} - 8 lines:" @@ -439,7 +162,7 @@ test_gpioinfo_a_chip() { local sim1=${GPIOSIM_CHIP_NAME[sim1]} # by name - run_tool gpioinfo --chip "$sim1" + run_prog gpioinfo --chip "$sim1" output_contains_line "$sim1 - 4 lines:" output_regex_match "\\s+line\\s+0:\\s+unnamed\\s+input" @@ -450,7 +173,7 @@ test_gpioinfo_a_chip() { status_is 0 # by path - run_tool gpioinfo --chip "$sim1" + run_prog gpioinfo --chip "$sim1" output_contains_line "$sim1 - 4 lines:" output_regex_match "\\s+line\\s+0:\\s+unnamed\\s+input" @@ -461,7 +184,7 @@ test_gpioinfo_a_chip() { status_is 0 # by number - run_tool gpioinfo --chip "$sim1" + run_prog gpioinfo --chip "$sim1" output_contains_line "$sim1 - 4 lines:" output_regex_match "\\s+line\\s+0:\\s+unnamed\\s+input" @@ -473,7 +196,7 @@ test_gpioinfo_a_chip() { # by symlink gpiosim_chip_symlink sim1 . - run_tool gpioinfo --chip "$GPIOSIM_CHIP_LINK" + run_prog gpioinfo --chip "$GPIOSIM_CHIP_LINK" output_contains_line "$sim1 - 4 lines:" output_regex_match "\\s+line\\s+0:\\s+unnamed\\s+input" @@ -492,28 +215,28 @@ test_gpioinfo_a_line() { local sim1=${GPIOSIM_CHIP_NAME[sim1]} # by offset - run_tool gpioinfo --chip "$sim1" 2 + run_prog gpioinfo --chip "$sim1" 2 output_regex_match "$sim1 2\\s+\"bar\"\\s+input" num_lines_is 1 status_is 0 # by name - run_tool gpioinfo bar + run_prog gpioinfo bar output_regex_match "$sim0 5\\s+\"bar\"\\s+input" num_lines_is 1 status_is 0 # by chip and name - run_tool gpioinfo --chip "$sim1" 2 + run_prog gpioinfo --chip "$sim1" 2 output_regex_match "$sim1 2\\s+\"bar\"\\s+input" num_lines_is 1 status_is 0 # unquoted - run_tool gpioinfo --unquoted --chip "$sim1" 2 + run_prog gpioinfo --unquoted --chip "$sim1" 2 output_regex_match "$sim1 2\\s+bar\\s+input" num_lines_is 1 @@ -530,7 +253,7 @@ test_gpioinfo_first_matching_named_line() { local sim0=${GPIOSIM_CHIP_NAME[sim0]} - run_tool gpioinfo foobar + run_prog gpioinfo foobar output_regex_match "$sim0 3\\s+\"foobar\"\\s+input" num_lines_is 1 @@ -545,7 +268,7 @@ test_gpioinfo_multiple_lines() { local sim1=${GPIOSIM_CHIP_NAME[sim1]} # by offset - run_tool gpioinfo --chip "$sim1" 1 2 + run_prog gpioinfo --chip "$sim1" 1 2 output_regex_match "$sim1 1\\s+unnamed\\s+input" output_regex_match "$sim1 2\\s+\"baz\"\\s+input" @@ -553,7 +276,7 @@ test_gpioinfo_multiple_lines() { status_is 0 # by name - run_tool gpioinfo bar baz + run_prog gpioinfo bar baz output_regex_match "$sim0 5\\s+\"bar\"\\s+input" output_regex_match "$sim1 2\\s+\"baz\"\\s+input" @@ -561,7 +284,7 @@ test_gpioinfo_multiple_lines() { status_is 0 # by name and offset - run_tool gpioinfo --chip "$sim0" bar 3 + run_prog gpioinfo --chip "$sim0" bar 3 output_regex_match "$sim0 5\\s+\"bar\"\\s+input" output_regex_match "$sim0 3\\s+unnamed\\s+input" @@ -578,7 +301,7 @@ test_gpioinfo_line_attribute_menagerie() { dut_run gpioset --banner --active-low --bias=pull-up --drive=open-source foo=1 baz=0 - run_tool gpioinfo foo baz + run_prog gpioinfo foo baz output_regex_match \ "$sim0 1\\s+\"foo\"\\s+output active-low drive=open-source bias=pull-up consumer=\"gpioset\"" @@ -592,7 +315,7 @@ test_gpioinfo_line_attribute_menagerie() { dut_run gpioset --banner --bias=pull-down --drive=open-drain foo=1 baz=0 - run_tool gpioinfo foo baz + run_prog gpioinfo foo baz output_regex_match \ "$sim0 1\\s+\"foo\"\\s+output drive=open-drain bias=pull-down consumer=\"gpioset\"" @@ -606,7 +329,7 @@ test_gpioinfo_line_attribute_menagerie() { dut_run gpiomon --banner --bias=disabled --utc -p 10ms foo baz - run_tool gpioinfo foo baz + run_prog gpioinfo foo baz output_regex_match \ "$sim0 1\\s+\"foo\"\\s+input bias=disabled edges=both event-clock=realtime debounce-period=10ms consumer=\"gpiomon\"" @@ -620,7 +343,7 @@ test_gpioinfo_line_attribute_menagerie() { dut_run gpiomon --banner --edges=rising --localtime foo baz - run_tool gpioinfo foo baz + run_prog gpioinfo foo baz output_regex_match \ "$sim0 1\\s+\"foo\"\\s+input edges=rising event-clock=realtime consumer=\"gpiomon\"" @@ -634,7 +357,7 @@ test_gpioinfo_line_attribute_menagerie() { dut_run gpiomon --banner --edges=falling foo baz - run_tool gpioinfo foo baz + run_prog gpioinfo foo baz output_regex_match \ "$sim0 1\\s+\"foo\"\\s+input edges=falling consumer=\"gpiomon\"" @@ -650,7 +373,7 @@ test_gpioinfo_with_same_line_twice() { local sim0=${GPIOSIM_CHIP_NAME[sim0]} # by offset - run_tool gpioinfo --chip "$sim0" 1 1 + run_prog gpioinfo --chip "$sim0" 1 1 output_regex_match "$sim0 1\\s+\"foo\"\\s+input" output_regex_match ".*lines '1' and '1' are the same line" @@ -658,7 +381,7 @@ test_gpioinfo_with_same_line_twice() { status_is 1 # by name - run_tool gpioinfo foo foo + run_prog gpioinfo foo foo output_regex_match "$sim0 1\\s+\"foo\"\\s+input" output_regex_match ".*lines 'foo' and 'foo' are the same line" @@ -666,7 +389,7 @@ test_gpioinfo_with_same_line_twice() { status_is 1 # by name and offset - run_tool gpioinfo --chip "$sim0" foo 1 + run_prog gpioinfo --chip "$sim0" foo 1 output_regex_match "$sim0 1\\s+\"foo\"\\s+input" output_regex_match ".*lines 'foo' and '1' are the same line" @@ -684,7 +407,7 @@ test_gpioinfo_all_lines_matching_name() { local sim0=${GPIOSIM_CHIP_NAME[sim0]} local sim1=${GPIOSIM_CHIP_NAME[sim1]} - run_tool gpioinfo --strict foobar + run_prog gpioinfo --strict foobar output_regex_match "$sim0 3\\s+\"foobar\"\\s+input" output_regex_match "$sim1 2\\s+\"foobar\"\\s+input" @@ -701,7 +424,7 @@ test_gpioinfo_with_lines_strictly_by_name() { local sim0=${GPIOSIM_CHIP_NAME[sim0]} # first by offset (to show offsets match first) - run_tool gpioinfo --chip "$sim0" 1 6 + run_prog gpioinfo --chip "$sim0" 1 6 output_regex_match "$sim0 1\\s+\"6\"\\s+input" output_regex_match "$sim0 6\\s+\"1\"\\s+input" @@ -709,7 +432,7 @@ test_gpioinfo_with_lines_strictly_by_name() { status_is 0 # then strictly by name - run_tool gpioinfo --by-name --chip "$sim0" 1 + run_prog gpioinfo --by-name --chip "$sim0" 1 output_regex_match "$sim0 6\\s+\"1\"\\s+input" num_lines_is 1 @@ -717,7 +440,7 @@ test_gpioinfo_with_lines_strictly_by_name() { } test_gpioinfo_with_nonexistent_chip() { - run_tool gpioinfo --chip nonexistent-chip + run_prog gpioinfo --chip nonexistent-chip output_regex_match \ ".*cannot find GPIO chip character device 'nonexistent-chip'" @@ -727,12 +450,12 @@ test_gpioinfo_with_nonexistent_chip() { test_gpioinfo_with_nonexistent_line() { gpiosim_chip sim0 num_lines=8 - run_tool gpioinfo nonexistent-line + run_prog gpioinfo nonexistent-line output_regex_match ".*cannot find line 'nonexistent-line'" status_is 1 - run_tool gpioinfo --chip "${GPIOSIM_CHIP_NAME[sim0]}" nonexistent-line + run_prog gpioinfo --chip "${GPIOSIM_CHIP_NAME[sim0]}" nonexistent-line output_regex_match ".*cannot find line 'nonexistent-line'" status_is 1 @@ -743,7 +466,7 @@ test_gpioinfo_with_offset_out_of_range() { local sim0=${GPIOSIM_CHIP_NAME[sim0]} - run_tool gpioinfo --chip "$sim0" 0 1 2 3 4 5 + run_prog gpioinfo --chip "$sim0" 0 1 2 3 4 5 output_regex_match "$sim0 0\\s+unnamed\\s+input" output_regex_match "$sim0 1\\s+unnamed\\s+input" @@ -764,12 +487,12 @@ test_gpioget_by_name() { gpiosim_set_pull sim0 1 pull-up - run_tool gpioget foo + run_prog gpioget foo output_is "\"foo\"=active" status_is 0 - run_tool gpioget --unquoted foo + run_prog gpioget --unquoted foo output_is "foo=active" status_is 0 @@ -780,12 +503,12 @@ test_gpioget_by_offset() { gpiosim_set_pull sim0 1 pull-up - run_tool gpioget --chip "${GPIOSIM_CHIP_NAME[sim0]}" 1 + run_prog gpioget --chip "${GPIOSIM_CHIP_NAME[sim0]}" 1 output_is "\"1\"=active" status_is 0 - run_tool gpioget --unquoted --chip "${GPIOSIM_CHIP_NAME[sim0]}" 1 + run_prog gpioget --unquoted --chip "${GPIOSIM_CHIP_NAME[sim0]}" 1 output_is "1=active" status_is 0 @@ -797,7 +520,7 @@ test_gpioget_by_symlink() { gpiosim_set_pull sim0 1 pull-up - run_tool gpioget --chip "$GPIOSIM_CHIP_LINK" 1 + run_prog gpioget --chip "$GPIOSIM_CHIP_LINK" 1 output_is "\"1\"=active" status_is 0 @@ -809,12 +532,12 @@ test_gpioget_by_chip_and_name() { gpiosim_set_pull sim1 3 pull-up - run_tool gpioget --chip "${GPIOSIM_CHIP_NAME[sim1]}" foo + run_prog gpioget --chip "${GPIOSIM_CHIP_NAME[sim1]}" foo output_is "\"foo\"=active" status_is 0 - run_tool gpioget --unquoted --chip "${GPIOSIM_CHIP_NAME[sim1]}" foo + run_prog gpioget --unquoted --chip "${GPIOSIM_CHIP_NAME[sim1]}" foo output_is "foo=active" status_is 0 @@ -829,7 +552,7 @@ test_gpioget_first_matching_named_line() { gpiosim_set_pull sim0 3 pull-up - run_tool gpioget foobar + run_prog gpioget foobar output_is "\"foobar\"=active" status_is 0 @@ -843,7 +566,7 @@ test_gpioget_multiple_lines() { gpiosim_set_pull sim0 5 pull-up gpiosim_set_pull sim0 7 pull-up - run_tool gpioget --unquoted --chip "${GPIOSIM_CHIP_NAME[sim0]}" 0 1 2 3 4 5 6 7 + run_prog gpioget --unquoted --chip "${GPIOSIM_CHIP_NAME[sim0]}" 0 1 2 3 4 5 6 7 output_is \ "0=inactive 1=inactive 2=active 3=active 4=inactive 5=active 6=inactive 7=active" @@ -859,7 +582,7 @@ test_gpioget_multiple_lines_by_name_and_offset() { gpiosim_set_pull sim0 4 pull-up gpiosim_set_pull sim0 6 pull-up - run_tool gpioget --chip "$sim0" 0 foo 4 bar + run_prog gpioget --chip "$sim0" 0 foo 4 bar output_is "\"0\"=inactive \"foo\"=active \"4\"=active \"bar\"=active" status_is 0 @@ -872,7 +595,7 @@ test_gpioget_multiple_lines_across_multiple_chips() { gpiosim_set_pull sim0 1 pull-up gpiosim_set_pull sim1 4 pull-up - run_tool gpioget baz bar foo xyz + run_prog gpioget baz bar foo xyz output_is "\"baz\"=inactive \"bar\"=inactive \"foo\"=active \"xyz\"=active" status_is 0 @@ -888,7 +611,7 @@ test_gpioget_with_numeric_values() { local sim0=${GPIOSIM_CHIP_NAME[sim0]} - run_tool gpioget --numeric --chip "$sim0" 0 1 2 3 4 5 6 7 + run_prog gpioget --numeric --chip "$sim0" 0 1 2 3 4 5 6 7 output_is "0 0 1 1 0 1 0 1" status_is 0 @@ -904,7 +627,7 @@ test_gpioget_with_active_low() { local sim0=${GPIOSIM_CHIP_NAME[sim0]} - run_tool gpioget --active-low --unquoted --chip "$sim0" 0 1 2 3 4 5 6 7 + run_prog gpioget --active-low --unquoted --chip "$sim0" 0 1 2 3 4 5 6 7 output_is \ "0=active 1=active 2=inactive 3=inactive 4=active 5=inactive 6=active 7=inactive" @@ -917,7 +640,7 @@ test_gpioget_with_consumer() { dut_run gpionotify --banner -F "%l %E %C" foo baz - run_tool gpioget --consumer gpio-tools-tests foo baz + run_prog gpioget --consumer gpio-tools-tests foo baz status_is 0 dut_read @@ -935,7 +658,7 @@ test_gpioget_with_pull_up() { local sim0=${GPIOSIM_CHIP_NAME[sim0]} - run_tool gpioget --bias=pull-up --unquoted --chip "$sim0" 0 1 2 3 4 5 6 7 + run_prog gpioget --bias=pull-up --unquoted --chip "$sim0" 0 1 2 3 4 5 6 7 output_is \ "0=active 1=active 2=active 3=active 4=active 5=active 6=active 7=active" @@ -952,7 +675,7 @@ test_gpioget_with_pull_down() { local sim0=${GPIOSIM_CHIP_NAME[sim0]} - run_tool gpioget --bias=pull-down --unquoted --chip "$sim0" 0 1 2 3 4 5 6 7 + run_prog gpioget --bias=pull-down --unquoted --chip "$sim0" 0 1 2 3 4 5 6 7 output_is \ "0=inactive 1=inactive 2=inactive 3=inactive 4=inactive 5=inactive 6=inactive 7=inactive" @@ -965,31 +688,31 @@ test_gpioget_with_direction_as_is() { local sim0=${GPIOSIM_CHIP_NAME[sim0]} # flip to output - run_tool gpioset -t0 foo=1 + run_prog gpioset -t0 foo=1 status_is 0 - run_tool gpioinfo foo + run_prog gpioinfo foo output_regex_match "$sim0 1\\s+\"foo\"\\s+output" status_is 0 - run_tool gpioget --as-is foo + run_prog gpioget --as-is foo # note gpio-sim reverts line to its pull when released output_is "\"foo\"=inactive" status_is 0 - run_tool gpioinfo foo + run_prog gpioinfo foo output_regex_match "$sim0 1\\s+\"foo\"\\s+output" status_is 0 # whereas the default behaviour forces to input - run_tool gpioget foo + run_prog gpioget foo # note gpio-sim reverts line to its pull when released # (defaults to pull-down) output_is "\"foo\"=inactive" status_is 0 - run_tool gpioinfo foo + run_prog gpioinfo foo output_regex_match "$sim0 1\\s+\"foo\"\\s+input" status_is 0 } @@ -998,7 +721,7 @@ test_gpioget_with_hold_period() { gpiosim_chip sim0 num_lines=8 line_name=1:foo # only test parsing - testing the hold-period itself is tricky - run_tool gpioget --hold-period=100ms foo + run_prog gpioget --hold-period=100ms foo output_is "\"foo\"=inactive" status_is 0 } @@ -1010,7 +733,7 @@ test_gpioget_with_strict_named_line_check() { line_name=4:xyz line_name=7:foobar gpiosim_chip sim2 num_lines=16 - run_tool gpioget --strict foobar + run_prog gpioget --strict foobar output_regex_match ".*line 'foobar' is not unique" status_is 1 @@ -1024,12 +747,12 @@ test_gpioget_with_lines_by_offset() { gpiosim_set_pull sim0 1 pull-up gpiosim_set_pull sim0 6 pull-down - run_tool gpioget --chip "${GPIOSIM_CHIP_NAME[sim0]}" 1 6 + run_prog gpioget --chip "${GPIOSIM_CHIP_NAME[sim0]}" 1 6 output_is "\"1\"=active \"6\"=inactive" status_is 0 - run_tool gpioget --unquoted --chip "${GPIOSIM_CHIP_NAME[sim0]}" 1 6 + run_prog gpioget --unquoted --chip "${GPIOSIM_CHIP_NAME[sim0]}" 1 6 output_is "1=active 6=inactive" status_is 0 @@ -1043,19 +766,19 @@ test_gpioget_with_lines_strictly_by_name() { gpiosim_set_pull sim0 1 pull-up gpiosim_set_pull sim0 6 pull-down - run_tool gpioget --by-name --chip "${GPIOSIM_CHIP_NAME[sim0]}" 1 6 + run_prog gpioget --by-name --chip "${GPIOSIM_CHIP_NAME[sim0]}" 1 6 output_is "\"1\"=inactive \"6\"=active" status_is 0 - run_tool gpioget --by-name --unquoted --chip "${GPIOSIM_CHIP_NAME[sim0]}" 1 6 + run_prog gpioget --by-name --unquoted --chip "${GPIOSIM_CHIP_NAME[sim0]}" 1 6 output_is "1=inactive 6=active" status_is 0 } test_gpioget_with_no_arguments() { - run_tool gpioget + run_prog gpioget output_regex_match ".*at least one GPIO line must be specified" status_is 1 @@ -1064,7 +787,7 @@ test_gpioget_with_no_arguments() { test_gpioget_with_chip_but_no_line_specified() { gpiosim_chip sim0 num_lines=8 - run_tool gpioget --chip "${GPIOSIM_CHIP_NAME[sim0]}" + run_prog gpioget --chip "${GPIOSIM_CHIP_NAME[sim0]}" output_regex_match ".*at least one GPIO line must be specified" status_is 1 @@ -1074,7 +797,7 @@ test_gpioget_with_offset_out_of_range() { gpiosim_chip sim0 num_lines=4 local sim0=${GPIOSIM_CHIP_NAME[sim0]} - run_tool gpioget --chip "$sim0" 0 1 2 3 4 5 + run_prog gpioget --chip "$sim0" 0 1 2 3 4 5 output_regex_match ".*offset 4 is out of range on chip '$sim0'" output_regex_match ".*offset 5 is out of range on chip '$sim0'" @@ -1082,7 +805,7 @@ test_gpioget_with_offset_out_of_range() { } test_gpioget_with_nonexistent_line() { - run_tool gpioget nonexistent-line + run_prog gpioget nonexistent-line output_regex_match ".*cannot find line 'nonexistent-line'" status_is 1 @@ -1093,31 +816,31 @@ test_gpioget_with_same_line_twice() { local sim0=${GPIOSIM_CHIP_NAME[sim0]} # by offset - run_tool gpioget --chip "$sim0" 0 0 + run_prog gpioget --chip "$sim0" 0 0 output_regex_match ".*lines '0' and '0' are the same line" status_is 1 # by name - run_tool gpioget foo foo + run_prog gpioget foo foo output_regex_match ".*lines 'foo' and 'foo' are the same line" status_is 1 # by chip and name - run_tool gpioget --chip "$sim0" foo foo + run_prog gpioget --chip "$sim0" foo foo output_regex_match ".*lines 'foo' and 'foo' are the same line" status_is 1 # by name and offset - run_tool gpioget --chip "$sim0" foo 1 + run_prog gpioget --chip "$sim0" foo 1 output_regex_match ".*lines 'foo' and '1' are the same line" status_is 1 # by offset and name - run_tool gpioget --chip "$sim0" 1 foo + run_prog gpioget --chip "$sim0" 1 foo output_regex_match ".*lines '1' and 'foo' are the same line" status_is 1 @@ -1126,7 +849,7 @@ test_gpioget_with_same_line_twice() { test_gpioget_with_invalid_bias() { gpiosim_chip sim0 num_lines=8 - run_tool gpioget --bias=bad --chip "${GPIOSIM_CHIP_NAME[sim0]}" 0 1 + run_prog gpioget --bias=bad --chip "${GPIOSIM_CHIP_NAME[sim0]}" 0 1 output_regex_match ".*invalid bias.*" status_is 1 @@ -1135,7 +858,7 @@ test_gpioget_with_invalid_bias() { test_gpioget_with_invalid_hold_period() { gpiosim_chip sim0 num_lines=8 - run_tool gpioget --hold-period=bad --chip "${GPIOSIM_CHIP_NAME[sim0]}" 0 + run_prog gpioget --hold-period=bad --chip "${GPIOSIM_CHIP_NAME[sim0]}" 0 output_regex_match ".*invalid period.*" status_is 1 @@ -1255,7 +978,7 @@ test_gpioset_with_consumer() { dut_run gpioset --banner --consumer gpio-tools-tests foo=1 baz=0 - run_tool gpioinfo + run_prog gpioinfo output_regex_match "\\s+line\\s+0:\\s+unnamed\\s+input" output_regex_match \ @@ -1627,7 +1350,7 @@ test_gpioset_with_invalid_toggle_period() { gpiosim_chip sim0 num_lines=8 line_name=1:foo line_name=4:bar \ line_name=7:baz - run_tool gpioset --toggle 1ns foo=1 bar=0 baz=0 + run_prog gpioset --toggle 1ns foo=1 bar=0 baz=0 output_regex_match ".*invalid period.*" status_is 1 @@ -1640,7 +1363,7 @@ test_gpioset_with_strict_named_line_check() { line_name=4:xyz line_name=7:foobar gpiosim_chip sim2 num_lines=16 - run_tool gpioset --strict foobar=active + run_prog gpioset --strict foobar=active output_regex_match ".*line 'foobar' is not unique" status_is 1 @@ -1697,7 +1420,7 @@ test_gpioset_interactive_after_SIGTERM() { } test_gpioset_with_no_arguments() { - run_tool gpioset + run_prog gpioset status_is 1 output_regex_match ".*at least one GPIO line value must be specified" @@ -1706,7 +1429,7 @@ test_gpioset_with_no_arguments() { test_gpioset_with_chip_but_no_line_specified() { gpiosim_chip sim0 num_lines=8 - run_tool gpioset --chip "${GPIOSIM_CHIP_NAME[sim0]}" + run_prog gpioset --chip "${GPIOSIM_CHIP_NAME[sim0]}" output_regex_match ".*at least one GPIO line value must be specified" status_is 1 @@ -1717,7 +1440,7 @@ test_gpioset_with_offset_out_of_range() { local sim0=${GPIOSIM_CHIP_NAME[sim0]} - run_tool gpioset --chip "$sim0" 0=1 1=1 2=1 3=1 4=1 5=1 + run_prog gpioset --chip "$sim0" 0=1 1=1 2=1 3=1 4=1 5=1 output_regex_match ".*offset 4 is out of range on chip '$sim0'" output_regex_match ".*offset 5 is out of range on chip '$sim0'" @@ -1729,7 +1452,7 @@ test_gpioset_with_invalid_hold_period() { local sim0=${GPIOSIM_CHIP_NAME[sim0]} - run_tool gpioset --hold-period=bad --chip "$sim0" 0=1 + run_prog gpioset --hold-period=bad --chip "$sim0" 0=1 output_regex_match ".*invalid period.*" status_is 1 @@ -1741,13 +1464,13 @@ test_gpioset_with_invalid_value() { local sim0=${GPIOSIM_CHIP_NAME[sim0]} # by name - run_tool gpioset --chip "$sim0" 0=c + run_prog gpioset --chip "$sim0" 0=c output_regex_match ".*invalid line value.*" status_is 1 # by value - run_tool gpioset --chip "$sim0" 0=3 + run_prog gpioset --chip "$sim0" 0=3 output_regex_match ".*invalid line value.*" status_is 1 @@ -1756,7 +1479,7 @@ test_gpioset_with_invalid_value() { test_gpioset_with_invalid_offset() { gpiosim_chip sim0 num_lines=8 - run_tool gpioset --chip "${GPIOSIM_CHIP_NAME[sim0]}" 4000000000=0 + run_prog gpioset --chip "${GPIOSIM_CHIP_NAME[sim0]}" 4000000000=0 output_regex_match ".*cannot find line '4000000000'" status_is 1 @@ -1765,7 +1488,7 @@ test_gpioset_with_invalid_offset() { test_gpioset_with_invalid_bias() { gpiosim_chip sim0 num_lines=8 - run_tool gpioset --bias=bad --chip "${GPIOSIM_CHIP_NAME[sim0]}" 0=1 1=1 + run_prog gpioset --bias=bad --chip "${GPIOSIM_CHIP_NAME[sim0]}" 0=1 1=1 output_regex_match ".*invalid bias.*" status_is 1 @@ -1774,7 +1497,7 @@ test_gpioset_with_invalid_bias() { test_gpioset_with_invalid_drive() { gpiosim_chip sim0 num_lines=8 - run_tool gpioset --drive=bad --chip "${GPIOSIM_CHIP_NAME[sim0]}" 0=1 1=1 + run_prog gpioset --drive=bad --chip "${GPIOSIM_CHIP_NAME[sim0]}" 0=1 1=1 output_regex_match ".*invalid drive.*" status_is 1 @@ -1785,14 +1508,14 @@ test_gpioset_with_interactive_and_toggle() { local sim0=${GPIOSIM_CHIP_NAME[sim0]} - run_tool gpioset --interactive --toggle 1s --chip "$sim0" 0=1 + run_prog gpioset --interactive --toggle 1s --chip "$sim0" 0=1 output_regex_match ".*can't combine interactive with toggle" status_is 1 } test_gpioset_with_nonexistent_line() { - run_tool gpioset nonexistent-line=0 + run_prog gpioset nonexistent-line=0 output_regex_match ".*cannot find line 'nonexistent-line'" status_is 1 @@ -1804,25 +1527,25 @@ test_gpioset_with_same_line_twice() { local sim0=${GPIOSIM_CHIP_NAME[sim0]} # by offset - run_tool gpioset --chip "$sim0" 0=1 0=1 + run_prog gpioset --chip "$sim0" 0=1 0=1 output_regex_match ".*lines '0' and '0' are the same line" status_is 1 # by name - run_tool gpioset --chip "$sim0" foo=1 foo=1 + run_prog gpioset --chip "$sim0" foo=1 foo=1 output_regex_match ".*lines 'foo' and 'foo' are the same line" status_is 1 # by name and offset - run_tool gpioset --chip "$sim0" foo=1 1=1 + run_prog gpioset --chip "$sim0" foo=1 1=1 output_regex_match ".*lines 'foo' and '1' are the same line" status_is 1 # by offset and name - run_tool gpioset --chip "$sim0" 1=1 foo=1 + run_prog gpioset --chip "$sim0" 1=1 foo=1 output_regex_match ".*lines '1' and 'foo' are the same line" status_is 1 @@ -2018,7 +1741,7 @@ test_gpiomon_with_consumer() { dut_run gpiomon --banner --consumer gpio-tools-tests foo baz - run_tool gpioinfo + run_prog gpioinfo output_regex_match "\\s+line\\s+0:\\s+unnamed\\s+input" output_regex_match \ @@ -2089,7 +1812,7 @@ test_gpiomon_with_debounce_period() { dut_run gpiomon --banner --debounce-period 123us foo baz - run_tool gpioinfo + run_prog gpioinfo output_regex_match "\\s+line\\s+0:\\s+unnamed\\s+input" output_regex_match \ @@ -2195,7 +1918,7 @@ test_gpiomon_exit_after_SIGTERM() { } test_gpiomon_with_nonexistent_line() { - run_tool gpiomon nonexistent-line + run_prog gpiomon nonexistent-line status_is 1 output_regex_match ".*cannot find line 'nonexistent-line'" @@ -2207,19 +1930,19 @@ test_gpiomon_with_same_line_twice() { local sim0=${GPIOSIM_CHIP_NAME[sim0]} # by offset - run_tool gpiomon --chip "$sim0" 0 0 + run_prog gpiomon --chip "$sim0" 0 0 output_regex_match ".*lines '0' and '0' are the same line" status_is 1 # by name - run_tool gpiomon foo foo + run_prog gpiomon foo foo output_regex_match ".*lines 'foo' and 'foo' are the same line" status_is 1 # by name and offset - run_tool gpiomon --chip "$sim0" 1 foo + run_prog gpiomon --chip "$sim0" 1 foo output_regex_match ".*lines '1' and 'foo' are the same line" status_is 1 @@ -2232,7 +1955,7 @@ test_gpiomon_with_strict_named_line_check() { line_name=4:xyz line_name=7:foobar gpiosim_chip sim2 num_lines=16 - run_tool gpiomon --strict foobar + run_prog gpiomon --strict foobar output_regex_match ".*line 'foobar' is not unique" status_is 1 @@ -2292,7 +2015,7 @@ test_gpiomon_with_lines_strictly_by_name() { } test_gpiomon_with_no_arguments() { - run_tool gpiomon + run_prog gpiomon output_regex_match ".*at least one GPIO line must be specified" status_is 1 @@ -2301,7 +2024,7 @@ test_gpiomon_with_no_arguments() { test_gpiomon_with_no_line_specified() { gpiosim_chip sim0 num_lines=8 - run_tool gpiomon --chip "${GPIOSIM_CHIP_NAME[sim0]}" + run_prog gpiomon --chip "${GPIOSIM_CHIP_NAME[sim0]}" output_regex_match ".*at least one GPIO line must be specified" status_is 1 @@ -2312,7 +2035,7 @@ test_gpiomon_with_offset_out_of_range() { local sim0=${GPIOSIM_CHIP_NAME[sim0]} - run_tool gpiomon --chip "$sim0" 5 + run_prog gpiomon --chip "$sim0" 5 output_regex_match ".*offset 5 is out of range on chip '$sim0'" status_is 1 @@ -2323,7 +2046,7 @@ test_gpiomon_with_invalid_bias() { local sim0=${GPIOSIM_CHIP_NAME[sim0]} - run_tool gpiomon --bias=bad -c "$sim0" 0 1 + run_prog gpiomon --bias=bad -c "$sim0" 0 1 output_regex_match ".*invalid bias.*" status_is 1 @@ -2334,7 +2057,7 @@ test_gpiomon_with_invalid_debounce_period() { local sim0=${GPIOSIM_CHIP_NAME[sim0]} - run_tool gpiomon --debounce-period bad -c "$sim0" 0 1 + run_prog gpiomon --debounce-period bad -c "$sim0" 0 1 output_regex_match ".*invalid period: bad" status_is 1 @@ -2345,7 +2068,7 @@ test_gpiomon_with_invalid_idle_timeout() { local sim0=${GPIOSIM_CHIP_NAME[sim0]} - run_tool gpiomon --idle-timeout bad -c "$sim0" 0 1 + run_prog gpiomon --idle-timeout bad -c "$sim0" 0 1 output_regex_match ".*invalid period: bad" status_is 1 @@ -2770,7 +2493,7 @@ test_gpionotify_exit_after_SIGTERM() { } test_gpionotify_with_nonexistent_line() { - run_tool gpionotify nonexistent-line + run_prog gpionotify nonexistent-line status_is 1 output_regex_match ".*cannot find line 'nonexistent-line'" @@ -2782,21 +2505,21 @@ test_gpionotify_with_same_line_twice() { local sim0=${GPIOSIM_CHIP_NAME[sim0]} # by offset - run_tool gpionotify --chip "$sim0" 0 0 + run_prog gpionotify --chip "$sim0" 0 0 output_regex_match ".*lines '0' and '0' are the same line" num_lines_is 1 status_is 1 # by name - run_tool gpionotify foo foo + run_prog gpionotify foo foo output_regex_match ".*lines 'foo' and 'foo' are the same line" num_lines_is 1 status_is 1 # by name and offset - run_tool gpionotify --chip "$sim0" 1 foo + run_prog gpionotify --chip "$sim0" 1 foo output_regex_match ".*lines '1' and 'foo' are the same line" num_lines_is 1 @@ -2810,7 +2533,7 @@ test_gpionotify_with_strict_named_line_check() { line_name=4:xyz line_name=7:foobar gpiosim_chip sim2 num_lines=16 - run_tool gpionotify --strict foobar + run_prog gpionotify --strict foobar output_regex_match ".*line 'foobar' is not unique" status_is 1 @@ -2854,7 +2577,7 @@ test_gpionotify_with_lines_strictly_by_name() { } test_gpionotify_with_no_arguments() { - run_tool gpionotify + run_prog gpionotify output_regex_match ".*at least one GPIO line must be specified" status_is 1 @@ -2863,7 +2586,7 @@ test_gpionotify_with_no_arguments() { test_gpionotify_with_no_line_specified() { gpiosim_chip sim0 num_lines=8 - run_tool gpionotify --chip "${GPIOSIM_CHIP_NAME[sim0]}" + run_prog gpionotify --chip "${GPIOSIM_CHIP_NAME[sim0]}" output_regex_match ".*at least one GPIO line must be specified" status_is 1 @@ -2874,7 +2597,7 @@ test_gpionotify_with_offset_out_of_range() { local sim0=${GPIOSIM_CHIP_NAME[sim0]} - run_tool gpionotify --chip "$sim0" 5 + run_prog gpionotify --chip "$sim0" 5 output_regex_match ".*offset 5 is out of range on chip '$sim0'" status_is 1 @@ -2885,7 +2608,7 @@ test_gpionotify_with_invalid_idle_timeout() { local sim0=${GPIOSIM_CHIP_NAME[sim0]} - run_tool gpionotify --idle-timeout bad -c "$sim0" 0 1 + run_prog gpionotify --idle-timeout bad -c "$sim0" 0 1 output_regex_match ".*invalid period: bad" status_is 1 @@ -3037,53 +2760,8 @@ test_gpionotify_with_custom_format_unknown_specifier() { output_is "%x" } -die() { - echo "$@" 1>&2 - exit 1 -} - -# 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 - FIRST=$(printf "%s\n%s\n" "$SHUNIT_VERSION" "$MIN_SHUNIT_VERSION" | sort -Vr | head -1) - test "$FIRST" = "$MIN_SHUNIT_VERSION" && \ - die "minimum shunit version required is $MIN_SHUNIT_VERSION (current version is $SHUNIT_VERSION" -} - -check_kernel() { - local REQUIRED=$1 - local CURRENT - CURRENT=$(uname -r) - - SORTED=$(printf "%s\n%s" "$REQUIRED" "$CURRENT" | sort -V | head -n 1) - - if [ "$SORTED" != "$REQUIRED" ] - then - die "linux kernel version must be at least: v$REQUIRED - got: v$CURRENT" - fi -} - -check_prog() { - local PROG=$1 - - if ! which "$PROG" > /dev/null - then - die "$PROG not found - needed to run the tests" - fi -} - -# Check all required non-coreutils tools -check_prog shunit2 -check_prog modprobe -check_prog timeout - -# Check if we're running a kernel at the required version or later -check_kernel $MIN_KERNEL_VERSION - -modprobe gpio-sim || die "unable to load the gpio-sim module" -mountpoint /sys/kernel/config/ > /dev/null 2> /dev/null || \ - die "configfs not mounted at /sys/kernel/config/" +# shellcheck source=tests/scripts/gpiod-bash-test-helper.inc +source gpiod-bash-test-helper.inc # shellcheck source=/dev/null -. shunit2 +source shunit2 From patchwork Wed Aug 7 09:10:34 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Bartosz Golaszewski X-Patchwork-Id: 817499 Received: from mail-lf1-f46.google.com (mail-lf1-f46.google.com [209.85.167.46]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5031A1CCB40 for ; Wed, 7 Aug 2024 09:10:49 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.167.46 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723021851; cv=none; b=oCVqJX8g9MnxwAb6JiZn7gStfiU7QWhcfHCKdOf6MTICt4xSEKNxvnLQNVFslxSDoD62w06uOJm0hJGMvvsUENboVpuNUEMb6DcsuijVBdBKKGuju1G2Rgxi4ohWTUu9e4Ji6k2msjgbGdM50OyM0BjCtQARyDjQAFMVTKAQchI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723021851; c=relaxed/simple; bh=M7BzYNzswDegV/EPZzkFJ6rIb+PLFWbu3HlzNer8mBI=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=r4nMr7oOVGD7J7m5GCmt+qpF1ptX+9PUVcByRPAsv4lKBPLjXaK+Bpa0b1JA3sCzTTqmt90vEnqiNl2I03ZR40fxb+tvgQAJnL4vn7kXNW7o4m3nKeKEuG98bHVxYygcKIMqmLXP7UeMo4zh6rI3OtoayKlS+IE8/uLwSk/j6ms= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=bgdev.pl; spf=none smtp.mailfrom=bgdev.pl; dkim=pass (2048-bit key) header.d=bgdev-pl.20230601.gappssmtp.com header.i=@bgdev-pl.20230601.gappssmtp.com header.b=yvvMfjj7; arc=none smtp.client-ip=209.85.167.46 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=bgdev.pl Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=bgdev.pl Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=bgdev-pl.20230601.gappssmtp.com header.i=@bgdev-pl.20230601.gappssmtp.com header.b="yvvMfjj7" Received: by mail-lf1-f46.google.com with SMTP id 2adb3069b0e04-52fc4388a64so2005498e87.1 for ; Wed, 07 Aug 2024 02:10:48 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bgdev-pl.20230601.gappssmtp.com; s=20230601; t=1723021847; x=1723626647; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=HEK2A+GmCMH7zi/jRLrexqlL+pcwjeyIYU7Mz21dDMc=; b=yvvMfjj7HK3rXIyXUq8r8laYkigSGSLZp/3nCm+9Sg7VfRVxPteCk1VZtsR6iKpoo5 m8VbQBrpA0MYSqhfhQ8J7UJ01XNHv8A+xKwm/3NGhKfyGtcpF4y0TH3jcFYI9jcdcsvt klxjH8ALi/Skya9Pz4b1rcygPBt/kOd4b7dz+F6ZiFl+JxZlGb5tgZe6K+qUi8gUIyAm g5pVxGC4iaZI/eOISoRWXbCAcwBAH+5S4T1hdaRl9K0xph/ftOjoDnt81It+bdMATUZL T/nqfqWxDydLz4BRETkC+Bit0S2k+qHFv2E5Mvy0VGSEFWhJLXh+2pDlRXLzsW2kwzfn +jNg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1723021847; x=1723626647; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=HEK2A+GmCMH7zi/jRLrexqlL+pcwjeyIYU7Mz21dDMc=; b=e4u5B7Io/7Ovu7TqDDQndRH2j+z3n9N8mErk8t77Natvmmjl7goOte6WkWbtwcdS8r RSgAOtA0k43zMQySOw0by/DyTfbdrKNlLRK9GWlO7Nw+56ZKF3BemnYAQ8+vrGbTp9JP KkvIGfvSQAose4Iz1eu9GF+aimozyAnkqnCRu6mKlZM+oXwMrQ++HnTOpdwlykmRveOJ eI84QZlIlFj/DUKewObk3FE/aTMSXKbmfZL6K+PvOSu4xK296GIjPwgM13BKBePg7FE3 27mXtOI+kj7HKv2FIYkW27BHhmwqfQbJqpR6rItvy867WAdg94d2XR0A57GuWJleInkm PrvQ== X-Gm-Message-State: AOJu0YytGBZcqaU5tPAm67b00FF2Ol4End4kTmZG8NQLYR9YTJyITAzj G4hQ8yVgDYV0ts7iHwC8ZsAsXpO64jOi05zVo5ECuWF6BVbaxjoo8A3cey4n5dM= X-Google-Smtp-Source: AGHT+IGRFh6KtsV1Gdj+CFEJBlj3tBbTVZ4Ho/tNI5hl7J/0oqnT39ysCrCZswjj+UeOUMEQXTpa3g== X-Received: by 2002:a05:6512:4002:b0:52f:79f8:a42e with SMTP id 2adb3069b0e04-530bb36bd82mr13708784e87.4.1723021847227; Wed, 07 Aug 2024 02:10:47 -0700 (PDT) Received: from [127.0.1.1] ([2a01:cb1d:dc:7e00:a3e7:be71:fe4f:c360]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-429059cd2b3sm18936355e9.44.2024.08.07.02.10.46 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 07 Aug 2024 02:10:46 -0700 (PDT) From: Bartosz Golaszewski Date: Wed, 07 Aug 2024 11:10:34 +0200 Subject: [PATCH libgpiod v4 03/18] bindings: glib: add build files Precedence: bulk X-Mailing-List: linux-gpio@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20240807-dbus-v4-3-64ea80169e51@linaro.org> References: <20240807-dbus-v4-0-64ea80169e51@linaro.org> In-Reply-To: <20240807-dbus-v4-0-64ea80169e51@linaro.org> To: Linus Walleij , Kent Gibson , Erik Schilling , Phil Howard , Andy Shevchenko , Viresh Kumar , Dan Carpenter , Philip Withnall Cc: linux-gpio@vger.kernel.org, Bartosz Golaszewski , Alexander Sverdlin X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=11670; i=bartosz.golaszewski@linaro.org; h=from:subject:message-id; bh=hCPightF2v5zvwlMII+krMKSbabnFlgCPs/PuZGcmaQ=; b=owEBbQKS/ZANAwAKARGnLqAUcddyAcsmYgBmszoQMKXkUxOnJYwMeFkwymnSqwzVjCHtvih6d 8Rx4PeArSKJAjMEAAEKAB0WIQQWnetsC8PEYBPSx58Rpy6gFHHXcgUCZrM6EAAKCRARpy6gFHHX cn/YEADCxUQVuMxKChZu3VvqFvPgpAmbiL9IUoPKwWGE2lbEHrM+ovHAAZpAQW+QJvAQDY5dI4i ar/5UfliaP780TIZRBEtUBtcbBtr/2QuMq/ZW8IwulREsVyUqDb2kF+TghCmKy4X3L8mMgERVIv iE9f91mZ8rLOK/c6VzP+UQtZrvvQl4AORozq2hRrNreLhIAMA8oez5tT4Y4AvUPZbCSQJ72oyNY e2Fflc74NmSPS4v6cquFkHA3CUO0m+YNw8JZgOAkvMirVNlo+0CHONTTg6T31p4x+AJJ8dxh51K 1RH31kE2wvshbCiUFFyyQkm2wjOL1Gll6A2LWdYxwx3aehDJGbb68BvrULgPWp0XOsJ5Bif2j9w VasMPxxnosNKTtv4onw1Uq7jyQOLhQayfDI/L3DG25UaqRnCEnIdqPbXAvqqwiD+laddBTNtic0 rEx4a7fDFrm4yGBAaEf2MckBE9PNdf33lHFUBGwng9J83oYyaywFQmGltV8tx3NeTT9TzhVJNOK Cfl6SkZYscBsJPhodxxLas9RvJiH/FIUWRmxjdZJS5+YpWXUvQZsr1IDE0SjP2Zdpsap0E3BJtd sXhMUtfB1vSxyqBgg/rTdbxzVhovnzt9VhlnFFLyZOteu5G7MSiwNX3+UVm6ooFk8cv1GZYF8Uq C6FmTvNTVwgQr3A== X-Developer-Key: i=bartosz.golaszewski@linaro.org; a=openpgp; fpr=169DEB6C0BC3C46013D2C79F11A72EA01471D772 From: Bartosz Golaszewski Add the directory structure and build files as well as changes to .gitignore and Doxygen.in for GLib bindings. Tested-by: Alexander Sverdlin Signed-off-by: Bartosz Golaszewski --- .gitignore | 2 + bindings/Makefile.am | 7 ++ bindings/glib/.gitignore | 6 ++ bindings/glib/Makefile.am | 131 +++++++++++++++++++++++++++++++++++++ bindings/glib/examples/.gitignore | 14 ++++ bindings/glib/examples/Makefile.am | 22 +++++++ bindings/glib/gpiod-glib.pc.in | 15 +++++ bindings/glib/tests/.gitignore | 4 ++ bindings/glib/tests/Makefile.am | 29 ++++++++ configure.ac | 36 ++++++++++ 10 files changed, 266 insertions(+) diff --git a/.gitignore b/.gitignore index cf66e97..c3a29d8 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ *.o *.lo *.la +generated-*.c +generated-*.h doc *.pc *.tar.gz diff --git a/bindings/Makefile.am b/bindings/Makefile.am index 004ae23..a177187 100644 --- a/bindings/Makefile.am +++ b/bindings/Makefile.am @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-or-later # SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski +# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski SUBDIRS = . @@ -20,3 +21,9 @@ if WITH_BINDINGS_RUST SUBDIRS += rust endif + +if WITH_BINDINGS_GLIB + +SUBDIRS += glib + +endif diff --git a/bindings/glib/.gitignore b/bindings/glib/.gitignore new file mode 100644 index 0000000..aa399b8 --- /dev/null +++ b/bindings/glib/.gitignore @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: CC0-1.0 +# SPDX-FileCopyrightText: 2024 Bartosz Golaszewski + +*.gir +*.typelib +Gpiodglib-1.0 diff --git a/bindings/glib/Makefile.am b/bindings/glib/Makefile.am new file mode 100644 index 0000000..6ecef94 --- /dev/null +++ b/bindings/glib/Makefile.am @@ -0,0 +1,131 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# SPDX-FileCopyrightText: 2022-2023 Bartosz Golaszewski + +SUBDIRS = . + +if WITH_TESTS + +SUBDIRS += tests + +endif + +if WITH_EXAMPLES + +SUBDIRS += examples + +endif + +lib_LTLIBRARIES = libgpiod-glib.la + +libgpiod_glib_la_SOURCES = \ + chip.c \ + chip-info.c \ + edge-event.c \ + error.c \ + info-event.c \ + internal.c \ + internal.h \ + line-config.c \ + line-info.c \ + line-request.c \ + line-settings.c \ + misc.c \ + request-config.c + +otherincludedir = $(includedir)/gpiod-glib +otherinclude_HEADERS = \ + gpiod-glib/chip.h \ + gpiod-glib/chip-info.h \ + gpiod-glib/edge-event.h \ + gpiod-glib/error.h \ + gpiod-glib/generated-enums.h \ + gpiod-glib/info-event.h \ + gpiod-glib/line.h \ + gpiod-glib/line-config.h \ + gpiod-glib/line-info.h \ + gpiod-glib/line-request.h \ + gpiod-glib/line-settings.h \ + gpiod-glib/misc.h \ + gpiod-glib/request-config.h + +EXTRA_DIST = \ + generated-enums.c.template \ + generated-enums.h.template + +project_headers = \ + $(srcdir)/gpiod-glib/line.h \ + $(srcdir)/gpiod-glib/edge-event.h \ + $(srcdir)/gpiod-glib/info-event.h + +generated-enums.c: $(project_headers) generated-enums.c.template + $(AM_V_GEN)$(GLIB_MKENUMS) \ + --template=$(srcdir)/generated-enums.c.template \ + --output=$(builddir)/$@ \ + $(project_headers) + +gpiod-glib/generated-enums.h: $(project_headers) generated-enums.h.template + $(AM_V_GEN)$(GLIB_MKENUMS) \ + --template=$(srcdir)/generated-enums.h.template \ + --output=$(srcdir)/$@ \ + $(project_headers) + +nodist_libgpiod_glib_la_SOURCES = \ + generated-enums.c \ + gpiod-glib/generated-enums.h + +BUILT_SOURCES = $(nodist_libgpiod_glib_la_SOURCES) +CLEANFILES = $(nodist_libgpiod_glib_la_SOURCES) + +libgpiod_glib_la_CFLAGS = -Wall -Wextra -g +libgpiod_glib_la_CFLAGS += -I$(top_srcdir)/include/ -include $(top_builddir)/config.h +libgpiod_glib_la_CFLAGS += $(GLIB_CFLAGS) $(GIO_CFLAGS) $(GIO_UNIX_CFLAGS) +libgpiod_glib_la_CFLAGS += -DG_LOG_DOMAIN=\"gpiod-glib\" +libgpiod_glib_la_CFLAGS += $(PROFILING_CFLAGS) +libgpiod_glib_la_LDFLAGS = -version-info $(subst .,:,$(ABI_GLIB_VERSION)) +libgpiod_glib_la_LDFLAGS += -lgpiod -L$(top_builddir)/lib +libgpiod_glib_la_LDFLAGS += $(GLIB_LIBS) $(GIO_LIBS) $(GIO_UNIX_LIBS) +libgpiod_glib_la_LDFLAGS += $(PROFILING_LDFLAGS) + +include_HEADERS = gpiod-glib.h + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = gpiod-glib.pc + +if HAVE_INTROSPECTION + +INTROSPECTION_GIRS = Gpiodglib-1.0.gir + +girdir = $(INTROSPECTION_GIRDIR) +gir_DATA = Gpiodglib-1.0.gir + +typelibsdir = $(INTROSPECTION_TYPELIBDIR) +typelibs_DATA = Gpiodglib-1.0.typelib + +Gpiodglib_1_0_gir_SCANNERFLAGS = \ + --c-include="gpiod-glib.h" \ + --warn-all \ + --namespace Gpiodglib \ + --identifier-prefix Gpiodglib \ + --symbol-prefix gpiodglib + +Gpiodglib_1_0_gir_CFLAGS = \ + $(libgpiod_glib_la_CFLAGS) \ + -DGPIODGLIB_COMPILATION + +Gpiodglib-1.0.gir: libgpiod-glib.la +Gpiodglib_1_0_gir_INCLUDES = Gio-2.0 +Gpiodglib_1_0_gir_LIBS = libgpiod-glib.la +Gpiodglib_1_0_gir_FILES = $(otherinclude_HEADERS) $(libgpiod_glib_la_SOURCES) +Gpiodglib_1_0_gir_EXPORT_PACKAGES = gpiod-glib + +include $(INTROSPECTION_MAKEFILE) + +endif + +if HAS_GI_DOCGEN + +doc: Gpiodglib-1.0.gir + $(AM_V_GEN)gi-docgen generate Gpiodglib-1.0.gir +.PHONY: doc + +endif diff --git a/bindings/glib/examples/.gitignore b/bindings/glib/examples/.gitignore new file mode 100644 index 0000000..c2415ae --- /dev/null +++ b/bindings/glib/examples/.gitignore @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: CC0-1.0 +# SPDX-FileCopyrightText: 2023 Bartosz Golaszewski + +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 diff --git a/bindings/glib/examples/Makefile.am b/bindings/glib/examples/Makefile.am new file mode 100644 index 0000000..fb4e5b1 --- /dev/null +++ b/bindings/glib/examples/Makefile.am @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# SPDX-FileCopyrightText: 2022-2023 Bartosz Golaszewski + +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) diff --git a/bindings/glib/gpiod-glib.pc.in b/bindings/glib/gpiod-glib.pc.in new file mode 100644 index 0000000..15d2b3f --- /dev/null +++ b/bindings/glib/gpiod-glib.pc.in @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# SPDX-FileCopyrightText: 2022-2023 Bartosz Golaszewski + +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: gpiod-glib +Description: GObject bindings for libgpiod +URL: @PACKAGE_URL@ +Version: @PACKAGE_VERSION@ +Requires.private: libgpiod >= 2.1 +Libs: -L${libdir} -lgpiod-glib +Cflags: -I${includedir} diff --git a/bindings/glib/tests/.gitignore b/bindings/glib/tests/.gitignore new file mode 100644 index 0000000..8eb499f --- /dev/null +++ b/bindings/glib/tests/.gitignore @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: CC0-1.0 +# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski + +gpiod-glib-test diff --git a/bindings/glib/tests/Makefile.am b/bindings/glib/tests/Makefile.am new file mode 100644 index 0000000..a90587a --- /dev/null +++ b/bindings/glib/tests/Makefile.am @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# SPDX-FileCopyrightText: 2022-2023 Bartosz Golaszewski + +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) diff --git a/configure.ac b/configure.ac index 93d9d75..31cb8d1 100644 --- a/configure.ac +++ b/configure.ac @@ -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]) @@ -248,6 +250,36 @@ 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])) + + AC_CHECK_PROG([has_gi_docgen], [gi-docgen], [true], [false]) + AM_CONDITIONAL([HAS_GI_DOCGEN], [test "x$has_gi_docgen" = xtrue]) + if test "x$has_gi_docgen" = xfalse + then + AC_MSG_NOTICE([gi-docgen not found - GLib documentation cannot be generated]) + fi +fi + +# GObject-introspection +found_introspection=no +m4_ifdef([GOBJECT_INTROSPECTION_CHECK], + [GOBJECT_INTROSPECTION_CHECK([0.6.2])], + [AM_CONDITIONAL(HAVE_INTROSPECTION, test "x$found_introspection" = "xyes")]) + AC_CHECK_PROG([has_doxygen], [doxygen], [true], [false]) AM_CONDITIONAL([HAS_DOXYGEN], [test "x$has_doxygen" = xtrue]) if test "x$has_doxygen" = xfalse @@ -284,6 +316,10 @@ 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/tests/Makefile bindings/python/Makefile bindings/python/gpiod/Makefile bindings/python/gpiod/ext/Makefile From patchwork Wed Aug 7 09:10:35 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Bartosz Golaszewski X-Patchwork-Id: 817498 Received: from mail-wm1-f50.google.com (mail-wm1-f50.google.com [209.85.128.50]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 6D83B1C9DE2 for ; Wed, 7 Aug 2024 09:10:51 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.50 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723021855; cv=none; b=eHyf6V37WZAybgLl+qh6vNL7jcEsWAxt77WnDAdD1x9LwRkSZX5H8POP+lxid6CFdz9lvW+V4GS9viWNQ2PbQmpq8QoG+6yT2w9lV5zwquvOaFIoBT/DLu1Q3icmwQYqsTGJZ2fMA14X6QdH1LeirfSe6EVN3MUj679ORvzGwa4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723021855; c=relaxed/simple; bh=cvNS54/qvflJCGwpqbSXxjwITqSF141hEaAF+cxSx5A=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=H3rpZuLE+2eplqnP6uVRzyPogNKBizdOPXZ/ba9634zd3wqbMDWxLL69fh95MfX2Brg/vTwPOjlGjwFEwr3HvFgN1FtLiYz0xb24LApnOMRmYkpSZkjlHwhv3YRo5roYmxVWrK30aIb6r0kULO7O8tFoN0749EM1P0iPkNTy1ag= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=bgdev.pl; spf=none smtp.mailfrom=bgdev.pl; dkim=pass (2048-bit key) header.d=bgdev-pl.20230601.gappssmtp.com header.i=@bgdev-pl.20230601.gappssmtp.com header.b=nrq0QUBP; arc=none smtp.client-ip=209.85.128.50 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=bgdev.pl Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=bgdev.pl Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=bgdev-pl.20230601.gappssmtp.com header.i=@bgdev-pl.20230601.gappssmtp.com header.b="nrq0QUBP" Received: by mail-wm1-f50.google.com with SMTP id 5b1f17b1804b1-42816ca782dso11499475e9.2 for ; Wed, 07 Aug 2024 02:10:51 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bgdev-pl.20230601.gappssmtp.com; s=20230601; t=1723021850; x=1723626650; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=In4CQJdLy6Uh24c1DZ/o+8qcSBbGHXtoX5c4M2wjh94=; b=nrq0QUBPTm50PHCaJs6dfVKZ4hVHbEGPFrlrIpQ9IckCQsVcWU36U7u1UOMiRiYMGx M4y0lO3P76ksK0+dnpe/nirZIAUT5rookGNPwKaVx5XKg76niIxTe9uYe3dP7eWOE5uV XEuRPRuFatq6F505PM6vgV2Xq/uODUdmHFu3KyBMzIh8DCCbo6y48k89zLAzl9nhoggP U6+uibOiU9xFBRIvQrFGEMoeWGsxT095+kiE/qyib/2yt3ZWIMLXBCRE5EOBw3lyTcbS KWC2J4eEGVZE3XktYtwnCi1xRBtbgftirzFIpqLvQwJRxRCf7sJKgm/AQFLP1d0Io/sG Zylg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1723021850; x=1723626650; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=In4CQJdLy6Uh24c1DZ/o+8qcSBbGHXtoX5c4M2wjh94=; b=kUnw8POysxcuIwzEGzmILcDB3B8O+GDKlXIOQTX3I+6Vs/0VhMxqaD2ip8Urf7aoww DEBGWVWLcJwACWVJ/+DG4ICBwW/8AHENGesnG7mGW71n1V6X/55jbX1Wl5OqOZ/MBWVq a+U30xKOwlKeTwx1A12pUNoM8PHizyshvEpjG5xv4vtSwJkzwtVxQeqMqVlayGelefZ0 rVA0HOXRw1h2eeZ1+W9xlQ+wsyuN8XyzWV6Q/IfqWOay0bR56U5a/fSkVqhmkFquoQK8 /sZiyY3ibTAee8AAWdBsj//rNisr4x+xkS63U4xASX73B6y9qFIhk8Aay265Ye4YVfzN 77vQ== X-Gm-Message-State: AOJu0YxqOCVcRXQ2UhFyCSqKKNt1f247oKRiPk0sadv3//YMNYbLxyxp eANBN9Jx2vZ5fzq3JSE2ydl9eeC2JBBKcIXaJBsh3B/mCWbkEvhFEfMkX5DYHrA= X-Google-Smtp-Source: AGHT+IEfQwNdQRDUSz7+gjkbUCW/ifzMu+hIseJXlxZ8jX7vxQYHmSu7yc5GRgWqbG5kPbfPXZfVNQ== X-Received: by 2002:a05:600c:1549:b0:426:6eb9:2643 with SMTP id 5b1f17b1804b1-428e6af6c58mr107338035e9.11.1723021849386; Wed, 07 Aug 2024 02:10:49 -0700 (PDT) Received: from [127.0.1.1] ([2a01:cb1d:dc:7e00:a3e7:be71:fe4f:c360]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-429059cd2b3sm18936355e9.44.2024.08.07.02.10.47 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 07 Aug 2024 02:10:47 -0700 (PDT) From: Bartosz Golaszewski Date: Wed, 07 Aug 2024 11:10:35 +0200 Subject: [PATCH libgpiod v4 04/18] bindings: glib: add public headers Precedence: bulk X-Mailing-List: linux-gpio@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20240807-dbus-v4-4-64ea80169e51@linaro.org> References: <20240807-dbus-v4-0-64ea80169e51@linaro.org> In-Reply-To: <20240807-dbus-v4-0-64ea80169e51@linaro.org> To: Linus Walleij , Kent Gibson , Erik Schilling , Phil Howard , Andy Shevchenko , Viresh Kumar , Dan Carpenter , Philip Withnall Cc: linux-gpio@vger.kernel.org, Bartosz Golaszewski , Alexander Sverdlin X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=48213; i=bartosz.golaszewski@linaro.org; h=from:subject:message-id; bh=bIDg0v1OGxX1NsMoHwxRSqN7f4vc/ZFdNGV8inxLkkU=; b=owEBbQKS/ZANAwAKARGnLqAUcddyAcsmYgBmszoQFC9yxv75m/0/qU3cFOom4DSpJ3D/UblDK HUGBwlR0WGJAjMEAAEKAB0WIQQWnetsC8PEYBPSx58Rpy6gFHHXcgUCZrM6EAAKCRARpy6gFHHX cnmCEACto0ExZ6r9lMIfHgJ/dpyAuVDLLo1p6/i//2Bl2aKMRj35UaEbaJsoOtK/zuhgbWMSwVC pZ6+m+U2K4l/0wRJy3cYFilwlA9d23UMAbhN7faPbz+VdMSh6Zknm3b5cUbv6XVOgDFPFrXNyek VMRGg5Wfov70rtfRhkEmQVls18K6cTyQNwXZf7saWUVFhl6yz5E37My4+CdBkpWsKaLyN/Q9FQV YNF3HZPlMIMIi9vesdetnpUGUbMhrpOBW//rxF1SCu1Iq2AK3/XqS69EDWWSo9OS+FxzKnYqf2q u4WxMT0LS2BpmLYou1+d4iFZkjMQtDfIwVTe9YQOpVRNyoDWp6IUMb30hkkMQRlQjAr3w71MA9u SALTQ7eX4mKQtLVw4bZaf78P/WAdlub2P5ZWyvNWFnHItPEYg9JX0+jFZQS2bGYjXWIpCydj0BQ XS9xt3z+CQeWoR2tCJkRty8NXfYwd3WuZpFd3xRHTAX58cd7V/isLtqGZgN1xDmhrGtww5TdKTB S/erkkh7ICv9wuKwqZCXDYv6xD8XZmuc0NOSFxF2+cblmTUCKC0lqi285Ot9JVZSN1T06m0dSTS Z0gVGxMLieK13tnbHpSOkc4gcCCASa51GfLYsliX09tWkaTicwEqAKIOaH21AuGzMuYzS8cKwN1 kSPHR+aT34bIiGw== X-Developer-Key: i=bartosz.golaszewski@linaro.org; a=openpgp; fpr=169DEB6C0BC3C46013D2C79F11A72EA01471D772 From: Bartosz Golaszewski Add the public headers for GLib bindings. They contain the entire API together with Doxygen comments. Tested-by: Alexander Sverdlin Signed-off-by: Bartosz Golaszewski --- bindings/glib/generated-enums.h.template | 30 ++++ bindings/glib/gpiod-glib.h | 22 +++ bindings/glib/gpiod-glib/chip-info.h | 62 +++++++++ bindings/glib/gpiod-glib/chip.h | 157 +++++++++++++++++++++ bindings/glib/gpiod-glib/edge-event.h | 97 +++++++++++++ bindings/glib/gpiod-glib/error.h | 45 ++++++ bindings/glib/gpiod-glib/info-event.h | 76 +++++++++++ bindings/glib/gpiod-glib/line-config.h | 101 ++++++++++++++ bindings/glib/gpiod-glib/line-info.h | 171 +++++++++++++++++++++++ bindings/glib/gpiod-glib/line-request.h | 186 +++++++++++++++++++++++++ bindings/glib/gpiod-glib/line-settings.h | 220 ++++++++++++++++++++++++++++++ bindings/glib/gpiod-glib/line.h | 113 +++++++++++++++ bindings/glib/gpiod-glib/misc.h | 39 ++++++ bindings/glib/gpiod-glib/request-config.h | 93 +++++++++++++ 14 files changed, 1412 insertions(+) diff --git a/bindings/glib/generated-enums.h.template b/bindings/glib/generated-enums.h.template new file mode 100644 index 0000000..d69d809 --- /dev/null +++ b/bindings/glib/generated-enums.h.template @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +// SPDX-FileCopyrightText: 2023-2024 Bartosz Golaszewski + +/*** BEGIN file-header ***/ + +#ifndef __GPIODGLIB_GENERATED_ENUMS_H__ +#define __GPIODGLIB_GENERATED_ENUMS_H__ + +#if !defined(__INSIDE_GPIOD_GLIB_H__) && !defined(GPIODGLIB_COMPILATION) +#error "Only 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 /* __GPIODGLIB_GENERATED_ENUMS_H__ */ +/*** END file-tail ***/ diff --git a/bindings/glib/gpiod-glib.h b/bindings/glib/gpiod-glib.h new file mode 100644 index 0000000..8f30452 --- /dev/null +++ b/bindings/glib/gpiod-glib.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* SPDX-FileCopyrightText: 2022-2024 Bartosz Golaszewski */ + +#ifndef __GPIODGLIB_H__ +#define __GPIODGLIB_H__ + +#define __INSIDE_GPIOD_GLIB_H__ +#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 __INSIDE_GPIOD_GLIB_H__ + +#endif /* __GPIODGLIB_H__ */ diff --git a/bindings/glib/gpiod-glib/chip-info.h b/bindings/glib/gpiod-glib/chip-info.h new file mode 100644 index 0000000..9b3b87a --- /dev/null +++ b/bindings/glib/gpiod-glib/chip-info.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* SPDX-FileCopyrightText: 2022-2024 Bartosz Golaszewski */ + +#ifndef __GPIODGLIB_CHIP_INFO_H__ +#define __GPIODGLIB_CHIP_INFO_H__ + +#if !defined(__INSIDE_GPIOD_GLIB_H__) && !defined(GPIODGLIB_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE(GpiodglibChipInfo, gpiodglib_chip_info, + GPIODGLIB, CHIP_INFO, GObject); + +#define GPIODGLIB_CHIP_INFO_TYPE (gpiodglib_chip_info_get_type()) +#define GPIODGLIB_CHIP_INFO_OBJ(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), GPIODGLIB_CHIP_INFO_TYPE, \ + GpiodglibChipInfo)) + +/** + * gpiodglib_chip_info_dup_name: + * @self: #GpiodglibChipInfo to manipulate. + * + * Get the name of the chip as represented in the kernel. + * + * Returns: (transfer full): Valid pointer to a human-readable string + * containing the chip name. The returned string is a copy and must be freed by + * the caller using g_free(). + */ +gchar * G_GNUC_WARN_UNUSED_RESULT +gpiodglib_chip_info_dup_name(GpiodglibChipInfo *self); + +/** + * gpiodglib_chip_info_dup_label: + * @self: #GpiodglibChipInfo to manipulate. + * + * Get the label of the chip as represented in the kernel. + * + * Returns: (transfer full): Valid pointer to a human-readable string + * containing the chip label. The returned string is a copy and must be freed + * by the caller using g_free(). + */ +gchar * G_GNUC_WARN_UNUSED_RESULT +gpiodglib_chip_info_dup_label(GpiodglibChipInfo *self); + +/** + * gpiodglib_chip_info_get_num_lines: + * @self: #GpiodglibChipInfo to manipulate. + * + * Get the number of lines exposed by the chip. + * + * Returns: Number of GPIO lines. + */ +guint gpiodglib_chip_info_get_num_lines(GpiodglibChipInfo *self); + +G_END_DECLS + +#endif /* __GPIODGLIB_CHIP_INFO_H__ */ diff --git a/bindings/glib/gpiod-glib/chip.h b/bindings/glib/gpiod-glib/chip.h new file mode 100644 index 0000000..d15d798 --- /dev/null +++ b/bindings/glib/gpiod-glib/chip.h @@ -0,0 +1,157 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* SPDX-FileCopyrightText: 2022-2024 Bartosz Golaszewski */ + +#ifndef __GPIODGLIB_CHIP_H__ +#define __GPIODGLIB_CHIP_H__ + +#if !defined(__INSIDE_GPIOD_GLIB_H__) && !defined(GPIODGLIB_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include + +#include "chip-info.h" +#include "line-config.h" +#include "line-info.h" +#include "line-request.h" +#include "request-config.h" + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE(GpiodglibChip, gpiodglib_chip, GPIODGLIB, CHIP, GObject); + +#define GPIODGLIB_CHIP_TYPE (gpiodglib_chip_get_type()) +#define GPIODGLIB_CHIP_OBJ(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), GPIODGLIB_CHIP_TYPE, GpiodglibChip)) + +/** + * gpiodglib_chip_new: + * @path: Path to the device file to open. + * @err: Return location for error or %NULL. + * + * Instantiates a new chip object by opening the device file indicated by path. + * + * Returns: (transfer full): New GPIO chip object. + */ +GpiodglibChip *gpiodglib_chip_new(const gchar *path, GError **err); + +/** + * gpiodglib_chip_close: + * @self: #GpiodglibChip to close. + * + * Close the GPIO chip device file and free associated resources. + * + * The chip object can live after calling this method but any of the chip's + * methods will result in an error being set. + */ +void gpiodglib_chip_close(GpiodglibChip *self); + +/** + * gpiodglib_chip_is_closed: + * @self: #GpiodglibChip to manipulate. + * + * @brief Check if this object is valid. + * + * Returns: TRUE if this object's methods can be used, FALSE otherwise. + */ +gboolean gpiodglib_chip_is_closed(GpiodglibChip *self); + +/** + * gpiodglib_chip_dup_path: + * @self: #GpiodglibChip to manipulate. + * + * Get the filesystem path that was used to open this GPIO chip. + * + * Returns: Path to the underlying character device file. The string is a copy + * and must be freed by the caller with g_free(). + */ +gchar * G_GNUC_WARN_UNUSED_RESULT +gpiodglib_chip_dup_path(GpiodglibChip *self); + +/** + * gpiodglib_chip_get_info: + * @self: #GpiodglibChip to manipulate. + * @err: Return location for error or %NULL. + * + * Get information about the chip. + * + * Returns: (transfer full): New #GpiodglibChipInfo. + */ +GpiodglibChipInfo *gpiodglib_chip_get_info(GpiodglibChip *self, GError **err); + +/** + * gpiodglib_chip_get_line_info: + * @self: #GpiodglibChip to manipulate. + * @offset: Offset of the line to get the info for. + * @err: Return location for error or %NULL. + * + * Retrieve the current snapshot of line information for a single line. + * + * Returns: (transfer full): New #GpiodglibLineInfo. + */ +GpiodglibLineInfo * +gpiodglib_chip_get_line_info(GpiodglibChip *self, guint offset, GError **err); + +/** + * gpiodglib_chip_watch_line_info: + * @self: #GpiodglibChip to manipulate. + * @offset: Offset of the line to get the info for and to watch. + * @err: Return location for error or %NULL. + * + * Retrieve the current snapshot of line information for a single line and + * start watching this line for future changes. + * + * Returns: (transfer full): New #GpiodglibLineInfo. + */ +GpiodglibLineInfo * +gpiodglib_chip_watch_line_info(GpiodglibChip *self, guint offset, GError **err); + +/** + * gpiodglib_chip_unwatch_line_info: + * @self: #GpiodglibChip to manipulate. + * @offset: Offset of the line to get the info for. + * @err: Return location for error or %NULL. + * + * Stop watching the line at given offset for info events. + * + * Returns: TRUE on success, FALSE on failure. + */ +gboolean +gpiodglib_chip_unwatch_line_info(GpiodglibChip *self, guint offset, + GError **err); + +/** + * gpiodglib_chip_get_line_offset_from_name: + * @self: #GpiodglibChip to manipulate. + * @name: Name of the GPIO line to map. + * @offset: Return location for the mapped offset. + * @err: Return location for error or %NULL. + * + * Map a GPIO line's name to its offset within the chip. + * + * Returns: TRUE on success, FALSE on failure. + */ +gboolean +gpiodglib_chip_get_line_offset_from_name(GpiodglibChip *self, const gchar *name, + guint *offset, GError **err); + +/** + * gpiodglib_chip_request_lines: + * @self: #GpiodglibChip to manipulate. + * @req_cfg: Request config object. Can be NULL for default settings. + * @line_cfg: Line config object. + * @err: Return location for error or %NULL. + * + * Request a set of lines for exclusive usage. + * + * Returns: (transfer full): New #GpiodglibLineRequest. + */ +GpiodglibLineRequest * +gpiodglib_chip_request_lines(GpiodglibChip *self, + GpiodglibRequestConfig *req_cfg, + GpiodglibLineConfig *line_cfg, GError **err); + +G_END_DECLS + +#endif /* __GPIODGLIB_CHIP_H__ */ diff --git a/bindings/glib/gpiod-glib/edge-event.h b/bindings/glib/gpiod-glib/edge-event.h new file mode 100644 index 0000000..2fa8339 --- /dev/null +++ b/bindings/glib/gpiod-glib/edge-event.h @@ -0,0 +1,97 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* SPDX-FileCopyrightText: 2023-2024 Bartosz Golaszewski */ + +#ifndef __GPIODGLIB_EDGE_EVENT_H__ +#define __GPIODGLIB_EDGE_EVENT_H__ + +#if !defined(__INSIDE_GPIOD_GLIB_H__) && !defined(GPIODGLIB_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include + +#include "line-info.h" + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE(GpiodglibEdgeEvent, gpiodglib_edge_event, + GPIODGLIB, EDGE_EVENT, GObject); + +#define GPIODGLIB_EDGE_EVENT_TYPE (gpiodglib_edge_event_get_type()) +#define GPIODGLIB_EDGE_EVENT_OBJ(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), GPIODGLIB_EDGE_EVENT_TYPE, \ + GpiodglibEdgeEvent)) + +/** + * GpiodglibEdgeEventType: + * @GPIODGLIB_EDGE_EVENT_RISING_EDGE: Rising edge event. + * @GPIODGLIB_EDGE_EVENT_FALLING_EDGE: Falling edge event. + * + * Edge event types. + */ +typedef enum { + GPIODGLIB_EDGE_EVENT_RISING_EDGE = 1, + GPIODGLIB_EDGE_EVENT_FALLING_EDGE, +} GpiodglibEdgeEventType; + +/** + * gpiodglib_edge_event_get_event_type: + * @self: #GpiodglibEdgeEvent to manipulate. + * + * Get the event type. + * + * Returns: The event type (@GPIODGLIB_EDGE_EVENT_RISING_EDGE or + * @GPIODGLIB_EDGE_EVENT_FALLING_EDGE). + */ +GpiodglibEdgeEventType +gpiodglib_edge_event_get_event_type(GpiodglibEdgeEvent *self); + +/** + * gpiodglib_edge_event_get_timestamp_ns: + * @self: #GpiodglibEdgeEvent to manipulate. + * + * Get the timestamp of the event. + * + * The source clock for the timestamp depends on the event_clock setting for + * the line. + * + * Returns: Timestamp in nanoseconds. + */ +guint64 gpiodglib_edge_event_get_timestamp_ns(GpiodglibEdgeEvent *self); + +/** + * gpiodglib_edge_event_get_line_offset: + * @self: #GpiodglibEdgeEvent to manipulate. + * + * Get the offset of the line which triggered the event. + * + * Returns: Line offset. + */ +guint gpiodglib_edge_event_get_line_offset(GpiodglibEdgeEvent *self); + +/** + * gpiodglib_edge_event_get_global_seqno: + * @self: #GpiodglibEdgeEvent to manipulate. + * + * Get the global sequence number of the event. + * + * Returns: Sequence number of the event in the series of events for all lines + * in the associated line request. + */ +gulong gpiodglib_edge_event_get_global_seqno(GpiodglibEdgeEvent *self); + +/** + * gpiodglib_edge_event_get_line_seqno: + * @self: #GpiodglibEdgeEvent to manipulate. + * + * Get the event sequence number specific to the line. + * + * Returns: Sequence number of the event in the series of events only for this + * line within the lifetime of the associated line request. + */ +gulong gpiodglib_edge_event_get_line_seqno(GpiodglibEdgeEvent *self); + +G_END_DECLS + +#endif /* __GPIODGLIB_EDGE_EVENT_H__ */ diff --git a/bindings/glib/gpiod-glib/error.h b/bindings/glib/gpiod-glib/error.h new file mode 100644 index 0000000..e23f07e --- /dev/null +++ b/bindings/glib/gpiod-glib/error.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* SPDX-FileCopyrightText: 2022-2024 Bartosz Golaszewski */ + +#ifndef __GPIODGLIB_ERROR_H__ +#define __GPIODGLIB_ERROR_H__ + +#if !defined(__INSIDE_GPIOD_GLIB_H__) && !defined(GPIODGLIB_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +#define GPIODGLIB_ERROR gpiodglib_error_quark() + +typedef enum { + GPIODGLIB_ERR_FAILED = 1, + GPIODGLIB_ERR_CHIP_CLOSED, + GPIODGLIB_ERR_REQUEST_RELEASED, + GPIODGLIB_ERR_PERM, + GPIODGLIB_ERR_NOENT, + GPIODGLIB_ERR_INTR, + GPIODGLIB_ERR_IO, + GPIODGLIB_ERR_NXIO, + GPIODGLIB_ERR_E2BIG, + GPIODGLIB_ERR_BADFD, + GPIODGLIB_ERR_CHILD, + GPIODGLIB_ERR_AGAIN, + GPIODGLIB_ERR_NOMEM, + GPIODGLIB_ERR_ACCES, + GPIODGLIB_ERR_FAULT, + GPIODGLIB_ERR_BUSY, + GPIODGLIB_ERR_EXIST, + GPIODGLIB_ERR_NODEV, + GPIODGLIB_ERR_INVAL, + GPIODGLIB_ERR_NOTTY, + GPIODGLIB_ERR_PIPE, +} GpiodglibError; + +GQuark gpiodglib_error_quark(void); + +G_END_DECLS + +#endif /* __GPIODGLIB_ERROR_H__ */ diff --git a/bindings/glib/gpiod-glib/info-event.h b/bindings/glib/gpiod-glib/info-event.h new file mode 100644 index 0000000..ba8ad54 --- /dev/null +++ b/bindings/glib/gpiod-glib/info-event.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* SPDX-FileCopyrightText: 2023-2024 Bartosz Golaszewski */ + +#ifndef __GPIODGLIB_INFO_EVENT_H__ +#define __GPIODGLIB_INFO_EVENT_H__ + +#if !defined(__INSIDE_GPIOD_GLIB_H__) && !defined(GPIODGLIB_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include + +#include "line-info.h" + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE(GpiodglibInfoEvent, gpiodglib_info_event, + GPIODGLIB, INFO_EVENT, GObject); + +#define GPIODGLIB_INFO_EVENT_TYPE (gpiodglib_info_event_get_type()) +#define GPIODGLIB_INFO_EVENT_OBJ(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), GPIODGLIB_INFO_EVENT_TYPE, \ + GpiodglibInfoEvent)) + +/** + * GpiodglibInfoEventType: + * @GPIODGLIB_INFO_EVENT_LINE_REQUESTED: Line has been requested. + * @GPIODGLIB_INFO_EVENT_LINE_RELEASED: Previously requested line has been + * released. + * @GPIODGLIB_INFO_EVENT_LINE_CONFIG_CHANGED: Line configuration has changed. + * + * Line status change event types. + */ +typedef enum { + GPIODGLIB_INFO_EVENT_LINE_REQUESTED = 1, + GPIODGLIB_INFO_EVENT_LINE_RELEASED, + GPIODGLIB_INFO_EVENT_LINE_CONFIG_CHANGED, +} GpiodglibInfoEventType; + +/** + * gpiodglib_info_event_get_event_type: + * @self: #GpiodglibInfoEvent to manipulate. + * + * Get the event type of the status change event. + * + * Returns: One of @GPIODGLIB_INFO_EVENT_LINE_REQUESTED, + * @GPIODGLIB_INFO_EVENT_LINE_RELEASED or + * @GPIODGLIB_INFO_EVENT_LINE_CONFIG_CHANGED. + */ +GpiodglibInfoEventType +gpiodglib_info_event_get_event_type(GpiodglibInfoEvent *self); + +/** + * gpiodglib_info_event_get_timestamp_ns: + * @self: #GpiodglibInfoEvent to manipulate. + * + * Get the timestamp of the event. + * + * Returns: Timestamp in nanoseconds, read from the monotonic clock. + */ +guint64 gpiodglib_info_event_get_timestamp_ns(GpiodglibInfoEvent *self); + +/** + * gpiodglib_info_event_get_line_info: + * @self #GpiodglibInfoEvent to manipulate. + * + * Get the snapshot of line-info associated with the event. + * + * Returns: (transfer full): New reference to the associated line-info object. + */ +GpiodglibLineInfo *gpiodglib_info_event_get_line_info(GpiodglibInfoEvent *self); + +G_END_DECLS + +#endif /* __GPIODGLIB_INFO_EVENT_H__ */ diff --git a/bindings/glib/gpiod-glib/line-config.h b/bindings/glib/gpiod-glib/line-config.h new file mode 100644 index 0000000..20ce33d --- /dev/null +++ b/bindings/glib/gpiod-glib/line-config.h @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* SPDX-FileCopyrightText: 2023-2024 Bartosz Golaszewski */ + +#ifndef __GPIODGLIB_LINE_CONFIG_H__ +#define __GPIODGLIB_LINE_CONFIG_H__ + +#if !defined(__INSIDE_GPIOD_GLIB_H__) && !defined(GPIODGLIB_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include + +#include "line-settings.h" + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE(GpiodglibLineConfig, gpiodglib_line_config, + GPIODGLIB, LINE_CONFIG, GObject); + +#define GPIODGLIB_LINE_CONFIG_TYPE (gpiodglib_line_config_get_type()) +#define GPIODGLIB_LINE_CONFIG_OBJ(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), GPIODGLIB_LINE_CONFIG_TYPE, \ + GpiodglibLineConfig)) + +/** + * gpiodglib_line_config_new: + * + * Create a new #GpiodglibLineConfig. + * + * Returns: (transfer full): Empty #GpiodglibLineConfig. + */ +GpiodglibLineConfig *gpiodglib_line_config_new(void); + +/** + * gpiodglib_line_config_reset: + * @self: #GpiodglibLineConfig to manipulate. + * + * Reset the line config object. + */ +void gpiodglib_line_config_reset(GpiodglibLineConfig *self); + +/** + * gpiodglib_line_config_add_line_settings: + * @self: #GpiodglibLineConfig to manipulate. + * @offsets: (element-type GArray): GArray of offsets for which to apply the + * settings. + * @settings: #GpiodglibLineSettings to apply. + * @err: Return location for error or NULL. + * + * Add line settings for a set of offsets. + * + * Returns: TRUE on success, FALSE on failure. + */ +gboolean +gpiodglib_line_config_add_line_settings(GpiodglibLineConfig *self, + const GArray *offsets, + GpiodglibLineSettings *settings, + GError **err); + +/** + * gpiodglib_line_config_get_line_settings: + * @self: #GpiodglibLineConfig to manipulate. + * @offset: Offset for which to get line settings. + * + * Get line settings for offset. + * + * Returns: (transfer full): New reference to a #GpiodglibLineSettings. + */ +GpiodglibLineSettings * +gpiodglib_line_config_get_line_settings(GpiodglibLineConfig *self, + guint offset); + +/** + * gpiodglib_line_config_set_output_values: + * @self: #GpiodglibLineConfig to manipulate. + * @values: (element-type GArray): GArray containing the output values. + * @err: Return location for error or NULL. + * + * @brief Set output values for a number of lines. + * + * Returns: TRUE on success, FALSE on error. + */ +gboolean gpiodglib_line_config_set_output_values(GpiodglibLineConfig *self, + const GArray *values, + GError **err); + +/** + * gpiodglib_line_config_get_configured_offsets: + * @self: #GpiodglibLineConfig to manipulate. + * + * Get configured offsets. + * + * Returns: (transfer full) (element-type GArray): GArray containing the + * offsets for which configuration has been set. + */ +GArray *gpiodglib_line_config_get_configured_offsets(GpiodglibLineConfig *self); + +G_END_DECLS + +#endif /* __GPIODGLIB_LINE_CONFIG_H__ */ diff --git a/bindings/glib/gpiod-glib/line-info.h b/bindings/glib/gpiod-glib/line-info.h new file mode 100644 index 0000000..60fcad7 --- /dev/null +++ b/bindings/glib/gpiod-glib/line-info.h @@ -0,0 +1,171 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* SPDX-FileCopyrightText: 2023-2024 Bartosz Golaszewski */ + +#ifndef __GPIODGLIB_LINE_INFO_H__ +#define __GPIODGLIB_LINE_INFO_H__ + +#if !defined(__INSIDE_GPIOD_GLIB_H__) && !defined(GPIODGLIB_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include + +#include "line.h" + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE(GpiodglibLineInfo, gpiodglib_line_info, + GPIODGLIB, LINE_INFO, GObject); + +#define GPIODGLIB_LINE_INFO_TYPE (gpiodglib_line_info_get_type()) +#define GPIODGLIB_LINE_INFO_OBJ(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), GPIODGLIB_LINE_INFO_TYPE, \ + GpiodglibLineInfo)) + +/** + * gpiodglib_line_info_get_offset: + * @self: #GpiodglibLineInfo to manipulate. + * + * Get the offset of the line. + * + * The offset uniquely identifies the line on the chip. The combination of the + * chip and offset uniquely identifies the line within the system. + * + * Returns: Offset of the line within the parent chip. + */ +guint gpiodglib_line_info_get_offset(GpiodglibLineInfo *self); + +/** + * gpiodglib_line_info_dup_name: + * @self: #GpiodglibLineInfo to manipulate. + * + * Get the name of the line. + * + * Returns: Name of the GPIO line as it is represented in the kernel. This + * function returns a valid pointer to a null-terminated string or NULL if the + * line is unnamed. The string is a copy of the line name and must be freed by + * the caller with g_free(). + */ +gchar * G_GNUC_WARN_UNUSED_RESULT +gpiodglib_line_info_dup_name(GpiodglibLineInfo *self); + +/** + * gpiodglib_line_info_is_used: + * @self: #GpiodglibLineInfo to manipulate. + * + * Check if the line is in use. + * + * The exact reason a line is busy cannot be determined from user space. + * It may have been requested by another process or hogged by the kernel. + * It only matters that the line is used and can't be requested until + * released by the existing consumer. + * + * Returns: TRUE if the line is in use, FALSE otherwise. + */ +gboolean gpiodglib_line_info_is_used(GpiodglibLineInfo *self); + +/** + * gpiodglib_line_info_dup_consumer: + * @self: #GpiodglibLineInfo to manipulate. + * + * Get the name of the consumer of the line. + * + * Returns: Name of the GPIO consumer as it is represented in the kernel. This + * function returns a valid pointer to a null-terminated string or NULL if the + * consumer name is not set. The string is a copy of the consumer label and + * must be freed by the caller with g_free(). + */ +gchar * G_GNUC_WARN_UNUSED_RESULT +gpiodglib_line_info_dup_consumer(GpiodglibLineInfo *self); + +/** + * gpiodglib_line_info_get_direction: + * @self: #GpiodglibLineInfo to manipulate. + * + * Get the direction setting of the line. + * + * Returns: @GPIODGLIB_LINE_DIRECTION_INPUT or @GPIODGLIB_LINE_DIRECTION_OUTPUT. + */ +GpiodglibLineDirection +gpiodglib_line_info_get_direction(GpiodglibLineInfo *self); + +/** + * gpiodglib_line_info_get_edge_detection: + * @self: #GpiodglibLineInfo to manipulate. + * + * Get the edge detection setting of the line. + * + * Returns: @GPIODGLIB_LINE_EDGE_NONE, @GPIODGLIB_LINE_EDGE_RISING, + * @GPIODGLIB_LINE_EDGE_FALLING or @GPIODGLIB_LINE_EDGE_BOTH. + */ +GpiodglibLineEdge +gpiodglib_line_info_get_edge_detection(GpiodglibLineInfo *self); + +/** + * gpiodglib_line_info_get_bias: + * @self: #GpiodglibLineInfo to manipulate. + * + * Get the bias setting of the line. + * + * Returns: @GPIODGLIB_LINE_BIAS_PULL_UP, @GPIODGLIB_LINE_BIAS_PULL_DOWN, + * @GPIODGLIB_LINE_BIAS_DISABLED or @GPIODGLIB_LINE_BIAS_UNKNOWN. + */ +GpiodglibLineBias gpiodglib_line_info_get_bias(GpiodglibLineInfo *self); + +/** + * gpiodglib_line_info_get_drive: + * @self: #GpiodglibLineInfo to manipulate. + * + * Get the drive setting of the line. + * + * Returns: @GPIODGLIB_LINE_DRIVE_PUSH_PULL, @GPIODGLIB_LINE_DRIVE_OPEN_DRAIN + * or @GPIODGLIB_LINE_DRIVE_OPEN_SOURCE. + */ +GpiodglibLineDrive gpiodglib_line_info_get_drive(GpiodglibLineInfo *self); + +/** + * gpiodglib_line_info_is_active_low: + * @self: #GpiodglibLineInfo to manipulate. + * + * Check if the logical value of the line is inverted compared to the physical. + * + * Returns: TRUE if the line is "active-low", FALSE otherwise. + */ +gboolean gpiodglib_line_info_is_active_low(GpiodglibLineInfo *self); + +/** + * gpiodglib_line_info_is_debounced: + * @self: #GpiodglibLineInfo to manipulate. + * + * Check if the line is debounced (either by hardware or by the kernel + * software debouncer). + * + * Returns: TRUE if the line is debounced, FALSE otherwise. + */ +gboolean gpiodglib_line_info_is_debounced(GpiodglibLineInfo *self); + +/** + * gpiodglib_line_info_get_debounce_period_us: + * @self: #GpiodglibLineInfo to manipulate. + * + * Get the debounce period of the line, in microseconds. + * + * Returns: Debounce period in microseconds. 0 if the line is not debounced. + */ +GTimeSpan gpiodglib_line_info_get_debounce_period_us(GpiodglibLineInfo *self); + +/** + * gpiodglib_line_info_get_event_clock: + * @self: #GpiodglibLineInfo to manipulate. + * + * Get the event clock setting used for edge event timestamps for the line. + * + * Returns: @GPIODGLIB_LINE_CLOCK_MONOTONIC, @GPIODGLIB_LINE_CLOCK_HTE or + * @GPIODGLIB_LINE_CLOCK_REALTIME. + */ +GpiodglibLineClock gpiodglib_line_info_get_event_clock(GpiodglibLineInfo *self); + +G_END_DECLS + +#endif /* __GPIODGLIB_LINE_INFO_H__ */ diff --git a/bindings/glib/gpiod-glib/line-request.h b/bindings/glib/gpiod-glib/line-request.h new file mode 100644 index 0000000..98393ec --- /dev/null +++ b/bindings/glib/gpiod-glib/line-request.h @@ -0,0 +1,186 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* SPDX-FileCopyrightText: 2023-2024 Bartosz Golaszewski */ + +#ifndef __GPIODGLIB_LINE_REQUEST_H__ +#define __GPIODGLIB_LINE_REQUEST_H__ + +#if !defined(__INSIDE_GPIOD_GLIB_H__) && !defined(GPIODGLIB_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE(GpiodglibLineRequest, gpiodglib_line_request, + GPIODGLIB, LINE_REQUEST, GObject); + +#define GPIODGLIB_LINE_REQUEST_TYPE (gpiodglib_line_request_get_type()) +#define GPIODGLIB_LINE_REQUEST_OBJ(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), GPIODGLIB_LINE_REQUEST_TYPE, \ + GpiodglibLineRequest)) + +/** + * gpiodglib_line_request_release: + * @self: #GpiodglibLineRequest to manipulate. + * + * Release the requested lines and free all associated resources. + */ +void gpiodglib_line_request_release(GpiodglibLineRequest *self); + +/** + * gpiodglib_line_request_is_released: + * @self: #GpiodglibLineRequest to manipulate. + * + * Check if this request was released. + * + * Returns: TRUE if this request was released and is no longer valid, FALSE + * otherwise. + */ +gboolean gpiodglib_line_request_is_released(GpiodglibLineRequest *self); + +/** + * gpiodglib_line_request_dup_chip_name: + * @self: #GpiodglibLineRequest to manipulate. + * + * Get the name of the chip this request was made on. + * + * Returns: Name the GPIO chip device. The string is a copy and must be freed + * by the caller using g_free(). + */ +gchar * G_GNUC_WARN_UNUSED_RESULT +gpiodglib_line_request_dup_chip_name(GpiodglibLineRequest *self); + +/** + * gpiodglib_line_request_get_requested_offsets: + * @self: #GpiodglibLineRequest to manipulate. + * + * Get the offsets of the lines in the request. + * + * Returns: (transfer full) (element-type GArray): Array containing the + * requested offsets. + */ +GArray * +gpiodglib_line_request_get_requested_offsets(GpiodglibLineRequest *self); + +/** + * gpiodglib_line_request_reconfigure_lines: + * @self: #GpiodglibLineRequest to manipulate. + * @config: New line config to apply. + * @err: Return location for error or NULL. + * + * Update the configuration of lines associated with a line request. + * + * The new line configuration completely replaces the old. Any requested lines + * without overrides are configured to the requested defaults. Any configured + * overrides for lines that have not been requested are silently ignored. + * + * Returns: TRUE on success, FALSE on failure. + */ +gboolean gpiodglib_line_request_reconfigure_lines(GpiodglibLineRequest *self, + GpiodglibLineConfig *config, + GError **err); + +/** + * gpiodglib_line_request_get_value: + * @self: #GpiodglibLineRequest to manipulate. + * @offset: The offset of the line of which the value should be read. + * @value: Return location for the value. + * @err: Return location for error or NULL. + * + * Get the value of a single requested line. + * + * Returns: TRUE on success, FALSE on failure. + */ +gboolean +gpiodglib_line_request_get_value(GpiodglibLineRequest *self, guint offset, + GpiodglibLineValue *value, GError **err); + +/** + * gpiodglib_line_request_get_values_subset: + * @self: #GpiodglibLineRequest to manipulate. + * @offsets: (element-type GArray): Array of offsets identifying the subset of + * requested lines from which to read values. + * @values: (element-type GArray): Array in which the values will be stored. + * Can be NULL in which case a new array will be created and its location + * stored here. + * @err: Return location for error or NULL. + * + * Get the values of a subset of requested lines. + * + * Returns: TRUE on success, FALSE on failure. + */ +gboolean gpiodglib_line_request_get_values_subset(GpiodglibLineRequest *self, + const GArray *offsets, + GArray **values, + GError **err); + +/** + * gpiodglib_line_request_get_values: + * @self: #GpiodglibLineRequest to manipulate. + * @values: (element-type GArray): Array in which the values will be stored. + * Can be NULL in which case a new array will be created and its location + * stored here. + * @err: Return location for error or NULL. + * + * Get the values of all requested lines. + * + * Returns: TRUE on success, FALSE on failure. + */ +gboolean gpiodglib_line_request_get_values(GpiodglibLineRequest *self, + GArray **values, GError **err); + +/** + * gpiodglib_line_request_set_value: + * @self: #GpiodglibLineRequest to manipulate. + * @offset: The offset of the line for which the value should be set. + * @value: Value to set. + * @err: Return location for error or NULL. + * + * Set the value of a single requested line. + * + * Returns: TRUE on success, FALSE on failure. + */ +gboolean +gpiodglib_line_request_set_value(GpiodglibLineRequest *self, guint offset, + GpiodglibLineValue value, GError **err); + +/** + * gpiodglib_line_request_set_values_subset: + * @self: #GpiodglibLineRequest to manipulate. + * @offsets: (element-type GArray): Array of offsets identifying the requested + * lines for which to set values. + * @values: (element-type GArray): Array in which the values will be stored. + * Can be NULL in which case a new array will be created and its location + * stored here. + * @err: Return location for error or NULL. + * + * Set the values of a subset of requested lines. + * + * Returns: TRUE on success, FALSE on failure. + */ +gboolean gpiodglib_line_request_set_values_subset(GpiodglibLineRequest *self, + const GArray *offsets, + const GArray *values, + GError **err); + +/** + * gpiodglib_line_request_set_values: + * @self: #GpiodglibLineRequest to manipulate. + * @values: (element-type GArray): Array containing the values to set. Must be + * sized to contain the number of values equal to the number of requested lines. + * Each value is associated with the line identified by the corresponding entry + * in the offset array filled by @gpiodglib_line_request_get_requested_offsets. + * @err: Return location for error or NULL. + * + * Set the values of all lines associated with a request. + * + * Returns: TRUE on success, FALSE on failure. + */ +gboolean gpiodglib_line_request_set_values(GpiodglibLineRequest *self, + GArray *values, GError **err); + +G_END_DECLS + +#endif /* __GPIODGLIB_LINE_REQUEST_H__ */ diff --git a/bindings/glib/gpiod-glib/line-settings.h b/bindings/glib/gpiod-glib/line-settings.h new file mode 100644 index 0000000..3f14b91 --- /dev/null +++ b/bindings/glib/gpiod-glib/line-settings.h @@ -0,0 +1,220 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* SPDX-FileCopyrightText: 2023-2024 Bartosz Golaszewski */ + +#ifndef __GPIODGLIB_LINE_SETTINGS_H__ +#define __GPIODGLIB_LINE_SETTINGS_H__ + +#if !defined(__INSIDE_GPIOD_GLIB_H__) && !defined(GPIODGLIB_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include + +#include "line.h" + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE(GpiodglibLineSettings, gpiodglib_line_settings, + GPIODGLIB, LINE_SETTINGS, GObject); + +#define GPIODGLIB_LINE_SETTINGS_TYPE (gpiodglib_line_settings_get_type()) +#define GPIODGLIB_LINE_SETTINGS_OBJ(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), GPIODGLIB_LINE_SETTINGS_TYPE, \ + GpiodglibLineSettings)) + +/** + * gpiodglib_line_settings_new: + * @first_prop: Name of the first property to set. + * + * Create a new line settings object. + * + * The constructor allows to set object's properties when it's first created + * instead of having to build an empty object and then call mutators separately. + * + * Currently supported properties are: `direction`, `edge-detection`, `bias`, + * `drive`, `debounce-period-us`, `active-low`, 'event-clock` and + * `output-value`. + * + * Returns: New #GpiodglibLineSettings. + */ +GpiodglibLineSettings * +gpiodglib_line_settings_new(const gchar *first_prop, ...); + +/** + * gpiodglib_line_settings_reset: + * @self: #GpiodglibLineSettings to manipulate. + * + * Reset the line settings object to its default values. + */ +void gpiodglib_line_settings_reset(GpiodglibLineSettings *self); + +/** + * gpiodglib_line_settings_set_direction: + * @self: #GpiodglibLineSettings to manipulate. + * @direction: New direction. + * + * Set direction. + */ +void gpiodglib_line_settings_set_direction(GpiodglibLineSettings *self, + GpiodglibLineDirection direction); + +/** + * gpiodglib_line_settings_get_direction: + * @self: #GpiodglibLineSettings to manipulate. + * + * Get direction. + * + * Returns: Current direction. + */ +GpiodglibLineDirection +gpiodglib_line_settings_get_direction(GpiodglibLineSettings *self); + +/** + * gpiodglib_line_settings_set_edge_detection: + * @self: #GpiodglibLineSettings to manipulate. + * @edge: New edge detection setting. + * + * Set edge detection. + */ +void gpiodglib_line_settings_set_edge_detection(GpiodglibLineSettings *self, + GpiodglibLineEdge edge); + +/** + * gpiodglib_line_settings_get_edge_detection: + * @self: #GpiodglibLineSettings to manipulate. + * + * Get edge detection. + * + * Returns: Current edge detection setting. + */ +GpiodglibLineEdge +gpiodglib_line_settings_get_edge_detection(GpiodglibLineSettings *self); + +/** + * gpiodglib_line_settings_set_bias: + * @self: #GpiodglibLineSettings to manipulate. + * @bias: New bias. + * + * Set bias. + */ +void gpiodglib_line_settings_set_bias(GpiodglibLineSettings *self, + GpiodglibLineBias bias); + +/** + * gpiodglib_line_settings_get_bias: + * @self: #GpiodglibLineSettings to manipulate. + * + * Get bias. + * + * Returns: Current bias setting. + */ +GpiodglibLineBias gpiodglib_line_settings_get_bias(GpiodglibLineSettings *self); + +/** + * gpiodglib_line_settings_set_drive: + * @self: #GpiodglibLineSettings to manipulate. + * @drive: New drive setting. + * + * Set drive. + */ +void gpiodglib_line_settings_set_drive(GpiodglibLineSettings *self, + GpiodglibLineDrive drive); + +/** + * gpiodglib_line_settings_get_drive: + * @self: #GpiodglibLineSettings to manipulate. + * + * Get drive. + * + * Returns: Current drive setting. + */ +GpiodglibLineDrive +gpiodglib_line_settings_get_drive(GpiodglibLineSettings *self); + +/** + * gpiodglib_line_settings_set_active_low: + * @self: #GpiodglibLineSettings to manipulate. + * @active_low: New active-low setting. + * + * Set active-low setting. + */ +void gpiodglib_line_settings_set_active_low(GpiodglibLineSettings *self, + gboolean active_low); + +/** + * gpiodglib_line_settings_get_active_low: + * @self: #GpiodglibLineSettings to manipulate. + * + * Get active-low setting. + * + * Returns: TRUE if active-low is enabled, FALSE otherwise. + */ +gboolean gpiodglib_line_settings_get_active_low(GpiodglibLineSettings *self); + +/** + * gpiodglib_line_settings_set_debounce_period_us: + * @self: #GpiodglibLineSettings to manipulate. + * @period: New debounce period in microseconds. + * + * Set debounce period. + */ +void gpiodglib_line_settings_set_debounce_period_us(GpiodglibLineSettings *self, + GTimeSpan period); + +/** + * gpiodglib_line_settings_get_debounce_period_us: + * @self: #GpiodglibLineSettings to manipulate. + * + * Get debounce period. + * + * Returns: Current debounce period in microseconds. + */ +GTimeSpan +gpiodglib_line_settings_get_debounce_period_us(GpiodglibLineSettings *self); + +/** + * gpiodglib_line_settings_set_event_clock: + * @self: #GpiodglibLineSettings to manipulate. + * @event_clock: New event clock. + * + * Set event clock. + */ +void gpiodglib_line_settings_set_event_clock(GpiodglibLineSettings *self, + GpiodglibLineClock event_clock); + +/** + * gpiodglib_line_settings_get_event_clock: + * @self: #GpiodglibLineSettings to manipulate. + * + * Get event clock setting. + * + * Returns: Current event clock setting. + */ +GpiodglibLineClock +gpiodglib_line_settings_get_event_clock(GpiodglibLineSettings *self); + +/** + * gpiodglib_line_settings_set_output_value: + * @self: #GpiodglibLineSettings to manipulate. + * @value: New output value. + * + * Set the output value. + */ +void gpiodglib_line_settings_set_output_value(GpiodglibLineSettings *self, + GpiodglibLineValue value); + +/** + * gpiodglib_line_settings_get_output_value: + * @self: #GpiodglibLineSettings to manipulate. + * + * Get the output value. + * + * Returns: Current output value. + */ +GpiodglibLineValue +gpiodglib_line_settings_get_output_value(GpiodglibLineSettings *self); + +G_END_DECLS + +#endif /* __GPIODGLIB_LINE_SETTINGS_H__ */ diff --git a/bindings/glib/gpiod-glib/line.h b/bindings/glib/gpiod-glib/line.h new file mode 100644 index 0000000..16bcd9c --- /dev/null +++ b/bindings/glib/gpiod-glib/line.h @@ -0,0 +1,113 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* SPDX-FileCopyrightText: 2023-2024 Bartosz Golaszewski */ + +#ifndef __GPIODGLIB_LINE_H__ +#define __GPIODGLIB_LINE_H__ + +#if !defined(__INSIDE_GPIOD_GLIB_H__) && !defined(GPIODGLIB_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + + +/** + * GpiodglibLineValue: + * @GPIODGLIB_LINE_VALUE_INACTIVE: Line is logically inactive. + * @GPIODGLIB_LINE_VALUE_ACTIVE: Line is logically active. + * + * Logical line state. + */ +typedef enum { + GPIODGLIB_LINE_VALUE_INACTIVE = 0, + GPIODGLIB_LINE_VALUE_ACTIVE = 1, +} GpiodglibLineValue; + +/** + * GpiodglibLineDirection: + * @GPIODGLIB_LINE_DIRECTION_AS_IS: Request the line(s), but don't change + * direction. + * @GPIODGLIB_LINE_DIRECTION_INPUT: Direction is input - for reading the value + * of an externally driven GPIO line. + * @GPIODGLIB_LINE_DIRECTION_OUTPUT: Direction is output - for driving the GPIO + * line. + * + * Direction settings. + */ +typedef enum { + GPIODGLIB_LINE_DIRECTION_AS_IS = 1, + GPIODGLIB_LINE_DIRECTION_INPUT, + GPIODGLIB_LINE_DIRECTION_OUTPUT, +} GpiodglibLineDirection; + +/** + * GpiodglibLineEdge + * @GPIODGLIB_LINE_EDGE_NONE: Line edge detection is disabled. + * @GPIODGLIB_LINE_EDGE_RISING: Line detects rising edge events. + * @GPIODGLIB_LINE_EDGE_FALLING: Line detects falling edge events. + * @GPIODGLIB_LINE_EDGE_BOTH: Line detects both rising and falling edge events. + * + * Edge detection settings. + */ +typedef enum { + GPIODGLIB_LINE_EDGE_NONE = 1, + GPIODGLIB_LINE_EDGE_RISING, + GPIODGLIB_LINE_EDGE_FALLING, + GPIODGLIB_LINE_EDGE_BOTH, +} GpiodglibLineEdge; + +/** + * GpiodglibLineBias: + * @GPIODGLIB_LINE_BIAS_AS_IS: Don't change the bias setting when applying line + * config. + * @GPIODGLIB_LINE_BIAS_UNKNOWN: The internal bias state is unknown. + * @GPIODGLIB_LINE_BIAS_DISABLED: The internal bias is disabled. + * @GPIODGLIB_LINE_BIAS_PULL_UP: The internal pull-up bias is enabled. + * @GPIODGLIB_LINE_BIAS_PULL_DOWN: The internal pull-down bias is enabled. + * + * Internal bias settings. + */ +typedef enum { + GPIODGLIB_LINE_BIAS_AS_IS = 1, + GPIODGLIB_LINE_BIAS_UNKNOWN, + GPIODGLIB_LINE_BIAS_DISABLED, + GPIODGLIB_LINE_BIAS_PULL_UP, + GPIODGLIB_LINE_BIAS_PULL_DOWN, +} GpiodglibLineBias; + +/** + * GpiodglibLineDrive: + * @GPIODGLIB_LINE_DRIVE_PUSH_PULL: Drive setting is push-pull. + * @GPIODGLIB_LINE_DRIVE_OPEN_DRAIN: Line output is open-drain. + * @GPIODGLIB_LINE_DRIVE_OPEN_SOURCE: Line output is open-source. + * + * Drive settings. + */ +typedef enum { + GPIODGLIB_LINE_DRIVE_PUSH_PULL = 1, + GPIODGLIB_LINE_DRIVE_OPEN_DRAIN, + GPIODGLIB_LINE_DRIVE_OPEN_SOURCE, +} GpiodglibLineDrive; + +/** + * GpiodglibLineClock: + * @GPIODGLIB_LINE_CLOCK_MONOTONIC: Line uses the monotonic clock for edge + * event timestamps. + * @GPIODGLIB_LINE_CLOCK_REALTIME: Line uses the realtime clock for edge event + * timestamps. + * @GPIODGLIB_LINE_CLOCK_HTE: Line uses the hardware timestamp engine for event + * timestamps. + * + * Clock settings. + */ +typedef enum { + GPIODGLIB_LINE_CLOCK_MONOTONIC = 1, + GPIODGLIB_LINE_CLOCK_REALTIME, + GPIODGLIB_LINE_CLOCK_HTE, +} GpiodglibLineClock; + +G_END_DECLS + +#endif /* __GPIODGLIB_LINE_H__ */ diff --git a/bindings/glib/gpiod-glib/misc.h b/bindings/glib/gpiod-glib/misc.h new file mode 100644 index 0000000..2d30dbc --- /dev/null +++ b/bindings/glib/gpiod-glib/misc.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* SPDX-FileCopyrightText: 2022-2024 Bartosz Golaszewski */ + +#ifndef __GPIODGLIB_MISC_H__ +#define __GPIODGLIB_MISC_H__ + +#if !defined(__INSIDE_GPIOD_GLIB_H__) && !defined(GPIODGLIB_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +/** + * gpiodglib_is_gpiochip_device: + * @path: Path to check. + * + * Check if the file pointed to by path is a GPIO chip character device. + * + * Returns: TRUE if the file exists and is either a GPIO chip character device + * or a symbolic link to one, FALSE otherwise. + */ +gboolean gpiodglib_is_gpiochip_device(const gchar *path); + +/** + * gpiodglib_api_version: + * + * Get the API version of the library as a human-readable string. + * + * Returns: A valid pointer to a human-readable string containing the library + * version. The pointer is valid for the lifetime of the program and must not + * be freed by the caller. + */ +const gchar *gpiodglib_api_version(void); + +G_END_DECLS + +#endif /* __GPIODGLIB_MISC_H__ */ diff --git a/bindings/glib/gpiod-glib/request-config.h b/bindings/glib/gpiod-glib/request-config.h new file mode 100644 index 0000000..76e884b --- /dev/null +++ b/bindings/glib/gpiod-glib/request-config.h @@ -0,0 +1,93 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* SPDX-FileCopyrightText: 2023-2024 Bartosz Golaszewski */ + +#ifndef __GPIODGLIB_REQUEST_CONFIG_H__ +#define __GPIODGLIB_REQUEST_CONFIG_H__ + +#if !defined(__INSIDE_GPIOD_GLIB_H__) && !defined(GPIODGLIB_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE(GpiodglibRequestConfig, gpiodglib_request_config, + GPIODGLIB, REQUEST_CONFIG, GObject); + +#define GPIODGLIB_REQUEST_CONFIG_TYPE (gpiodglib_request_config_get_type()) +#define GPIODGLIB_REQUEST_CONFIG_OBJ(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), GPIODGLIB_REQUEST_CONFIG_TYPE, \ + GpiodglibRequestConfig)) + +/** + * gpiodglib_request_config_new: + * @first_prop: Name of the first property to set. + * + * Create a new request config object. + * + * Returns: New #GpiodglibRequestConfig. + * + * The constructor allows to set object's properties when it's first created + * instead of having to build an empty object and then call mutators separately. + * + * Currently supported properties are: `consumer` and `event-buffer-size`. + */ +GpiodglibRequestConfig * +gpiodglib_request_config_new(const gchar *first_prop, ...); + +/** + * gpiodglib_request_config_set_consumer: + * @self: #GpiodglibRequestConfig object to manipulate. + * @consumer: Consumer name. + * + * Set the consumer name for the request. + * + * If the consumer string is too long, it will be truncated to the max + * accepted length. + */ +void gpiodglib_request_config_set_consumer(GpiodglibRequestConfig *self, + const gchar *consumer); + +/** + * gpiodglib_request_config_dup_consumer: + * @self: #GpiodglibRequestConfig object to manipulate. + * + * Get the consumer name configured in the request config. + * + * Returns: Consumer name stored in the request config. The returned string is + * a copy and must be freed by the caller using g_free(). + */ +gchar * G_GNUC_WARN_UNUSED_RESULT +gpiodglib_request_config_dup_consumer(GpiodglibRequestConfig *self); + +/** + * gpiodglib_request_config_set_event_buffer_size: + * @self: #GpiodglibRequestConfig object to manipulate. + * @event_buffer_size: New event buffer size. + * + * Set the size of the kernel event buffer for the request. + * + * The kernel may adjust the value if it's too high. If set to 0, the default + * value will be used. + */ +void +gpiodglib_request_config_set_event_buffer_size(GpiodglibRequestConfig *self, + guint event_buffer_size); + + +/** + * gpiodglib_request_config_get_event_buffer_size: + * @self: #GpiodglibRequestConfig object to manipulate. + * + * Get the edge event buffer size for the request config. + * + * Returns: Edge event buffer size setting from the request config. + */ +guint +gpiodglib_request_config_get_event_buffer_size(GpiodglibRequestConfig *self); + +G_END_DECLS + +#endif /* __GPIODGLIB_REQUEST_CONFIG_H__ */ From patchwork Wed Aug 7 09:10:36 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Bartosz Golaszewski X-Patchwork-Id: 818162 Received: from mail-lj1-f181.google.com (mail-lj1-f181.google.com [209.85.208.181]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 1E1961C9EC8 for ; Wed, 7 Aug 2024 09:10:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.208.181 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723021857; cv=none; b=L0TTr9MmrGx0OqjfOVyT7yQBPp+KL84jysmxBqnSP8VsCZKaGVKUcxLDkDY0aW7GA+qL0ECye534hzWHQA/MJnCSbvDeTOgPdqu02I9Ko6tjIvWn5BNx0lULPlpEWcJKzrfSmJ7PCaS+oaVzd859a+DFG7lZV7Ocar/6BsZF/Uo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723021857; c=relaxed/simple; bh=TDlPXdQMueCBmTmPpSnKPuY7eK0T65yVlDwoGw4dhpU=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=u/AMr9ht+C9x6FbpLeeVQOqX7tieE04UjUd+erK3LNeWIPD+grxZvswzaZevAlG47s2y3pWH80Iny+7dOJaCGRUoKMZNEZUTBP2CmGNnv9G9RD6FDpeVBZ2JYqLC4lIgdWp5irTaJ2KXHv3bulbctlW78L7cjt/1mrsj+w934n0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=bgdev.pl; spf=none smtp.mailfrom=bgdev.pl; dkim=pass (2048-bit key) header.d=bgdev-pl.20230601.gappssmtp.com header.i=@bgdev-pl.20230601.gappssmtp.com header.b=t6kzwrrJ; arc=none smtp.client-ip=209.85.208.181 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=bgdev.pl Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=bgdev.pl Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=bgdev-pl.20230601.gappssmtp.com header.i=@bgdev-pl.20230601.gappssmtp.com header.b="t6kzwrrJ" Received: by mail-lj1-f181.google.com with SMTP id 38308e7fff4ca-2f1870c355cso16470831fa.1 for ; Wed, 07 Aug 2024 02:10:52 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bgdev-pl.20230601.gappssmtp.com; s=20230601; t=1723021851; x=1723626651; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=ICJxrxAGHc0hWgP5vhwVgLrwmzvl3iIt6EyChH4hKAM=; b=t6kzwrrJS8e2tmBgaSaO0kktxu0ZcB+6zYkLeTCdUzjUkYK+NRhzlkV2q9H828MoEY sdoBmpLew+rC1i8rV8u9+jnBZLnIXzR30SC8GAUlPuyo8J3dASyB+RKddmshOnREZt7O Vazk5FQ4Dq2L8fD5m6Sy46YMCpnDNwc5lCr6caWP0lICSel/h9YAn1gz7t2Av5/R8r/S i9CCFogzjhHmVc3jSRAptrQtYshhSkgwiVTzZkOs9hg0iW01dlGIwlOBP3ESN0DJo86m n+iUh9O1NlW9mpu+uyIiiGkFuj63QuN66XNk6HBmHxjbTaZYzEWipIeNWxpWiwFzIm5/ c4dg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1723021851; x=1723626651; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=ICJxrxAGHc0hWgP5vhwVgLrwmzvl3iIt6EyChH4hKAM=; b=XuaYj+4KYi5WEa6yccdJk27DLWv6XfAL6IFFerD0YzRTBhqJZhbOo6mkilYAruJhg7 1K1AlO/g8w1h7k4+A4KMmaZcYt5tylTQXINi229Kx8I1wIdHCMzN0tCYnvkRv9TwcY1F 6/P6NZxkeRlK22dCn2cLDLJbYKfYBDQkHquNKw7JzXRvkJExlPM5qu/gFBT33LfxpJOe 3ieWHqFse1MtR6+MIcgpy9tfIN1PLuqPoQ6WOH28ODfFFz/HCFzIOcSIf2YG3nYpV0oW Q5IrYHqlOaipi3qfFHkoQaxTcsX7OVcuKIlZQiAoAltog6FN8/BZyd57qdVbW3gbJR5E uFkA== X-Gm-Message-State: AOJu0YyzaeZvCPPxaW3wMciYYxVNjHfzEIZAJ2d90aZIZxH9M285xLGT 4BvFqp2xYbRM7etmHN0Ebl4OksIGw3GkAV7dXp0oFtMs7WxA9Qyg2KLHDctoSAH2UP+SWhK/wkt nm7o= X-Google-Smtp-Source: AGHT+IFo+ZHbfr2iMzMCe8vbcTNGbNDvybQGV38Hb/ZjLgKHCbbnpQaO9Pq3IAZ6oZE1tWFY/WnhHw== X-Received: by 2002:a2e:9b88:0:b0:2ef:320e:cbe9 with SMTP id 38308e7fff4ca-2f15aa84d50mr117167061fa.9.1723021850577; Wed, 07 Aug 2024 02:10:50 -0700 (PDT) Received: from [127.0.1.1] ([2a01:cb1d:dc:7e00:a3e7:be71:fe4f:c360]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-429059cd2b3sm18936355e9.44.2024.08.07.02.10.49 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 07 Aug 2024 02:10:50 -0700 (PDT) From: Bartosz Golaszewski Date: Wed, 07 Aug 2024 11:10:36 +0200 Subject: [PATCH libgpiod v4 05/18] bindings: glib: add core code Precedence: bulk X-Mailing-List: linux-gpio@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20240807-dbus-v4-5-64ea80169e51@linaro.org> References: <20240807-dbus-v4-0-64ea80169e51@linaro.org> In-Reply-To: <20240807-dbus-v4-0-64ea80169e51@linaro.org> To: Linus Walleij , Kent Gibson , Erik Schilling , Phil Howard , Andy Shevchenko , Viresh Kumar , Dan Carpenter , Philip Withnall Cc: linux-gpio@vger.kernel.org, Bartosz Golaszewski , Alexander Sverdlin X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=90238; i=bartosz.golaszewski@linaro.org; h=from:subject:message-id; bh=DvK7EobztQvpPD1sHaGmD5xyiwdOZu2T3IBEcd/uk1A=; b=owEBbQKS/ZANAwAKARGnLqAUcddyAcsmYgBmszoQwiHvFv/DMZuN1LFXk30V1IZbDWHvcX6Ul QX/b0/ePtWJAjMEAAEKAB0WIQQWnetsC8PEYBPSx58Rpy6gFHHXcgUCZrM6EAAKCRARpy6gFHHX cntZD/9KOm/IsXQBJrmqM/gMZG8YyAV5EKIrvu4a/nXHRqcdH9xuwr+FakMb8HJqmgGRnXUvWkm N7Z36gaGNVpEOYYbs/P2N56IGl9ZbRiA1oU8sVV15GGuUs0sIcFJw2Tiy1A2GQN/XeknNMpufYW u+u/K1u04i7NQhC/+xT74bsCki3f7GjaelrypzhPOGeB2WxeNddPNYjW7Iowd7FAARXb4ZakJa5 LHu28AaHNG0z1ZVrfGYD1gO+7WS69cxJqQNtfEcOnbZ4AKIsyZSWGB6bqvNTpjBeH5eRZ02tns0 7QMXunOFKZ3wsRCusCZ5zY1LuSVsF3nXUPa4RoiNsfVzd0607jK/pgQv/mJk3QT7hdCZBwhvEIk YxMcolxZdTUri/KGcPTn4Z5sabfo8GFSRoTgYjBLpiRCwXJnHs50I3KT/vWwsW9bePrselKQo4Y 8b9g/m+DZNAtrvK/I2fcLswGqm0FFGOebfT5ZWnXbKBcJKsWZS5E2G1SZd8w3Gvg1kHJMp/c99x YRjgdjpUKGxFZDq+CM9xjobFxxuIVlMBeT+AOn1Ob2UNwfUomHSUHVMRdqIBHG3jsRtxIbpllnT RDdi3Pk6cydN1WnrRJ8aPWzRB5pPzxRLo+g7lUSDKLwLuHY632Fuh4Awv2fZM42XIAc73VVTf7O vECEwx24FlheCaA== X-Developer-Key: i=bartosz.golaszewski@linaro.org; a=openpgp; fpr=169DEB6C0BC3C46013D2C79F11A72EA01471D772 From: Bartosz Golaszewski Add the files implementing the public API of the GLib bindings to libgpiod. Tested-by: Alexander Sverdlin Signed-off-by: Bartosz Golaszewski --- bindings/glib/chip-info.c | 129 +++++++++ bindings/glib/chip.c | 397 +++++++++++++++++++++++++++ bindings/glib/edge-event.c | 186 +++++++++++++ bindings/glib/error.c | 67 +++++ bindings/glib/generated-enums.c.template | 43 +++ bindings/glib/info-event.c | 163 +++++++++++ bindings/glib/internal.c | 327 ++++++++++++++++++++++ bindings/glib/internal.h | 79 ++++++ bindings/glib/line-config.c | 193 +++++++++++++ bindings/glib/line-info.c | 342 +++++++++++++++++++++++ bindings/glib/line-request.c | 452 +++++++++++++++++++++++++++++++ bindings/glib/line-settings.c | 408 ++++++++++++++++++++++++++++ bindings/glib/misc.c | 17 ++ bindings/glib/request-config.c | 170 ++++++++++++ 14 files changed, 2973 insertions(+) diff --git a/bindings/glib/chip-info.c b/bindings/glib/chip-info.c new file mode 100644 index 0000000..5c67018 --- /dev/null +++ b/bindings/glib/chip-info.c @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +// SPDX-FileCopyrightText: 2022-2024 Bartosz Golaszewski + +#include + +#include "internal.h" + +/** + * GpiodglibChipInfo: + * + * Represents an immutable snapshot of GPIO chip information. + */ +struct _GpiodglibChipInfo { + GObject parent_instance; + struct gpiod_chip_info *handle; +}; + +typedef enum { + GPIODGLIB_CHIP_INFO_PROP_NAME = 1, + GPIODGLIB_CHIP_INFO_PROP_LABEL, + GPIODGLIB_CHIP_INFO_PROP_NUM_LINES, +} GpiodglibChipInfoProp; + +G_DEFINE_TYPE(GpiodglibChipInfo, gpiodglib_chip_info, G_TYPE_OBJECT); + +static void gpiodglib_chip_info_get_property(GObject *obj, guint prop_id, + GValue *val, GParamSpec *pspec) +{ + GpiodglibChipInfo *self = GPIODGLIB_CHIP_INFO_OBJ(obj); + + g_assert(self->handle); + + switch ((GpiodglibChipInfoProp)prop_id) { + case GPIODGLIB_CHIP_INFO_PROP_NAME: + g_value_set_string(val, + gpiod_chip_info_get_name(self->handle)); + break; + case GPIODGLIB_CHIP_INFO_PROP_LABEL: + g_value_set_string(val, + gpiod_chip_info_get_label(self->handle)); + break; + case GPIODGLIB_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 gpiodglib_chip_info_finalize(GObject *obj) +{ + GpiodglibChipInfo *self = GPIODGLIB_CHIP_INFO_OBJ(obj); + + g_clear_pointer(&self->handle, gpiod_chip_info_free); + + G_OBJECT_CLASS(gpiodglib_chip_info_parent_class)->finalize(obj); +} + +static void +gpiodglib_chip_info_class_init(GpiodglibChipInfoClass *chip_info_class) +{ + GObjectClass *class = G_OBJECT_CLASS(chip_info_class); + + class->get_property = gpiodglib_chip_info_get_property; + class->finalize = gpiodglib_chip_info_finalize; + + /** + * GpiodglibChipInfo:name: + * + * Name of this GPIO chip device. + */ + g_object_class_install_property(class, GPIODGLIB_CHIP_INFO_PROP_NAME, + g_param_spec_string("name", "Name", + "Name of this GPIO chip device.", NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * GpiodglibChipInfo:label: + * + * Label of this GPIO chip device. + */ + g_object_class_install_property(class, GPIODGLIB_CHIP_INFO_PROP_LABEL, + g_param_spec_string("label", "Label", + "Label of this GPIO chip device.", NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * GpiodglibChipInfo:num-lines: + * + * Number of GPIO lines exposed by this chip. + */ + g_object_class_install_property(class, GPIODGLIB_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 | G_PARAM_STATIC_STRINGS)); +} + +static void gpiodglib_chip_info_init(GpiodglibChipInfo *self) +{ + self->handle = NULL; +} + +gchar *gpiodglib_chip_info_dup_name(GpiodglibChipInfo *self) +{ + return _gpiodglib_dup_prop_string(G_OBJECT(self), "name"); +} + +gchar *gpiodglib_chip_info_dup_label(GpiodglibChipInfo *self) +{ + return _gpiodglib_dup_prop_string(G_OBJECT(self), "label"); +} + +guint gpiodglib_chip_info_get_num_lines(GpiodglibChipInfo *self) +{ + return _gpiodglib_get_prop_uint(G_OBJECT(self), "num-lines"); +} + +GpiodglibChipInfo *_gpiodglib_chip_info_new(struct gpiod_chip_info *handle) +{ + GpiodglibChipInfo *info; + + info = GPIODGLIB_CHIP_INFO_OBJ(g_object_new(GPIODGLIB_CHIP_INFO_TYPE, + NULL)); + info->handle = handle; + + return info; +} diff --git a/bindings/glib/chip.c b/bindings/glib/chip.c new file mode 100644 index 0000000..d4c0e15 --- /dev/null +++ b/bindings/glib/chip.c @@ -0,0 +1,397 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +// SPDX-FileCopyrightText: 2022-2024 Bartosz Golaszewski + +#include + +#include "internal.h" + +/** + * GpiodglibChip: + * + * Represents a single GPIO chip. + */ +struct _GpiodglibChip { + GObject parent_instance; + GString *path; + GError *construct_err; + struct gpiod_chip *handle; + GSource *info_event_src; + guint info_event_src_id; +}; + +typedef enum { + GPIODGLIB_CHIP_PROP_PATH = 1, +} GpiodglibChipProp; + +enum { + GPIODGLIB_CHIP_SIGNAL_INFO_EVENT, + GPIODGLIB_CHIP_SIGNAL_LAST, +}; + +static guint signals[GPIODGLIB_CHIP_SIGNAL_LAST]; + +static void g_string_free_complete(GString *str) +{ + g_string_free(str, TRUE); +} + +static gboolean +gpiodglib_chip_on_info_event(GIOChannel *source G_GNUC_UNUSED, + GIOCondition condition G_GNUC_UNUSED, + gpointer data) +{ + g_autoptr(GpiodglibInfoEvent) event = NULL; + struct gpiod_info_event *event_handle; + GpiodglibChip *self = data; + + event_handle = gpiod_chip_read_info_event(self->handle); + if (!event_handle) + return TRUE; + + event = _gpiodglib_info_event_new(event_handle); + + g_signal_emit(self, signals[GPIODGLIB_CHIP_SIGNAL_INFO_EVENT], 0, + event); + + return TRUE; +} + +static gboolean +gpiodglib_chip_initable_init(GInitable *initable, + GCancellable *cancellable G_GNUC_UNUSED, + GError **err) +{ + GpiodglibChip *self = GPIODGLIB_CHIP_OBJ(initable); + + if (self->construct_err) { + g_propagate_error(err, g_steal_pointer(&self->construct_err)); + return FALSE; + } + + return TRUE; +} + +static void gpiodglib_chip_initable_iface_init(GInitableIface *iface) +{ + iface->init = gpiodglib_chip_initable_init; +} + +G_DEFINE_TYPE_WITH_CODE(GpiodglibChip, gpiodglib_chip, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE( + G_TYPE_INITABLE, + gpiodglib_chip_initable_iface_init)); + +static void gpiodglib_chip_constructed(GObject *obj) +{ + GpiodglibChip *self = GPIODGLIB_CHIP_OBJ(obj); + g_autoptr(GIOChannel) channel = NULL; + + g_assert(!self->handle); + g_assert(self->path); + + self->handle = gpiod_chip_open(self->path->str); + if (!self->handle) { + _gpiodglib_set_error_from_errno(&self->construct_err, + "unable to open GPIO chip '%s'", + self->path->str); + return; + } + + channel = g_io_channel_unix_new(gpiod_chip_get_fd(self->handle)); + self->info_event_src = g_io_create_watch(channel, G_IO_IN); + g_source_set_callback(self->info_event_src, + G_SOURCE_FUNC(gpiodglib_chip_on_info_event), + self, NULL); + self->info_event_src_id = g_source_attach(self->info_event_src, NULL); + + G_OBJECT_CLASS(gpiodglib_chip_parent_class)->constructed(obj); +} + +static void gpiodglib_chip_get_property(GObject *obj, guint prop_id, + GValue *val, GParamSpec *pspec) +{ + GpiodglibChip *self = GPIODGLIB_CHIP_OBJ(obj); + + switch ((GpiodglibChipProp)prop_id) { + case GPIODGLIB_CHIP_PROP_PATH: + g_value_set_string(val, self->path->str); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec); + } +} + +static void gpiodglib_chip_set_property(GObject *obj, guint prop_id, + const GValue *val, GParamSpec *pspec) +{ + GpiodglibChip *self = GPIODGLIB_CHIP_OBJ(obj); + + switch ((GpiodglibChipProp)prop_id) { + case GPIODGLIB_CHIP_PROP_PATH: + self->path = g_string_new(g_value_get_string(val)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec); + } +} + +void gpiodglib_chip_close(GpiodglibChip *self) +{ + g_clear_pointer(&self->info_event_src, g_source_unref); + g_clear_pointer(&self->handle, gpiod_chip_close); +} + +static void gpiodglib_chip_dispose(GObject *obj) +{ + GpiodglibChip *self = GPIODGLIB_CHIP_OBJ(obj); + + if (self->info_event_src_id) + g_source_remove(self->info_event_src_id); + + gpiodglib_chip_close(self); + + G_OBJECT_CLASS(gpiodglib_chip_parent_class)->dispose(obj); +} + +static void gpiodglib_chip_finalize(GObject *obj) +{ + GpiodglibChip *self = GPIODGLIB_CHIP_OBJ(obj); + + g_clear_error(&self->construct_err); + g_clear_pointer(&self->path, g_string_free_complete); + + G_OBJECT_CLASS(gpiodglib_chip_parent_class)->finalize(obj); +} + +static void gpiodglib_chip_class_init(GpiodglibChipClass *chip_class) +{ + GObjectClass *class = G_OBJECT_CLASS(chip_class); + + class->constructed = gpiodglib_chip_constructed; + class->get_property = gpiodglib_chip_get_property; + class->set_property = gpiodglib_chip_set_property; + class->dispose = gpiodglib_chip_dispose; + class->finalize = gpiodglib_chip_finalize; + + /** + * GpiodglibChip:path: + * + * Path that was used to open this GPIO chip. + */ + g_object_class_install_property(class, GPIODGLIB_CHIP_PROP_PATH, + g_param_spec_string("path", "Path", + "Path to the GPIO chip device used to create this chip.", + NULL, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + /** + * GpiodglibChip::info-event: + * @chip: #GpiodglibChip receiving the event + * @event: The #GpiodglibInfoEvent + * + * Emitted when the state of a watched GPIO line changes. + */ + signals[GPIODGLIB_CHIP_SIGNAL_INFO_EVENT] = + g_signal_new("info-event", + G_TYPE_FROM_CLASS(chip_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, + 1, + GPIODGLIB_INFO_EVENT_TYPE); +} + +static void gpiodglib_chip_init(GpiodglibChip *self) +{ + self->path = NULL; + self->construct_err = NULL; + self->handle = NULL; + self->info_event_src = NULL; + self->info_event_src_id = 0; +} + +GpiodglibChip *gpiodglib_chip_new(const gchar *path, GError **err) +{ + return GPIODGLIB_CHIP_OBJ(g_initable_new(GPIODGLIB_CHIP_TYPE, NULL, err, + "path", path, NULL)); +} + +gboolean gpiodglib_chip_is_closed(GpiodglibChip *self) +{ + return !self->handle; +} + +gchar *gpiodglib_chip_dup_path(GpiodglibChip *self) +{ + return _gpiodglib_dup_prop_string(G_OBJECT(self), "path"); +} + +static void set_err_chip_closed(GError **err) +{ + g_set_error(err, GPIODGLIB_ERROR, GPIODGLIB_ERR_CHIP_CLOSED, + "Chip was closed and cannot be used"); +} + +GpiodglibChipInfo *gpiodglib_chip_get_info(GpiodglibChip *self, GError **err) +{ + struct gpiod_chip_info *info; + + g_assert(self); + + if (gpiodglib_chip_is_closed(self)) { + set_err_chip_closed(err); + return NULL; + } + + info = gpiod_chip_get_info(self->handle); + if (!info) { + _gpiodglib_set_error_from_errno(err, + "unable to retrieve GPIO chip information"); + return NULL; + } + + return _gpiodglib_chip_info_new(info); +} + +static GpiodglibLineInfo * +gpiodglib_chip_do_get_line_info(GpiodglibChip *self, guint offset, GError **err, + struct gpiod_line_info *(*func)(struct gpiod_chip *, + unsigned int), + const gchar *err_action) +{ + struct gpiod_line_info *info; + + g_assert(self); + + if (gpiodglib_chip_is_closed(self)) { + set_err_chip_closed(err); + return NULL; + } + + info = func(self->handle, offset); + if (!info) { + _gpiodglib_set_error_from_errno(err, "unable to %s for offset %u", + err_action, offset); + return NULL; + } + + return _gpiodglib_line_info_new(info); +} + +GpiodglibLineInfo * +gpiodglib_chip_get_line_info(GpiodglibChip *self, guint offset, GError **err) +{ + return gpiodglib_chip_do_get_line_info(self, offset, err, + gpiod_chip_get_line_info, + "retrieve GPIO line-info"); +} + +GpiodglibLineInfo * +gpiodglib_chip_watch_line_info(GpiodglibChip *self, guint offset, GError **err) +{ + return gpiodglib_chip_do_get_line_info(self, offset, err, + gpiod_chip_watch_line_info, + "setup a line-info watch"); +} + +gboolean +gpiodglib_chip_unwatch_line_info(GpiodglibChip *self, guint offset, + GError **err) +{ + int ret; + + g_assert(self); + + if (gpiodglib_chip_is_closed(self)) { + set_err_chip_closed(err); + return FALSE; + } + + ret = gpiod_chip_unwatch_line_info(self->handle, offset); + if (ret) { + _gpiodglib_set_error_from_errno(err, + "unable to unwatch line-info events for offset %u", + offset); + return FALSE; + } + + return TRUE; +} + +gboolean +gpiodglib_chip_get_line_offset_from_name(GpiodglibChip *self, const gchar *name, + guint *offset, GError **err) +{ + gint ret; + + g_assert(self); + + if (gpiodglib_chip_is_closed(self)) { + set_err_chip_closed(err); + return FALSE; + } + + if (!name) { + g_set_error(err, GPIODGLIB_ERROR, GPIODGLIB_ERR_INVAL, + "name must not be NULL"); + return FALSE; + } + + ret = gpiod_chip_get_line_offset_from_name(self->handle, name); + if (ret < 0) { + if (errno != ENOENT) + _gpiodglib_set_error_from_errno(err, + "failed to map line name to offset"); + else + errno = 0; + + return FALSE; + } + + if (offset) + *offset = ret; + + return TRUE; +} + +GpiodglibLineRequest * +gpiodglib_chip_request_lines(GpiodglibChip *self, + GpiodglibRequestConfig *req_cfg, + GpiodglibLineConfig *line_cfg, GError **err) +{ + struct gpiod_request_config *req_cfg_handle; + struct gpiod_line_config *line_cfg_handle; + struct gpiod_line_request *req; + + g_assert(self); + + if (gpiodglib_chip_is_closed(self)) { + set_err_chip_closed(err); + return NULL; + } + + if (!line_cfg) { + g_set_error(err, GPIODGLIB_ERROR, GPIODGLIB_ERR_INVAL, + "line-config is required for request"); + return NULL; + } + + req_cfg_handle = req_cfg ? + _gpiodglib_request_config_get_handle(req_cfg) : NULL; + line_cfg_handle = _gpiodglib_line_config_get_handle(line_cfg); + + req = gpiod_chip_request_lines(self->handle, + req_cfg_handle, line_cfg_handle); + if (!req) { + _gpiodglib_set_error_from_errno(err, + "failed to request GPIO lines"); + return NULL; + } + + return _gpiodglib_line_request_new(req); +} diff --git a/bindings/glib/edge-event.c b/bindings/glib/edge-event.c new file mode 100644 index 0000000..a7791c7 --- /dev/null +++ b/bindings/glib/edge-event.c @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +// SPDX-FileCopyrightText: 2023-2024 Bartosz Golaszewski + +#include + +#include "internal.h" + +/** + * GpiodglibEdgeEvent: + * + * #GpiodglibEdgeEvent stores information about a single line edge event. + * It contains the event type, timestamp and the offset of the line on which + * the event occurred as well as two sequence numbers (global for all lines + * in the associated request and local for this line only). + */ +struct _GpiodglibEdgeEvent { + GObject parent_instance; + struct gpiod_edge_event *handle; +}; + +typedef enum { + GPIODGLIB_EDGE_EVENT_PROP_EVENT_TYPE = 1, + GPIODGLIB_EDGE_EVENT_PROP_TIMESTAMP_NS, + GPIODGLIB_EDGE_EVENT_PROP_LINE_OFFSET, + GPIODGLIB_EDGE_EVENT_PROP_GLOBAL_SEQNO, + GPIODGLIB_EDGE_EVENT_PROP_LINE_SEQNO, +} GpiodglibEdgeEventProp; + +G_DEFINE_TYPE(GpiodglibEdgeEvent, gpiodglib_edge_event, G_TYPE_OBJECT); + +static void gpiodglib_edge_event_get_property(GObject *obj, guint prop_id, + GValue *val, GParamSpec *pspec) +{ + GpiodglibEdgeEvent *self = GPIODGLIB_EDGE_EVENT_OBJ(obj); + GpiodglibEdgeEventType type; + + g_assert(self->handle); + + switch ((GpiodglibEdgeEventProp)prop_id) { + case GPIODGLIB_EDGE_EVENT_PROP_EVENT_TYPE: + type = _gpiodglib_edge_event_type_from_library( + gpiod_edge_event_get_event_type(self->handle)); + g_value_set_enum(val, type); + break; + case GPIODGLIB_EDGE_EVENT_PROP_TIMESTAMP_NS: + g_value_set_uint64(val, + gpiod_edge_event_get_timestamp_ns(self->handle)); + break; + case GPIODGLIB_EDGE_EVENT_PROP_LINE_OFFSET: + g_value_set_uint(val, + gpiod_edge_event_get_line_offset(self->handle)); + break; + case GPIODGLIB_EDGE_EVENT_PROP_GLOBAL_SEQNO: + g_value_set_ulong(val, + gpiod_edge_event_get_global_seqno(self->handle)); + break; + case GPIODGLIB_EDGE_EVENT_PROP_LINE_SEQNO: + g_value_set_ulong(val, + gpiod_edge_event_get_line_seqno(self->handle)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec); + } +} + +static void gpiodglib_edge_event_finalize(GObject *obj) +{ + GpiodglibEdgeEvent *self = GPIODGLIB_EDGE_EVENT_OBJ(obj); + + g_clear_pointer(&self->handle, gpiod_edge_event_free); + + G_OBJECT_CLASS(gpiodglib_edge_event_parent_class)->finalize(obj); +} + +static void +gpiodglib_edge_event_class_init(GpiodglibEdgeEventClass *edge_event_class) +{ + GObjectClass *class = G_OBJECT_CLASS(edge_event_class); + + class->get_property = gpiodglib_edge_event_get_property; + class->finalize = gpiodglib_edge_event_finalize; + + /** + * GpiodglibEdgeEvent:event-type: + * + * Type of the edge event. + */ + g_object_class_install_property(class, + GPIODGLIB_EDGE_EVENT_PROP_EVENT_TYPE, + g_param_spec_enum("event-type", "Event Type", + "Type of the edge event.", + GPIODGLIB_EDGE_EVENT_TYPE_TYPE, + GPIODGLIB_EDGE_EVENT_RISING_EDGE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * GpiodglibEdgeEvent:timestamp-ns: + * + * Timestamp of the edge event expressed in nanoseconds. + */ + g_object_class_install_property(class, + GPIODGLIB_EDGE_EVENT_PROP_TIMESTAMP_NS, + g_param_spec_uint64("timestamp-ns", + "Timestamp (in nanoseconds)", + "Timestamp of the edge event expressed in nanoseconds.", + 0, G_MAXUINT64, 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * GpiodglibEdgeEvent:line-offset: + * + * Offset of the line on which this event was registered. + */ + g_object_class_install_property(class, + GPIODGLIB_EDGE_EVENT_PROP_LINE_OFFSET, + g_param_spec_uint("line-offset", "Line Offset", + "Offset of the line on which this event was registered.", + 0, G_MAXUINT, 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * GpiodglibEdgeEvent:global-seqno: + * + * Global sequence number of this event. + */ + g_object_class_install_property(class, + GPIODGLIB_EDGE_EVENT_PROP_GLOBAL_SEQNO, + g_param_spec_ulong("global-seqno", "Global Sequence Number", + "Global sequence number of this event.", + 0, G_MAXULONG, 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * GpiodglibEdgeEvent:line-seqno: + * + * Event sequence number specific to the line. + */ + g_object_class_install_property(class, + GPIODGLIB_EDGE_EVENT_PROP_LINE_SEQNO, + g_param_spec_ulong("line-seqno", "Line Sequence Number", + "Event sequence number specific to the line.", + 0, G_MAXULONG, 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); +} + +static void gpiodglib_edge_event_init(GpiodglibEdgeEvent *self) +{ + self->handle = NULL; +} + +GpiodglibEdgeEventType +gpiodglib_edge_event_get_event_type(GpiodglibEdgeEvent *self) +{ + return _gpiodglib_get_prop_enum(G_OBJECT(self), "event-type"); +} + +guint64 gpiodglib_edge_event_get_timestamp_ns(GpiodglibEdgeEvent *self) +{ + return _gpiodglib_get_prop_uint64(G_OBJECT(self), "timestamp-ns"); +} + +guint gpiodglib_edge_event_get_line_offset(GpiodglibEdgeEvent *self) +{ + return _gpiodglib_get_prop_uint(G_OBJECT(self), "line-offset"); +} + +gulong gpiodglib_edge_event_get_global_seqno(GpiodglibEdgeEvent *self) +{ + return _gpiodglib_get_prop_ulong(G_OBJECT(self), "global-seqno"); +} + +gulong gpiodglib_edge_event_get_line_seqno(GpiodglibEdgeEvent *self) +{ + return _gpiodglib_get_prop_ulong(G_OBJECT(self), "line-seqno"); +} + +GpiodglibEdgeEvent *_gpiodglib_edge_event_new(struct gpiod_edge_event *handle) +{ + GpiodglibEdgeEvent *event; + + event = GPIODGLIB_EDGE_EVENT_OBJ( + g_object_new(GPIODGLIB_EDGE_EVENT_TYPE, NULL)); + event->handle = handle; + + return event; +} diff --git a/bindings/glib/error.c b/bindings/glib/error.c new file mode 100644 index 0000000..cc0250a --- /dev/null +++ b/bindings/glib/error.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +// SPDX-FileCopyrightText: 2022-2024 Bartosz Golaszewski + +#include +#include +#include +#include + +G_DEFINE_QUARK(g-gpiod-error, gpiodglib_error) + +static GpiodglibError error_from_errno(void) +{ + switch (errno) { + case EPERM: + return GPIODGLIB_ERR_PERM; + case ENOENT: + return GPIODGLIB_ERR_NOENT; + case EINTR: + return GPIODGLIB_ERR_INTR; + case EIO: + return GPIODGLIB_ERR_IO; + case ENXIO: + return GPIODGLIB_ERR_NXIO; + case E2BIG: + return GPIODGLIB_ERR_E2BIG; + case EBADFD: + return GPIODGLIB_ERR_BADFD; + case ECHILD: + return GPIODGLIB_ERR_CHILD; + case EAGAIN: + return GPIODGLIB_ERR_AGAIN; + case ENOMEM: + /* Special case - as a convention GLib just aborts on ENOMEM. */ + g_error("out of memory"); + case EACCES: + return GPIODGLIB_ERR_ACCES; + case EFAULT: + return GPIODGLIB_ERR_FAULT; + case EBUSY: + return GPIODGLIB_ERR_BUSY; + case EEXIST: + return GPIODGLIB_ERR_EXIST; + case ENODEV: + return GPIODGLIB_ERR_NODEV; + case EINVAL: + return GPIODGLIB_ERR_INVAL; + case ENOTTY: + return GPIODGLIB_ERR_NOTTY; + case EPIPE: + return GPIODGLIB_ERR_PIPE; + default: + return GPIODGLIB_ERR_FAILED; + } +} + +void _gpiodglib_set_error_from_errno(GError **err, const gchar *fmt, ...) +{ + g_autofree gchar *msg = NULL; + va_list va; + + va_start(va, fmt); + msg = g_strdup_vprintf(fmt, va); + va_end(va); + + g_set_error(err, GPIODGLIB_ERROR, error_from_errno(), + "%s: %s", msg, g_strerror(errno)); +} diff --git a/bindings/glib/generated-enums.c.template b/bindings/glib/generated-enums.c.template new file mode 100644 index 0000000..c124eb7 --- /dev/null +++ b/bindings/glib/generated-enums.c.template @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +// SPDX-FileCopyrightText: 2023 Bartosz Golaszewski + +/*** BEGIN file-header ***/ + +#include + +/*** END file-header ***/ + +/*** BEGIN file-production ***/ + +/* enumerations from "@basename@" */ + +/*** END file-production ***/ + +/*** BEGIN value-header ***/ + +GType @enum_name@_get_type(void) +{ + static gsize static_g_@type@_type_id; + + if (g_once_init_enter(&static_g_@type@_type_id)) { + static const G@Type@Value values[] = { +/*** END value-header ***/ + +/*** BEGIN value-production ***/ + {@VALUENAME@, "@VALUENAME@", "@valuenick@"}, +/*** END value-production ***/ + +/*** BEGIN value-tail ***/ + { 0, NULL, NULL } + }; + + GType g_@type@_type_id = g_@type@_register_static( + g_intern_static_string("@EnumName@"), values); + + g_once_init_leave (&static_g_@type@_type_id, g_@type@_type_id); + } + + return static_g_@type@_type_id; +} + +/*** END value-tail ***/ diff --git a/bindings/glib/info-event.c b/bindings/glib/info-event.c new file mode 100644 index 0000000..1c339db --- /dev/null +++ b/bindings/glib/info-event.c @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +// SPDX-FileCopyrightText: 2023-2024 Bartosz Golaszewski + +#include + +#include "internal.h" + +/** + * GpiodglibInfoEvent: + * + * #GpiodglibInfoEvent contains information about the event itself (timestamp, + * type) as well as a snapshot of line's status in the form of a line-info + * object. + */ +struct _GpiodglibInfoEvent { + GObject parent_instance; + struct gpiod_info_event *handle; + GpiodglibLineInfo *info; +}; + +typedef enum { + GPIODGLIB_INFO_EVENT_PROP_EVENT_TYPE = 1, + GPIODGLIB_INFO_EVENT_PROP_TIMESTAMP, + GPIODGLIB_INFO_EVENT_PROP_LINE_INFO, +} GpiodglibInfoEventProp; + +G_DEFINE_TYPE(GpiodglibInfoEvent, gpiodglib_info_event, G_TYPE_OBJECT); + +static void gpiodglib_info_event_get_property(GObject *obj, guint prop_id, + GValue *val, GParamSpec *pspec) +{ + GpiodglibInfoEvent *self = GPIODGLIB_INFO_EVENT_OBJ(obj); + struct gpiod_line_info *info, *cpy; + GpiodglibInfoEventType type; + + g_assert(self->handle); + + switch ((GpiodglibInfoEventProp)prop_id) { + case GPIODGLIB_INFO_EVENT_PROP_EVENT_TYPE: + type = _gpiodglib_info_event_type_from_library( + gpiod_info_event_get_event_type(self->handle)); + g_value_set_enum(val, type); + break; + case GPIODGLIB_INFO_EVENT_PROP_TIMESTAMP: + g_value_set_uint64(val, + gpiod_info_event_get_timestamp_ns(self->handle)); + break; + case GPIODGLIB_INFO_EVENT_PROP_LINE_INFO: + if (!self->info) { + info = gpiod_info_event_get_line_info(self->handle); + cpy = gpiod_line_info_copy(info); + if (!cpy) + g_error("Failed to allocate memory for line-info object"); + + self->info = _gpiodglib_line_info_new(cpy); + } + + g_value_set_object(val, self->info); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec); + } +} + +static void gpiodglib_info_event_dispose(GObject *obj) +{ + GpiodglibInfoEvent *self = GPIODGLIB_INFO_EVENT_OBJ(obj); + + g_clear_object(&self->info); + + G_OBJECT_CLASS(gpiodglib_info_event_parent_class)->dispose(obj); +} + +static void gpiodglib_info_event_finalize(GObject *obj) +{ + GpiodglibInfoEvent *self = GPIODGLIB_INFO_EVENT_OBJ(obj); + + g_clear_pointer(&self->handle, gpiod_info_event_free); + + G_OBJECT_CLASS(gpiodglib_info_event_parent_class)->finalize(obj); +} + +static void gpiodglib_info_event_class_init(GpiodglibInfoEventClass *info_event_class) +{ + GObjectClass *class = G_OBJECT_CLASS(info_event_class); + + class->get_property = gpiodglib_info_event_get_property; + class->dispose = gpiodglib_info_event_dispose; + class->finalize = gpiodglib_info_event_finalize; + + /** + * GpiodglibInfoEvent:event-type + * + * Type of the info event. One of @GPIODGLIB_INFO_EVENT_LINE_REQUESTED, + * @GPIODGLIB_INFO_EVENT_LINE_RELEASED or + * @GPIODGLIB_INFO_EVENT_LINE_CONFIG_CHANGED. + */ + g_object_class_install_property(class, + GPIODGLIB_INFO_EVENT_PROP_EVENT_TYPE, + g_param_spec_enum("event-type", "Event Type", + "Type of the info event.", + GPIODGLIB_INFO_EVENT_TYPE_TYPE, + GPIODGLIB_INFO_EVENT_LINE_REQUESTED, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * GpiodglibInfoEvent:timestamp-ns + * + * Timestamp (in nanoseconds). + */ + g_object_class_install_property(class, + GPIODGLIB_INFO_EVENT_PROP_TIMESTAMP, + g_param_spec_uint64("timestamp-ns", + "Timestamp (in nanoseconds).", + "Timestamp of the info event expressed in nanoseconds.", + 0, G_MAXUINT64, 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * GpiodglibInfoEvent:line-info + * + * New line-info snapshot associated with this info event. + */ + g_object_class_install_property(class, + GPIODGLIB_INFO_EVENT_PROP_LINE_INFO, + g_param_spec_object("line-info", "Line Info", + "New line-info snapshot associated with this info event.", + GPIODGLIB_LINE_INFO_TYPE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); +} + +static void gpiodglib_info_event_init(GpiodglibInfoEvent *self) +{ + self->handle = NULL; + self->info = NULL; +} + +GpiodglibInfoEventType gpiodglib_info_event_get_event_type(GpiodglibInfoEvent *self) +{ + return _gpiodglib_get_prop_enum(G_OBJECT(self), "event-type"); +} + +guint64 gpiodglib_info_event_get_timestamp_ns(GpiodglibInfoEvent *self) +{ + return _gpiodglib_get_prop_uint64(G_OBJECT(self), "timestamp-ns"); +} + +GpiodglibLineInfo *gpiodglib_info_event_get_line_info(GpiodglibInfoEvent *self) +{ + return GPIODGLIB_LINE_INFO_OBJ( + _gpiodglib_get_prop_object(G_OBJECT(self), "line-info")); +} + +GpiodglibInfoEvent *_gpiodglib_info_event_new(struct gpiod_info_event *handle) +{ + GpiodglibInfoEvent *event; + + event = GPIODGLIB_INFO_EVENT_OBJ( + g_object_new(GPIODGLIB_INFO_EVENT_TYPE, NULL)); + event->handle = handle; + + return event; +} diff --git a/bindings/glib/internal.c b/bindings/glib/internal.c new file mode 100644 index 0000000..6898637 --- /dev/null +++ b/bindings/glib/internal.c @@ -0,0 +1,327 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +// SPDX-FileCopyrightText: 2022-2024 Bartosz Golaszewski + +#include "internal.h" + +#define get_prop(_obj, _prop, _type) \ + ({ \ + _type _ret; \ + g_object_get(_obj, _prop, &_ret, NULL); \ + _ret; \ + }) + +G_GNUC_INTERNAL gchar * +_gpiodglib_dup_prop_string(GObject *obj, const gchar *prop) +{ + return get_prop(obj, prop, gchar *); +} + +G_GNUC_INTERNAL gboolean +_gpiodglib_get_prop_bool(GObject *obj, const gchar *prop) +{ + return get_prop(obj, prop, gboolean); +} + +G_GNUC_INTERNAL gint _gpiodglib_get_prop_enum(GObject *obj, const gchar *prop) +{ + return get_prop(obj, prop, gint); +} + +G_GNUC_INTERNAL guint _gpiodglib_get_prop_uint(GObject *obj, const gchar *prop) +{ + return get_prop(obj, prop, guint); +} + +G_GNUC_INTERNAL guint64 +_gpiodglib_get_prop_uint64(GObject *obj, const gchar *prop) +{ + return get_prop(obj, prop, guint64); +} + +G_GNUC_INTERNAL gulong _gpiodglib_get_prop_ulong(GObject *obj, const gchar *prop) +{ + return get_prop(obj, prop, gulong); +} + +G_GNUC_INTERNAL GTimeSpan +_gpiodglib_get_prop_timespan(GObject *obj, const gchar *prop) +{ + return get_prop(obj, prop, GTimeSpan); +} + +G_GNUC_INTERNAL GObject * +_gpiodglib_get_prop_object(GObject *obj, const gchar *prop) +{ + return G_OBJECT(get_prop(obj, prop, gpointer)); +} + +G_GNUC_INTERNAL gpointer +_gpiodglib_get_prop_pointer(GObject *obj, const gchar *prop) +{ + return get_prop(obj, prop, gpointer); +} + +G_GNUC_INTERNAL gpointer +_gpiodglib_get_prop_boxed_array(GObject *obj, const gchar *prop) +{ + return get_prop(obj, prop, gpointer); +} + +#define set_prop(_obj, _prop, _val) \ + do { \ + g_object_set(_obj, _prop, _val, NULL); \ + } while (0) + +G_GNUC_INTERNAL void +_gpiodglib_set_prop_uint(GObject *obj, const gchar *prop, guint val) +{ + set_prop(obj, prop, val); +} + +G_GNUC_INTERNAL void +_gpiodglib_set_prop_string(GObject *obj, const gchar *prop, const gchar *val) +{ + set_prop(obj, prop, val); +} + +G_GNUC_INTERNAL void +_gpiodglib_set_prop_enum(GObject *obj, const gchar *prop, gint val) +{ + set_prop(obj, prop, val); +} + +G_GNUC_INTERNAL void +_gpiodglib_set_prop_bool(GObject *obj, const gchar *prop, gboolean val) +{ + set_prop(obj, prop, val); +} + +G_GNUC_INTERNAL void +_gpiodglib_set_prop_timespan(GObject *obj, const gchar *prop, GTimeSpan val) +{ + set_prop(obj, prop, val); +} + +G_GNUC_INTERNAL GpiodglibLineDirection +_gpiodglib_line_direction_from_library(enum gpiod_line_direction direction, + gboolean allow_as_is) +{ + switch (direction) { + case GPIOD_LINE_DIRECTION_AS_IS: + if (allow_as_is) + return GPIODGLIB_LINE_DIRECTION_AS_IS; + break; + case GPIOD_LINE_DIRECTION_INPUT: + return GPIODGLIB_LINE_DIRECTION_INPUT; + case GPIOD_LINE_DIRECTION_OUTPUT: + return GPIODGLIB_LINE_DIRECTION_OUTPUT; + } + + g_error("invalid line direction value returned by libgpiod"); +} + +G_GNUC_INTERNAL GpiodglibLineEdge +_gpiodglib_line_edge_from_library(enum gpiod_line_edge edge) +{ + switch (edge) { + case GPIOD_LINE_EDGE_NONE: + return GPIODGLIB_LINE_EDGE_NONE; + case GPIOD_LINE_EDGE_RISING: + return GPIODGLIB_LINE_EDGE_RISING; + case GPIOD_LINE_EDGE_FALLING: + return GPIODGLIB_LINE_EDGE_FALLING; + case GPIOD_LINE_EDGE_BOTH: + return GPIODGLIB_LINE_EDGE_BOTH; + } + + g_error("invalid line edge value returned by libgpiod"); +} + +G_GNUC_INTERNAL GpiodglibLineBias +_gpiodglib_line_bias_from_library(enum gpiod_line_bias bias, + gboolean allow_as_is) +{ + switch (bias) { + case GPIOD_LINE_BIAS_AS_IS: + if (allow_as_is) + return GPIODGLIB_LINE_BIAS_AS_IS; + break; + case GPIOD_LINE_BIAS_UNKNOWN: + return GPIODGLIB_LINE_BIAS_UNKNOWN; + case GPIOD_LINE_BIAS_DISABLED: + return GPIODGLIB_LINE_BIAS_DISABLED; + case GPIOD_LINE_BIAS_PULL_UP: + return GPIODGLIB_LINE_BIAS_PULL_UP; + case GPIOD_LINE_BIAS_PULL_DOWN: + return GPIODGLIB_LINE_BIAS_PULL_DOWN; + } + + g_error("invalid line bias value returned by libgpiod"); +} + +G_GNUC_INTERNAL GpiodglibLineDrive +_gpiodglib_line_drive_from_library(enum gpiod_line_drive drive) +{ + switch (drive) { + case GPIOD_LINE_DRIVE_PUSH_PULL: + return GPIODGLIB_LINE_DRIVE_PUSH_PULL; + case GPIOD_LINE_DRIVE_OPEN_DRAIN: + return GPIODGLIB_LINE_DRIVE_OPEN_DRAIN; + case GPIOD_LINE_DRIVE_OPEN_SOURCE: + return GPIODGLIB_LINE_DRIVE_OPEN_SOURCE; + } + + g_error("invalid line drive value returned by libgpiod"); +} + +G_GNUC_INTERNAL GpiodglibLineClock +_gpiodglib_line_clock_from_library(enum gpiod_line_clock event_clock) +{ + switch (event_clock) { + case GPIOD_LINE_CLOCK_MONOTONIC: + return GPIODGLIB_LINE_CLOCK_MONOTONIC; + case GPIOD_LINE_CLOCK_REALTIME: + return GPIODGLIB_LINE_CLOCK_REALTIME; + case GPIOD_LINE_CLOCK_HTE: + return GPIODGLIB_LINE_CLOCK_HTE; + } + + g_error("invalid line event clock value returned by libgpiod"); +} + +G_GNUC_INTERNAL GpiodglibLineValue +_gpiodglib_line_value_from_library(enum gpiod_line_value value) +{ + switch (value) { + case GPIOD_LINE_VALUE_INACTIVE: + return GPIODGLIB_LINE_VALUE_INACTIVE; + case GPIOD_LINE_VALUE_ACTIVE: + return GPIODGLIB_LINE_VALUE_ACTIVE; + default: + break; + } + + g_error("invalid line value returned by libgpiod"); +} + +G_GNUC_INTERNAL GpiodglibInfoEventType +_gpiodglib_info_event_type_from_library(enum gpiod_info_event_type type) +{ + switch (type) { + case GPIOD_INFO_EVENT_LINE_REQUESTED: + return GPIODGLIB_INFO_EVENT_LINE_REQUESTED; + case GPIOD_INFO_EVENT_LINE_RELEASED: + return GPIODGLIB_INFO_EVENT_LINE_RELEASED; + case GPIOD_INFO_EVENT_LINE_CONFIG_CHANGED: + return GPIODGLIB_INFO_EVENT_LINE_CONFIG_CHANGED; + } + + g_error("invalid info-event type returned by libgpiod"); +} + +G_GNUC_INTERNAL GpiodglibEdgeEventType +_gpiodglib_edge_event_type_from_library(enum gpiod_edge_event_type type) +{ + switch (type) { + case GPIOD_EDGE_EVENT_RISING_EDGE: + return GPIODGLIB_EDGE_EVENT_RISING_EDGE; + case GPIOD_EDGE_EVENT_FALLING_EDGE: + return GPIODGLIB_EDGE_EVENT_FALLING_EDGE; + } + + g_error("invalid edge-event type returned by libgpiod"); +} + +G_GNUC_INTERNAL enum gpiod_line_direction +_gpiodglib_line_direction_to_library(GpiodglibLineDirection direction) +{ + switch (direction) { + case GPIODGLIB_LINE_DIRECTION_AS_IS: + return GPIOD_LINE_DIRECTION_AS_IS; + case GPIODGLIB_LINE_DIRECTION_INPUT: + return GPIOD_LINE_DIRECTION_INPUT; + case GPIODGLIB_LINE_DIRECTION_OUTPUT: + return GPIOD_LINE_DIRECTION_OUTPUT; + } + + g_error("invalid line direction value"); +} + +G_GNUC_INTERNAL enum gpiod_line_edge +_gpiodglib_line_edge_to_library(GpiodglibLineEdge edge) +{ + switch (edge) { + case GPIODGLIB_LINE_EDGE_NONE: + return GPIOD_LINE_EDGE_NONE; + case GPIODGLIB_LINE_EDGE_RISING: + return GPIOD_LINE_EDGE_RISING; + case GPIODGLIB_LINE_EDGE_FALLING: + return GPIOD_LINE_EDGE_FALLING; + case GPIODGLIB_LINE_EDGE_BOTH: + return GPIOD_LINE_EDGE_BOTH; + } + + g_error("invalid line edge value"); +} + +G_GNUC_INTERNAL enum gpiod_line_bias +_gpiodglib_line_bias_to_library(GpiodglibLineBias bias) +{ + switch (bias) { + case GPIODGLIB_LINE_BIAS_AS_IS: + return GPIOD_LINE_BIAS_AS_IS; + case GPIODGLIB_LINE_BIAS_DISABLED: + return GPIOD_LINE_BIAS_DISABLED; + case GPIODGLIB_LINE_BIAS_PULL_UP: + return GPIOD_LINE_BIAS_PULL_UP; + case GPIODGLIB_LINE_BIAS_PULL_DOWN: + return GPIOD_LINE_BIAS_PULL_DOWN; + default: + break; + } + + g_error("invalid line bias value"); +} + +G_GNUC_INTERNAL enum gpiod_line_drive +_gpiodglib_line_drive_to_library(GpiodglibLineDrive drive) +{ + switch (drive) { + case GPIODGLIB_LINE_DRIVE_PUSH_PULL: + return GPIOD_LINE_DRIVE_PUSH_PULL; + case GPIODGLIB_LINE_DRIVE_OPEN_SOURCE: + return GPIOD_LINE_DRIVE_OPEN_SOURCE; + case GPIODGLIB_LINE_DRIVE_OPEN_DRAIN: + return GPIOD_LINE_DRIVE_OPEN_DRAIN; + } + + g_error("invalid line drive value"); +} + +G_GNUC_INTERNAL enum gpiod_line_clock +_gpiodglib_line_clock_to_library(GpiodglibLineClock event_clock) +{ + switch (event_clock) { + case GPIODGLIB_LINE_CLOCK_MONOTONIC: + return GPIOD_LINE_CLOCK_MONOTONIC; + case GPIODGLIB_LINE_CLOCK_REALTIME: + return GPIOD_LINE_CLOCK_REALTIME; + case GPIODGLIB_LINE_CLOCK_HTE: + return GPIOD_LINE_CLOCK_HTE; + } + + g_error("invalid line clock value"); +} + +G_GNUC_INTERNAL enum gpiod_line_value +_gpiodglib_line_value_to_library(GpiodglibLineValue value) +{ + switch (value) { + case GPIODGLIB_LINE_VALUE_INACTIVE: + return GPIOD_LINE_VALUE_INACTIVE; + case GPIODGLIB_LINE_VALUE_ACTIVE: + return GPIOD_LINE_VALUE_ACTIVE; + } + + g_error("invalid line value"); +} diff --git a/bindings/glib/internal.h b/bindings/glib/internal.h new file mode 100644 index 0000000..b6f8f42 --- /dev/null +++ b/bindings/glib/internal.h @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* SPDX-FileCopyrightText: 2022-2024 Bartosz Golaszewski */ + +#ifndef __GPIODGLIB_INTERNAL_H__ +#define __GPIODGLIB_INTERNAL_H__ + +#include +#include +#include + +#include "gpiod-glib.h" + +GpiodglibLineSettings * +_gpiodglib_line_settings_new(struct gpiod_line_settings *handle); +GpiodglibChipInfo *_gpiodglib_chip_info_new(struct gpiod_chip_info *handle); +GpiodglibLineInfo *_gpiodglib_line_info_new(struct gpiod_line_info *handle); +GpiodglibEdgeEvent *_gpiodglib_edge_event_new(struct gpiod_edge_event *handle); +GpiodglibInfoEvent *_gpiodglib_info_event_new(struct gpiod_info_event *handle); +GpiodglibLineRequest * +_gpiodglib_line_request_new(struct gpiod_line_request *handle); + +struct gpiod_request_config * +_gpiodglib_request_config_get_handle(GpiodglibRequestConfig *req_cfg); +struct gpiod_line_config * +_gpiodglib_line_config_get_handle(GpiodglibLineConfig *line_cfg); +struct gpiod_line_settings * +_gpiodglib_line_settings_get_handle(GpiodglibLineSettings *settings); + +void _gpiodglib_set_error_from_errno(GError **err, + const gchar *fmt, ...) G_GNUC_PRINTF(2, 3); + +gchar *_gpiodglib_dup_prop_string(GObject *obj, const gchar *prop); +gboolean _gpiodglib_get_prop_bool(GObject *obj, const gchar *prop); +gint _gpiodglib_get_prop_enum(GObject *obj, const gchar *prop); +guint _gpiodglib_get_prop_uint(GObject *obj, const gchar *prop); +guint64 _gpiodglib_get_prop_uint64(GObject *obj, const gchar *prop); +gulong _gpiodglib_get_prop_ulong(GObject *obj, const gchar *prop); +GTimeSpan _gpiodglib_get_prop_timespan(GObject *obj, const gchar *prop); +GObject *_gpiodglib_get_prop_object(GObject *obj, const gchar *prop); +gpointer _gpiodglib_get_prop_pointer(GObject *obj, const gchar *prop); +gpointer _gpiodglib_get_prop_boxed_array(GObject *obj, const gchar *prop); + +void _gpiodglib_set_prop_uint(GObject *obj, const gchar *prop, guint val); +void _gpiodglib_set_prop_string(GObject *obj, const gchar *prop, + const gchar *val); +void _gpiodglib_set_prop_enum(GObject *obj, const gchar *prop, gint val); +void _gpiodglib_set_prop_bool(GObject *obj, const gchar *prop, gboolean val); +void _gpiodglib_set_prop_timespan(GObject *obj, const gchar *prop, + GTimeSpan val); + +GpiodglibLineDirection +_gpiodglib_line_direction_from_library(enum gpiod_line_direction direction, + gboolean allow_as_is); +GpiodglibLineEdge _gpiodglib_line_edge_from_library(enum gpiod_line_edge edge); +GpiodglibLineBias _gpiodglib_line_bias_from_library(enum gpiod_line_bias bias, + gboolean allow_as_is); +GpiodglibLineDrive +_gpiodglib_line_drive_from_library(enum gpiod_line_drive drive); +GpiodglibLineClock +_gpiodglib_line_clock_from_library(enum gpiod_line_clock event_clock); +GpiodglibLineValue +_gpiodglib_line_value_from_library(enum gpiod_line_value value); +GpiodglibInfoEventType +_gpiodglib_info_event_type_from_library(enum gpiod_info_event_type type); +GpiodglibEdgeEventType +_gpiodglib_edge_event_type_from_library(enum gpiod_edge_event_type type); + +enum gpiod_line_direction +_gpiodglib_line_direction_to_library(GpiodglibLineDirection direction); +enum gpiod_line_edge _gpiodglib_line_edge_to_library(GpiodglibLineEdge edge); +enum gpiod_line_bias _gpiodglib_line_bias_to_library(GpiodglibLineBias bias); +enum gpiod_line_drive +_gpiodglib_line_drive_to_library(GpiodglibLineDrive drive); +enum gpiod_line_clock +_gpiodglib_line_clock_to_library(GpiodglibLineClock event_clock); +enum gpiod_line_value +_gpiodglib_line_value_to_library(GpiodglibLineValue value); + +#endif /* __GPIODGLIB_INTERNAL_H__ */ diff --git a/bindings/glib/line-config.c b/bindings/glib/line-config.c new file mode 100644 index 0000000..37d3c21 --- /dev/null +++ b/bindings/glib/line-config.c @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +// SPDX-FileCopyrightText: 2023-2024 Bartosz Golaszewski + +#include + +#include "internal.h" + +/** + * GpiodglibLineConfig: + * + * The line-config object contains the configuration for lines that can be + * used in two cases: + * - when making a line request + * - when reconfiguring a set of already requested lines. + */ +struct _GpiodglibLineConfig { + GObject parent_instance; + struct gpiod_line_config *handle; +}; + +typedef enum { + GPIODGLIB_LINE_CONFIG_PROP_CONFIGURED_OFFSETS = 1, +} GpiodglibLineConfigProp; + +G_DEFINE_TYPE(GpiodglibLineConfig, gpiodglib_line_config, G_TYPE_OBJECT); + +static void gpiodglib_line_config_get_property(GObject *obj, guint prop_id, + GValue *val, GParamSpec *pspec) +{ + GpiodglibLineConfig *self = GPIODGLIB_LINE_CONFIG_OBJ(obj); + g_autofree guint *offsets = NULL; + g_autoptr(GArray) boxed = NULL; + gsize num_offsets, i; + + switch ((GpiodglibLineConfigProp)prop_id) { + case GPIODGLIB_LINE_CONFIG_PROP_CONFIGURED_OFFSETS: + num_offsets = gpiod_line_config_get_num_configured_offsets( + self->handle); + offsets = g_malloc0(num_offsets * sizeof(guint)); + gpiod_line_config_get_configured_offsets(self->handle, offsets, + num_offsets); + + boxed = g_array_new(FALSE, TRUE, sizeof(guint)); + for (i = 0; i < num_offsets; i++) + g_array_append_val(boxed, offsets[i]); + + g_value_set_boxed(val, boxed); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec); + } +} + +static void gpiodglib_line_config_finalize(GObject *obj) +{ + GpiodglibLineConfig *self = GPIODGLIB_LINE_CONFIG_OBJ(obj); + + g_clear_pointer(&self->handle, gpiod_line_config_free); + + G_OBJECT_CLASS(gpiodglib_line_config_parent_class)->finalize(obj); +} + +static void +gpiodglib_line_config_class_init(GpiodglibLineConfigClass *line_config_class) +{ + GObjectClass *class = G_OBJECT_CLASS(line_config_class); + + class->get_property = gpiodglib_line_config_get_property; + class->finalize = gpiodglib_line_config_finalize; + + /** + * GpiodglibLineConfig:configured-offsets: + * + * Array of offsets for which line settings have been set. + */ + g_object_class_install_property(class, + GPIODGLIB_LINE_CONFIG_PROP_CONFIGURED_OFFSETS, + g_param_spec_boxed("configured-offsets", "Configured Offsets", + "Array of offsets for which line settings have been set.", + G_TYPE_ARRAY, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); +} + +static void gpiodglib_line_config_init(GpiodglibLineConfig *self) +{ + self->handle = gpiod_line_config_new(); + if (!self->handle) + /* The only possible error is ENOMEM. */ + g_error("Failed to allocate memory for the request-config object."); +} + +GpiodglibLineConfig *gpiodglib_line_config_new(void) +{ + return GPIODGLIB_LINE_CONFIG_OBJ( + g_object_new(GPIODGLIB_LINE_CONFIG_TYPE, NULL)); +} + +void gpiodglib_line_config_reset(GpiodglibLineConfig *self) +{ + g_assert(self); + + gpiod_line_config_reset(self->handle); +} + +gboolean +gpiodglib_line_config_add_line_settings(GpiodglibLineConfig *self, + const GArray *offsets, + GpiodglibLineSettings *settings, + GError **err) +{ + struct gpiod_line_settings *settings_handle; + int ret; + + g_assert(self); + + if (!offsets || !offsets->len) { + g_set_error(err, GPIODGLIB_ERROR, GPIODGLIB_ERR_INVAL, + "at least one offset must be specified when adding line settings"); + return FALSE; + } + + settings_handle = settings ? + _gpiodglib_line_settings_get_handle(settings) : NULL; + ret = gpiod_line_config_add_line_settings(self->handle, + (unsigned int *)offsets->data, + offsets->len, + settings_handle); + if (ret) { + _gpiodglib_set_error_from_errno(err, + "failed to add line settings to line config"); + return FALSE; + } + + return TRUE; +} + +GpiodglibLineSettings * +gpiodglib_line_config_get_line_settings(GpiodglibLineConfig *self, guint offset) +{ + struct gpiod_line_settings *settings; + + g_assert(self); + + settings = gpiod_line_config_get_line_settings(self->handle, offset); + if (!settings) { + if (errno == ENOENT) + return NULL; + + /* Let's bail-out on ENOMEM/ */ + g_error("failed to retrieve line settings for offset %u: %s", + offset, g_strerror(errno)); + } + + return _gpiodglib_line_settings_new(settings); +} + +gboolean gpiodglib_line_config_set_output_values(GpiodglibLineConfig *self, + const GArray *values, + GError **err) +{ + g_autofree enum gpiod_line_value *vals = NULL; + gint ret; + guint i; + + g_assert(self); + + vals = g_malloc0(sizeof(*vals) * values->len); + for (i = 0; i < values->len; i++) + vals[i] = _gpiodglib_line_value_to_library( + g_array_index(values, GpiodglibLineValue, i)); + + ret = gpiod_line_config_set_output_values(self->handle, vals, + values->len); + if (ret) { + _gpiodglib_set_error_from_errno(err, + "unable to set output values"); + return FALSE; + } + + return TRUE; +} + +GArray *gpiodglib_line_config_get_configured_offsets(GpiodglibLineConfig *self) +{ + return _gpiodglib_get_prop_boxed_array(G_OBJECT(self), + "configured-offsets"); +} + +struct gpiod_line_config * +_gpiodglib_line_config_get_handle(GpiodglibLineConfig *line_cfg) +{ + return line_cfg->handle; +} diff --git a/bindings/glib/line-info.c b/bindings/glib/line-info.c new file mode 100644 index 0000000..37cca37 --- /dev/null +++ b/bindings/glib/line-info.c @@ -0,0 +1,342 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +// SPDX-FileCopyrightText: 2023-2024 Bartosz Golaszewski + +#include + +#include "internal.h" + +/** + * GpiodglibLineInfo: + * + * Line info object contains an immutable snapshot of a line's status. + * + * The line info contains all the publicly available information about a + * line, which does not include the line value. The line must be requested + * to access the line value. + */ +struct _GpiodglibLineInfo { + GObject parent_instance; + struct gpiod_line_info *handle; +}; + +typedef enum { + GPIODGLIB_LINE_INFO_PROP_OFFSET = 1, + GPIODGLIB_LINE_INFO_PROP_NAME, + GPIODGLIB_LINE_INFO_PROP_USED, + GPIODGLIB_LINE_INFO_PROP_CONSUMER, + GPIODGLIB_LINE_INFO_PROP_DIRECTION, + GPIODGLIB_LINE_INFO_PROP_EDGE_DETECTION, + GPIODGLIB_LINE_INFO_PROP_BIAS, + GPIODGLIB_LINE_INFO_PROP_DRIVE, + GPIODGLIB_LINE_INFO_PROP_ACTIVE_LOW, + GPIODGLIB_LINE_INFO_PROP_DEBOUNCED, + GPIODGLIB_LINE_INFO_PROP_DEBOUNCE_PERIOD, + GPIODGLIB_LINE_INFO_PROP_EVENT_CLOCK, +} GpiodglibLineInfoProp; + +G_DEFINE_TYPE(GpiodglibLineInfo, gpiodglib_line_info, G_TYPE_OBJECT); + +static void gpiodglib_line_info_get_property(GObject *obj, guint prop_id, + GValue *val, GParamSpec *pspec) +{ + GpiodglibLineInfo *self = GPIODGLIB_LINE_INFO_OBJ(obj); + + g_assert(self->handle); + + switch ((GpiodglibLineInfoProp)prop_id) { + case GPIODGLIB_LINE_INFO_PROP_OFFSET: + g_value_set_uint(val, gpiod_line_info_get_offset(self->handle)); + break; + case GPIODGLIB_LINE_INFO_PROP_NAME: + g_value_set_string(val, + gpiod_line_info_get_name(self->handle)); + break; + case GPIODGLIB_LINE_INFO_PROP_USED: + g_value_set_boolean(val, gpiod_line_info_is_used(self->handle)); + break; + case GPIODGLIB_LINE_INFO_PROP_CONSUMER: + g_value_set_string(val, + gpiod_line_info_get_consumer(self->handle)); + break; + case GPIODGLIB_LINE_INFO_PROP_DIRECTION: + g_value_set_enum(val, + _gpiodglib_line_direction_from_library( + gpiod_line_info_get_direction(self->handle), + FALSE)); + break; + case GPIODGLIB_LINE_INFO_PROP_EDGE_DETECTION: + g_value_set_enum(val, + _gpiodglib_line_edge_from_library( + gpiod_line_info_get_edge_detection( + self->handle))); + break; + case GPIODGLIB_LINE_INFO_PROP_BIAS: + g_value_set_enum(val, + _gpiodglib_line_bias_from_library( + gpiod_line_info_get_bias(self->handle), + FALSE)); + break; + case GPIODGLIB_LINE_INFO_PROP_DRIVE: + g_value_set_enum(val, + _gpiodglib_line_drive_from_library( + gpiod_line_info_get_drive(self->handle))); + break; + case GPIODGLIB_LINE_INFO_PROP_ACTIVE_LOW: + g_value_set_boolean(val, + gpiod_line_info_is_active_low(self->handle)); + break; + case GPIODGLIB_LINE_INFO_PROP_DEBOUNCED: + g_value_set_boolean(val, + gpiod_line_info_is_debounced(self->handle)); + break; + case GPIODGLIB_LINE_INFO_PROP_DEBOUNCE_PERIOD: + g_value_set_int64(val, + gpiod_line_info_get_debounce_period_us(self->handle)); + break; + case GPIODGLIB_LINE_INFO_PROP_EVENT_CLOCK: + g_value_set_enum(val, + _gpiodglib_line_clock_from_library( + gpiod_line_info_get_event_clock(self->handle))); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec); + } +} + +static void gpiodglib_line_info_finalize(GObject *obj) +{ + GpiodglibLineInfo *self = GPIODGLIB_LINE_INFO_OBJ(obj); + + g_clear_pointer(&self->handle, gpiod_line_info_free); + + G_OBJECT_CLASS(gpiodglib_line_info_parent_class)->finalize(obj); +} + +static void +gpiodglib_line_info_class_init(GpiodglibLineInfoClass *line_info_class) +{ + GObjectClass *class = G_OBJECT_CLASS(line_info_class); + + class->get_property = gpiodglib_line_info_get_property; + class->finalize = gpiodglib_line_info_finalize; + + /** + * GpiodglibLineInfo:offset: + * + * Offset of the GPIO line. + */ + g_object_class_install_property(class, GPIODGLIB_LINE_INFO_PROP_OFFSET, + g_param_spec_uint("offset", "Offset", + "Offset of the GPIO line.", + 0, G_MAXUINT, 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * GpiodglibLineInfo:name: + * + * Name of the GPIO line, if named. + */ + g_object_class_install_property(class, GPIODGLIB_LINE_INFO_PROP_NAME, + g_param_spec_string("name", "Name", + "Name of the GPIO line, if named.", + NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * GpiodglibLineInfo:used: + * + * Indicates whether the GPIO line is requested for exclusive usage. + */ + g_object_class_install_property(class, GPIODGLIB_LINE_INFO_PROP_USED, + g_param_spec_boolean("used", "Is Used", + "Indicates whether the GPIO line is requested for exclusive usage.", + FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * GpiodglibLineInfo:consumer: + * + * Name of the consumer of the GPIO line, if requested. + */ + g_object_class_install_property(class, + GPIODGLIB_LINE_INFO_PROP_CONSUMER, + g_param_spec_string("consumer", "Consumer", + "Name of the consumer of the GPIO line, if requested.", + NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * GpiodglibLineInfo:direction: + * + * Direction of the GPIO line. + */ + g_object_class_install_property(class, + GPIODGLIB_LINE_INFO_PROP_DIRECTION, + g_param_spec_enum("direction", "Direction", + "Direction of the GPIO line.", + GPIODGLIB_LINE_DIRECTION_TYPE, + GPIODGLIB_LINE_DIRECTION_INPUT, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * GpiodglibLineInfo:edge-detection: + * + * Edge detection setting of the GPIO line. + */ + g_object_class_install_property(class, + GPIODGLIB_LINE_INFO_PROP_EDGE_DETECTION, + g_param_spec_enum("edge-detection", "Edge Detection", + "Edge detection setting of the GPIO line.", + GPIODGLIB_LINE_EDGE_TYPE, + GPIODGLIB_LINE_EDGE_NONE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * GpiodglibLineInfo:bias: + * + * Bias setting of the GPIO line. + */ + g_object_class_install_property(class, GPIODGLIB_LINE_INFO_PROP_BIAS, + g_param_spec_enum("bias", "Bias", + "Bias setting of the GPIO line.", + GPIODGLIB_LINE_BIAS_TYPE, + GPIODGLIB_LINE_BIAS_UNKNOWN, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * GpiodglibLineInfo:drive: + * + * Drive setting of the GPIO line. + */ + g_object_class_install_property(class, GPIODGLIB_LINE_INFO_PROP_DRIVE, + g_param_spec_enum("drive", "Drive", + "Drive setting of the GPIO line.", + GPIODGLIB_LINE_DRIVE_TYPE, + GPIODGLIB_LINE_DRIVE_PUSH_PULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * GpiodglibLineInfo:active-low: + * + * Indicates whether the signal of the line is inverted. + */ + g_object_class_install_property(class, + GPIODGLIB_LINE_INFO_PROP_ACTIVE_LOW, + g_param_spec_boolean("active-low", "Is Active-Low", + "Indicates whether the signal of the line is inverted.", + FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * GpiodglibLineInfo:debounced: + * + * Indicates whether the line is debounced (by hardware or by the + * kernel software debouncer). + */ + g_object_class_install_property(class, + GPIODGLIB_LINE_INFO_PROP_DEBOUNCED, + g_param_spec_boolean("debounced", "Is Debounced", + "Indicates whether the line is debounced (by hardware or by the kernel software debouncer).", + FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * GpiodglibLineInfo:debounce-period-us: + * + * Debounce period of the line (expressed in microseconds). + */ + g_object_class_install_property(class, + GPIODGLIB_LINE_INFO_PROP_DEBOUNCE_PERIOD, + g_param_spec_int64("debounce-period-us", + "Debounce Period (in microseconds)", + "Debounce period of the line (expressed in microseconds).", + 0, G_MAXINT64, 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * GpiodglibLineInfo:event-clock: + * + * Event clock used to timestamp the edge events of the line. + */ + g_object_class_install_property(class, + GPIODGLIB_LINE_INFO_PROP_EVENT_CLOCK, + g_param_spec_enum("event-clock", "Event Clock", + "Event clock used to timestamp the edge events of the line.", + GPIODGLIB_LINE_CLOCK_TYPE, + GPIODGLIB_LINE_CLOCK_MONOTONIC, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); +} + +static void gpiodglib_line_info_init(GpiodglibLineInfo *self) +{ + self->handle = NULL; +} + +guint gpiodglib_line_info_get_offset(GpiodglibLineInfo *self) +{ + return _gpiodglib_get_prop_uint(G_OBJECT(self), "offset"); +} + +gchar *gpiodglib_line_info_dup_name(GpiodglibLineInfo *self) +{ + return _gpiodglib_dup_prop_string(G_OBJECT(self), "name"); +} + +gboolean gpiodglib_line_info_is_used(GpiodglibLineInfo *self) +{ + return _gpiodglib_get_prop_bool(G_OBJECT(self), "used"); +} + +gchar *gpiodglib_line_info_dup_consumer(GpiodglibLineInfo *self) +{ + return _gpiodglib_dup_prop_string(G_OBJECT(self), "consumer"); +} + +GpiodglibLineDirection +gpiodglib_line_info_get_direction(GpiodglibLineInfo *self) +{ + return _gpiodglib_get_prop_enum(G_OBJECT(self), "direction"); +} + +GpiodglibLineEdge +gpiodglib_line_info_get_edge_detection(GpiodglibLineInfo *self) +{ + return _gpiodglib_get_prop_enum(G_OBJECT(self), "edge-detection"); +} + +GpiodglibLineBias gpiodglib_line_info_get_bias(GpiodglibLineInfo *self) +{ + return _gpiodglib_get_prop_enum(G_OBJECT(self), "bias"); +} + +GpiodglibLineDrive gpiodglib_line_info_get_drive(GpiodglibLineInfo *self) +{ + return _gpiodglib_get_prop_enum(G_OBJECT(self), "drive"); +} + +gboolean gpiodglib_line_info_is_active_low(GpiodglibLineInfo *self) +{ + return _gpiodglib_get_prop_bool(G_OBJECT(self), "active-low"); +} + +gboolean gpiodglib_line_info_is_debounced(GpiodglibLineInfo *self) +{ + return _gpiodglib_get_prop_bool(G_OBJECT(self), "debounced"); +} + +GTimeSpan gpiodglib_line_info_get_debounce_period_us(GpiodglibLineInfo *self) +{ + return _gpiodglib_get_prop_timespan(G_OBJECT(self), + "debounce-period-us"); +} + +GpiodglibLineClock gpiodglib_line_info_get_event_clock(GpiodglibLineInfo *self) +{ + return _gpiodglib_get_prop_enum(G_OBJECT(self), "event-clock"); +} + +GpiodglibLineInfo *_gpiodglib_line_info_new(struct gpiod_line_info *handle) +{ + GpiodglibLineInfo *info; + + info = GPIODGLIB_LINE_INFO_OBJ(g_object_new(GPIODGLIB_LINE_INFO_TYPE, + NULL)); + info->handle = handle; + + return info; +} diff --git a/bindings/glib/line-request.c b/bindings/glib/line-request.c new file mode 100644 index 0000000..1720c75 --- /dev/null +++ b/bindings/glib/line-request.c @@ -0,0 +1,452 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +// SPDX-FileCopyrightText: 2023-2024 Bartosz Golaszewski + +#include + +#include "internal.h" + +static const gsize event_buf_size = 64; + +/** + * GpiodglibLineRequest: + * + * Line request object allows interacting with a set of requested GPIO lines. + */ +struct _GpiodglibLineRequest { + GObject parent_instance; + struct gpiod_line_request *handle; + struct gpiod_edge_event_buffer *event_buf; + GSource *edge_event_src; + guint edge_event_src_id; + enum gpiod_line_value *val_buf; + gboolean released; +}; + +typedef enum { + GPIODGLIB_LINE_REQUEST_PROP_CHIP_NAME = 1, + GPIODGLIB_LINE_REQUEST_PROP_REQUESTED_OFFSETS, +} GpiodglibLineRequestProp; + +enum { + GPIODGLIB_LINE_REQUEST_SIGNAL_EDGE_EVENT, + GPIODGLIB_LINE_REQUEST_SIGNAL_LAST, +}; + +static guint signals[GPIODGLIB_LINE_REQUEST_SIGNAL_LAST]; + +G_DEFINE_TYPE(GpiodglibLineRequest, gpiodglib_line_request, G_TYPE_OBJECT); + +static gboolean +gpiodglib_line_request_on_edge_event(GIOChannel *source G_GNUC_UNUSED, + GIOCondition condition G_GNUC_UNUSED, + gpointer data) +{ + struct gpiod_edge_event *event_handle, *event_copy; + GpiodglibLineRequest *self = data; + gint ret, i; + + ret = gpiod_line_request_read_edge_events(self->handle, + self->event_buf, + event_buf_size); + if (ret < 0) + return TRUE; + + for (i = 0; i < ret; i++) { + g_autoptr(GpiodglibEdgeEvent) event = NULL; + + event_handle = gpiod_edge_event_buffer_get_event( + self->event_buf, i); + event_copy = gpiod_edge_event_copy(event_handle); + if (!event_copy) + g_error("failed to copy the edge event"); + + event = _gpiodglib_edge_event_new(event_copy); + + g_signal_emit(self, + signals[GPIODGLIB_LINE_REQUEST_SIGNAL_EDGE_EVENT], + 0, + event); + } + + return TRUE; +} + +static void gpiodglib_line_request_get_property(GObject *obj, guint prop_id, + GValue *val, GParamSpec *pspec) +{ + GpiodglibLineRequest *self = GPIODGLIB_LINE_REQUEST_OBJ(obj); + g_autofree guint *offsets = NULL; + g_autoptr(GArray) boxed = NULL; + gsize num_offsets; + + g_assert(self->handle); + + switch ((GpiodglibLineRequestProp)prop_id) { + case GPIODGLIB_LINE_REQUEST_PROP_CHIP_NAME: + if (gpiodglib_line_request_is_released(self)) + g_value_set_static_string(val, NULL); + else + g_value_set_string(val, + gpiod_line_request_get_chip_name(self->handle)); + break; + case GPIODGLIB_LINE_REQUEST_PROP_REQUESTED_OFFSETS: + boxed = g_array_new(FALSE, TRUE, sizeof(guint)); + + if (!gpiodglib_line_request_is_released(self)) { + num_offsets = + gpiod_line_request_get_num_requested_lines( + self->handle); + offsets = g_malloc0(num_offsets * sizeof(guint)); + gpiod_line_request_get_requested_offsets(self->handle, + offsets, + num_offsets); + g_array_append_vals(boxed, offsets, num_offsets); + } + + g_value_set_boxed(val, boxed); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec); + } +} + +static void gpiodglib_line_request_dispose(GObject *obj) +{ + GpiodglibLineRequest *self = GPIODGLIB_LINE_REQUEST_OBJ(obj); + + if (self->edge_event_src_id) + g_source_remove(self->edge_event_src_id); + + G_OBJECT_CLASS(gpiodglib_line_request_parent_class)->dispose(obj); +} + +static void gpiodglib_line_request_finalize(GObject *obj) +{ + GpiodglibLineRequest *self = GPIODGLIB_LINE_REQUEST_OBJ(obj); + + if (!self->released) + gpiodglib_line_request_release(self); + + g_clear_pointer(&self->event_buf, gpiod_edge_event_buffer_free); + g_clear_pointer(&self->val_buf, g_free); + + G_OBJECT_CLASS(gpiodglib_line_request_parent_class)->finalize(obj); +} + +static void +gpiodglib_line_request_class_init(GpiodglibLineRequestClass *line_request_class) +{ + GObjectClass *class = G_OBJECT_CLASS(line_request_class); + + class->get_property = gpiodglib_line_request_get_property; + class->dispose = gpiodglib_line_request_dispose; + class->finalize = gpiodglib_line_request_finalize; + + /** + * GpiodglibLineRequest:chip-name + * + * Name of the GPIO chip this request was made on. + */ + g_object_class_install_property(class, + GPIODGLIB_LINE_REQUEST_PROP_CHIP_NAME, + g_param_spec_string("chip-name", "Chip Name", + "Name of the GPIO chip this request was made on.", + NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * GpiodglibLineRequest:requested-offsets + * + * Array of requested offsets. + */ + g_object_class_install_property(class, + GPIODGLIB_LINE_REQUEST_PROP_REQUESTED_OFFSETS, + g_param_spec_boxed("requested-offsets", "Requested offsets", + "Array of requested offsets.", + G_TYPE_ARRAY, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * GpiodglibLineRequest::edge-event: + * @chip: #GpiodglibLineRequest receiving the event + * @event: The #GpiodglibEdgeEvent + * + * Emitted when an edge event is detected on one of the requested GPIO + * line. + */ + signals[GPIODGLIB_LINE_REQUEST_SIGNAL_EDGE_EVENT] = + g_signal_new("edge-event", + G_TYPE_FROM_CLASS(line_request_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, + 1, + GPIODGLIB_EDGE_EVENT_TYPE); +} + +static void gpiodglib_line_request_init(GpiodglibLineRequest *self) +{ + self->handle = NULL; + self->event_buf = NULL; + self->edge_event_src = NULL; + self->released = FALSE; +} + +void gpiodglib_line_request_release(GpiodglibLineRequest *self) +{ + g_assert(self); + + g_clear_pointer(&self->edge_event_src, g_source_unref); + gpiod_line_request_release(self->handle); + self->released = TRUE; +} + +gboolean gpiodglib_line_request_is_released(GpiodglibLineRequest *self) +{ + g_assert(self); + + return self->released; +} + +static void set_err_request_released(GError **err) +{ + g_set_error(err, GPIODGLIB_ERROR, GPIODGLIB_ERR_REQUEST_RELEASED, + "line request was released and cannot be used"); +} + +gchar *gpiodglib_line_request_dup_chip_name(GpiodglibLineRequest *self) +{ + return _gpiodglib_dup_prop_string(G_OBJECT(self), "chip-name"); +} + +GArray *gpiodglib_line_request_get_requested_offsets(GpiodglibLineRequest *self) +{ + return _gpiodglib_get_prop_boxed_array(G_OBJECT(self), + "requested-offsets"); +} + +gboolean gpiodglib_line_request_reconfigure_lines(GpiodglibLineRequest *self, + GpiodglibLineConfig *config, + GError **err) +{ + struct gpiod_line_config *config_handle; + gint ret; + + g_assert(self && self->handle); + + if (gpiodglib_line_request_is_released(self)) { + set_err_request_released(err); + return FALSE; + } + + if (!config) { + g_set_error(err, GPIODGLIB_ERROR, GPIODGLIB_ERR_INVAL, + "line-config is required to reconfigure lines"); + return FALSE; + } + + config_handle = _gpiodglib_line_config_get_handle(config); + + ret = gpiod_line_request_reconfigure_lines(self->handle, config_handle); + if (ret) { + _gpiodglib_set_error_from_errno(err, + "failed to reconfigure lines"); + return FALSE; + } + + return TRUE; +} + +gboolean +gpiodglib_line_request_get_value(GpiodglibLineRequest *self, guint offset, + GpiodglibLineValue *value, GError **err) +{ + enum gpiod_line_value val; + + g_assert(self && self->handle); + + if (gpiodglib_line_request_is_released(self)) { + set_err_request_released(err); + return FALSE; + } + + val = gpiod_line_request_get_value(self->handle, offset); + if (val == GPIOD_LINE_VALUE_ERROR) { + _gpiodglib_set_error_from_errno(err, + "failed to get line value for offset %u", offset); + return FALSE; + } + + *value = _gpiodglib_line_value_from_library(val); + return TRUE; +} + +gboolean gpiodglib_line_request_get_values_subset(GpiodglibLineRequest *self, + const GArray *offsets, + GArray **values, GError **err) +{ + guint i; + int ret; + + g_assert(self && self->handle); + + if (gpiodglib_line_request_is_released(self)) { + set_err_request_released(err); + return FALSE; + } + + if (!offsets || !values) { + g_set_error(err, GPIODGLIB_ERROR, GPIODGLIB_ERR_INVAL, + "offsets and values must not be NULL"); + return FALSE; + } + + ret = gpiod_line_request_get_values_subset(self->handle, offsets->len, + (const unsigned int *)offsets->data, + self->val_buf); + if (ret) { + _gpiodglib_set_error_from_errno(err, "failed to read line values"); + return FALSE; + } + + if (!(*values)) { + *values = g_array_sized_new(FALSE, TRUE, + sizeof(GpiodglibLineValue), + offsets->len); + } + + g_array_set_size(*values, offsets->len); + + for (i = 0; i < offsets->len; i++) { + GpiodglibLineValue *val = &g_array_index(*values, + GpiodglibLineValue, i); + *val = _gpiodglib_line_value_from_library(self->val_buf[i]); + } + + return TRUE; +} + +gboolean gpiodglib_line_request_get_values(GpiodglibLineRequest *self, + GArray **values, GError **err) +{ + g_autoptr(GArray) offsets = NULL; + + offsets = gpiodglib_line_request_get_requested_offsets(self); + + return gpiodglib_line_request_get_values_subset(self, offsets, + values, err); +} + +gboolean gpiodglib_line_request_set_value(GpiodglibLineRequest *self, + guint offset, + GpiodglibLineValue value, + GError **err) +{ + int ret; + + g_assert(self && self->handle); + + if (gpiodglib_line_request_is_released(self)) { + set_err_request_released(err); + return FALSE; + } + + ret = gpiod_line_request_set_value(self->handle, offset, + _gpiodglib_line_value_to_library(value)); + if (ret) { + _gpiodglib_set_error_from_errno(err, + "failed to set line value for offset: %u", offset); + return FALSE; + } + + return TRUE; +} + +gboolean gpiodglib_line_request_set_values_subset(GpiodglibLineRequest *self, + const GArray *offsets, + const GArray *values, + GError **err) +{ + guint i; + int ret; + + g_assert(self && self->handle); + + if (gpiodglib_line_request_is_released(self)) { + set_err_request_released(err); + return FALSE; + } + + if (!offsets || !values) { + g_set_error(err, GPIODGLIB_ERROR, GPIODGLIB_ERR_INVAL, + "offsets and values must not be NULL"); + return FALSE; + } + + if (offsets->len != values->len) { + g_set_error(err, GPIODGLIB_ERROR, GPIODGLIB_ERR_INVAL, + "offsets and values must have the sme size"); + return FALSE; + } + + for (i = 0; i < values->len; i++) + self->val_buf[i] = _gpiodglib_line_value_to_library( + g_array_index(values, + GpiodglibLineValue, i)); + + ret = gpiod_line_request_set_values_subset(self->handle, + offsets->len, + (unsigned int *)offsets->data, + self->val_buf); + if (ret) { + _gpiodglib_set_error_from_errno(err, + "failed to set line values"); + return FALSE; + } + + return TRUE; +} + +gboolean gpiodglib_line_request_set_values(GpiodglibLineRequest *self, + GArray *values, GError **err) +{ + g_autoptr(GArray) offsets = NULL; + + offsets = gpiodglib_line_request_get_requested_offsets(self); + + return gpiodglib_line_request_set_values_subset(self, offsets, + values, err); +} + +GpiodglibLineRequest * +_gpiodglib_line_request_new(struct gpiod_line_request *handle) +{ + g_autoptr(GIOChannel) channel = NULL; + GpiodglibLineRequest *req; + gsize num_lines; + + req = GPIODGLIB_LINE_REQUEST_OBJ( + g_object_new(GPIODGLIB_LINE_REQUEST_TYPE, NULL)); + req->handle = handle; + + req->event_buf = gpiod_edge_event_buffer_new(event_buf_size); + if (!req->event_buf) + g_error("failed to allocate the edge event buffer"); + + channel = g_io_channel_unix_new( + gpiod_line_request_get_fd(req->handle)); + req->edge_event_src = g_io_create_watch(channel, G_IO_IN); + g_source_set_callback( + req->edge_event_src, + G_SOURCE_FUNC(gpiodglib_line_request_on_edge_event), + req, NULL); + req->edge_event_src_id = g_source_attach(req->edge_event_src, NULL); + + num_lines = gpiod_line_request_get_num_requested_lines(req->handle); + req->val_buf = g_malloc0(sizeof(enum gpiod_line_value) * num_lines); + + + return req; +} diff --git a/bindings/glib/line-settings.c b/bindings/glib/line-settings.c new file mode 100644 index 0000000..2d7d52a --- /dev/null +++ b/bindings/glib/line-settings.c @@ -0,0 +1,408 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +// SPDX-FileCopyrightText: 2023-2024 Bartosz Golaszewski + +#include +#include + +#include "internal.h" + +/** + * GpiodglibLineSettings: + * + * Line settings object contains a set of line properties that can be used + * when requesting lines or reconfiguring an existing request. + */ +struct _GpiodglibLineSettings { + GObject parent_instance; + struct gpiod_line_settings *handle; +}; + +typedef enum { + GPIODGLIB_LINE_SETTINGS_PROP_DIRECTION = 1, + GPIODGLIB_LINE_SETTINGS_PROP_EDGE_DETECTION, + GPIODGLIB_LINE_SETTINGS_PROP_BIAS, + GPIODGLIB_LINE_SETTINGS_PROP_DRIVE, + GPIODGLIB_LINE_SETTINGS_PROP_ACTIVE_LOW, + GPIODGLIB_LINE_SETTINGS_PROP_DEBOUNCE_PERIOD_US, + GPIODGLIB_LINE_SETTINGS_PROP_EVENT_CLOCK, + GPIODGLIB_LINE_SETTINGS_PROP_OUTPUT_VALUE, +} GpiodglibLineSettingsProp; + +G_DEFINE_TYPE(GpiodglibLineSettings, gpiodglib_line_settings, G_TYPE_OBJECT); + +static void gpiodglib_line_settings_init_handle(GpiodglibLineSettings *self) +{ + if (!self->handle) { + self->handle = gpiod_line_settings_new(); + if (!self->handle) + /* The only possible error is ENOMEM. */ + g_error("Failed to allocate memory for the line-settings object."); + } +} + +static void gpiodglib_line_settings_get_property(GObject *obj, guint prop_id, + GValue *val, GParamSpec *pspec) +{ + GpiodglibLineSettings *self = GPIODGLIB_LINE_SETTINGS_OBJ(obj); + + gpiodglib_line_settings_init_handle(self); + + switch ((GpiodglibLineSettingsProp)prop_id) { + case GPIODGLIB_LINE_SETTINGS_PROP_DIRECTION: + g_value_set_enum(val, + _gpiodglib_line_direction_from_library( + gpiod_line_settings_get_direction( + self->handle), TRUE)); + break; + case GPIODGLIB_LINE_SETTINGS_PROP_EDGE_DETECTION: + g_value_set_enum(val, + _gpiodglib_line_edge_from_library( + gpiod_line_settings_get_edge_detection( + self->handle))); + break; + case GPIODGLIB_LINE_SETTINGS_PROP_BIAS: + g_value_set_enum(val, + _gpiodglib_line_bias_from_library( + gpiod_line_settings_get_bias(self->handle), + TRUE)); + break; + case GPIODGLIB_LINE_SETTINGS_PROP_DRIVE: + g_value_set_enum(val, + _gpiodglib_line_drive_from_library( + gpiod_line_settings_get_drive(self->handle))); + break; + case GPIODGLIB_LINE_SETTINGS_PROP_ACTIVE_LOW: + g_value_set_boolean(val, + gpiod_line_settings_get_active_low(self->handle)); + break; + case GPIODGLIB_LINE_SETTINGS_PROP_DEBOUNCE_PERIOD_US: + g_value_set_int64(val, + gpiod_line_settings_get_debounce_period_us( + self->handle)); + break; + case GPIODGLIB_LINE_SETTINGS_PROP_EVENT_CLOCK: + g_value_set_enum(val, + _gpiodglib_line_clock_from_library( + gpiod_line_settings_get_event_clock( + self->handle))); + break; + case GPIODGLIB_LINE_SETTINGS_PROP_OUTPUT_VALUE: + g_value_set_enum(val, + _gpiodglib_line_value_from_library( + gpiod_line_settings_get_output_value( + self->handle))); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec); + } +} + +static void gpiodglib_line_settings_set_property(GObject *obj, guint prop_id, + const GValue *val, + GParamSpec *pspec) +{ + GpiodglibLineSettings *self = GPIODGLIB_LINE_SETTINGS_OBJ(obj); + + gpiodglib_line_settings_init_handle(self); + + switch ((GpiodglibLineSettingsProp)prop_id) { + case GPIODGLIB_LINE_SETTINGS_PROP_DIRECTION: + gpiod_line_settings_set_direction(self->handle, + _gpiodglib_line_direction_to_library( + g_value_get_enum(val))); + break; + case GPIODGLIB_LINE_SETTINGS_PROP_EDGE_DETECTION: + gpiod_line_settings_set_edge_detection(self->handle, + _gpiodglib_line_edge_to_library(g_value_get_enum(val))); + break; + case GPIODGLIB_LINE_SETTINGS_PROP_BIAS: + gpiod_line_settings_set_bias(self->handle, + _gpiodglib_line_bias_to_library(g_value_get_enum(val))); + break; + case GPIODGLIB_LINE_SETTINGS_PROP_DRIVE: + gpiod_line_settings_set_drive(self->handle, + _gpiodglib_line_drive_to_library(g_value_get_enum(val))); + break; + case GPIODGLIB_LINE_SETTINGS_PROP_ACTIVE_LOW: + gpiod_line_settings_set_active_low(self->handle, + g_value_get_boolean(val)); + break; + case GPIODGLIB_LINE_SETTINGS_PROP_DEBOUNCE_PERIOD_US: + gpiod_line_settings_set_debounce_period_us(self->handle, + g_value_get_int64(val)); + break; + case GPIODGLIB_LINE_SETTINGS_PROP_EVENT_CLOCK: + gpiod_line_settings_set_event_clock(self->handle, + _gpiodglib_line_clock_to_library(g_value_get_enum(val))); + break; + case GPIODGLIB_LINE_SETTINGS_PROP_OUTPUT_VALUE: + gpiod_line_settings_set_output_value(self->handle, + _gpiodglib_line_value_to_library(g_value_get_enum(val))); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec); + } +} + +static void gpiodglib_line_settings_finalize(GObject *obj) +{ + GpiodglibLineSettings *self = GPIODGLIB_LINE_SETTINGS_OBJ(obj); + + g_clear_pointer(&self->handle, gpiod_line_settings_free); + + G_OBJECT_CLASS(gpiodglib_line_settings_parent_class)->finalize(obj); +} + +static void gpiodglib_line_settings_class_init( + GpiodglibLineSettingsClass *line_settings_class) +{ + GObjectClass *class = G_OBJECT_CLASS(line_settings_class); + + class->set_property = gpiodglib_line_settings_set_property; + class->get_property = gpiodglib_line_settings_get_property; + class->finalize = gpiodglib_line_settings_finalize; + + /** + * GpiodglibLineSettings:direction + * + * Line direction setting. + */ + g_object_class_install_property(class, + GPIODGLIB_LINE_SETTINGS_PROP_DIRECTION, + g_param_spec_enum("direction", "Direction", + "Line direction setting.", + GPIODGLIB_LINE_DIRECTION_TYPE, + GPIODGLIB_LINE_DIRECTION_AS_IS, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GpiodglibLineSettings:edge-detection + * + * Line edge detection setting. + */ + g_object_class_install_property(class, + GPIODGLIB_LINE_SETTINGS_PROP_EDGE_DETECTION, + g_param_spec_enum("edge-detection", "Edge Detection", + "Line edge detection setting.", + GPIODGLIB_LINE_EDGE_TYPE, + GPIODGLIB_LINE_EDGE_NONE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GpiodglibLineSettings:bias + * + * Line bias setting. + */ + g_object_class_install_property(class, + GPIODGLIB_LINE_SETTINGS_PROP_BIAS, + g_param_spec_enum("bias", "Bias", + "Line bias setting.", + GPIODGLIB_LINE_BIAS_TYPE, + GPIODGLIB_LINE_BIAS_AS_IS, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GpiodglibLineSettings:drive + * + * Line drive setting. + */ + g_object_class_install_property(class, + GPIODGLIB_LINE_SETTINGS_PROP_DRIVE, + g_param_spec_enum("drive", "Drive", + "Line drive setting.", + GPIODGLIB_LINE_DRIVE_TYPE, + GPIODGLIB_LINE_DRIVE_PUSH_PULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GpiodglibLineSettings:active-low + * + * Line active-low settings. + */ + g_object_class_install_property(class, + GPIODGLIB_LINE_SETTINGS_PROP_ACTIVE_LOW, + g_param_spec_boolean("active-low", "Active-Low", + "Line active-low settings.", + FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GpiodglibLineSettings:debounce-period-us + * + * Line debounce period (expressed in microseconds). + */ + g_object_class_install_property(class, + GPIODGLIB_LINE_SETTINGS_PROP_DEBOUNCE_PERIOD_US, + g_param_spec_int64("debounce-period-us", + "Debounce Period (in microseconds)", + "Line debounce period (expressed in microseconds).", + 0, G_MAXINT64, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GpiodglibLineSettings:event-clock + * + * Clock used to timestamp edge events. + */ + g_object_class_install_property(class, + GPIODGLIB_LINE_SETTINGS_PROP_EVENT_CLOCK, + g_param_spec_enum("event-clock", "Event Clock", + "Clock used to timestamp edge events.", + GPIODGLIB_LINE_CLOCK_TYPE, + GPIODGLIB_LINE_CLOCK_MONOTONIC, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GpiodglibLineSettings:output-value + * + * Line output value. + */ + g_object_class_install_property(class, + GPIODGLIB_LINE_SETTINGS_PROP_OUTPUT_VALUE, + g_param_spec_enum("output-value", "Output Value", + "Line output value.", + GPIODGLIB_LINE_VALUE_TYPE, + GPIODGLIB_LINE_VALUE_INACTIVE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +} + +static void gpiodglib_line_settings_init(GpiodglibLineSettings *self) +{ + self->handle = NULL; +} + +GpiodglibLineSettings *gpiodglib_line_settings_new(const gchar *first_prop, ...) +{ + GpiodglibLineSettings *settings; + va_list va; + + va_start(va, first_prop); + settings = GPIODGLIB_LINE_SETTINGS_OBJ( + g_object_new_valist(GPIODGLIB_LINE_SETTINGS_TYPE, + first_prop, va)); + va_end(va); + + return settings; +} + +void gpiodglib_line_settings_reset(GpiodglibLineSettings *self) +{ + g_assert(self); + + if (self->handle) + gpiod_line_settings_reset(self->handle); +} + +void gpiodglib_line_settings_set_direction(GpiodglibLineSettings *self, + GpiodglibLineDirection direction) +{ + _gpiodglib_set_prop_enum(G_OBJECT(self), "direction", direction); +} + +GpiodglibLineDirection +gpiodglib_line_settings_get_direction(GpiodglibLineSettings *self) +{ + return _gpiodglib_get_prop_enum(G_OBJECT(self), "direction"); +} + +void gpiodglib_line_settings_set_edge_detection(GpiodglibLineSettings *self, + GpiodglibLineEdge edge) +{ + _gpiodglib_set_prop_enum(G_OBJECT(self), "edge-detection", edge); +} + +GpiodglibLineEdge +gpiodglib_line_settings_get_edge_detection(GpiodglibLineSettings *self) +{ + return _gpiodglib_get_prop_enum(G_OBJECT(self), "edge-detection"); +} + +void gpiodglib_line_settings_set_bias(GpiodglibLineSettings *self, + GpiodglibLineBias bias) +{ + _gpiodglib_set_prop_enum(G_OBJECT(self), "bias", bias); +} + +GpiodglibLineBias gpiodglib_line_settings_get_bias(GpiodglibLineSettings *self) +{ + return _gpiodglib_get_prop_enum(G_OBJECT(self), "bias"); +} + +void gpiodglib_line_settings_set_drive(GpiodglibLineSettings *self, + GpiodglibLineDrive drive) +{ + _gpiodglib_set_prop_enum(G_OBJECT(self), "drive", drive); +} + +GpiodglibLineDrive +gpiodglib_line_settings_get_drive(GpiodglibLineSettings *self) +{ + return _gpiodglib_get_prop_enum(G_OBJECT(self), "drive"); +} + +void gpiodglib_line_settings_set_active_low(GpiodglibLineSettings *self, + gboolean active_low) +{ + _gpiodglib_set_prop_bool(G_OBJECT(self), "active-low", active_low); +} + +gboolean gpiodglib_line_settings_get_active_low(GpiodglibLineSettings *self) +{ + return _gpiodglib_get_prop_bool(G_OBJECT(self), "active-low"); +} + +void gpiodglib_line_settings_set_debounce_period_us(GpiodglibLineSettings *self, + GTimeSpan period) +{ + _gpiodglib_set_prop_timespan(G_OBJECT(self), + "debounce-period-us", period); +} + +GTimeSpan +gpiodglib_line_settings_get_debounce_period_us(GpiodglibLineSettings *self) +{ + return _gpiodglib_get_prop_timespan(G_OBJECT(self), + "debounce-period-us"); +} + +void gpiodglib_line_settings_set_event_clock(GpiodglibLineSettings *self, + GpiodglibLineClock event_clock) +{ + _gpiodglib_set_prop_enum(G_OBJECT(self), "event-clock", event_clock); +} + +GpiodglibLineClock +gpiodglib_line_settings_get_event_clock(GpiodglibLineSettings *self) +{ + return _gpiodglib_get_prop_enum(G_OBJECT(self), "event-clock"); +} + +void gpiodglib_line_settings_set_output_value(GpiodglibLineSettings *self, + GpiodglibLineValue value) +{ + _gpiodglib_set_prop_enum(G_OBJECT(self), "output-value", value); +} + +GpiodglibLineValue +gpiodglib_line_settings_get_output_value(GpiodglibLineSettings *self) +{ + return _gpiodglib_get_prop_enum(G_OBJECT(self), "output-value"); +} + +struct gpiod_line_settings * +_gpiodglib_line_settings_get_handle(GpiodglibLineSettings *settings) +{ + return settings->handle; +} + +GpiodglibLineSettings * +_gpiodglib_line_settings_new(struct gpiod_line_settings *handle) +{ + GpiodglibLineSettings *settings; + + settings = GPIODGLIB_LINE_SETTINGS_OBJ( + g_object_new(GPIODGLIB_LINE_SETTINGS_TYPE, NULL)); + gpiod_line_settings_free(settings->handle); + settings->handle = handle; + + return settings; +} diff --git a/bindings/glib/misc.c b/bindings/glib/misc.c new file mode 100644 index 0000000..d0563bd --- /dev/null +++ b/bindings/glib/misc.c @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +// SPDX-FileCopyrightText: 2022-2024 Bartosz Golaszewski + +#include +#include + +gboolean gpiodglib_is_gpiochip_device(const gchar *path) +{ + g_assert(path); + + return gpiod_is_gpiochip_device(path); +} + +const gchar *gpiodglib_api_version(void) +{ + return gpiod_api_version(); +} diff --git a/bindings/glib/request-config.c b/bindings/glib/request-config.c new file mode 100644 index 0000000..65ce4c3 --- /dev/null +++ b/bindings/glib/request-config.c @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +// SPDX-FileCopyrightText: 2023-2024 Bartosz Golaszewski + +#include +#include + +#include "internal.h" + +/** + * GpiodglibRequestConfig: + * + * Request config objects are used to pass a set of options to the kernel at + * the time of the line request. + */ +struct _GpiodglibRequestConfig { + GObject parent_instance; + struct gpiod_request_config *handle; +}; + +typedef enum { + GPIODGLIB_REQUEST_CONFIG_PROP_CONSUMER = 1, + GPIODGLIB_REQUEST_CONFIG_PROP_EVENT_BUFFER_SIZE, +} GpiodglibRequestConfigProp; + +G_DEFINE_TYPE(GpiodglibRequestConfig, gpiodglib_request_config, G_TYPE_OBJECT); + +static void gpiodglib_request_config_get_property(GObject *obj, guint prop_id, + GValue *val, + GParamSpec *pspec) +{ + GpiodglibRequestConfig *self = GPIODGLIB_REQUEST_CONFIG_OBJ(obj); + + switch ((GpiodglibRequestConfigProp)prop_id) { + case GPIODGLIB_REQUEST_CONFIG_PROP_CONSUMER: + g_value_set_string(val, + gpiod_request_config_get_consumer(self->handle)); + break; + case GPIODGLIB_REQUEST_CONFIG_PROP_EVENT_BUFFER_SIZE: + g_value_set_uint(val, + gpiod_request_config_get_event_buffer_size( + self->handle)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec); + } +} + +static void gpiodglib_request_config_set_property(GObject *obj, guint prop_id, + const GValue *val, + GParamSpec *pspec) +{ + GpiodglibRequestConfig *self = GPIODGLIB_REQUEST_CONFIG_OBJ(obj); + + switch ((GpiodglibRequestConfigProp)prop_id) { + case GPIODGLIB_REQUEST_CONFIG_PROP_CONSUMER: + gpiod_request_config_set_consumer(self->handle, + g_value_get_string(val)); + break; + case GPIODGLIB_REQUEST_CONFIG_PROP_EVENT_BUFFER_SIZE: + gpiod_request_config_set_event_buffer_size(self->handle, + g_value_get_uint(val)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec); + } +} + +static void gpiodglib_request_config_finalize(GObject *obj) +{ + GpiodglibRequestConfig *self = GPIODGLIB_REQUEST_CONFIG_OBJ(obj); + + g_clear_pointer(&self->handle, gpiod_request_config_free); + + G_OBJECT_CLASS(gpiodglib_request_config_parent_class)->finalize(obj); +} + +static void gpiodglib_request_config_class_init( + GpiodglibRequestConfigClass *request_config_class) +{ + GObjectClass *class = G_OBJECT_CLASS(request_config_class); + + class->set_property = gpiodglib_request_config_set_property; + class->get_property = gpiodglib_request_config_get_property; + class->finalize = gpiodglib_request_config_finalize; + + /** + * GpiodglibRequestConfig:consumer: + * + * Name of the request consumer. + */ + g_object_class_install_property(class, + GPIODGLIB_REQUEST_CONFIG_PROP_CONSUMER, + g_param_spec_string("consumer", "Consumer", + "Name of the request consumer.", + NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GpiodglibRequestConfig:event-buffer-size: + * + * Size of the kernel event buffer size of the request. + */ + g_object_class_install_property(class, + GPIODGLIB_REQUEST_CONFIG_PROP_EVENT_BUFFER_SIZE, + g_param_spec_uint("event-buffer-size", "Event Buffer Size", + "Size of the kernel event buffer size of the request.", + 0, G_MAXUINT, 64, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +} + +static void gpiodglib_request_config_init(GpiodglibRequestConfig *self) +{ + self->handle = gpiod_request_config_new(); + if (!self->handle) + /* The only possible error is ENOMEM. */ + g_error("Failed to allocate memory for the request-config object."); +} + +GpiodglibRequestConfig * +gpiodglib_request_config_new(const gchar *first_prop, ...) +{ + GpiodglibRequestConfig *settings; + va_list va; + + va_start(va, first_prop); + settings = GPIODGLIB_REQUEST_CONFIG_OBJ( + g_object_new_valist(GPIODGLIB_REQUEST_CONFIG_TYPE, + first_prop, va)); + va_end(va); + + return settings; +} + +void gpiodglib_request_config_set_consumer(GpiodglibRequestConfig *self, + const gchar *consumer) +{ + g_assert(self); + + _gpiodglib_set_prop_string(G_OBJECT(self), "consumer", consumer); +} + +gchar *gpiodglib_request_config_dup_consumer(GpiodglibRequestConfig *self) +{ + g_assert(self); + + return _gpiodglib_dup_prop_string(G_OBJECT(self), "consumer"); +} + +void +gpiodglib_request_config_set_event_buffer_size(GpiodglibRequestConfig *self, + guint event_buffer_size) +{ + g_assert(self); + + _gpiodglib_set_prop_uint(G_OBJECT(self), "event-buffer-size", + event_buffer_size); +} + +guint +gpiodglib_request_config_get_event_buffer_size(GpiodglibRequestConfig *self) +{ + g_assert(self); + + return _gpiodglib_get_prop_uint(G_OBJECT(self), "event-buffer-size"); +} + +struct gpiod_request_config * +_gpiodglib_request_config_get_handle(GpiodglibRequestConfig *req_cfg) +{ + return req_cfg->handle; +} From patchwork Wed Aug 7 09:10:37 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Bartosz Golaszewski X-Patchwork-Id: 818163 Received: from mail-wm1-f46.google.com (mail-wm1-f46.google.com [209.85.128.46]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9A3A61CB30B for ; Wed, 7 Aug 2024 09:10:53 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.46 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723021856; cv=none; b=TPAyBlOr8xYPDGAEc5cidOCMPOMNn6Hqe8Sh/tuC99QnPzhHALs/lc39F6zw34PrEFv1VWxRqgv6SoVkLSXTwXBJdwIL7OMqFhS1penwulth2HDIo26E3Wqm1yfD/eIsezC90/81tG2BsfJaa/3uulCPHxOhZzhUrSZveu6Vw9s= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723021856; c=relaxed/simple; bh=0wcMfzQFhzlij302GMY4WZ9ixIi9No6G7LpkeBET8qU=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=FfaLh56hqVBzUPOMr1GnV+NyO8zBIqdESdwtJ7AyVleshiQLhFhfzXYtPR6bVxoshwsiiZmPEuLF+5oUrFxlz1v1mU0PpVeYdJyOeDFKE9f7PNxS6tZpAOPLfVt63jAZi1gFUA3/EvoR9q9nf3dqMhMx8G5FeWDDzkk2ECsHChM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=bgdev.pl; spf=none smtp.mailfrom=bgdev.pl; dkim=pass (2048-bit key) header.d=bgdev-pl.20230601.gappssmtp.com header.i=@bgdev-pl.20230601.gappssmtp.com header.b=iKI0MqtL; arc=none smtp.client-ip=209.85.128.46 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=bgdev.pl Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=bgdev.pl Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=bgdev-pl.20230601.gappssmtp.com header.i=@bgdev-pl.20230601.gappssmtp.com header.b="iKI0MqtL" Received: by mail-wm1-f46.google.com with SMTP id 5b1f17b1804b1-428085a3ad1so11288065e9.1 for ; Wed, 07 Aug 2024 02:10:53 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bgdev-pl.20230601.gappssmtp.com; s=20230601; t=1723021852; x=1723626652; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=Qd6HF4U7ga46pzIv7o3cy2nIOXwA07Q8xGTDHh89CXo=; b=iKI0MqtLrB938Az06AYJ4r6FMGv2BIzixDm/ExrpDoKcT/PLx7cr1C4k/08ec8u2Qj TOuwLot8UtFGNAZYLvwTnUFKZr4P0Boq4DtSNNzT8mcSw8OvwWYAqv3j+FK1retKgY3E bRn6+p4c4OOtIUzuEJ19HIpOSRvQH0bnPf6KccR26j8TLisZSsZmzrCQXSyEAK3IONA6 +pVKJpPar28R5W1coePD3NbNqdMu+3BoELk8jwyxZypk7tgf598eL6foFi1iqojfZsHe NkvNlIHi2zp/GnYm9eKBfSL4lSYWd5QWV6+kUrIuZKDts0IeCJZsbkGdRIf4LpZDeP7Z qOJw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1723021852; x=1723626652; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=Qd6HF4U7ga46pzIv7o3cy2nIOXwA07Q8xGTDHh89CXo=; b=N3v7EYtnqRwPhygyKkSSwicn2nDtwOWNSwoY8Ctvo5jti6AYKKLBDwXtCtGiJzG7OH Q+RcAswNQt6ZRqPx57IlHGequhBPQdEhr8yfTseJSdFs8WOCoLwe65OMnint2p9Azoft QggrttEfn2nOAjFRJQZYq2jW3xDsM/8bTynIioPbtbZIHuEg1oMeqJ8hVF/4n+/L2H03 hLEUOPG5IWQ4RXGNpaJyl6JCZQ1AVS0ylk3Dw0MXBtKol0ePxoprs1weEGDHNzHAeAzT 2UfiAjUGLjAILiV5VgF/jMN/a1aVL8OfOMGsxrtPUQ/S6Jb5zhVaSk0/w44T9YMi+Ay1 cPkg== X-Gm-Message-State: AOJu0YwiA6Mxc4QL3T4HvEKhrPXdTCLVn7vYTLceBRjMsTErVMdKJHee FQYfWB4rYAmcwyaJs926Mi1xni5y+qtWtJZNUJlHUAUMqVqefTq7CTbRajtBiLACIKxlogC4S+L InH0= X-Google-Smtp-Source: AGHT+IGk45s5P5pn2GziuVDplE58WRqksa4unFoi8yOsIDD0CevPulfL2ViLaqfUjphZe7TIR7R4hA== X-Received: by 2002:a05:600c:4fc6:b0:426:5ddf:fd22 with SMTP id 5b1f17b1804b1-428e6ae20d4mr118342755e9.6.1723021851792; Wed, 07 Aug 2024 02:10:51 -0700 (PDT) Received: from [127.0.1.1] ([2a01:cb1d:dc:7e00:a3e7:be71:fe4f:c360]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-429059cd2b3sm18936355e9.44.2024.08.07.02.10.50 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 07 Aug 2024 02:10:51 -0700 (PDT) From: Bartosz Golaszewski Date: Wed, 07 Aug 2024 11:10:37 +0200 Subject: [PATCH libgpiod v4 06/18] bindings: glib: add examples Precedence: bulk X-Mailing-List: linux-gpio@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20240807-dbus-v4-6-64ea80169e51@linaro.org> References: <20240807-dbus-v4-0-64ea80169e51@linaro.org> In-Reply-To: <20240807-dbus-v4-0-64ea80169e51@linaro.org> To: Linus Walleij , Kent Gibson , Erik Schilling , Phil Howard , Andy Shevchenko , Viresh Kumar , Dan Carpenter , Philip Withnall Cc: linux-gpio@vger.kernel.org, Bartosz Golaszewski , Alexander Sverdlin X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=31642; i=bartosz.golaszewski@linaro.org; h=from:subject:message-id; bh=Nx2jQK96MCHStiHw0hjoMB4LTQ6/Kzp5NdUqfe+LYVk=; b=owEBbQKS/ZANAwAKARGnLqAUcddyAcsmYgBmszoQ0jWeaUxU7yiKYuArLoZXeQRf29il/8sQq j7Lu8dEFMeJAjMEAAEKAB0WIQQWnetsC8PEYBPSx58Rpy6gFHHXcgUCZrM6EAAKCRARpy6gFHHX cqGuD/43MrLuBUqhpnQMVYHBrB8lj1wapnph6ifkwifSQ8B3TBhGj6f4y37AHlGVjmPVJrY/y5S 6NFtAm7g0jA/L6449l/K17YI4bE4YEmM2b1XLALsQpo8zPvctzkDA86f7t0PMw6CLRe+Sj1DmX2 yzR3T/IpwKlO5cDPRlIpm/7s5WglwXWrN0aDuh5goPz8q24ltrxRNhbmKZ54jsRHkqbhA8jRchH Vp0pLN9lhKch+TFrWsGWFRWJOzbCNx8nrAx4+9DhJwi363z1D3yodwjFIu8ZWUWrhQjKa/eqKAC xLnsI6ONzweHIQz+xXWkyvE0pmuCycT2Nvh9gIMb2AyaM2XCkE/38dSdbpktlWtisyiD+vgR0EW xVoEJYHKwwg/GMjNQWjS+wJq+R1u6eheakEnzXvKrdKZFSt3WZXMp8KEFhbC2bvTnXhfL9CqwbI rBZSKPcxeSl9znweNxq94iDzbFmSvj4dNHw+TqG04L89mq51/bp+kEubzkTEP2tXXRPBY2GiOii ZdFPwedR3ch6TYqhvpJ85gr7EXvo7QzhKgFk1DeYBAB5vkc12RtbB+j3PAd+s3RBE2VlaZzNXbL kEMHqsNWYQiWYYZb2bODaAybvy1wn1ZQxLg0DEW0N6oAFoOT/KVZymgTuQIJTCuLBZpW+/khVJu NJKJcYxqTVdyPvA== X-Developer-Key: i=bartosz.golaszewski@linaro.org; a=openpgp; fpr=169DEB6C0BC3C46013D2C79F11A72EA01471D772 From: Bartosz Golaszewski Add example programs showcasing the usage of GLib bindings to libgpiod. Tested-by: Alexander Sverdlin Signed-off-by: Bartosz Golaszewski --- bindings/glib/examples/find_line_by_name_glib.c | 71 +++++++++++ bindings/glib/examples/get_chip_info_glib.c | 42 +++++++ bindings/glib/examples/get_line_info_glib.c | 80 +++++++++++++ bindings/glib/examples/get_line_value_glib.c | 68 +++++++++++ .../glib/examples/get_multiple_line_values_glib.c | 73 ++++++++++++ .../examples/reconfigure_input_to_output_glib.c | 104 ++++++++++++++++ bindings/glib/examples/toggle_line_value_glib.c | 99 ++++++++++++++++ .../examples/toggle_multiple_line_values_glib.c | 132 +++++++++++++++++++++ bindings/glib/examples/watch_line_info_glib.c | 63 ++++++++++ bindings/glib/examples/watch_line_value_glib.c | 91 ++++++++++++++ .../examples/watch_multiple_edge_rising_glib.c | 95 +++++++++++++++ 11 files changed, 918 insertions(+) diff --git a/bindings/glib/examples/find_line_by_name_glib.c b/bindings/glib/examples/find_line_by_name_glib.c new file mode 100644 index 0000000..ee8766e --- /dev/null +++ b/bindings/glib/examples/find_line_by_name_glib.c @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2023-2024 Bartosz Golaszewski + +/* Minimal example of finding a line with the given name. */ + +#include +#include +#include + +int main(void) +{ + /* Example configuration - customize to suit your situation. */ + static const gchar *const line_name = "GPIO0"; + + g_autoptr(GpiodglibChipInfo) 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(GpiodglibChip) chip = NULL; + g_autofree gchar *path = NULL; + g_autofree gchar *name = NULL; + + path = g_build_filename("/dev", filename, NULL); + if (!gpiodglib_is_gpiochip_device(path)) + continue; + + chip = gpiodglib_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 = gpiodglib_chip_get_line_offset_from_name(chip, line_name, + &offset, &err); + if (!ret) { + g_printerr("Failed to map the line name '%s' to offset: %s\n", + line_name, err->message); + return EXIT_FAILURE; + } + + info = gpiodglib_chip_get_info(chip, &err); + if (!info) { + g_printerr("Failed to get chip info: %s\n", + err->message); + return EXIT_FAILURE; + } + + name = gpiodglib_chip_info_dup_name(info); + + g_print("%s %u\n", name, offset); + } + + g_print("line '%s' not found\n", line_name); + + return EXIT_SUCCESS; +} diff --git a/bindings/glib/examples/get_chip_info_glib.c b/bindings/glib/examples/get_chip_info_glib.c new file mode 100644 index 0000000..ccdf437 --- /dev/null +++ b/bindings/glib/examples/get_chip_info_glib.c @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2023-2024 Bartosz Golaszewski + +/* Minimal example of reading the info for a chip. */ + +#include +#include +#include + +int main(void) +{ + /* Example configuration - customize to suit your situation. */ + static const gchar *const chip_path = "/dev/gpiochip0"; + + g_autoptr(GpiodglibChipInfo) info = NULL; + g_autoptr(GpiodglibChip) chip = NULL; + g_autofree gchar *label = NULL; + g_autofree gchar *name = NULL; + g_autoptr(GError) err = NULL; + + chip = gpiodglib_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 = gpiodglib_chip_get_info(chip, &err); + if (err) { + g_printerr("Failed to retrieve GPIO chip info: %s\n", + err->message); + return EXIT_FAILURE; + } + + name = gpiodglib_chip_info_dup_name(info); + label = gpiodglib_chip_info_dup_label(info); + + g_print("%s [%s] (%u lines)\n", + name, label, gpiodglib_chip_info_get_num_lines(info)); + + return EXIT_SUCCESS; +} diff --git a/bindings/glib/examples/get_line_info_glib.c b/bindings/glib/examples/get_line_info_glib.c new file mode 100644 index 0000000..bd49332 --- /dev/null +++ b/bindings/glib/examples/get_line_info_glib.c @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2022-2024 Bartosz Golaszewski + +/* Minimal example of reading the info for a line. */ + +#include +#include +#include + +static GString *make_flags(GpiodglibLineInfo *info) +{ + g_autofree gchar *drive_str = NULL; + g_autofree gchar *edge_str = NULL; + g_autofree gchar *bias_str = NULL; + GpiodglibLineDrive drive; + GpiodglibLineEdge edge; + GpiodglibLineBias bias; + GString *ret; + + edge = gpiodglib_line_info_get_edge_detection(info); + bias = gpiodglib_line_info_get_bias(info); + drive = gpiodglib_line_info_get_drive(info); + + edge_str = g_enum_to_string(GPIODGLIB_LINE_EDGE_TYPE, edge); + bias_str = g_enum_to_string(GPIODGLIB_LINE_BIAS_TYPE, bias); + drive_str = g_enum_to_string(GPIODGLIB_LINE_DRIVE_TYPE, drive); + + ret = g_string_new(NULL); + g_string_printf(ret, "%s, %s, %s", edge_str, bias_str, drive_str); + g_string_replace(ret, "GPIODGLIB_LINE_", "", 0); + + return ret; +} + +int main(void) +{ + /* Example configuration - customize to suit your situation. */ + static const gchar *const chip_path = "/dev/gpiochip0"; + static const guint line_offset = 4; + + g_autoptr(GpiodglibLineInfo) info = NULL; + g_autoptr(GpiodglibChip) chip = NULL; + g_autofree gchar *consumer = NULL; + GpiodglibLineDirection direction; + g_autoptr(GString) flags = NULL; + g_autofree gchar *name = NULL; + g_autoptr(GError) err = NULL; + gboolean active_low; + + chip = gpiodglib_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 = gpiodglib_chip_get_line_info(chip, line_offset, &err); + if (err) { + g_printerr("Failed to retrieve GPIO line info: %s\n", + err->message); + return EXIT_FAILURE; + } + + name = gpiodglib_line_info_dup_name(info); + consumer = gpiodglib_line_info_dup_consumer(info); + direction = gpiodglib_line_info_get_direction(info); + active_low = gpiodglib_line_info_is_active_low(info); + flags = make_flags(info); + + g_print("\tline: %u %s %s %s %s [%s]\n", + line_offset, + name ?: "unnamed", + consumer ?: "unused", + direction == GPIODGLIB_LINE_DIRECTION_INPUT ? + "input" : "output", + active_low ? "active-low" : "active-high", + flags->str); + + return EXIT_SUCCESS; +} diff --git a/bindings/glib/examples/get_line_value_glib.c b/bindings/glib/examples/get_line_value_glib.c new file mode 100644 index 0000000..660ba7d --- /dev/null +++ b/bindings/glib/examples/get_line_value_glib.c @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2023-2024 Bartosz Golaszewski + +/* Minimal example of reading a single line. */ + +#include +#include +#include + +int main(void) +{ + /* Example configuration - customize to suit your situation. */ + static const gchar *const chip_path = "/dev/gpiochip1"; + static const guint line_offset = 5; + + g_autoptr(GpiodglibRequestConfig) req_cfg = NULL; + g_autoptr(GpiodglibLineSettings) settings = NULL; + g_autoptr(GpiodglibLineRequest) request = NULL; + g_autoptr(GpiodglibLineConfig) line_cfg = NULL; + g_autoptr(GpiodglibChip) chip = NULL; + g_autoptr(GArray) offsets = NULL; + g_autoptr(GError) err = NULL; + guint offset; + gboolean ret; + + chip = gpiodglib_chip_new(chip_path, &err); + if (!chip) { + g_printerr("unable to open %s: %s\n", chip_path, err->message); + return EXIT_FAILURE; + } + + offsets = g_array_new(FALSE, TRUE, sizeof(guint)); + g_array_append_val(offsets, line_offset); + + settings = gpiodglib_line_settings_new("direction", + GPIODGLIB_LINE_DIRECTION_INPUT, + NULL); + + line_cfg = gpiodglib_line_config_new(); + ret = gpiodglib_line_config_add_line_settings(line_cfg, offsets, + settings, &err); + if (!ret) { + g_printerr("failed to add line settings to line config: %s\n", + err->message); + return EXIT_FAILURE; + } + + req_cfg = gpiodglib_request_config_new("consumer", + "get-line-value-glib", + NULL); + + request = gpiodglib_chip_request_lines(chip, req_cfg, line_cfg, &err); + if (!request) { + g_printerr("failed to request lines: %s\n", err->message); + return EXIT_FAILURE; + } + + ret = gpiodglib_line_request_get_value(request, line_offset, + &offset, &err); + if (!ret) { + g_printerr("failed to read line values: %s\n", err->message); + return EXIT_FAILURE; + } + + g_print("%u\n", offset); + + return EXIT_SUCCESS; +} diff --git a/bindings/glib/examples/get_multiple_line_values_glib.c b/bindings/glib/examples/get_multiple_line_values_glib.c new file mode 100644 index 0000000..2b2e547 --- /dev/null +++ b/bindings/glib/examples/get_multiple_line_values_glib.c @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2023-2024 Bartosz Golaszewski + +/* Minimal example of reading multiple lines. */ + +#include +#include +#include + +int main(void) +{ + /* Example configuration - customize to suit your situation. */ + static const gchar *const chip_path = "/dev/gpiochip1"; + static const guint line_offsets[] = { 5, 3, 7 }; + static const gsize num_lines = 3; + + g_autoptr(GpiodglibRequestConfig) req_cfg = NULL; + g_autoptr(GpiodglibLineSettings) settings = NULL; + g_autoptr(GpiodglibLineRequest) request = NULL; + g_autoptr(GpiodglibLineConfig) line_cfg = NULL; + g_autoptr(GpiodglibChip) chip = NULL; + g_autoptr(GArray) offsets = NULL; + g_autoptr(GArray) values = NULL; + g_autoptr(GError) err = NULL; + gboolean ret; + guint i, j; + + offsets = g_array_new(FALSE, TRUE, sizeof(guint)); + for (i = 0; i < num_lines; i++) + g_array_append_val(offsets, line_offsets[i]); + + chip = gpiodglib_chip_new(chip_path, &err); + if (!chip) { + g_printerr("unable to open %s: %s\n", chip_path, err->message); + return EXIT_FAILURE; + } + + settings = gpiodglib_line_settings_new("direction", + GPIODGLIB_LINE_DIRECTION_INPUT, + NULL); + + line_cfg = gpiodglib_line_config_new(); + ret = gpiodglib_line_config_add_line_settings(line_cfg, offsets, + settings, &err); + if (!ret) { + g_printerr("failed to add line settings to line config: %s\n", + err->message); + return EXIT_FAILURE; + } + + req_cfg = gpiodglib_request_config_new("consumer", + "get-multiple-line-values", + NULL); + + request = gpiodglib_chip_request_lines(chip, req_cfg, line_cfg, &err); + if (!request) { + g_printerr("failed to request lines: %s\n", err->message); + return EXIT_FAILURE; + } + + ret = gpiodglib_line_request_get_values_subset(request, offsets, + &values, &err); + if (!ret) { + g_printerr("failed to read line values: %s\n", err->message); + return EXIT_FAILURE; + } + + for (j = 0; j < values->len; j++) + g_print("%d ", g_array_index(values, GpiodglibLineValue, j)); + g_print("\n"); + + return EXIT_SUCCESS; +} diff --git a/bindings/glib/examples/reconfigure_input_to_output_glib.c b/bindings/glib/examples/reconfigure_input_to_output_glib.c new file mode 100644 index 0000000..9254cfb --- /dev/null +++ b/bindings/glib/examples/reconfigure_input_to_output_glib.c @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2023-2024 Bartosz Golaszewski + +/* + * Example of a bi-directional line requested as input and then switched + * to output. + */ + +#include +#include +#include + +int main(void) +{ + /* Example configuration - customize to suit your situation */ + static const gchar *const chip_path = "/dev/gpiochip1"; + static const guint line_offset = 5; + + g_autoptr(GpiodglibRequestConfig) req_cfg = NULL; + g_autoptr(GpiodglibLineSettings) settings = NULL; + g_autoptr(GpiodglibLineRequest) request = NULL; + g_autoptr(GpiodglibLineConfig) line_cfg = NULL; + g_autoptr(GpiodglibChip) chip = NULL; + g_autoptr(GArray) offsets = NULL; + g_autoptr(GError) err = NULL; + GpiodglibLineValue value; + gboolean ret; + + offsets = g_array_new(FALSE, TRUE, sizeof(guint)); + g_array_append_val(offsets, line_offset); + + chip = gpiodglib_chip_new(chip_path, &err); + if (!chip) { + g_printerr("unable to open %s: %s\n", chip_path, err->message); + return EXIT_FAILURE; + } + + settings = gpiodglib_line_settings_new("direction", + GPIODGLIB_LINE_DIRECTION_INPUT, + NULL); + + line_cfg = gpiodglib_line_config_new(); + ret = gpiodglib_line_config_add_line_settings(line_cfg, offsets, + settings, &err); + if (!ret) { + g_printerr("failed to add line settings to line config: %s\n", + err->message); + return EXIT_FAILURE; + } + + req_cfg = gpiodglib_request_config_new("consumer", + "reconfigure-input-to-output", + NULL); + + request = gpiodglib_chip_request_lines(chip, req_cfg, line_cfg, &err); + if (!request) { + g_printerr("failed to request lines: %s\n", err->message); + return EXIT_FAILURE; + } + + /* Read the current line value. */ + ret = gpiodglib_line_request_get_value(request, line_offset, + &value, &err); + if (!ret) { + g_printerr("failed to read line value: %s\n", err->message); + return EXIT_FAILURE; + } + + g_print("%s (input)\n", + value == GPIODGLIB_LINE_VALUE_ACTIVE ? "Active" : "Inactive"); + + /* Switch the line to an output and drive it high. */ + gpiodglib_line_settings_set_direction(settings, + GPIODGLIB_LINE_DIRECTION_OUTPUT); + gpiodglib_line_settings_set_output_value(settings, + GPIODGLIB_LINE_VALUE_ACTIVE); + ret = gpiodglib_line_config_add_line_settings(line_cfg, offsets, + settings, &err); + if (!ret) { + g_printerr("failed to add line settings to line config: %s\n", + err->message); + return EXIT_FAILURE; + } + + /* Reconfigure lines. */ + ret = gpiodglib_line_request_reconfigure_lines(request, line_cfg, &err); + if (!ret) { + g_printerr("failed to reconfigure lines: %s\n", err->message); + return EXIT_FAILURE; + } + + /* Report the current driven value. */ + ret = gpiodglib_line_request_get_value(request, line_offset, + &value, &err); + if (!ret) { + g_printerr("failed to read line value: %s\n", err->message); + return EXIT_FAILURE; + } + + g_print("%s (output)\n", + value == GPIODGLIB_LINE_VALUE_ACTIVE ? "Active" : "Inactive"); + + return EXIT_SUCCESS; +} diff --git a/bindings/glib/examples/toggle_line_value_glib.c b/bindings/glib/examples/toggle_line_value_glib.c new file mode 100644 index 0000000..e9e0e41 --- /dev/null +++ b/bindings/glib/examples/toggle_line_value_glib.c @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2023-2024 Bartosz Golaszewski + +/* Minimal example of periodically toggling a single line. */ + +#include +#include +#include + +typedef struct { + GpiodglibLineRequest *request; + guint line_offset; + GpiodglibLineValue value; +} ToggleData; + +static gboolean toggle_line(gpointer user_data) +{ + ToggleData *data = user_data; + g_autoptr(GError) err = NULL; + gboolean ret; + + data->value = data->value == GPIODGLIB_LINE_VALUE_ACTIVE ? + GPIODGLIB_LINE_VALUE_INACTIVE : + GPIODGLIB_LINE_VALUE_ACTIVE; + + ret = gpiodglib_line_request_set_value(data->request, data->line_offset, + data->value, &err); + if (!ret) { + g_printerr("failed to set line value: %s\n", err->message); + exit(EXIT_FAILURE); + } + + g_print("%u=%s\n", + data->line_offset, + data->value == GPIODGLIB_LINE_VALUE_ACTIVE ? + "active" : "inactive"); + + return G_SOURCE_CONTINUE; +} + +int main(void) +{ + /* Example configuration - customize to suit your situation. */ + static const gchar *const chip_path = "/dev/gpiochip1"; + static const guint line_offset = 5; + + g_autoptr(GpiodglibRequestConfig) req_cfg = NULL; + g_autoptr(GpiodglibLineSettings) settings = NULL; + g_autoptr(GpiodglibLineRequest) request = NULL; + g_autoptr(GpiodglibLineConfig) line_cfg = NULL; + g_autoptr(GpiodglibChip) chip = NULL; + g_autoptr(GArray) offsets = NULL; + g_autoptr(GMainLoop) loop = NULL; + g_autoptr(GError) err = NULL; + ToggleData data; + gboolean ret; + + offsets = g_array_new(FALSE, TRUE, sizeof(guint)); + g_array_append_val(offsets, line_offset); + + chip = gpiodglib_chip_new(chip_path, &err); + if (!chip) { + g_printerr("unable to open %s: %s\n", chip_path, err->message); + return EXIT_FAILURE; + } + + settings = gpiodglib_line_settings_new("direction", + GPIODGLIB_LINE_DIRECTION_OUTPUT, + NULL); + line_cfg = gpiodglib_line_config_new(); + ret = gpiodglib_line_config_add_line_settings(line_cfg, offsets, + settings, &err); + if (!ret) { + g_printerr("failed to add line settings to line config: %s\n", + err->message); + return EXIT_FAILURE; + } + + req_cfg = gpiodglib_request_config_new("consumer", "toggle-line-value", + NULL); + + request = gpiodglib_chip_request_lines(chip, req_cfg, line_cfg, &err); + if (!request) { + g_printerr("failed to request lines: %s\n", err->message); + return EXIT_FAILURE; + } + + data.request = request; + data.line_offset = line_offset; + data.value = GPIODGLIB_LINE_VALUE_INACTIVE; + + loop = g_main_loop_new(NULL, FALSE); + /* Do the GLib way: add a callback to be invoked from the main loop. */ + g_timeout_add_seconds(1, toggle_line, &data); + + g_main_loop_run(loop); + + return EXIT_SUCCESS; +} diff --git a/bindings/glib/examples/toggle_multiple_line_values_glib.c b/bindings/glib/examples/toggle_multiple_line_values_glib.c new file mode 100644 index 0000000..d1b37b3 --- /dev/null +++ b/bindings/glib/examples/toggle_multiple_line_values_glib.c @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2023-2024 Bartosz Golaszewski + +/* Minimal example of periodically toggling multiple lines. */ + +#include +#include +#include + +typedef struct { + GpiodglibLineRequest *request; + GArray *offsets; + GArray *values; +} ToggleData; + +static void toggle_values(GArray *values) +{ + GpiodglibLineValue *value; + guint i; + + for (i = 0; i < values->len; i++) { + value = &g_array_index(values, GpiodglibLineValue, i); + *value = *value == GPIODGLIB_LINE_VALUE_ACTIVE ? + GPIODGLIB_LINE_VALUE_INACTIVE : + GPIODGLIB_LINE_VALUE_ACTIVE; + } +} + +static gboolean toggle_lines(gpointer user_data) +{ + ToggleData *data = user_data; + g_autoptr(GError) err = NULL; + gboolean ret; + guint i; + + toggle_values(data->values); + + ret = gpiodglib_line_request_set_values_subset(data->request, + data->offsets, + data->values, &err); + if (!ret) { + g_printerr("failed to set line values: %s\n", err->message); + exit(EXIT_FAILURE); + } + + for (i = 0; i < data->offsets->len; i++) + g_print("%u=%s ", + g_array_index(data->offsets, guint, i), + g_array_index(data->values, + GpiodglibLineValue, + i) == GPIODGLIB_LINE_VALUE_ACTIVE ? + "active" : "inactive"); + g_print("\n"); + + return G_SOURCE_CONTINUE; +} + +int main(void) +{ + /* Example configuration - customize to suit your situation. */ + static const gchar *const chip_path = "/dev/gpiochip1"; + static const guint line_offsets[] = { 5, 3, 7 }; + static const GpiodglibLineValue line_values[] = { + GPIODGLIB_LINE_VALUE_ACTIVE, + GPIODGLIB_LINE_VALUE_ACTIVE, + GPIODGLIB_LINE_VALUE_INACTIVE + }; + static const gsize num_lines = 3; + + g_autoptr(GpiodglibRequestConfig) req_cfg = NULL; + g_autoptr(GpiodglibLineSettings) settings = NULL; + g_autoptr(GpiodglibLineRequest) request = NULL; + g_autoptr(GpiodglibLineConfig) line_cfg = NULL; + g_autoptr(GpiodglibChip) chip = NULL; + g_autoptr(GArray) offsets = NULL; + g_autoptr(GMainLoop) loop = NULL; + g_autoptr(GArray) values = NULL; + g_autoptr(GError) err = NULL; + ToggleData data; + gboolean ret; + + offsets = g_array_new(FALSE, TRUE, sizeof(guint)); + g_array_append_vals(offsets, line_offsets, num_lines); + + values = g_array_new(FALSE, TRUE, sizeof(GpiodglibLineValue)); + g_array_append_vals(values, line_values, num_lines); + + chip = gpiodglib_chip_new(chip_path, &err); + if (!chip) { + g_printerr("unable to open %s: %s\n", chip_path, err->message); + return EXIT_FAILURE; + } + + settings = gpiodglib_line_settings_new("direction", + GPIODGLIB_LINE_DIRECTION_OUTPUT, + NULL); + line_cfg = gpiodglib_line_config_new(); + ret = gpiodglib_line_config_add_line_settings(line_cfg, offsets, + settings, &err); + if (!ret) { + g_printerr("failed to add line settings to line config: %s\n", + err->message); + return EXIT_FAILURE; + } + + ret = gpiodglib_line_config_set_output_values(line_cfg, values, &err); + if (!ret) { + g_printerr("failed to set output values: %s\n", err->message); + return EXIT_FAILURE; + } + + req_cfg = gpiodglib_request_config_new("consumer", "toggle-line-value", + NULL); + + request = gpiodglib_chip_request_lines(chip, req_cfg, line_cfg, &err); + if (!request) { + g_printerr("failed to request lines: %s\n", err->message); + return EXIT_FAILURE; + } + + data.request = request; + data.offsets = offsets; + data.values = values; + + loop = g_main_loop_new(NULL, FALSE); + /* Do the GLib way: add a callback to be invoked from the main loop. */ + g_timeout_add_seconds(1, toggle_lines, &data); + + g_main_loop_run(loop); + + return EXIT_SUCCESS; +} diff --git a/bindings/glib/examples/watch_line_info_glib.c b/bindings/glib/examples/watch_line_info_glib.c new file mode 100644 index 0000000..e3b3ae4 --- /dev/null +++ b/bindings/glib/examples/watch_line_info_glib.c @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2023-2024 Bartosz Golaszewski + +/* Minimal example of watching for requests on particular lines. */ + +#include +#include +#include + +static void on_info_event(GpiodglibChip *chip G_GNUC_UNUSED, + GpiodglibInfoEvent *event, + gpointer data G_GNUC_UNUSED) +{ + g_autoptr(GpiodglibLineInfo) info = NULL; + g_autoptr(GString) event_name = NULL; + guint offset; + + event_name = g_string_new( + g_enum_to_string(GPIODGLIB_INFO_EVENT_TYPE_TYPE, + gpiodglib_info_event_get_event_type(event))); + g_string_replace(event_name, "GPIODGLIB_INFO_EVENT_LINE_", "", 0); + info = gpiodglib_info_event_get_line_info(event); + offset = gpiodglib_line_info_get_offset(info); + + g_print("%s %u\n", event_name->str, offset); +} + +int main(void) +{ + static const gchar *const chip_path = "/dev/gpiochip1"; + static const guint line_offsets[] = { 5, 3, 7 }; + static const gsize num_lines = 3; + + g_autoptr(GMainLoop) loop = NULL; + g_autoptr(GpiodglibChip) chip = NULL; + g_autoptr(GError) err = NULL; + guint i; + + chip = gpiodglib_chip_new(chip_path, &err); + if (!chip) { + g_printerr("unable to open %s: %s\n", chip_path, err->message); + return EXIT_FAILURE; + } + + for (i = 0; i < num_lines; i++) { + g_autoptr(GpiodglibLineInfo) info = + gpiodglib_chip_watch_line_info(chip, line_offsets[i], + &err); + if (!info) { + g_printerr("unable to watch line info for offset %u: %s", + line_offsets[1], err->message); + return EXIT_FAILURE; + } + } + + loop = g_main_loop_new(NULL, FALSE); + + g_signal_connect(chip, "info-event", G_CALLBACK(on_info_event), NULL); + + g_main_loop_run(loop); + + return EXIT_SUCCESS; +} diff --git a/bindings/glib/examples/watch_line_value_glib.c b/bindings/glib/examples/watch_line_value_glib.c new file mode 100644 index 0000000..2292f16 --- /dev/null +++ b/bindings/glib/examples/watch_line_value_glib.c @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2023-2024 Bartosz Golaszewski + +/* Minimal example of asynchronously watching for edges on a single line. */ + +#include +#include +#include + +static void on_edge_event(GpiodglibLineRequest *request G_GNUC_UNUSED, + GpiodglibEdgeEvent *event, + gpointer data G_GNUC_UNUSED) +{ + g_autoptr(GString) event_name = NULL; + guint64 timestamp; + guint offset; + + event_name = g_string_new( + g_enum_to_string(GPIODGLIB_EDGE_EVENT_TYPE_TYPE, + gpiodglib_edge_event_get_event_type(event))); + g_string_replace(event_name, "GPIODGLIB_EDGE_EVENT_", "", 0); + timestamp = gpiodglib_edge_event_get_timestamp_ns(event); + offset = gpiodglib_edge_event_get_line_offset(event); + + g_print("%s %lu %u\n", event_name->str, timestamp, offset); +} + +int main(void) +{ + /* Example configuration - customize to suit your situation. */ + static const gchar *const chip_path = "/dev/gpiochip1"; + static const guint line_offset = 5; + + g_autoptr(GpiodglibRequestConfig) req_cfg = NULL; + g_autoptr(GpiodglibLineSettings) settings = NULL; + g_autoptr(GpiodglibLineRequest) request = NULL; + g_autoptr(GpiodglibLineConfig) line_cfg = NULL; + g_autoptr(GpiodglibChip) chip = NULL; + g_autoptr(GArray) offsets = NULL; + g_autoptr(GMainLoop) loop = NULL; + g_autoptr(GError) err = NULL; + gboolean ret; + + offsets = g_array_new(FALSE, TRUE, sizeof(guint)); + g_array_append_val(offsets, line_offset); + + chip = gpiodglib_chip_new(chip_path, &err); + if (!chip) { + g_printerr("unable to open %s: %s\n", chip_path, err->message); + return EXIT_FAILURE; + } + + /* + * Assume a button connecting the pin to ground, so pull it up and + * provide some debounce. + */ + settings = gpiodglib_line_settings_new( + "direction", GPIODGLIB_LINE_DIRECTION_INPUT, + "edge-detection", GPIODGLIB_LINE_EDGE_BOTH, + "bias", GPIODGLIB_LINE_BIAS_PULL_UP, + "debounce-period-us", 1000, + NULL); + + line_cfg = gpiodglib_line_config_new(); + ret = gpiodglib_line_config_add_line_settings(line_cfg, offsets, + settings, &err); + if (!ret) { + g_printerr("failed to add line settings to line config: %s", + err->message); + return EXIT_FAILURE; + } + + req_cfg = gpiodglib_request_config_new("consumer", "watch-line-value", + NULL); + + request = gpiodglib_chip_request_lines(chip, req_cfg, line_cfg, &err); + if (!request) { + g_printerr("failed to request lines: %s", err->message); + return EXIT_FAILURE; + } + + loop = g_main_loop_new(NULL, FALSE); + + /* Connect to the edge-event signal on the line-request. */ + g_signal_connect(request, "edge-event", + G_CALLBACK(on_edge_event), NULL); + + g_main_loop_run(loop); + + return EXIT_SUCCESS; +} diff --git a/bindings/glib/examples/watch_multiple_edge_rising_glib.c b/bindings/glib/examples/watch_multiple_edge_rising_glib.c new file mode 100644 index 0000000..aa47713 --- /dev/null +++ b/bindings/glib/examples/watch_multiple_edge_rising_glib.c @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2023-2024 Bartosz Golaszewski + +/* + * Minimal example of asynchronously watching for rising edges on multiple + * lines. + */ + +#include +#include +#include + +static void on_edge_event(GpiodglibLineRequest *request G_GNUC_UNUSED, + GpiodglibEdgeEvent *event, + gpointer data G_GNUC_UNUSED) +{ + g_autoptr(GString) event_name = NULL; + guint64 timestamp; + guint offset; + + event_name = g_string_new( + g_enum_to_string(GPIODGLIB_EDGE_EVENT_TYPE_TYPE, + gpiodglib_edge_event_get_event_type(event))); + g_string_replace(event_name, "GPIODGLIB_EDGE_EVENT_", "", 0); + timestamp = gpiodglib_edge_event_get_timestamp_ns(event); + offset = gpiodglib_edge_event_get_line_offset(event); + + g_print("%s %lu %u\n", event_name->str, timestamp, offset); +} + +int main(void) +{ + /* Example configuration - customize to suit your situation. */ + static const gchar *const chip_path = "/dev/gpiochip1"; + static const guint line_offsets[] = { 5, 3, 7 }; + static const gsize num_lines = 3; + + g_autoptr(GpiodglibRequestConfig) req_cfg = NULL; + g_autoptr(GpiodglibLineSettings) settings = NULL; + g_autoptr(GpiodglibLineRequest) request = NULL; + g_autoptr(GpiodglibLineConfig) line_cfg = NULL; + g_autoptr(GpiodglibChip) chip = NULL; + g_autoptr(GArray) offsets = NULL; + g_autoptr(GMainLoop) loop = NULL; + g_autoptr(GError) err = NULL; + gboolean ret; + + offsets = g_array_new(FALSE, TRUE, sizeof(guint)); + g_array_append_vals(offsets, line_offsets, num_lines); + + chip = gpiodglib_chip_new(chip_path, &err); + if (!chip) { + g_printerr("unable to open %s: %s\n", chip_path, err->message); + return EXIT_FAILURE; + } + + /* + * Assume a button connecting the pin to ground, so pull it up and + * provide some debounce. + */ + settings = gpiodglib_line_settings_new( + "direction", GPIODGLIB_LINE_DIRECTION_INPUT, + "edge-detection", GPIODGLIB_LINE_EDGE_RISING, + "bias", GPIODGLIB_LINE_BIAS_PULL_UP, + "debounce-period-us", 1000, + NULL); + + line_cfg = gpiodglib_line_config_new(); + ret = gpiodglib_line_config_add_line_settings(line_cfg, offsets, + settings, &err); + if (!ret) { + g_printerr("failed to add line settings to line config: %s", + err->message); + return EXIT_FAILURE; + } + + req_cfg = gpiodglib_request_config_new(NULL); + gpiodglib_request_config_set_consumer(req_cfg, "watch-multiline-value"); + + request = gpiodglib_chip_request_lines(chip, req_cfg, line_cfg, &err); + if (!request) { + g_printerr("failed to request lines: %s", err->message); + return EXIT_FAILURE; + } + + loop = g_main_loop_new(NULL, FALSE); + + /* Connect to the edge-event signal on the line-request. */ + g_signal_connect(request, "edge-event", + G_CALLBACK(on_edge_event), NULL); + + g_main_loop_run(loop); + + return EXIT_SUCCESS; +} From patchwork Wed Aug 7 09:10:38 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Bartosz Golaszewski X-Patchwork-Id: 817497 Received: from mail-wm1-f53.google.com (mail-wm1-f53.google.com [209.85.128.53]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id ECDFC1C9DE2 for ; Wed, 7 Aug 2024 09:10:55 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.53 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723021860; cv=none; b=BXbUJDnzIg5AF8MsgEdjsaJGLkZKV4zoUXQtKkwVWHuU9/CoE5eNzFhOWmcDZHliAuU9hLSjZ3P66ERnBeIM+ahltp36w/snralwjbfrca/8eEadTDzRVSdQimSVApOB10SafJnkxwXG5osu7SUS5HCVbn+ev3E3ibbzSc20ULE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723021860; c=relaxed/simple; bh=22k6gf9vThz/+MGYyPlbKPRGCAtVOX5+GV4a3RUdeks=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=NoptqVXSPNsTPInDfN6Lk14fZUSpsoaw3/xgGNktFqh9ARX/q0blIghua5+jcDwfN8j+745oPHaxpJ8ztKUhYqIBlEL0K4H5u2BjBUiCwSHW91Xvsz7GsCriAvgGqOzreQmZqM2jcsminqJHLEAIz3mznGSR+4FDd31USbRGCOE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=bgdev.pl; spf=none smtp.mailfrom=bgdev.pl; dkim=pass (2048-bit key) header.d=bgdev-pl.20230601.gappssmtp.com header.i=@bgdev-pl.20230601.gappssmtp.com header.b=ubhm+3Bf; arc=none smtp.client-ip=209.85.128.53 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=bgdev.pl Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=bgdev.pl Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=bgdev-pl.20230601.gappssmtp.com header.i=@bgdev-pl.20230601.gappssmtp.com header.b="ubhm+3Bf" Received: by mail-wm1-f53.google.com with SMTP id 5b1f17b1804b1-4280ca0791bso10518885e9.1 for ; Wed, 07 Aug 2024 02:10:55 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bgdev-pl.20230601.gappssmtp.com; s=20230601; t=1723021854; x=1723626654; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=Szuob1E6Fo+djoRcPBa+Eqm4pVOXSw1upHsLCvTr1YQ=; b=ubhm+3Bf2lUNhJBRqIGpKnQJGju4yAy2IOmVd7chKhuFCL8GQIaulY1gJf7kZApVG0 1wazurKf8RQOVjJlPxDpgCiff5uiTsHG84Eny4KlvOMG3mv+4DXFtdsCXjlDL1y4a0Da bI460g8lh90fI6Eh5SyGCYHrvsPp3lrIL5/uKFVMdfV3fhlGfQAagOGGWL4E0H7rVyzt u6rTZTf5OFUkKL4kpu3Z52iemlQE2+Z6BFoj2RdcxxaoxgCGS7lsoiY5PW+MhysTepTx JNJ2Gv7CmCmsaQNCXoWGjH3kOU0GEsOYq7DfLGmE/0IP1lwq28QN7oGoUVkOzGlv7GD8 v9FA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1723021854; x=1723626654; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=Szuob1E6Fo+djoRcPBa+Eqm4pVOXSw1upHsLCvTr1YQ=; b=ggLWB8PZwV5BgkMF5indvmqPTt0aueq0o0F1H9V1CNMjzkPLNKH14Nk5clUbvXrKcT okRn88fOYKS8gO29WQB4s/++lN7K0MElKl4ts+mV/Z/lrmRPFc9PewQg5/STPpgm6w7m in0XEc2k5WT4/Vy5dRqncSU6/1GvQ9Q67Bh5w3ATk/srIfm65z0dDGMTz+f47O67KTfN hbZPWMlp4PodPCAIh4eMKxirH+UFJvIeXa2/tgYaAh0oor8wObLjP85eojBE66iHjKWI 7Zy4ESCgedd/Y7BeHWmZo5HiKtwGkOPKsDitl7CGr8Q57VlT1hEzddTf9LZA4V3AqDon p5RA== X-Gm-Message-State: AOJu0Yw/ch18W2/iaJVfkvaHdyLXaWgFQZgX4rh0j0VuVeFGK+FB8g84 2uJ7j8VPGlEvpEWD1fUp75+0viLSoGQfaMWvt/IpXZMX5AVzlJcuPWQtalWN8QI= X-Google-Smtp-Source: AGHT+IF6Mg9mZUzUfPxwceApjsshu2hiwAoDRYzq27Fss8OkiRagp+E+UBYjjdtWYqZde0aJS3VTfQ== X-Received: by 2002:a05:600c:5125:b0:426:62c5:473e with SMTP id 5b1f17b1804b1-428e6b7b323mr131633365e9.26.1723021853903; Wed, 07 Aug 2024 02:10:53 -0700 (PDT) Received: from [127.0.1.1] ([2a01:cb1d:dc:7e00:a3e7:be71:fe4f:c360]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-429059cd2b3sm18936355e9.44.2024.08.07.02.10.51 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 07 Aug 2024 02:10:52 -0700 (PDT) From: Bartosz Golaszewski Date: Wed, 07 Aug 2024 11:10:38 +0200 Subject: [PATCH libgpiod v4 07/18] bindings: glib: add tests Precedence: bulk X-Mailing-List: linux-gpio@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20240807-dbus-v4-7-64ea80169e51@linaro.org> References: <20240807-dbus-v4-0-64ea80169e51@linaro.org> In-Reply-To: <20240807-dbus-v4-0-64ea80169e51@linaro.org> To: Linus Walleij , Kent Gibson , Erik Schilling , Phil Howard , Andy Shevchenko , Viresh Kumar , Dan Carpenter , Philip Withnall Cc: linux-gpio@vger.kernel.org, Bartosz Golaszewski , Alexander Sverdlin X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=79672; i=bartosz.golaszewski@linaro.org; h=from:subject:message-id; bh=u3b7dMwlVXfU9YJkHGmlOqw3gEGLUnMjNHpIOFGPGqw=; b=owEBbQKS/ZANAwAKARGnLqAUcddyAcsmYgBmszoRMfmOYwuXcxb7KOWsFRek8515IcuYzR3OF 1Q55KgJiR+JAjMEAAEKAB0WIQQWnetsC8PEYBPSx58Rpy6gFHHXcgUCZrM6EQAKCRARpy6gFHHX cqHFD/0XChlS7XKpzFdZIm3I6oESi4MsMRb9/zv2PlrLffH9/h30kH5KeG0sxqU/RjOohXkFvjf FmacpP7/hG2onQhjbTh0x3mpVkF8UTZiH9dymLYN9NtabJ44qeVkxMlYjjrZsdDem67pjYgf9IQ cRq8wMM+08k0EdBMWsEI+kRBaRuEItTpZGNKpM8OQH76Sf6irlPjhrCOHCS2ecmffxhNMvOeDTE feyq5mYx0pC0r97Qw5pIFfJ1h6Fxi8G0IiByZWqm8N7JF/Fo56Zf+YFk65Y5zszcQamEr8xnkmy 4tteiv3c3/8RO8D6vhuGU0zD2dwpKniuBp4OhTkWDIdtgINKRpfQxuXeWz7UADtuqMEsHeeb42u dDNEZSfNIBCeRP9AEP20DxzmizstRQ9TJ/xpA+lDaqXXvmI9xslzmrF/ZrlVI6TTHvu8knm8sPh MCqyo7mRpjIKbrKPOGNXeM6qxqeYHScS2Kg0J+B9bcrLvlYXLlcG4vNfpaiuDZ32JARuAPsRNGg TzqGChZiLhD74r6dRNqKuahb+YRrql91+HJZ909NewnCcor0Ifmah0iV1Hvfh4gyZZhOA5yCg1s 6fN4HPO1gO/CDIOZujO9jYGLCjir0bvfesduwy44mDOk3/BYVH1VIwIww1qfWhvBBedx2lp0Upr i1vbNoUewdPXcgQ== X-Developer-Key: i=bartosz.golaszewski@linaro.org; a=openpgp; fpr=169DEB6C0BC3C46013D2C79F11A72EA01471D772 From: Bartosz Golaszewski Add a comprehensive set of test-cases for GLib bindings to libgpiod reusing the core test framework based around the gpio-sim module. Tested-by: Alexander Sverdlin Signed-off-by: Bartosz Golaszewski --- bindings/glib/tests/helpers.c | 12 + bindings/glib/tests/helpers.h | 140 ++++++ bindings/glib/tests/tests-chip-info.c | 58 +++ bindings/glib/tests/tests-chip.c | 187 ++++++++ bindings/glib/tests/tests-edge-event.c | 225 +++++++++ bindings/glib/tests/tests-info-event.c | 322 +++++++++++++ bindings/glib/tests/tests-line-config.c | 187 ++++++++ bindings/glib/tests/tests-line-info.c | 102 +++++ bindings/glib/tests/tests-line-request.c | 710 +++++++++++++++++++++++++++++ bindings/glib/tests/tests-line-settings.c | 256 +++++++++++ bindings/glib/tests/tests-misc.c | 88 ++++ bindings/glib/tests/tests-request-config.c | 64 +++ 12 files changed, 2351 insertions(+) diff --git a/bindings/glib/tests/helpers.c b/bindings/glib/tests/helpers.c new file mode 100644 index 0000000..202c2d5 --- /dev/null +++ b/bindings/glib/tests/helpers.c @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2024 Bartosz Golaszewski + +#include "helpers.h" + +GArray *gpiodglib_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); +} diff --git a/bindings/glib/tests/helpers.h b/bindings/glib/tests/helpers.h new file mode 100644 index 0000000..ad0a938 --- /dev/null +++ b/bindings/glib/tests/helpers.h @@ -0,0 +1,140 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* SPDX-FileCopyrightText: 2022-2024 Bartosz Golaszewski */ + +#ifndef __GPIODGLIB_TEST_HELPERS_H__ +#define __GPIODGLIB_TEST_HELPERS_H__ + +#include +#include + +#define gpiodglib_test_new_chip_or_fail(_path) \ + ({ \ + g_autoptr(GError) _err = NULL; \ + GpiodglibChip *_chip = gpiodglib_chip_new(_path, &_err); \ + g_assert_nonnull(_chip); \ + g_assert_no_error(_err); \ + gpiod_test_return_if_failed(); \ + _chip; \ + }) + +#define gpiodglib_test_chip_get_info_or_fail(_chip) \ + ({ \ + g_autoptr(GError) _err = NULL; \ + GpiodglibChipInfo *_info = gpiodglib_chip_get_info(_chip, \ + &_err); \ + g_assert_nonnull(_info); \ + g_assert_no_error(_err); \ + gpiod_test_return_if_failed(); \ + _info; \ + }) + +#define gpiodglib_test_chip_get_line_info_or_fail(_chip, _offset) \ + ({ \ + g_autoptr(GError) _err = NULL; \ + GpiodglibLineInfo *_info = \ + gpiodglib_chip_get_line_info(_chip, _offset, &_err); \ + g_assert_nonnull(_info); \ + g_assert_no_error(_err); \ + gpiod_test_return_if_failed(); \ + _info; \ + }) + +#define gpiodglib_test_chip_watch_line_info_or_fail(_chip, _offset) \ + ({ \ + g_autoptr(GError) _err = NULL; \ + GpiodglibLineInfo *_info = \ + gpiodglib_chip_watch_line_info(_chip, _offset, \ + &_err); \ + g_assert_nonnull(_info); \ + g_assert_no_error(_err); \ + gpiod_test_return_if_failed(); \ + _info; \ + }) + +#define gpiodglib_test_chip_unwatch_line_info_or_fail(_chip, _offset) \ + do { \ + g_autoptr(GError) _err = NULL; \ + gboolean ret = gpiodglib_chip_unwatch_line_info(_chip, \ + _offset, \ + &_err); \ + g_assert_true(ret); \ + g_assert_no_error(_err); \ + gpiod_test_return_if_failed(); \ + } while (0) + +#define gpiodglib_test_line_config_add_line_settings_or_fail(_config, \ + _offsets, \ + _settings) \ + do { \ + g_autoptr(GError) _err = NULL; \ + gboolean _ret = \ + gpiodglib_line_config_add_line_settings(_config, \ + _offsets,\ + _settings, \ + &_err); \ + g_assert_true(_ret); \ + g_assert_no_error(_err); \ + gpiod_test_return_if_failed(); \ + } while (0) + +#define gpiodglib_test_line_config_get_line_settings_or_fail(_config, \ + _offset) \ + ({ \ + GpiodglibLineSettings *_settings = \ + gpiodglib_line_config_get_line_settings(_config, \ + _offset); \ + g_assert_nonnull(_settings); \ + gpiod_test_return_if_failed(); \ + _settings; \ + }) + +#define gpiodglib_test_line_config_set_output_values_or_fail(_config, \ + _values) \ + do { \ + g_autoptr(GError) _err = NULL; \ + gboolean _ret = \ + gpiodglib_line_config_set_output_values(_config, \ + _values, \ + &_err); \ + g_assert_true(_ret); \ + g_assert_no_error(_err); \ + gpiod_test_return_if_failed(); \ + } while (0) + +#define gpiodglib_test_chip_request_lines_or_fail(_chip, _req_cfg, _line_cfg) \ + ({ \ + g_autoptr(GError) _err = NULL; \ + GpiodglibLineRequest *_req = \ + gpiodglib_chip_request_lines(_chip, _req_cfg, \ + _line_cfg, &_err); \ + g_assert_nonnull(_req); \ + g_assert_no_error(_err); \ + gpiod_test_return_if_failed(); \ + _req; \ + }) + +#define gpiodglib_test_request_lines_or_fail(_path, _req_cfg, _line_cfg) \ + ({ \ + g_autoptr(GpiodglibChip) _chip = \ + gpiodglib_test_new_chip_or_fail(_path); \ + GpiodglibLineRequest *_req = \ + gpiodglib_test_chip_request_lines_or_fail(_chip, \ + _req_cfg, \ + _line_cfg); \ + _req; \ + }) + +#define gpiodglib_test_check_error_or_fail(_err, _domain, _code) \ + do { \ + g_assert_nonnull(_err); \ + gpiod_test_return_if_failed(); \ + g_assert_cmpint(_domain, ==, (_err)->domain); \ + g_assert_cmpint(_code, ==, (_err)->code); \ + gpiod_test_return_if_failed(); \ + } while (0) + +GArray *gpiodglib_test_array_from_const(const gconstpointer data, gsize len, + gsize elem_size); + +#endif /* __GPIODGLIB_TEST_HELPERS_H__ */ + diff --git a/bindings/glib/tests/tests-chip-info.c b/bindings/glib/tests/tests-chip-info.c new file mode 100644 index 0000000..22b83c2 --- /dev/null +++ b/bindings/glib/tests/tests-chip-info.c @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2022-2024 Bartosz Golaszewski + +#include +#include +#include +#include + +#include "helpers.h" + +#define GPIOD_TEST_GROUP "glib/chip-info" + +GPIOD_TEST_CASE(get_name) +{ + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new(NULL); + g_autoptr(GpiodglibChip) chip = NULL; + g_autoptr(GpiodglibChipInfo) info = NULL; + g_autofree gchar *name = NULL; + + chip = gpiodglib_test_new_chip_or_fail( + g_gpiosim_chip_get_dev_path(sim)); + + info = gpiodglib_test_chip_get_info_or_fail(chip); + name = gpiodglib_chip_info_dup_name(info); + + g_assert_cmpstr(name, ==, g_gpiosim_chip_get_name(sim)); +} + +GPIOD_TEST_CASE(get_label) +{ + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("label", "foobar", + NULL); + g_autoptr(GpiodglibChip) chip = NULL; + g_autoptr(GpiodglibChipInfo) info = NULL; + g_autofree gchar *label = NULL; + + chip = gpiodglib_test_new_chip_or_fail( + g_gpiosim_chip_get_dev_path(sim)); + + info = gpiodglib_test_chip_get_info_or_fail(chip); + label = gpiodglib_chip_info_dup_label(info); + + g_assert_cmpstr(label, ==, "foobar"); +} + +GPIOD_TEST_CASE(get_num_lines) +{ + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 16, NULL); + g_autoptr(GpiodglibChip) chip = NULL; + g_autoptr(GpiodglibChipInfo) info = NULL; + + chip = gpiodglib_test_new_chip_or_fail( + g_gpiosim_chip_get_dev_path(sim)); + + info = gpiodglib_test_chip_get_info_or_fail(chip); + + g_assert_cmpuint(gpiodglib_chip_info_get_num_lines(info), ==, 16); +} diff --git a/bindings/glib/tests/tests-chip.c b/bindings/glib/tests/tests-chip.c new file mode 100644 index 0000000..9888b38 --- /dev/null +++ b/bindings/glib/tests/tests-chip.c @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2022-2024 Bartosz Golaszewski + +#include +#include +#include +#include +#include + +#include "helpers.h" + +#define GPIOD_TEST_GROUP "glib/chip" + +GPIOD_TEST_CASE(open_chip_good) +{ + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new(NULL); + g_autoptr(GpiodglibChip) chip = NULL; + g_autoptr(GError) err = NULL; + + chip = gpiodglib_chip_new(g_gpiosim_chip_get_dev_path(sim), &err); + g_assert_nonnull(chip); + g_assert_null(err); +} + +GPIOD_TEST_CASE(open_chip_nonexistent) +{ + g_autoptr(GpiodglibChip) chip = NULL; + g_autoptr(GError) err = NULL; + + chip = gpiodglib_chip_new("/dev/nonexistent", &err); + g_assert_null(chip); + gpiodglib_test_check_error_or_fail(err, GPIODGLIB_ERROR, + GPIODGLIB_ERR_NOENT); +} + +GPIOD_TEST_CASE(open_chip_not_a_character_device) +{ + g_autoptr(GpiodglibChip) chip = NULL; + g_autoptr(GError) err = NULL; + + chip = gpiodglib_chip_new("/tmp", &err); + g_assert_null(chip); + gpiodglib_test_check_error_or_fail(err, GPIODGLIB_ERROR, + GPIODGLIB_ERR_NOTTY); +} + +GPIOD_TEST_CASE(open_chip_not_a_gpio_device) +{ + g_autoptr(GpiodglibChip) chip = NULL; + g_autoptr(GError) err = NULL; + + chip = gpiodglib_chip_new("/dev/null", &err); + g_assert_null(chip); + gpiodglib_test_check_error_or_fail(err, GPIODGLIB_ERROR, + GPIODGLIB_ERR_NODEV); +} + +GPIOD_TEST_CASE(get_chip_path) +{ + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new(NULL); + g_autoptr(GpiodglibChip) chip = NULL; + const gchar *path = g_gpiosim_chip_get_dev_path(sim); + g_autofree gchar *chip_path = NULL; + + chip = gpiodglib_test_new_chip_or_fail(path); + + chip_path = gpiodglib_chip_dup_path(chip); + g_assert_cmpstr(chip_path, ==, path); +} + +GPIOD_TEST_CASE(closed_chip) +{ + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new(NULL); + g_autoptr(GpiodglibChip) chip = NULL; + g_autoptr(GError) err = NULL; + g_autoptr(GpiodglibChipInfo) info = NULL; + const gchar *path = g_gpiosim_chip_get_dev_path(sim); + g_autofree gchar *chip_path = NULL; + + chip = gpiodglib_test_new_chip_or_fail(path); + + gpiodglib_chip_close(chip); + + info = gpiodglib_chip_get_info(chip, &err); + g_assert_error(err, GPIODGLIB_ERROR, GPIODGLIB_ERR_CHIP_CLOSED); + + /* Properties still work. */ + chip_path = gpiodglib_chip_dup_path(chip); + g_assert_cmpstr(chip_path, ==, path); +} + +GPIOD_TEST_CASE(find_line_bad) +{ + static const GPIOSimLineName names[] = { + { .offset = 1, .name = "foo", }, + { .offset = 2, .name = "bar", }, + { .offset = 4, .name = "baz", }, + { .offset = 5, .name = "xyz", }, + { } + }; + + 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); + g_autoptr(GpiodglibChip) chip = NULL; + g_autoptr(GError) err = NULL; + guint offset; + + chip = gpiodglib_test_new_chip_or_fail( + g_gpiosim_chip_get_dev_path(sim)); + + g_assert_false(gpiodglib_chip_get_line_offset_from_name(chip, + "nonexistent", + &offset, &err)); + g_assert_no_error(err); +} + +GPIOD_TEST_CASE(find_line_good) +{ + static const GPIOSimLineName names[] = { + { .offset = 1, .name = "foo", }, + { .offset = 2, .name = "bar", }, + { .offset = 4, .name = "baz", }, + { .offset = 5, .name = "xyz", }, + { } + }; + + 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); + g_autoptr(GpiodglibChip) chip = NULL; + g_autoptr(GError) err = NULL; + guint offset; + + chip = gpiodglib_test_new_chip_or_fail( + g_gpiosim_chip_get_dev_path(sim)); + + g_assert_true(gpiodglib_chip_get_line_offset_from_name(chip, "baz", + &offset, &err)); + g_assert_no_error(err); + g_assert_cmpuint(offset, ==, 4); +} + +/* Verify that for duplicated line names, the first one is returned. */ +GPIOD_TEST_CASE(find_line_duplicate) +{ + static const GPIOSimLineName names[] = { + { .offset = 1, .name = "foo", }, + { .offset = 2, .name = "baz", }, + { .offset = 4, .name = "baz", }, + { .offset = 5, .name = "xyz", }, + { } + }; + + 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); + g_autoptr(GpiodglibChip) chip = NULL; + g_autoptr(GError) err = NULL; + guint offset; + + chip = gpiodglib_test_new_chip_or_fail( + g_gpiosim_chip_get_dev_path(sim)); + + g_assert_true(gpiodglib_chip_get_line_offset_from_name(chip, "baz", + &offset, &err)); + g_assert_no_error(err); + g_assert_cmpuint(offset, ==, 2); +} + +GPIOD_TEST_CASE(find_line_null_name) +{ + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new(NULL); + g_autoptr(GpiodglibChip) chip = NULL; + g_autoptr(GError) err = NULL; + guint offset; + + chip = gpiodglib_test_new_chip_or_fail( + g_gpiosim_chip_get_dev_path(sim)); + + g_assert_false(gpiodglib_chip_get_line_offset_from_name(chip, NULL, + &offset, &err)); + g_assert_error(err, GPIODGLIB_ERROR, GPIODGLIB_ERR_INVAL); +} diff --git a/bindings/glib/tests/tests-edge-event.c b/bindings/glib/tests/tests-edge-event.c new file mode 100644 index 0000000..4368e0f --- /dev/null +++ b/bindings/glib/tests/tests-edge-event.c @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2023-2024 Bartosz Golaszewski + +#include +#include +#include +#include + +#include "helpers.h" + +#define GPIOD_TEST_GROUP "glib/edge-event" + +static gpointer falling_and_rising_edge_events(gpointer data) +{ + GPIOSimChip *sim = data; + + g_usleep(1000); + + g_gpiosim_chip_set_pull(sim, 2, G_GPIOSIM_PULL_UP); + + g_usleep(1000); + + g_gpiosim_chip_set_pull(sim, 2, G_GPIOSIM_PULL_DOWN); + + return NULL; +} + +typedef struct { + gboolean rising; + gboolean falling; + gboolean failed; + guint64 falling_ts; + guint64 rising_ts; + guint falling_offset; + guint rising_offset; +} EdgeEventCallbackData; + +static void on_edge_event(GpiodglibLineRequest *request G_GNUC_UNUSED, + GpiodglibEdgeEvent *event, gpointer data) +{ + EdgeEventCallbackData *cb_data = data; + + if (gpiodglib_edge_event_get_event_type(event) == + GPIODGLIB_EDGE_EVENT_FALLING_EDGE) { + cb_data->falling = TRUE; + cb_data->falling_ts = + gpiodglib_edge_event_get_timestamp_ns(event); + cb_data->falling_offset = + gpiodglib_edge_event_get_line_offset(event); + } else { + cb_data->rising = TRUE; + cb_data->rising_ts = + gpiodglib_edge_event_get_timestamp_ns(event); + cb_data->rising_offset = + gpiodglib_edge_event_get_line_offset(event); + } +} + +static gboolean on_timeout(gpointer data) +{ + EdgeEventCallbackData *cb_data = data; + + g_test_fail_printf("timeout while waiting for edge events"); + cb_data->failed = TRUE; + + return G_SOURCE_CONTINUE; +} + +GPIOD_TEST_CASE(read_both_events) +{ + static const guint offset = 2; + + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL); + g_autoptr(GpiodglibChip) chip = NULL; + g_autoptr(GpiodglibLineSettings) settings = NULL; + g_autoptr(GpiodglibLineConfig) config = NULL; + g_autoptr(GpiodglibLineRequest) request = NULL; + g_autoptr(GArray) offsets = NULL; + g_autoptr(GThread) thread = NULL; + EdgeEventCallbackData cb_data = { }; + guint timeout_id; + + chip = gpiodglib_test_new_chip_or_fail( + g_gpiosim_chip_get_dev_path(sim)); + settings = gpiodglib_line_settings_new( + "direction", GPIODGLIB_LINE_DIRECTION_INPUT, + "edge-detection", GPIODGLIB_LINE_EDGE_BOTH, NULL); + config = gpiodglib_line_config_new(); + offsets = gpiodglib_test_array_from_const(&offset, 1, sizeof(guint)); + + gpiodglib_test_line_config_add_line_settings_or_fail(config, offsets, + settings); + + request = gpiodglib_test_chip_request_lines_or_fail(chip, NULL, config); + + g_signal_connect(request, "edge-event", + G_CALLBACK(on_edge_event), &cb_data); + timeout_id = g_timeout_add_seconds(5, on_timeout, &cb_data); + + thread = g_thread_new("rising-falling-edge-events", + falling_and_rising_edge_events, sim); + g_thread_ref(thread); + + while (!cb_data.failed && (!cb_data.falling || !cb_data.rising)) + g_main_context_iteration(NULL, TRUE); + + g_source_remove(timeout_id); + g_thread_join(thread); + + g_assert_cmpuint(cb_data.falling_ts, >, cb_data.rising_ts); + g_assert_cmpuint(cb_data.falling_offset, ==, offset); + g_assert_cmpuint(cb_data.rising_offset, ==, offset); +} + +typedef struct { + gboolean failed; + gboolean first; + gboolean second; + guint first_offset; + guint second_offset; + gulong first_line_seqno; + gulong second_line_seqno; + gulong first_global_seqno; + gulong second_global_seqno; +} SeqnoCallbackData; + +static void on_seqno_edge_event(GpiodglibLineRequest *request G_GNUC_UNUSED, + GpiodglibEdgeEvent *event, gpointer data) +{ + SeqnoCallbackData *cb_data = data; + + if (!cb_data->first) { + cb_data->first_offset = + gpiodglib_edge_event_get_line_offset(event); + cb_data->first_line_seqno = + gpiodglib_edge_event_get_line_seqno(event); + cb_data->first_global_seqno = + gpiodglib_edge_event_get_global_seqno(event); + cb_data->first = TRUE; + } else { + cb_data->second_offset = + gpiodglib_edge_event_get_line_offset(event); + cb_data->second_line_seqno = + gpiodglib_edge_event_get_line_seqno(event); + cb_data->second_global_seqno = + gpiodglib_edge_event_get_global_seqno(event); + cb_data->second = TRUE; + } +} + +static gpointer rising_edge_events_on_two_offsets(gpointer data) +{ + GPIOSimChip *sim = data; + + g_usleep(1000); + + g_gpiosim_chip_set_pull(sim, 2, G_GPIOSIM_PULL_UP); + + g_usleep(1000); + + g_gpiosim_chip_set_pull(sim, 3, G_GPIOSIM_PULL_UP); + + return NULL; +} + +static gboolean on_seqno_timeout(gpointer data) +{ + SeqnoCallbackData *cb_data = data; + + g_test_fail_printf("timeout while waiting for edge events"); + cb_data->failed = TRUE; + + return G_SOURCE_CONTINUE; +} + +GPIOD_TEST_CASE(seqno) +{ + static const guint offset_vals[] = { 2, 3 }; + + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL); + g_autoptr(GpiodglibChip) chip = NULL; + g_autoptr(GpiodglibLineSettings) settings = NULL; + g_autoptr(GpiodglibLineConfig) config = NULL; + g_autoptr(GpiodglibLineRequest) request = NULL; + g_autoptr(GArray) offsets = NULL; + g_autoptr(GThread) thread = NULL; + SeqnoCallbackData cb_data = { }; + guint timeout_id; + + chip = gpiodglib_test_new_chip_or_fail( + g_gpiosim_chip_get_dev_path(sim)); + settings = gpiodglib_line_settings_new( + "direction", GPIODGLIB_LINE_DIRECTION_INPUT, + "edge-detection", GPIODGLIB_LINE_EDGE_BOTH, NULL); + config = gpiodglib_line_config_new(); + offsets = gpiodglib_test_array_from_const(offset_vals, 2, + sizeof(guint)); + + gpiodglib_test_line_config_add_line_settings_or_fail(config, offsets, + settings); + + request = gpiodglib_test_chip_request_lines_or_fail(chip, NULL, + config); + g_signal_connect(request, "edge-event", + G_CALLBACK(on_seqno_edge_event), &cb_data); + + timeout_id = g_timeout_add_seconds(5, on_seqno_timeout, &cb_data); + + thread = g_thread_new("two-rising-edge-events", + rising_edge_events_on_two_offsets, sim); + g_thread_ref(thread); + + while (!cb_data.failed && (!cb_data.first || !cb_data.second)) + g_main_context_iteration(NULL, TRUE); + + g_source_remove(timeout_id); + g_thread_join(thread); + + g_assert_cmpuint(cb_data.first_offset, ==, 2); + g_assert_cmpuint(cb_data.second_offset, ==, 3); + g_assert_cmpuint(cb_data.first_line_seqno, ==, 1); + g_assert_cmpuint(cb_data.second_line_seqno, ==, 1); + g_assert_cmpuint(cb_data.first_global_seqno, ==, 1); + g_assert_cmpuint(cb_data.second_global_seqno, ==, 2); +} diff --git a/bindings/glib/tests/tests-info-event.c b/bindings/glib/tests/tests-info-event.c new file mode 100644 index 0000000..0234905 --- /dev/null +++ b/bindings/glib/tests/tests-info-event.c @@ -0,0 +1,322 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2023-2024 Bartosz Golaszewski + +#include +#include +#include +#include + +#include "helpers.h" + +#define GPIOD_TEST_GROUP "glib/info-event" + +GPIOD_TEST_CASE(watching_info_events_returns_line_info) +{ + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL); + g_autoptr(GpiodglibChip) chip = NULL; + g_autoptr(GpiodglibLineInfo) info = NULL; + + chip = gpiodglib_test_new_chip_or_fail( + g_gpiosim_chip_get_dev_path(sim)); + info = gpiodglib_test_chip_watch_line_info_or_fail(chip, 3); + g_assert_cmpuint(gpiodglib_line_info_get_offset(info), ==, 3); +} + +GPIOD_TEST_CASE(try_offset_of_out_range) +{ + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL); + g_autoptr(GpiodglibChip) chip = NULL; + g_autoptr(GpiodglibLineInfo) info = NULL; + g_autoptr(GError) err = NULL; + + chip = gpiodglib_test_new_chip_or_fail( + g_gpiosim_chip_get_dev_path(sim)); + info = gpiodglib_chip_watch_line_info(chip, 11, &err); + g_assert_null(info); + g_assert_error(err, GPIODGLIB_ERROR, GPIODGLIB_ERR_INVAL); +} + +static void on_bad_info_event(GpiodglibChip *chip G_GNUC_UNUSED, + GpiodglibInfoEvent *event G_GNUC_UNUSED, + gpointer data G_GNUC_UNUSED) +{ + g_test_fail_printf("unexpected info event received"); +} + +static gboolean on_expected_timeout(gpointer data) +{ + gboolean *done = data; + + *done = TRUE; + + return G_SOURCE_REMOVE; +} + +GPIOD_TEST_CASE(event_timeout) +{ + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL); + g_autoptr(GpiodglibChip) chip = NULL; + g_autoptr(GpiodglibLineInfo) info = NULL; + gboolean done = FALSE; + + chip = gpiodglib_test_new_chip_or_fail( + g_gpiosim_chip_get_dev_path(sim)); + + g_signal_connect(chip, "info-event", + G_CALLBACK(on_bad_info_event), NULL); + g_timeout_add(100, on_expected_timeout, &done); + + info = gpiodglib_test_chip_watch_line_info_or_fail(chip, 3); + + while (!done && !g_test_failed()) + g_main_context_iteration(NULL, TRUE); +} + +typedef struct { + const gchar *chip_path; + guint offset; +} RequestContext; + +typedef struct { + GPtrArray *events; + guint done; + gboolean failed; +} EventContext; + +static gpointer request_reconfigure_release_line(gpointer data) +{ + g_autoptr(GpiodglibLineRequest) request = NULL; + g_autoptr(GpiodglibLineSettings) settings = NULL; + g_autoptr(GpiodglibLineConfig) config = NULL; + g_autoptr(GArray) offsets = NULL; + g_autoptr(GpiodglibChip) chip = NULL; + g_autoptr(GError) err = NULL; + RequestContext *ctx = data; + gboolean ret; + + chip = gpiodglib_chip_new(ctx->chip_path, &err); + g_assert_no_error(err); + if (g_test_failed()) + return NULL; + + offsets = gpiodglib_test_array_from_const(&ctx->offset, 1, + sizeof(guint)); + config = gpiodglib_line_config_new(); + settings = gpiodglib_line_settings_new(NULL); + + ret = gpiodglib_line_config_add_line_settings(config, offsets, + settings, &err); + g_assert_true(ret); + g_assert_no_error(err); + if (g_test_failed()) + return NULL; + + g_usleep(1000); + + request = gpiodglib_chip_request_lines(chip, NULL, config, &err); + g_assert_nonnull(request); + g_assert_no_error(err); + + g_usleep(1000); + + gpiodglib_line_config_reset(config); + gpiodglib_line_settings_set_direction(settings, + GPIODGLIB_LINE_DIRECTION_OUTPUT); + ret = gpiodglib_line_config_add_line_settings(config, offsets, + settings, &err); + g_assert_true(ret); + g_assert_no_error(err); + if (g_test_failed()) + return NULL; + + ret = gpiodglib_line_request_reconfigure_lines(request, config, &err); + g_assert_true(ret); + g_assert_no_error(err); + if (g_test_failed()) + return NULL; + + g_usleep(1000); + + gpiodglib_line_request_release(request); + + return NULL; +} + +static void basic_on_info_event(GpiodglibChip *chip G_GNUC_UNUSED, + GpiodglibInfoEvent *event, gpointer data) +{ + EventContext *ctx = data; + + g_ptr_array_add(ctx->events, g_object_ref(event)); + ctx->done++; +} + +static gboolean on_timeout(gpointer data) +{ + gboolean *failed = data; + + g_test_fail_printf("wait for info event timed out"); + *failed = TRUE; + + return G_SOURCE_CONTINUE; +} + +GPIOD_TEST_CASE(request_reconfigure_release_events) +{ + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL); + g_autoptr(GpiodglibChip) chip = NULL; + g_autoptr(GpiodglibLineInfo) info = NULL; + g_autoptr(GPtrArray) events = NULL; + g_autoptr(GThread) thread = NULL; + const gchar *chip_path = g_gpiosim_chip_get_dev_path(sim); + GpiodglibInfoEvent *req_ev, *reconf_ev, *rel_ev; + guint64 req_ts, reconf_ts, rel_ts; + EventContext ev_ctx; + RequestContext req_ctx; + guint timeout_id; + + events = g_ptr_array_new_full(3, g_object_unref); + + chip = gpiodglib_test_new_chip_or_fail(chip_path); + g_signal_connect(chip, "info-event", G_CALLBACK(basic_on_info_event), + &ev_ctx); + timeout_id = g_timeout_add_seconds(5, on_timeout, &ev_ctx.failed); + + info = gpiodglib_test_chip_watch_line_info_or_fail(chip, 3); + + g_assert_false(gpiodglib_line_info_is_used(info)); + + req_ctx.chip_path = chip_path; + req_ctx.offset = 3; + + thread = g_thread_new("request-reconfigure-release", + request_reconfigure_release_line, &req_ctx); + g_thread_ref(thread); + + ev_ctx.done = 0; + ev_ctx.failed = FALSE; + ev_ctx.events = events; + + while (ev_ctx.done != 3 && !ev_ctx.failed) + g_main_context_iteration(NULL, TRUE); + + g_source_remove(timeout_id); + g_thread_join(thread); + + req_ev = g_ptr_array_index(events, 0); + reconf_ev = g_ptr_array_index(events, 1); + rel_ev = g_ptr_array_index(events, 2); + + g_assert_cmpint(gpiodglib_info_event_get_event_type(req_ev), ==, + GPIODGLIB_INFO_EVENT_LINE_REQUESTED); + g_assert_cmpint(gpiodglib_info_event_get_event_type(reconf_ev), ==, + GPIODGLIB_INFO_EVENT_LINE_CONFIG_CHANGED); + g_assert_cmpint(gpiodglib_info_event_get_event_type(rel_ev), ==, + GPIODGLIB_INFO_EVENT_LINE_RELEASED); + + req_ts = gpiodglib_info_event_get_timestamp_ns(req_ev); + reconf_ts = gpiodglib_info_event_get_timestamp_ns(reconf_ev); + rel_ts = gpiodglib_info_event_get_timestamp_ns(rel_ev); + + g_assert_cmpuint(req_ts, <, reconf_ts); + g_assert_cmpuint(reconf_ts, <, rel_ts); +} + +static void unwatch_on_info_event(GpiodglibChip *chip G_GNUC_UNUSED, + GpiodglibInfoEvent *event G_GNUC_UNUSED, + gpointer data) +{ + gboolean *got_event = data; + + *got_event = TRUE; +} + +GPIOD_TEST_CASE(unwatch_and_check_that_no_events_are_generated) +{ + static const guint offset = 3; + + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL); + g_autoptr(GpiodglibChip) chip = NULL; + g_autoptr(GpiodglibLineInfo) info = NULL; + g_autoptr(GpiodglibLineRequest) request = NULL; + g_autoptr(GpiodglibLineSettings) settings = NULL; + g_autoptr(GpiodglibLineConfig) config = NULL; + g_autoptr(GArray) offsets = NULL; + gboolean got_event = FALSE; + + chip = gpiodglib_test_new_chip_or_fail( + g_gpiosim_chip_get_dev_path(sim)); + g_signal_connect(chip, "info-event", G_CALLBACK(unwatch_on_info_event), + &got_event); + + offsets = gpiodglib_test_array_from_const(&offset, 1, sizeof(guint)); + config = gpiodglib_line_config_new(); + settings = gpiodglib_line_settings_new(NULL); + + gpiodglib_test_line_config_add_line_settings_or_fail(config, offsets, + settings); + + info = gpiodglib_test_chip_watch_line_info_or_fail(chip, offset); + + request = gpiodglib_test_chip_request_lines_or_fail(chip, NULL, + config); + + g_main_context_iteration(NULL, TRUE); + + g_assert_true(got_event); + + gpiodglib_test_chip_unwatch_line_info_or_fail(chip, offset); + + got_event = FALSE; + gpiodglib_line_request_release(request); + + g_main_context_iteration(NULL, TRUE); + + g_assert_false(got_event); +} + +static void check_line_info_on_info_event(GpiodglibChip *chip G_GNUC_UNUSED, + GpiodglibInfoEvent *event, + gpointer data) +{ + GpiodglibLineInfo **info = data; + + *info = gpiodglib_info_event_get_line_info(event); +} + +GPIOD_TEST_CASE(info_event_contains_new_line_info) +{ + static const guint offset = 3; + + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL); + g_autoptr(GpiodglibChip) chip = NULL; + g_autoptr(GpiodglibLineInfo) initial_info = NULL; + g_autoptr(GpiodglibLineInfo) event_info = NULL; + g_autoptr(GpiodglibLineRequest) request = NULL; + g_autoptr(GpiodglibLineSettings) settings = NULL; + g_autoptr(GpiodglibLineConfig) config = NULL; + g_autoptr(GArray) offsets = NULL; + + chip = gpiodglib_test_new_chip_or_fail( + g_gpiosim_chip_get_dev_path(sim)); + g_signal_connect(chip, "info-event", + G_CALLBACK(check_line_info_on_info_event), + &event_info); + + offsets = gpiodglib_test_array_from_const(&offset, 1, sizeof(guint)); + config = gpiodglib_line_config_new(); + settings = gpiodglib_line_settings_new(NULL); + + gpiodglib_test_line_config_add_line_settings_or_fail(config, offsets, + settings); + + initial_info = gpiodglib_test_chip_watch_line_info_or_fail(chip, + offset); + + request = gpiodglib_test_chip_request_lines_or_fail(chip, NULL, + config); + + g_main_context_iteration(NULL, TRUE); + + g_assert_nonnull(event_info); +} diff --git a/bindings/glib/tests/tests-line-config.c b/bindings/glib/tests/tests-line-config.c new file mode 100644 index 0000000..74cd440 --- /dev/null +++ b/bindings/glib/tests/tests-line-config.c @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2023-2024 Bartosz Golaszewski + +#include +#include +#include +#include + +#include "helpers.h" + +#define GPIOD_TEST_GROUP "glib/line-config" + +GPIOD_TEST_CASE(too_many_lines) +{ + g_autoptr(GpiodglibLineSettings) settings = NULL; + g_autoptr(GpiodglibLineConfig) config = NULL; + g_autoptr(GArray) offsets = NULL; + g_autoptr(GError) err = NULL; + gboolean ret; + guint i; + + settings = gpiodglib_line_settings_new(NULL); + config = gpiodglib_line_config_new(); + offsets = g_array_new(FALSE, TRUE, sizeof(guint)); + + for (i = 0; i < 65; i++) + g_array_append_val(offsets, i); + + ret = gpiodglib_line_config_add_line_settings(config, offsets, + settings, &err); + g_assert_false(ret); + g_assert_error(err, GPIODGLIB_ERROR, GPIODGLIB_ERR_E2BIG); +} + +GPIOD_TEST_CASE(get_line_settings) +{ + static const guint offset_vals[] = { 0, 1, 2, 3 }; + + g_autoptr(GpiodglibLineSettings) settings = NULL; + g_autoptr(GpiodglibLineSettings) retrieved = NULL; + g_autoptr(GpiodglibLineConfig) config = NULL; + g_autoptr(GArray) offsets = NULL; + + settings = gpiodglib_line_settings_new( + "direction", GPIODGLIB_LINE_DIRECTION_INPUT, + "bias", GPIODGLIB_LINE_BIAS_PULL_DOWN, + NULL); + config = gpiodglib_line_config_new(); + offsets = gpiodglib_test_array_from_const(offset_vals, 4, + sizeof(guint)); + + gpiodglib_test_line_config_add_line_settings_or_fail(config, offsets, + settings); + + retrieved = gpiodglib_test_line_config_get_line_settings_or_fail( + config, 2); + g_assert_cmpint(gpiodglib_line_settings_get_direction(retrieved), ==, + GPIODGLIB_LINE_DIRECTION_INPUT); + g_assert_cmpint(gpiodglib_line_settings_get_bias(retrieved), ==, + GPIODGLIB_LINE_BIAS_PULL_DOWN); +} + +GPIOD_TEST_CASE(null_settings) +{ + static const guint offset_vals[] = { 0, 1, 2, 3 }; + + g_autoptr(GpiodglibLineConfig) config = NULL; + g_autoptr(GpiodglibLineSettings) settings = NULL; + g_autoptr(GArray) offsets = NULL; + + config = gpiodglib_line_config_new(); + offsets = gpiodglib_test_array_from_const(offset_vals, 4, + sizeof(guint)); + + gpiodglib_test_line_config_add_line_settings_or_fail(config, offsets, + NULL); + + settings = gpiodglib_test_line_config_get_line_settings_or_fail(config, + 2); + + g_assert_cmpint(gpiodglib_line_settings_get_drive(settings), ==, + GPIODGLIB_LINE_DIRECTION_AS_IS); +} + +GPIOD_TEST_CASE(null_offsets) +{ + g_autoptr(GpiodglibLineConfig) config = NULL; + g_autoptr(GpiodglibLineSettings) settings = NULL; + g_autoptr(GArray) offsets = NULL; + g_autoptr(GError) err = NULL; + gboolean ret; + + settings = gpiodglib_line_settings_new(NULL); + config = gpiodglib_line_config_new(); + offsets = g_array_new(FALSE, TRUE, sizeof(guint)); + + ret = gpiodglib_line_config_add_line_settings(config, NULL, settings, + &err); + g_assert_false(ret); + g_assert_error(err, GPIODGLIB_ERROR, GPIODGLIB_ERR_INVAL); +} + +GPIOD_TEST_CASE(zero_offsets) +{ + g_autoptr(GpiodglibLineConfig) config = NULL; + g_autoptr(GpiodglibLineSettings) settings = NULL; + g_autoptr(GArray) offsets = NULL; + g_autoptr(GError) err = NULL; + gboolean ret; + + settings = gpiodglib_line_settings_new(NULL); + config = gpiodglib_line_config_new(); + offsets = g_array_new(FALSE, TRUE, sizeof(guint)); + + ret = gpiodglib_line_config_add_line_settings(config, offsets, settings, + &err); + g_assert_false(ret); + g_assert_error(err, GPIODGLIB_ERROR, GPIODGLIB_ERR_INVAL); +} + +GPIOD_TEST_CASE(set_global_output_values) +{ + static const guint offset_vals[] = { 0, 1, 2, 3 }; + static const GpiodglibLineValue output_values[] = { + GPIODGLIB_LINE_VALUE_ACTIVE, + GPIODGLIB_LINE_VALUE_INACTIVE, + GPIODGLIB_LINE_VALUE_ACTIVE, + GPIODGLIB_LINE_VALUE_INACTIVE, + }; + + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 4, NULL); + g_autoptr(GpiodglibChip) chip = NULL; + g_autoptr(GpiodglibLineConfig) config = NULL; + g_autoptr(GpiodglibLineSettings) settings = NULL; + g_autoptr(GArray) offsets = NULL; + g_autoptr(GArray) values = NULL; + g_autoptr(GpiodglibLineRequest) request = NULL; + + chip = gpiodglib_test_new_chip_or_fail( + g_gpiosim_chip_get_dev_path(sim)); + settings = gpiodglib_line_settings_new("direction", + GPIODGLIB_LINE_DIRECTION_OUTPUT, + NULL); + config = gpiodglib_line_config_new(); + offsets = gpiodglib_test_array_from_const(offset_vals, 4, + sizeof(guint)); + values = gpiodglib_test_array_from_const(output_values, 4, + sizeof(GpiodglibLineValue)); + + gpiodglib_test_line_config_add_line_settings_or_fail(config, offsets, + settings); + gpiodglib_test_line_config_set_output_values_or_fail(config, values); + + request = gpiodglib_test_chip_request_lines_or_fail(chip, NULL, + config); + + g_assert_cmpint(g_gpiosim_chip_get_value(sim, 0), ==, + G_GPIOSIM_VALUE_ACTIVE); + g_assert_cmpint(g_gpiosim_chip_get_value(sim, 1), ==, + G_GPIOSIM_VALUE_INACTIVE); + g_assert_cmpint(g_gpiosim_chip_get_value(sim, 2), ==, + G_GPIOSIM_VALUE_ACTIVE); + g_assert_cmpint(g_gpiosim_chip_get_value(sim, 3), ==, + G_GPIOSIM_VALUE_INACTIVE); +} + +GPIOD_TEST_CASE(handle_duplicate_offsets) +{ + static const guint offset_vals[] = { 0, 2, 2, 3 }; + + g_autoptr(GpiodglibLineConfig) config = NULL; + g_autoptr(GArray) offsets = NULL; + g_autoptr(GArray) retrieved = NULL; + + config = gpiodglib_line_config_new(); + offsets = gpiodglib_test_array_from_const(offset_vals, 4, + sizeof(guint)); + + gpiodglib_test_line_config_add_line_settings_or_fail(config, offsets, + NULL); + + retrieved = gpiodglib_line_config_get_configured_offsets(config); + g_assert_cmpuint(retrieved->len, ==, 3); + g_assert_cmpuint(g_array_index(retrieved, guint, 0), ==, 0); + g_assert_cmpuint(g_array_index(retrieved, guint, 1), ==, 2); + g_assert_cmpuint(g_array_index(retrieved, guint, 2), ==, 3); +} diff --git a/bindings/glib/tests/tests-line-info.c b/bindings/glib/tests/tests-line-info.c new file mode 100644 index 0000000..6ab3ab4 --- /dev/null +++ b/bindings/glib/tests/tests-line-info.c @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2023-2024 Bartosz Golaszewski + +#include +#include +#include +#include + +#include "helpers.h" + +#define GPIOD_TEST_GROUP "glib/line-info" + +GPIOD_TEST_CASE(get_line_info_good) +{ + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL); + g_autoptr(GpiodglibChip) chip = NULL; + g_autoptr(GpiodglibLineInfo) info = NULL; + + chip = gpiodglib_test_new_chip_or_fail( + g_gpiosim_chip_get_dev_path(sim)); + + info = gpiodglib_test_chip_get_line_info_or_fail(chip, 3); + + g_assert_cmpuint(gpiodglib_line_info_get_offset(info), ==, 3); +} + +GPIOD_TEST_CASE(get_line_info_offset_out_of_range) +{ + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL); + g_autoptr(GpiodglibChip) chip = NULL; + g_autoptr(GpiodglibLineInfo) info = NULL; + g_autoptr(GError) err = NULL; + + chip = gpiodglib_test_new_chip_or_fail( + g_gpiosim_chip_get_dev_path(sim)); + + info = gpiodglib_chip_get_line_info(chip, 8, &err); + g_assert_error(err, GPIODGLIB_ERROR, GPIODGLIB_ERR_INVAL); +} + +GPIOD_TEST_CASE(line_info_basic_properties) +{ + static const GPIOSimLineName names[] = { + { .offset = 1, .name = "foo", }, + { .offset = 2, .name = "bar", }, + { .offset = 4, .name = "baz", }, + { .offset = 5, .name = "xyz", }, + { } + }; + + static const GPIOSimHog hogs[] = { + { + .offset = 3, + .name = "hog3", + .direction = G_GPIOSIM_DIRECTION_OUTPUT_HIGH, + }, + { + .offset = 4, + .name = "hog4", + .direction = G_GPIOSIM_DIRECTION_OUTPUT_LOW, + }, + { } + }; + + g_autoptr(GVariant) vnames = g_gpiosim_package_line_names(names); + g_autoptr(GVariant) vhogs = g_gpiosim_package_hogs(hogs); + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, + "line-names", vnames, + "hogs", vhogs, + NULL); + g_autoptr(GpiodglibChip) chip = NULL; + g_autoptr(GpiodglibLineInfo) info4 = NULL; + g_autoptr(GpiodglibLineInfo) info6 = NULL; + g_autofree gchar *consumer = NULL; + g_autofree gchar *name = NULL; + + chip = gpiodglib_test_new_chip_or_fail( + g_gpiosim_chip_get_dev_path(sim)); + info4 = gpiodglib_test_chip_get_line_info_or_fail(chip, 4); + info6 = gpiodglib_test_chip_get_line_info_or_fail(chip, 6); + + g_assert_cmpuint(gpiodglib_line_info_get_offset(info4), ==, 4); + name = gpiodglib_line_info_dup_name(info4); + g_assert_cmpstr(name, ==, "baz"); + consumer = gpiodglib_line_info_dup_consumer(info4); + g_assert_cmpstr(consumer, ==, "hog4"); + g_assert_true(gpiodglib_line_info_is_used(info4)); + g_assert_cmpint(gpiodglib_line_info_get_direction(info4), ==, + GPIODGLIB_LINE_DIRECTION_OUTPUT); + g_assert_cmpint(gpiodglib_line_info_get_edge_detection(info4), ==, + GPIODGLIB_LINE_EDGE_NONE); + g_assert_false(gpiodglib_line_info_is_active_low(info4)); + g_assert_cmpint(gpiodglib_line_info_get_bias(info4), ==, + GPIODGLIB_LINE_BIAS_UNKNOWN); + g_assert_cmpint(gpiodglib_line_info_get_drive(info4), ==, + GPIODGLIB_LINE_DRIVE_PUSH_PULL); + g_assert_cmpint(gpiodglib_line_info_get_event_clock(info4), ==, + GPIODGLIB_LINE_CLOCK_MONOTONIC); + g_assert_false(gpiodglib_line_info_is_debounced(info4)); + g_assert_cmpuint(gpiodglib_line_info_get_debounce_period_us(info4), ==, + 0); +} diff --git a/bindings/glib/tests/tests-line-request.c b/bindings/glib/tests/tests-line-request.c new file mode 100644 index 0000000..5866282 --- /dev/null +++ b/bindings/glib/tests/tests-line-request.c @@ -0,0 +1,710 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2023-2024 Bartosz Golaszewski + +#include +#include +#include +#include + +#include "helpers.h" + +#define GPIOD_TEST_GROUP "glib/line-request" + +GPIOD_TEST_CASE(request_fails_with_no_offsets) +{ + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL); + g_autoptr(GpiodglibChip) chip = NULL; + g_autoptr(GpiodglibLineConfig) line_cfg = NULL; + g_autoptr(GpiodglibLineRequest) request = NULL; + g_autoptr(GError) err = NULL; + + line_cfg = gpiodglib_line_config_new(); + + chip = gpiodglib_test_new_chip_or_fail( + g_gpiosim_chip_get_dev_path(sim)); + + request = gpiodglib_chip_request_lines(chip, NULL, line_cfg, &err); + g_assert_null(request); + g_assert_error(err, GPIODGLIB_ERROR, GPIODGLIB_ERR_INVAL); +} + +GPIOD_TEST_CASE(request_fails_with_no_line_config) +{ + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL); + g_autoptr(GpiodglibChip) chip = NULL; + g_autoptr(GpiodglibLineRequest) request = NULL; + g_autoptr(GError) err = NULL; + + chip = gpiodglib_test_new_chip_or_fail( + g_gpiosim_chip_get_dev_path(sim)); + + request = gpiodglib_chip_request_lines(chip, NULL, NULL, &err); + g_assert_null(request); + g_assert_error(err, GPIODGLIB_ERROR, GPIODGLIB_ERR_INVAL); +} + +GPIOD_TEST_CASE(set_consumer) +{ + static const gchar *const consumer = "foobar"; + static const guint offset = 2; + + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL); + g_autoptr(GpiodglibChip) chip = NULL; + g_autoptr(GpiodglibRequestConfig) req_cfg = NULL; + g_autoptr(GpiodglibLineConfig) line_cfg = NULL; + g_autoptr(GpiodglibLineRequest) request = NULL; + g_autoptr(GArray) offsets = NULL; + g_autoptr(GpiodglibLineInfo) info = NULL; + g_autofree gchar *cpy = NULL; + + chip = gpiodglib_test_new_chip_or_fail( + g_gpiosim_chip_get_dev_path(sim)); + + req_cfg = gpiodglib_request_config_new("consumer", consumer, NULL); + line_cfg = gpiodglib_line_config_new(); + offsets = gpiodglib_test_array_from_const(&offset, 1, sizeof(guint)); + + gpiodglib_test_line_config_add_line_settings_or_fail(line_cfg, + offsets, NULL); + + request = gpiodglib_test_chip_request_lines_or_fail(chip, req_cfg, + line_cfg); + + info = gpiodglib_test_chip_get_line_info_or_fail(chip, offset); + cpy = gpiodglib_line_info_dup_consumer(info); + g_assert_cmpstr(cpy, ==, consumer); +} + +GPIOD_TEST_CASE(empty_consumer) +{ + static const guint offset = 2; + + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL); + g_autoptr(GpiodglibChip) chip = NULL; + g_autoptr(GpiodglibLineConfig) line_cfg = NULL; + g_autoptr(GpiodglibLineRequest) request = NULL; + g_autoptr(GArray) offsets = NULL; + g_autoptr(GpiodglibLineInfo) info = NULL; + g_autofree gchar *consumer = NULL; + + chip = gpiodglib_test_new_chip_or_fail( + g_gpiosim_chip_get_dev_path(sim)); + + line_cfg = gpiodglib_line_config_new(); + offsets = gpiodglib_test_array_from_const(&offset, 1, sizeof(guint)); + + gpiodglib_test_line_config_add_line_settings_or_fail(line_cfg, + offsets, NULL); + + request = gpiodglib_test_chip_request_lines_or_fail(chip, NULL, + line_cfg); + + info = gpiodglib_test_chip_get_line_info_or_fail(chip, offset); + consumer = gpiodglib_line_info_dup_consumer(info); + g_assert_cmpstr(consumer, ==, "?"); +} + +GPIOD_TEST_CASE(get_requested_offsets) +{ + static const guint offset_vals[] = { 2, 1, 6, 4 }; + + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL); + g_autoptr(GpiodglibLineConfig) line_cfg = NULL; + g_autoptr(GpiodglibLineRequest) request = NULL; + g_autoptr(GArray) offsets = NULL; + g_autoptr(GArray) retrieved = NULL; + + line_cfg = gpiodglib_line_config_new(); + offsets = gpiodglib_test_array_from_const(offset_vals, 4, + sizeof(guint)); + + gpiodglib_test_line_config_add_line_settings_or_fail(line_cfg, + offsets, NULL); + + request = gpiodglib_test_request_lines_or_fail( + g_gpiosim_chip_get_dev_path(sim), NULL, line_cfg); + + retrieved = gpiodglib_line_request_get_requested_offsets(request); + g_assert_cmpuint(retrieved->len, ==, 4); + g_assert_cmpuint(g_array_index(retrieved, guint, 0), ==, 2); + g_assert_cmpuint(g_array_index(retrieved, guint, 1), ==, 1); + g_assert_cmpuint(g_array_index(retrieved, guint, 2), ==, 6); + g_assert_cmpuint(g_array_index(retrieved, guint, 3), ==, 4); +} + +GPIOD_TEST_CASE(released_request_cannot_be_used_reconfigure) +{ + static const guint offset = 3; + + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL); + g_autoptr(GpiodglibLineConfig) line_cfg = NULL; + g_autoptr(GpiodglibLineRequest) request = NULL; + g_autoptr(GArray) offsets = NULL; + g_autoptr(GError) err = NULL; + gboolean ret; + + line_cfg = gpiodglib_line_config_new(); + offsets = gpiodglib_test_array_from_const(&offset, 1, sizeof(guint)); + + gpiodglib_test_line_config_add_line_settings_or_fail(line_cfg, + offsets, NULL); + + request = gpiodglib_test_request_lines_or_fail( + g_gpiosim_chip_get_dev_path(sim), NULL, line_cfg); + + gpiodglib_line_request_release(request); + + ret = gpiodglib_line_request_reconfigure_lines(request, line_cfg, &err); + g_assert_false(ret); + g_assert_error(err, GPIODGLIB_ERROR, GPIODGLIB_ERR_REQUEST_RELEASED); +} + +GPIOD_TEST_CASE(released_request_cannot_be_used_get_value) +{ + static const guint offset = 3; + + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL); + g_autoptr(GpiodglibLineSettings) settings = NULL; + g_autoptr(GpiodglibLineConfig) line_cfg = NULL; + g_autoptr(GpiodglibLineRequest) request = NULL; + g_autoptr(GArray) offsets = NULL; + g_autoptr(GArray) values = NULL; + g_autoptr(GError) err = NULL; + GpiodglibLineValue value; + gboolean ret; + + line_cfg = gpiodglib_line_config_new(); + settings = gpiodglib_line_settings_new( + "direction", GPIODGLIB_LINE_DIRECTION_INPUT, NULL); + offsets = gpiodglib_test_array_from_const(&offset, 1, sizeof(guint)); + gpiodglib_test_line_config_add_line_settings_or_fail(line_cfg, + offsets, NULL); + + request = gpiodglib_test_request_lines_or_fail( + g_gpiosim_chip_get_dev_path(sim), NULL, line_cfg); + + gpiodglib_line_request_release(request); + + ret = gpiodglib_line_request_get_value(request, offset, &value, &err); + g_assert_false(ret); + g_assert_error(err, GPIODGLIB_ERROR, GPIODGLIB_ERR_REQUEST_RELEASED); + + g_clear_pointer(&err, g_error_free); + + ret = gpiodglib_line_request_get_values(request, &values, &err); + g_assert_false(ret); + g_assert_error(err, GPIODGLIB_ERROR, GPIODGLIB_ERR_REQUEST_RELEASED); +} + +GPIOD_TEST_CASE(released_request_cannot_be_used_set_value) +{ + static const guint offset = 3; + static const GpiodglibLineValue value = GPIODGLIB_LINE_VALUE_ACTIVE; + + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL); + g_autoptr(GpiodglibLineSettings) settings = NULL; + g_autoptr(GpiodglibLineConfig) line_cfg = NULL; + g_autoptr(GpiodglibLineRequest) request = NULL; + g_autoptr(GArray) offsets = NULL; + g_autoptr(GArray) values = NULL; + g_autoptr(GError) err = NULL; + gboolean ret; + + line_cfg = gpiodglib_line_config_new(); + settings = gpiodglib_line_settings_new( + "direction", GPIODGLIB_LINE_DIRECTION_OUTPUT, NULL); + offsets = gpiodglib_test_array_from_const(&offset, 1, sizeof(guint)); + gpiodglib_test_line_config_add_line_settings_or_fail(line_cfg, + offsets, NULL); + + request = gpiodglib_test_request_lines_or_fail( + g_gpiosim_chip_get_dev_path(sim), NULL, line_cfg); + + gpiodglib_line_request_release(request); + + ret = gpiodglib_line_request_set_value(request, offset, value, &err); + g_assert_false(ret); + g_assert_error(err, GPIODGLIB_ERROR, GPIODGLIB_ERR_REQUEST_RELEASED); + + g_clear_pointer(&err, g_error_free); + + values = gpiodglib_test_array_from_const(&value, 1, sizeof(value)); + ret = gpiodglib_line_request_set_values(request, values, &err); + g_assert_false(ret); + g_assert_error(err, GPIODGLIB_ERROR, GPIODGLIB_ERR_REQUEST_RELEASED); +} + +GPIOD_TEST_CASE(reconfigure_lines) +{ + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL); + g_autoptr(GpiodglibLineConfig) line_cfg = NULL; + g_autoptr(GpiodglibLineSettings) settings = NULL; + g_autoptr(GpiodglibLineRequest) request = NULL; + g_autoptr(GArray) offsets = NULL; + g_autoptr(GError) err = NULL; + guint offset_vals[2]; + gboolean ret; + + line_cfg = gpiodglib_line_config_new(); + settings = gpiodglib_line_settings_new( + "direction", GPIODGLIB_LINE_DIRECTION_OUTPUT, + "output-value", GPIODGLIB_LINE_VALUE_ACTIVE, + NULL); + + offsets = g_array_new(FALSE, TRUE, sizeof(guint)); + offset_vals[0] = 0; + offset_vals[1] = 2; + g_array_append_vals(offsets, offset_vals, 2); + gpiodglib_test_line_config_add_line_settings_or_fail(line_cfg, + offsets, + settings); + g_free(g_array_steal(offsets, NULL)); + + gpiodglib_line_settings_set_output_value(settings, + GPIODGLIB_LINE_VALUE_INACTIVE); + offset_vals[0] = 1; + offset_vals[1] = 3; + g_array_append_vals(offsets, offset_vals, 2); + gpiodglib_test_line_config_add_line_settings_or_fail(line_cfg, + offsets, + settings); + g_free(g_array_steal(offsets, NULL)); + + request = gpiodglib_test_request_lines_or_fail( + g_gpiosim_chip_get_dev_path(sim), NULL, line_cfg); + + g_assert_cmpint(g_gpiosim_chip_get_value(sim, 0), ==, + G_GPIOSIM_VALUE_ACTIVE); + g_assert_cmpint(g_gpiosim_chip_get_value(sim, 1), ==, + G_GPIOSIM_VALUE_INACTIVE); + g_assert_cmpint(g_gpiosim_chip_get_value(sim, 2), ==, + G_GPIOSIM_VALUE_ACTIVE); + g_assert_cmpint(g_gpiosim_chip_get_value(sim, 3), ==, + G_GPIOSIM_VALUE_INACTIVE); + + gpiodglib_line_config_reset(line_cfg); + + gpiodglib_line_settings_set_output_value(settings, + GPIODGLIB_LINE_VALUE_INACTIVE); + offset_vals[0] = 0; + offset_vals[1] = 2; + g_array_append_vals(offsets, offset_vals, 2); + gpiodglib_test_line_config_add_line_settings_or_fail(line_cfg, + offsets, + settings); + g_free(g_array_steal(offsets, NULL)); + + gpiodglib_line_settings_set_output_value(settings, + GPIODGLIB_LINE_VALUE_ACTIVE); + offset_vals[0] = 1; + offset_vals[1] = 3; + g_array_append_vals(offsets, offset_vals, 2); + gpiodglib_test_line_config_add_line_settings_or_fail(line_cfg, + offsets, + settings); + + ret = gpiodglib_line_request_reconfigure_lines(request, line_cfg, &err); + g_assert_true(ret); + g_assert_no_error(err); + gpiod_test_return_if_failed(); + + g_assert_cmpint(g_gpiosim_chip_get_value(sim, 0), ==, + G_GPIOSIM_VALUE_INACTIVE); + g_assert_cmpint(g_gpiosim_chip_get_value(sim, 1), ==, + G_GPIOSIM_VALUE_ACTIVE); + g_assert_cmpint(g_gpiosim_chip_get_value(sim, 2), ==, + G_GPIOSIM_VALUE_INACTIVE); + g_assert_cmpint(g_gpiosim_chip_get_value(sim, 3), ==, + G_GPIOSIM_VALUE_ACTIVE); +} + +GPIOD_TEST_CASE(reconfigure_fails_without_config) +{ + static const guint offset = 3; + + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL); + g_autoptr(GpiodglibLineConfig) line_cfg = NULL; + g_autoptr(GpiodglibLineRequest) request = NULL; + g_autoptr(GArray) offsets = NULL; + g_autoptr(GError) err = NULL; + gboolean ret; + + line_cfg = gpiodglib_line_config_new(); + offsets = gpiodglib_test_array_from_const(&offset, 1, sizeof(guint)); + + gpiodglib_test_line_config_add_line_settings_or_fail(line_cfg, + offsets, NULL); + + request = gpiodglib_test_request_lines_or_fail( + g_gpiosim_chip_get_dev_path(sim), NULL, line_cfg); + + ret = gpiodglib_line_request_reconfigure_lines(request, NULL, &err); + g_assert_false(ret); + g_assert_error(err, GPIODGLIB_ERROR, GPIODGLIB_ERR_INVAL); +} + +GPIOD_TEST_CASE(reconfigure_with_different_offsets) +{ + static const guint offsets0[] = { 0, 1, 2, 3 }; + static const guint offsets1[] = { 2, 4, 5 }; + + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL); + g_autoptr(GpiodglibLineConfig) line_cfg = NULL; + g_autoptr(GpiodglibLineRequest) request = NULL; + g_autoptr(GArray) offsets = NULL; + g_autoptr(GError) err = NULL; + gboolean ret; + + line_cfg = gpiodglib_line_config_new(); + offsets = gpiodglib_test_array_from_const(offsets0, 4, sizeof(guint)); + gpiodglib_test_line_config_add_line_settings_or_fail(line_cfg, + offsets, NULL); + g_free(g_array_steal(offsets, NULL)); + + request = gpiodglib_test_request_lines_or_fail( + g_gpiosim_chip_get_dev_path(sim), NULL, line_cfg); + + gpiodglib_line_config_reset(line_cfg); + + g_array_append_vals(offsets, offsets1, 3); + gpiodglib_test_line_config_add_line_settings_or_fail(line_cfg, + offsets, NULL); + + ret = gpiodglib_line_request_reconfigure_lines(request, line_cfg, &err); + g_assert_false(ret); + g_assert_error(err, GPIODGLIB_ERROR, GPIODGLIB_ERR_INVAL); +} + +GPIOD_TEST_CASE(read_one_value) +{ + static const guint offset_vals[] = { 0, 2, 4 }; + static const gint pulls[] = { 0, 1, 0 }; + + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL); + g_autoptr(GpiodglibLineConfig) line_cfg = NULL; + g_autoptr(GpiodglibLineSettings) settings = NULL; + g_autoptr(GpiodglibLineRequest) request = NULL; + g_autoptr(GArray) offsets = NULL; + g_autoptr(GError) err = NULL; + GpiodglibLineValue value; + gboolean ret; + guint i; + + line_cfg = gpiodglib_line_config_new(); + settings = gpiodglib_line_settings_new( + "direction", GPIODGLIB_LINE_DIRECTION_INPUT, NULL); + offsets = gpiodglib_test_array_from_const(offset_vals, 3, + sizeof(guint)); + gpiodglib_test_line_config_add_line_settings_or_fail(line_cfg, + offsets, + settings); + + request = gpiodglib_test_request_lines_or_fail( + g_gpiosim_chip_get_dev_path(sim), NULL, line_cfg); + + for (i = 0; i < 3; i++) + g_gpiosim_chip_set_pull(sim, offset_vals[i], + pulls[i] ? G_GPIOSIM_PULL_UP : + G_GPIOSIM_PULL_DOWN); + + ret = gpiodglib_line_request_get_value(request, 2, &value, &err); + g_assert_true(ret); + g_assert_no_error(err); + gpiod_test_return_if_failed(); + g_assert_cmpint(value, ==, GPIODGLIB_LINE_VALUE_ACTIVE); +} + +GPIOD_TEST_CASE(read_all_values_null_array) +{ + static const guint offset_vals[] = { 0, 2, 4, 5, 7 }; + static const gint pulls[] = { 0, 1, 0, 1, 1 }; + + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL); + g_autoptr(GpiodglibLineConfig) line_cfg = NULL; + g_autoptr(GpiodglibLineSettings) settings = NULL; + g_autoptr(GpiodglibLineRequest) request = NULL; + g_autoptr(GArray) offsets = NULL; + g_autoptr(GArray) values = NULL; + g_autoptr(GError) err = NULL; + gboolean ret; + guint i; + + line_cfg = gpiodglib_line_config_new(); + settings = gpiodglib_line_settings_new( + "direction", GPIODGLIB_LINE_DIRECTION_INPUT, NULL); + offsets = gpiodglib_test_array_from_const(offset_vals, 5, + sizeof(guint)); + gpiodglib_test_line_config_add_line_settings_or_fail(line_cfg, + offsets, + settings); + + request = gpiodglib_test_request_lines_or_fail( + g_gpiosim_chip_get_dev_path(sim), NULL, line_cfg); + + for (i = 0; i < 5; i++) + g_gpiosim_chip_set_pull(sim, offset_vals[i], + pulls[i] ? G_GPIOSIM_PULL_UP : + G_GPIOSIM_PULL_DOWN); + + ret = gpiodglib_line_request_get_values(request, &values, &err); + g_assert_true(ret); + g_assert_no_error(err); + gpiod_test_return_if_failed(); + + g_assert_cmpuint(values->len, ==, 5); + + for (i = 0; i < 5; i++) + g_assert_cmpint(g_array_index(values, GpiodglibLineValue, i), ==, + pulls[i]); +} + +GPIOD_TEST_CASE(read_all_values_preallocated_array) +{ + static const guint offset_vals[] = { 0, 2, 4, 5, 7 }; + static const gint pulls[] = { 0, 1, 0, 1, 1 }; + + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL); + g_autoptr(GpiodglibLineConfig) line_cfg = NULL; + g_autoptr(GpiodglibLineSettings) settings = NULL; + g_autoptr(GpiodglibLineRequest) request = NULL; + g_autoptr(GArray) offsets = NULL; + g_autoptr(GArray) values = NULL; + g_autoptr(GError) err = NULL; + gboolean ret; + guint i; + + line_cfg = gpiodglib_line_config_new(); + settings = gpiodglib_line_settings_new( + "direction", GPIODGLIB_LINE_DIRECTION_INPUT, NULL); + offsets = gpiodglib_test_array_from_const(offset_vals, 5, + sizeof(guint)); + gpiodglib_test_line_config_add_line_settings_or_fail(line_cfg, + offsets, + settings); + + request = gpiodglib_test_request_lines_or_fail( + g_gpiosim_chip_get_dev_path(sim), NULL, line_cfg); + + for (i = 0; i < 5; i++) + g_gpiosim_chip_set_pull(sim, offset_vals[i], + pulls[i] ? G_GPIOSIM_PULL_UP : + G_GPIOSIM_PULL_DOWN); + + values = g_array_new(FALSE, TRUE, sizeof(GpiodglibLineValue)); + g_array_set_size(values, 5); + + ret = gpiodglib_line_request_get_values(request, &values, &err); + g_assert_true(ret); + g_assert_no_error(err); + gpiod_test_return_if_failed(); + + g_assert_cmpuint(values->len, ==, 5); + + for (i = 0; i < 5; i++) + g_assert_cmpint(g_array_index(values, GpiodglibLineValue, i), + ==, pulls[i]); +} + +GPIOD_TEST_CASE(set_one_value) +{ + static const guint offset = 4; + + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL); + g_autoptr(GpiodglibLineConfig) line_cfg = NULL; + g_autoptr(GpiodglibLineSettings) settings = NULL; + g_autoptr(GpiodglibLineRequest) request = NULL; + g_autoptr(GArray) offsets = NULL; + g_autoptr(GError) err = NULL; + gboolean ret; + + line_cfg = gpiodglib_line_config_new(); + settings = gpiodglib_line_settings_new( + "direction", GPIODGLIB_LINE_DIRECTION_OUTPUT, + "output-value", GPIODGLIB_LINE_VALUE_INACTIVE, + NULL); + offsets = gpiodglib_test_array_from_const(&offset, 1, sizeof(guint)); + gpiodglib_test_line_config_add_line_settings_or_fail(line_cfg, + offsets, + settings); + + request = gpiodglib_test_request_lines_or_fail( + g_gpiosim_chip_get_dev_path(sim), NULL, line_cfg); + + g_assert_cmpuint(g_gpiosim_chip_get_value(sim, offset), ==, + G_GPIOSIM_VALUE_INACTIVE); + + ret = gpiodglib_line_request_set_value(request, 4, + GPIODGLIB_LINE_VALUE_ACTIVE, + &err); + g_assert_true(ret); + g_assert_no_error(err); + + g_assert_cmpuint(g_gpiosim_chip_get_value(sim, offset), ==, + G_GPIOSIM_VALUE_ACTIVE); +} + +GPIOD_TEST_CASE(set_all_values) +{ + static const guint offset_vals[] = { 0, 2, 4, 5, 6 }; + static const GpiodglibLineValue value_vals[] = { + GPIODGLIB_LINE_VALUE_ACTIVE, + GPIODGLIB_LINE_VALUE_INACTIVE, + GPIODGLIB_LINE_VALUE_ACTIVE, + GPIODGLIB_LINE_VALUE_ACTIVE, + GPIODGLIB_LINE_VALUE_ACTIVE + }; + static const GPIOSimValue sim_values[] = { + G_GPIOSIM_VALUE_ACTIVE, + G_GPIOSIM_VALUE_INACTIVE, + G_GPIOSIM_VALUE_ACTIVE, + G_GPIOSIM_VALUE_ACTIVE, + G_GPIOSIM_VALUE_ACTIVE + }; + + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL); + g_autoptr(GpiodglibLineConfig) line_cfg = NULL; + g_autoptr(GpiodglibLineSettings) settings = NULL; + g_autoptr(GpiodglibLineRequest) request = NULL; + g_autoptr(GArray) offsets = NULL; + g_autoptr(GArray) values = NULL; + g_autoptr(GError) err = NULL; + gboolean ret; + guint i; + + line_cfg = gpiodglib_line_config_new(); + settings = gpiodglib_line_settings_new( + "direction", GPIODGLIB_LINE_DIRECTION_OUTPUT, NULL); + offsets = gpiodglib_test_array_from_const(offset_vals, 5, sizeof(guint)); + gpiodglib_test_line_config_add_line_settings_or_fail(line_cfg, + offsets, + settings); + + request = gpiodglib_test_request_lines_or_fail( + g_gpiosim_chip_get_dev_path(sim), NULL, line_cfg); + + values = gpiodglib_test_array_from_const(value_vals, 5, + sizeof(GpiodglibLineValue)); + + ret = gpiodglib_line_request_set_values(request, values, &err); + g_assert_true(ret); + g_assert_no_error(err); + gpiod_test_return_if_failed(); + + for (i = 0; i < 5; i++) + g_assert_cmpint(g_gpiosim_chip_get_value(sim, offset_vals[i]), + ==, sim_values[i]); +} + +GPIOD_TEST_CASE(get_values_invalid_arguments) +{ + static const guint offset = 3; + + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL); + g_autoptr(GpiodglibLineSettings) settings = NULL; + g_autoptr(GpiodglibLineConfig) line_cfg = NULL; + g_autoptr(GpiodglibLineRequest) request = NULL; + g_autoptr(GArray) offsets = NULL; + g_autoptr(GArray) values = NULL; + g_autoptr(GError) err = NULL; + gboolean ret; + + line_cfg = gpiodglib_line_config_new(); + settings = gpiodglib_line_settings_new( + "direction", GPIODGLIB_LINE_DIRECTION_INPUT, NULL); + offsets = gpiodglib_test_array_from_const(&offset, 1, sizeof(offset)); + gpiodglib_test_line_config_add_line_settings_or_fail(line_cfg, + offsets, + settings); + + request = gpiodglib_test_request_lines_or_fail( + g_gpiosim_chip_get_dev_path(sim), NULL, line_cfg); + + ret = gpiodglib_line_request_get_values_subset(request, offsets, NULL, + &err); + g_assert_false(ret); + g_assert_error(err, GPIODGLIB_ERROR, GPIODGLIB_ERR_INVAL); + + g_clear_pointer(&err, g_error_free); + + ret = gpiodglib_line_request_get_values_subset(request, NULL, &values, + &err); + g_assert_false(ret); + g_assert_error(err, GPIODGLIB_ERROR, GPIODGLIB_ERR_INVAL); +} + +GPIOD_TEST_CASE(set_values_invalid_arguments) +{ + static const guint offset = 3; + static const GpiodglibLineValue value_vals[] = { + GPIODGLIB_LINE_VALUE_ACTIVE, + GPIODGLIB_LINE_VALUE_INACTIVE, + }; + + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL); + g_autoptr(GpiodglibLineSettings) settings = NULL; + g_autoptr(GpiodglibLineConfig) line_cfg = NULL; + g_autoptr(GpiodglibLineRequest) request = NULL; + g_autoptr(GArray) offsets = NULL; + g_autoptr(GArray) values = NULL; + g_autoptr(GArray) vals_inval = NULL; + g_autoptr(GError) err = NULL; + gboolean ret; + + line_cfg = gpiodglib_line_config_new(); + settings = gpiodglib_line_settings_new( + "direction", GPIODGLIB_LINE_DIRECTION_OUTPUT, NULL); + offsets = gpiodglib_test_array_from_const(&offset, 1, sizeof(offset)); + values = gpiodglib_test_array_from_const(value_vals, 1, + sizeof(GpiodglibLineValue)); + gpiodglib_test_line_config_add_line_settings_or_fail(line_cfg, + offsets, + settings); + + request = gpiodglib_test_request_lines_or_fail( + g_gpiosim_chip_get_dev_path(sim), NULL, line_cfg); + + ret = gpiodglib_line_request_set_values_subset(request, offsets, NULL, + &err); + g_assert_false(ret); + g_assert_error(err, GPIODGLIB_ERROR, GPIODGLIB_ERR_INVAL); + + g_clear_pointer(&err, g_error_free); + + ret = gpiodglib_line_request_set_values_subset(request, NULL, values, + &err); + g_assert_false(ret); + g_assert_error(err, GPIODGLIB_ERROR, GPIODGLIB_ERR_INVAL); + + g_clear_pointer(&err, g_error_free); + + vals_inval = gpiodglib_test_array_from_const(value_vals, 2, + sizeof(GpiodglibLineValue)); + + ret = gpiodglib_line_request_set_values_subset(request, offsets, + vals_inval, &err); + g_assert_false(ret); + g_assert_error(err, GPIODGLIB_ERROR, GPIODGLIB_ERR_INVAL); +} + +GPIOD_TEST_CASE(get_chip_name) +{ + static const guint offset = 4; + + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL); + g_autoptr(GpiodglibLineConfig) line_cfg = NULL; + g_autoptr(GpiodglibLineRequest) request = NULL; + g_autoptr(GArray) offsets = NULL; + g_autofree gchar *name = NULL; + + line_cfg = gpiodglib_line_config_new(); + offsets = gpiodglib_test_array_from_const(&offset, 1, sizeof(guint)); + + gpiodglib_test_line_config_add_line_settings_or_fail(line_cfg, + offsets, NULL); + + request = gpiodglib_test_request_lines_or_fail( + g_gpiosim_chip_get_dev_path(sim), NULL, line_cfg); + + name = gpiodglib_line_request_dup_chip_name(request); + g_assert_cmpstr(g_gpiosim_chip_get_name(sim), ==, name); +} diff --git a/bindings/glib/tests/tests-line-settings.c b/bindings/glib/tests/tests-line-settings.c new file mode 100644 index 0000000..35d2a8d --- /dev/null +++ b/bindings/glib/tests/tests-line-settings.c @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2023-2024 Bartosz Golaszewski + +#include +#include + +#include "helpers.h" + +#define GPIOD_TEST_GROUP "glib/line-settings" + +GPIOD_TEST_CASE(default_config) +{ + g_autoptr(GpiodglibLineSettings) settings = NULL; + + settings = gpiodglib_line_settings_new(NULL); + + g_assert_cmpint(gpiodglib_line_settings_get_direction(settings), ==, + GPIODGLIB_LINE_DIRECTION_AS_IS); + g_assert_cmpint(gpiodglib_line_settings_get_edge_detection(settings), + ==, GPIODGLIB_LINE_EDGE_NONE); + g_assert_cmpint(gpiodglib_line_settings_get_bias(settings), ==, + GPIODGLIB_LINE_BIAS_AS_IS); + g_assert_cmpint(gpiodglib_line_settings_get_drive(settings), ==, + GPIODGLIB_LINE_DRIVE_PUSH_PULL); + g_assert_false(gpiodglib_line_settings_get_active_low(settings)); + g_assert_cmpint( + gpiodglib_line_settings_get_debounce_period_us(settings), + ==, 0); + g_assert_cmpint(gpiodglib_line_settings_get_event_clock(settings), ==, + GPIODGLIB_LINE_CLOCK_MONOTONIC); + g_assert_cmpint(gpiodglib_line_settings_get_output_value(settings), ==, + GPIODGLIB_LINE_VALUE_INACTIVE); +} + +GPIOD_TEST_CASE(set_direction) +{ + g_autoptr(GpiodglibLineSettings) settings = NULL; + + settings = gpiodglib_line_settings_new(NULL); + + gpiodglib_line_settings_set_direction(settings, + GPIODGLIB_LINE_DIRECTION_INPUT); + g_assert_cmpint(gpiodglib_line_settings_get_direction(settings), ==, + GPIODGLIB_LINE_DIRECTION_INPUT); + + gpiodglib_line_settings_set_direction(settings, + GPIODGLIB_LINE_DIRECTION_AS_IS); + g_assert_cmpint(gpiodglib_line_settings_get_direction(settings), ==, + GPIODGLIB_LINE_DIRECTION_AS_IS); + + gpiodglib_line_settings_set_direction(settings, + GPIODGLIB_LINE_DIRECTION_OUTPUT); + g_assert_cmpint(gpiodglib_line_settings_get_direction(settings), ==, + GPIODGLIB_LINE_DIRECTION_OUTPUT); +} + +GPIOD_TEST_CASE(set_edge_detection) +{ + g_autoptr(GpiodglibLineSettings) settings = NULL; + + settings = gpiodglib_line_settings_new(NULL); + + gpiodglib_line_settings_set_edge_detection(settings, + GPIODGLIB_LINE_EDGE_BOTH); + g_assert_cmpint(gpiodglib_line_settings_get_edge_detection(settings), + ==, GPIODGLIB_LINE_EDGE_BOTH); + + gpiodglib_line_settings_set_edge_detection(settings, + GPIODGLIB_LINE_EDGE_NONE); + g_assert_cmpint(gpiodglib_line_settings_get_edge_detection(settings), + ==, GPIODGLIB_LINE_EDGE_NONE); + + gpiodglib_line_settings_set_edge_detection(settings, + GPIODGLIB_LINE_EDGE_FALLING); + g_assert_cmpint(gpiodglib_line_settings_get_edge_detection(settings), + ==, GPIODGLIB_LINE_EDGE_FALLING); + + gpiodglib_line_settings_set_edge_detection(settings, + GPIODGLIB_LINE_EDGE_RISING); + g_assert_cmpint(gpiodglib_line_settings_get_edge_detection(settings), + ==, GPIODGLIB_LINE_EDGE_RISING); +} + +GPIOD_TEST_CASE(set_bias) +{ + g_autoptr(GpiodglibLineSettings) settings = NULL; + + settings = gpiodglib_line_settings_new(NULL); + + gpiodglib_line_settings_set_bias(settings, + GPIODGLIB_LINE_BIAS_DISABLED); + g_assert_cmpint(gpiodglib_line_settings_get_bias(settings), ==, + GPIODGLIB_LINE_BIAS_DISABLED); + + gpiodglib_line_settings_set_bias(settings, GPIODGLIB_LINE_BIAS_AS_IS); + g_assert_cmpint(gpiodglib_line_settings_get_bias(settings), ==, + GPIODGLIB_LINE_BIAS_AS_IS); + + gpiodglib_line_settings_set_bias(settings, + GPIODGLIB_LINE_BIAS_PULL_DOWN); + g_assert_cmpint(gpiodglib_line_settings_get_bias(settings), ==, + GPIODGLIB_LINE_BIAS_PULL_DOWN); + + gpiodglib_line_settings_set_bias(settings, GPIODGLIB_LINE_BIAS_PULL_UP); + g_assert_cmpint(gpiodglib_line_settings_get_bias(settings), ==, + GPIODGLIB_LINE_BIAS_PULL_UP); +} + +GPIOD_TEST_CASE(set_drive) +{ + g_autoptr(GpiodglibLineSettings) settings = NULL; + + settings = gpiodglib_line_settings_new(NULL); + + gpiodglib_line_settings_set_drive(settings, + GPIODGLIB_LINE_DRIVE_OPEN_DRAIN); + g_assert_cmpint(gpiodglib_line_settings_get_drive(settings), ==, + GPIODGLIB_LINE_DRIVE_OPEN_DRAIN); + + gpiodglib_line_settings_set_drive(settings, + GPIODGLIB_LINE_DRIVE_PUSH_PULL); + g_assert_cmpint(gpiodglib_line_settings_get_drive(settings), ==, + GPIODGLIB_LINE_DRIVE_PUSH_PULL); + + gpiodglib_line_settings_set_drive(settings, + GPIODGLIB_LINE_DRIVE_OPEN_SOURCE); + g_assert_cmpint(gpiodglib_line_settings_get_drive(settings), ==, + GPIODGLIB_LINE_DRIVE_OPEN_SOURCE); +} + +GPIOD_TEST_CASE(set_active_low) +{ + g_autoptr(GpiodglibLineSettings) settings = NULL; + + settings = gpiodglib_line_settings_new(NULL); + + gpiodglib_line_settings_set_active_low(settings, TRUE); + g_assert_true(gpiodglib_line_settings_get_active_low(settings)); + + gpiodglib_line_settings_set_active_low(settings, FALSE); + g_assert_false(gpiodglib_line_settings_get_active_low(settings)); +} + +GPIOD_TEST_CASE(set_debounce_period) +{ + g_autoptr(GpiodglibLineSettings) settings = NULL; + + settings = gpiodglib_line_settings_new(NULL); + + gpiodglib_line_settings_set_debounce_period_us(settings, 4000); + g_assert_cmpint(gpiodglib_line_settings_get_debounce_period_us(settings), + ==, 4000); +} + +GPIOD_TEST_CASE(set_event_clock) +{ + g_autoptr(GpiodglibLineSettings) settings = NULL; + + settings = gpiodglib_line_settings_new(NULL); + + gpiodglib_line_settings_set_event_clock(settings, + GPIODGLIB_LINE_CLOCK_MONOTONIC); + g_assert_cmpint(gpiodglib_line_settings_get_event_clock(settings), ==, + GPIODGLIB_LINE_CLOCK_MONOTONIC); + + gpiodglib_line_settings_set_event_clock(settings, + GPIODGLIB_LINE_CLOCK_REALTIME); + g_assert_cmpint(gpiodglib_line_settings_get_event_clock(settings), ==, + GPIODGLIB_LINE_CLOCK_REALTIME); + + gpiodglib_line_settings_set_event_clock(settings, + GPIODGLIB_LINE_CLOCK_HTE); + g_assert_cmpint(gpiodglib_line_settings_get_event_clock(settings), ==, + GPIODGLIB_LINE_CLOCK_HTE); +} + +GPIOD_TEST_CASE(set_output_value) +{ + g_autoptr(GpiodglibLineSettings) settings = NULL; + + settings = gpiodglib_line_settings_new(NULL); + + gpiodglib_line_settings_set_output_value(settings, + GPIODGLIB_LINE_VALUE_ACTIVE); + g_assert_cmpint(gpiodglib_line_settings_get_output_value(settings), ==, + GPIODGLIB_LINE_VALUE_ACTIVE); + + gpiodglib_line_settings_set_output_value(settings, + GPIODGLIB_LINE_VALUE_INACTIVE); + g_assert_cmpint(gpiodglib_line_settings_get_output_value(settings), ==, + GPIODGLIB_LINE_VALUE_INACTIVE); +} + +GPIOD_TEST_CASE(reset_settings) +{ + g_autoptr(GpiodglibLineSettings) settings = NULL; + + settings = gpiodglib_line_settings_new(NULL); + + gpiodglib_line_settings_set_direction(settings, + GPIODGLIB_LINE_DIRECTION_INPUT); + gpiodglib_line_settings_set_edge_detection(settings, + GPIODGLIB_LINE_EDGE_BOTH); + gpiodglib_line_settings_set_debounce_period_us(settings, 2000); + gpiodglib_line_settings_set_event_clock(settings, + GPIODGLIB_LINE_CLOCK_REALTIME); + + gpiodglib_line_settings_reset(settings); + + g_assert_cmpint(gpiodglib_line_settings_get_direction(settings), ==, + GPIODGLIB_LINE_DIRECTION_AS_IS); + g_assert_cmpint(gpiodglib_line_settings_get_edge_detection(settings), + ==, GPIODGLIB_LINE_EDGE_NONE); + g_assert_cmpint(gpiodglib_line_settings_get_bias(settings), ==, + GPIODGLIB_LINE_BIAS_AS_IS); + g_assert_cmpint(gpiodglib_line_settings_get_drive(settings), ==, + GPIODGLIB_LINE_DRIVE_PUSH_PULL); + g_assert_false(gpiodglib_line_settings_get_active_low(settings)); + g_assert_cmpint( + gpiodglib_line_settings_get_debounce_period_us(settings), + ==, 0); + g_assert_cmpint(gpiodglib_line_settings_get_event_clock(settings), ==, + GPIODGLIB_LINE_CLOCK_MONOTONIC); + g_assert_cmpint(gpiodglib_line_settings_get_output_value(settings), ==, + GPIODGLIB_LINE_VALUE_INACTIVE); +} + +GPIOD_TEST_CASE(set_props_in_constructor) +{ + g_autoptr(GpiodglibLineSettings) settings = NULL; + + settings = gpiodglib_line_settings_new( + "direction", GPIODGLIB_LINE_DIRECTION_INPUT, + "edge-detection", GPIODGLIB_LINE_EDGE_BOTH, + "active-low", TRUE, + "debounce-period-us", (GTimeSpan)3000, + "bias", GPIODGLIB_LINE_BIAS_PULL_UP, + "event-clock", GPIODGLIB_LINE_CLOCK_REALTIME, + NULL); + + g_assert_cmpint(gpiodglib_line_settings_get_direction(settings), ==, + GPIODGLIB_LINE_DIRECTION_INPUT); + g_assert_cmpint(gpiodglib_line_settings_get_edge_detection(settings), ==, + GPIODGLIB_LINE_EDGE_BOTH); + g_assert_cmpint(gpiodglib_line_settings_get_bias(settings), ==, + GPIODGLIB_LINE_BIAS_PULL_UP); + g_assert_cmpint(gpiodglib_line_settings_get_drive(settings), ==, + GPIODGLIB_LINE_DRIVE_PUSH_PULL); + g_assert_true(gpiodglib_line_settings_get_active_low(settings)); + g_assert_cmpint(gpiodglib_line_settings_get_debounce_period_us(settings), + ==, 3000); + g_assert_cmpint(gpiodglib_line_settings_get_event_clock(settings), ==, + GPIODGLIB_LINE_CLOCK_REALTIME); + g_assert_cmpint(gpiodglib_line_settings_get_output_value(settings), ==, + GPIODGLIB_LINE_VALUE_INACTIVE); +} diff --git a/bindings/glib/tests/tests-misc.c b/bindings/glib/tests/tests-misc.c new file mode 100644 index 0000000..a19a20e --- /dev/null +++ b/bindings/glib/tests/tests-misc.c @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2022-2024 Bartosz Golaszewski + +#include +#include +#include +#include +#include + +#define GPIOD_TEST_GROUP "glib/misc" + +GPIOD_TEST_CASE(is_gpiochip_bad) +{ + g_assert_false(gpiodglib_is_gpiochip_device("/dev/null")); + g_assert_false(gpiodglib_is_gpiochip_device("/dev/nonexistent")); +} + +GPIOD_TEST_CASE(is_gpiochip_good) +{ + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new(NULL); + + g_assert_true(gpiodglib_is_gpiochip_device( + g_gpiosim_chip_get_dev_path(sim))); +} + +GPIOD_TEST_CASE(is_gpiochip_link_bad) +{ + g_autofree gchar *link = NULL; + gint ret; + + link = g_strdup_printf("/tmp/gpiod-test-link.%u", getpid()); + ret = symlink("/dev/null", link); + g_assert_cmpint(ret, ==, 0); + gpiod_test_return_if_failed(); + + g_assert_false(gpiodglib_is_gpiochip_device(link)); + ret = unlink(link); + g_assert_cmpint(ret, ==, 0); +} + +GPIOD_TEST_CASE(is_gpiochip_link_good) +{ + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new(NULL); + g_autofree gchar *link = NULL; + gint ret; + + link = g_strdup_printf("/tmp/gpiod-test-link.%u", getpid()); + ret = symlink(g_gpiosim_chip_get_dev_path(sim), link); + g_assert_cmpint(ret, ==, 0); + gpiod_test_return_if_failed(); + + g_assert_true(gpiodglib_is_gpiochip_device(link)); + ret = unlink(link); + g_assert_cmpint(ret, ==, 0); +} + +GPIOD_TEST_CASE(version_string) +{ + static const gchar *const pattern = "^\\d+\\.\\d+(\\.\\d+|\\-devel|\\-rc\\d+)$"; + + g_autoptr(GError) err = NULL; + g_autoptr(GRegex) regex = NULL; + g_autoptr(GMatchInfo) match = NULL; + g_autofree gchar *res = NULL; + const gchar *ver; + gboolean ret; + + ver = gpiodglib_api_version(); + g_assert_nonnull(ver); + gpiod_test_return_if_failed(); + + regex = g_regex_new(pattern, 0, 0, &err); + g_assert_nonnull(regex); + g_assert_no_error(err); + gpiod_test_return_if_failed(); + + ret = g_regex_match(regex, ver, 0, &match); + g_assert_true(ret); + gpiod_test_return_if_failed(); + + g_assert_true(g_match_info_matches(match)); + res = g_match_info_fetch(match, 0); + g_assert_nonnull(res); + g_assert_cmpstr(res, ==, ver); + g_match_info_next(match, &err); + g_assert_no_error(err); + g_assert_false(g_match_info_matches(match)); +} diff --git a/bindings/glib/tests/tests-request-config.c b/bindings/glib/tests/tests-request-config.c new file mode 100644 index 0000000..23ebea5 --- /dev/null +++ b/bindings/glib/tests/tests-request-config.c @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2023-2024 Bartosz Golaszewski + +#include +#include +#include + +#include "helpers.h" + +#define GPIOD_TEST_GROUP "glib/request-config" + +GPIOD_TEST_CASE(default_config) +{ + g_autoptr(GpiodglibRequestConfig) config = NULL; + g_autofree gchar *consumer = NULL; + + config = gpiodglib_request_config_new(NULL); + consumer = gpiodglib_request_config_dup_consumer(config); + + g_assert_null(consumer); + g_assert_cmpuint(gpiodglib_request_config_get_event_buffer_size(config), + ==, 0); +} + +GPIOD_TEST_CASE(set_consumer) +{ + g_autoptr(GpiodglibRequestConfig) config = NULL; + g_autofree gchar *consumer = NULL; + + config = gpiodglib_request_config_new(NULL); + + gpiodglib_request_config_set_consumer(config, "foobar"); + consumer = gpiodglib_request_config_dup_consumer(config); + g_assert_cmpstr(consumer, ==, "foobar"); + + gpiodglib_request_config_set_consumer(config, NULL); + g_free(consumer); + consumer = gpiodglib_request_config_dup_consumer(config); + g_assert_null(consumer); +} + +GPIOD_TEST_CASE(set_event_buffer_size) +{ + g_autoptr(GpiodglibRequestConfig) config = NULL; + + config = gpiodglib_request_config_new(NULL); + + gpiodglib_request_config_set_event_buffer_size(config, 128); + g_assert_cmpuint(gpiodglib_request_config_get_event_buffer_size(config), + ==, 128); +} + +GPIOD_TEST_CASE(set_properties_in_constructor) +{ + g_autoptr(GpiodglibRequestConfig) config = NULL; + g_autofree gchar *consumer = NULL; + + config = gpiodglib_request_config_new("consumer", "foobar", + "event-buffer-size", 64, NULL); + consumer = gpiodglib_request_config_dup_consumer(config); + g_assert_cmpstr(consumer, ==, "foobar"); + g_assert_cmpuint(gpiodglib_request_config_get_event_buffer_size(config), + ==, 64); +} From patchwork Wed Aug 7 09:10:39 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Bartosz Golaszewski X-Patchwork-Id: 818161 Received: from mail-lj1-f177.google.com (mail-lj1-f177.google.com [209.85.208.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 3187F1CB30E for ; Wed, 7 Aug 2024 09:11:19 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.208.177 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723021881; cv=none; b=YhlsHhpxzaUnvov0SvCk6jku7IMdcWEg9AJTGSAfX/NAlt67vKAXz/tylaUBTaAuRKExwuxffgTDDkO0EKeelDzurElcMoBxTFhDkBaaypGMk/LwZFcIz7Sj5IHSCte4OAXtleKMBY6HMoPON/aZqIudBB9dMwPo6DVVCTrNKz4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723021881; c=relaxed/simple; bh=7jenabtNWenx7I8R7ceF3Ws1B4ti/sVn4dhPomjueEw=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=Tm7Lt35KFw2j6BX3cjgxgbBpYnMEDURmiDxBFgGxwrz0XOnlK+W8lJ2Yfr7NjtMxNoZxtPbfjGQ5FIqBEA1fT2z9AxFFVLjBnTU9hTm4y7S3IFYjc6eLuVzJKWsD4b56blFuAxSr4eSuISzppiiFxTbt946X6VRfa7Ym0fZnWp4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=bgdev.pl; spf=none smtp.mailfrom=bgdev.pl; dkim=pass (2048-bit key) header.d=bgdev-pl.20230601.gappssmtp.com header.i=@bgdev-pl.20230601.gappssmtp.com header.b=GUaJms5Z; arc=none smtp.client-ip=209.85.208.177 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=bgdev.pl Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=bgdev.pl Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=bgdev-pl.20230601.gappssmtp.com header.i=@bgdev-pl.20230601.gappssmtp.com header.b="GUaJms5Z" Received: by mail-lj1-f177.google.com with SMTP id 38308e7fff4ca-2ef7fef3ccfso16557571fa.3 for ; Wed, 07 Aug 2024 02:11:19 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bgdev-pl.20230601.gappssmtp.com; s=20230601; t=1723021878; x=1723626678; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=HDLk/fIApf4puRlKWFi5IhtCsHjKDrzzDuybhTWSeho=; b=GUaJms5Z7bOPu9/eLmLqsrLTVOfPTaUDbXYfeHlLk7vhwc1lEBLVc2MM0HuAvTbQI7 q+WUmrG+/pJv3wCqtzBK3DEzwl35dTp15kqTic++P8xoAPtTTqWOH4OOxdmA3u/mC7i1 2QKNsK6y45akq4IdQaebOmSil/Q13pXSdKvIrMi2PL+EC1Qgktnwav+Z1pl7JkbcQBXq m2+GQey8aQ5MvxKQ1QormYSDsb/eqgIkhwNk7vK0RENUNgmdxpPBcrLPIDiQy9gop0bX /vgpNeH6ikNTPYPngvLbff6cp50DtZzhB/69XTtJMi08qWKM1KaUkYmvK7mJTMKghfQ0 1AtQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1723021878; x=1723626678; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=HDLk/fIApf4puRlKWFi5IhtCsHjKDrzzDuybhTWSeho=; b=sZLpxDbpw7lNz1yj1A4EmV7e5MUenFdupaB32iWyipw6Y2T9YDUQaCtk8nwkOnR9os HIH/J5RgNTe4ij3IiZyZhgiG9c5ixKgYKjbBtqewHEGMpRhWBIov4VlzRMqtuzpfLX42 QdQZTeg4x67UsRM8A43xRpu9A4rkes4BPa6YeXYPGt4+CFT1YiU5IsJ7eEfrcEF9Zz1p x6qL5n55tBb+93bNq6QLn0Aa2pICdTBtwdkSJFGAZk8X91s3dL/v4QLCjvTCSVv2GRc4 Wwp5jnFVJKUiDQmo7Db4P+jEkk04djcQvTTR96eqftmLfK7MqAjS5GD+ll6K6f550H0J HT3Q== X-Gm-Message-State: AOJu0YyaEgTkkZ7brTzlNiP4eER4nMqkMMERRtzsa+A+x+oNb4FQ0wud K2p4QF+NrUVyk4JP9xc4Lxdt+UfgHE4n2PLzLKDcwqhXEZizIcI1TbqAGL9dnRc= X-Google-Smtp-Source: AGHT+IHXVfYyqJzByjAX09tVualFn1t1AnxvncPvMh4EZvYhdyBIOsRYYAKuGSb31HYh3ncLxc1sng== X-Received: by 2002:a2e:330e:0:b0:2ef:2472:300c with SMTP id 38308e7fff4ca-2f15aab0abfmr105233031fa.25.1723021854911; Wed, 07 Aug 2024 02:10:54 -0700 (PDT) Received: from [127.0.1.1] ([2a01:cb1d:dc:7e00:a3e7:be71:fe4f:c360]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-429059cd2b3sm18936355e9.44.2024.08.07.02.10.54 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 07 Aug 2024 02:10:54 -0700 (PDT) From: Bartosz Golaszewski Date: Wed, 07 Aug 2024 11:10:39 +0200 Subject: [PATCH libgpiod v4 08/18] README: document GLib bindings Precedence: bulk X-Mailing-List: linux-gpio@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20240807-dbus-v4-8-64ea80169e51@linaro.org> References: <20240807-dbus-v4-0-64ea80169e51@linaro.org> In-Reply-To: <20240807-dbus-v4-0-64ea80169e51@linaro.org> To: Linus Walleij , Kent Gibson , Erik Schilling , Phil Howard , Andy Shevchenko , Viresh Kumar , Dan Carpenter , Philip Withnall Cc: linux-gpio@vger.kernel.org, Bartosz Golaszewski , Alexander Sverdlin X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=1286; i=bartosz.golaszewski@linaro.org; h=from:subject:message-id; bh=1RfIuKDmdlsvZxR2wxOXsvxi+ZRtSkhXrPgsmfKsvsc=; b=owEBbQKS/ZANAwAKARGnLqAUcddyAcsmYgBmszoRDS5xyS6Nb+BV9OT3Sw6uPDKq6fCAHbmCX 9bXJ6zKnJSJAjMEAAEKAB0WIQQWnetsC8PEYBPSx58Rpy6gFHHXcgUCZrM6EQAKCRARpy6gFHHX cuCYD/0SvmZUtFQEloXBe4myGssxseU6bf+eI0pSvGGcu2n98CWMz3aN77UsmkUflW++vwGld8g a7/0tcomU0u2PyWbxkMfWyeazYI5h39ylILzPFKhXFqZ04x/1XXYTzK0YqG50WHOOYAVsj4pH+U wkRFb5exmUbUH+Bhqhi2edHlLmtGrQM4sFiT2WKrjF/oBaRYjCWGDlrSZvoXON21CAqlq4Pbyj1 3eZhX1ILhAzkxy/8VJVXrF/f5Nn3TZi0ZCqtRFVrVvclUwH39sPsC/0M9JshdpwN0m3ujYK5a3p uGyICDKaNQJBST5qRJwv0UzUYX/ee4VmwbI5WtE8iuz60n6sak8tPOIMJc3HTOY4gBRY0jdVp5d wUjEmDfb+apVzLRbldTCeTzereNqRSofD6HT8c1ALwFzMI6yeb9O60qs6P8SmB6MRJMdQuj59AZ EYLAoFAX+f6nfWS33oxH0DlsF8d1bztFfT+BgtEW4AzCEAL6bxCyx0qX4Gckkb14uiClOMeRZt4 qM0HHQZEK8AtrrzMEUvEfKHTjqp2enhjI0xSyfQK4wwODd4erkh714SsC8gF9Tw/BRQIt3GF8Fi 0s6COwS9NQfoemdgDrn9M7eJ7706gkx3Dy2tEiUYXamsSTHFEwfo2jfQKXgBSxN9xrVsoWhirhp klk81OXXSAm8C8Q== X-Developer-Key: i=bartosz.golaszewski@linaro.org; a=openpgp; fpr=169DEB6C0BC3C46013D2C79F11A72EA01471D772 From: Bartosz Golaszewski Add information on the requirements of and the configure build switch for GLib bindings to libgpiod. Tested-by: Alexander Sverdlin Signed-off-by: Bartosz Golaszewski --- README | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README b/README index a01cfc5..658a77e 100644 --- a/README +++ b/README @@ -211,13 +211,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. From patchwork Wed Aug 7 09:10:40 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Bartosz Golaszewski X-Patchwork-Id: 817496 Received: from mail-lj1-f182.google.com (mail-lj1-f182.google.com [209.85.208.182]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5A7291CB316 for ; Wed, 7 Aug 2024 09:11:21 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.208.182 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723021883; cv=none; b=LAPdEjTjcsgtbDyKbKkpPKXPBiISkmyWjXRyDtgT3jc9CCJKrxH9DY5dBClrkge4xcJp0OwjDmzn0W+SVjZXtiytr+aMoVdCOSNJTAy/wKs6uzHi4QjLpc0usPCRZeX0dD6U8/JL3GVwtP1z1gCuOn7XkH4LCNCLlqYU+Z83pK0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723021883; c=relaxed/simple; bh=cbNt9OxgthpfxTg18kku9Y82rWjbfvwDP6lLleKNJco=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=Q/YBnP5aY2ZvWTkxt17Cz6PH9IU3AGZY+VzxwnNYxyA0UwuhwJijnEOQKD1EFRVaTFBmbHK5I6ldU+mM1vyEJtjFhkdVcVPVzBxYz6ssmv2Oz3HoNuiD+bcCYWesyregmM9NVhiwuaMmVLFjsmjTp+ij4zkPRNp3ZBTuJ00vBJE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=bgdev.pl; spf=none smtp.mailfrom=bgdev.pl; dkim=pass (2048-bit key) header.d=bgdev-pl.20230601.gappssmtp.com header.i=@bgdev-pl.20230601.gappssmtp.com header.b=b7oZC9Hd; arc=none smtp.client-ip=209.85.208.182 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=bgdev.pl Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=bgdev.pl Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=bgdev-pl.20230601.gappssmtp.com header.i=@bgdev-pl.20230601.gappssmtp.com header.b="b7oZC9Hd" Received: by mail-lj1-f182.google.com with SMTP id 38308e7fff4ca-2ef2cce8be8so16467701fa.1 for ; Wed, 07 Aug 2024 02:11:21 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bgdev-pl.20230601.gappssmtp.com; s=20230601; t=1723021879; x=1723626679; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=+fZLAUxOQUoiH0tEm871oLxFyTLck29UFSWzCSGp/FM=; b=b7oZC9HdBuQhEeTcxfl8bJavZ6eRnihbTHbRsiMy3LeVQ39nOVJusycFeHhikbLh8U vbFRdJ6EEkkqSMQIFrKPaOKHBB3LhAwtxZJ3Bn8EgTvn9UfMt0WIY34Bixj8CbiM/d7W tjec8FgPTXV0Qgiqe1f42hX8FcpqMMYN9NwDTsx03jT0Fu7EhokdAj5XLj6/fQGejpcp 2atpDccIb4U9lzgf2hi7J4SZYGVP3fq57Bo41SfjWBReNk8xPfNW2hRANG97m8qcJLmh MpwTIjU7BYboEayxnxystWcbWKctSOOx/rIoqkbRJenRAPJggqYzm5eumnFeVdNn/1jH IIdw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1723021879; x=1723626679; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=+fZLAUxOQUoiH0tEm871oLxFyTLck29UFSWzCSGp/FM=; b=tY1BB7NA+5RqU929/GLQy5ji+qrL3J2HCx6a26rZDSElUD6oNf88xwytlz94lAaRr8 kPj3JCCZmvfnMFYmYmPvGkzsXzD0patxQsbfti6i3QcRkHIv+5Z9AYN9+Aq2CnQ/IDu3 kk9MA7tHzhIrhzc2H4fcYxnIHHvzCcJic5/Ftt8rU6Inhlgkvl0hnUWCARuvZ12s1GZf BugcP7okbM0LeOVaL7ehXZX00MbzOMMwKO2m1Qxbe9WM9r2cLW8YJCLUnrpAlzB0kgjM Dq++lg0x8D2SC2ZilHmmxXgClUXoCtbm1sTY9Mbpj0hN+0Qu3EG3ZxBMclfwFdh0RGXt b3Qw== X-Gm-Message-State: AOJu0Yx2e9UT6c8DPI+GiUyRUY6YXyJdRnM4jj+CLk4+RdGgrk7ZzpZ4 kumCeKNtMGyiA/sWHnALpmnhZBsa0zDGGN3OIY/zG1PGdtrbLkQVkrgqEsIX0v6dsBHHxtaBgRa ckhU= X-Google-Smtp-Source: AGHT+IFOKxbhBs0gzpbGACG5ShzMuzP3aZUc9k5g05styj1XlIL+0W99QMJqBuUCL92ole6mWAc7/g== X-Received: by 2002:a2e:97ce:0:b0:2ef:205d:4847 with SMTP id 38308e7fff4ca-2f15ab24d1cmr129513001fa.46.1723021879414; Wed, 07 Aug 2024 02:11:19 -0700 (PDT) Received: from [127.0.1.1] ([2a01:cb1d:dc:7e00:a3e7:be71:fe4f:c360]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-429059cd2b3sm18936355e9.44.2024.08.07.02.11.18 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 07 Aug 2024 02:11:18 -0700 (PDT) From: Bartosz Golaszewski Date: Wed, 07 Aug 2024 11:10:40 +0200 Subject: [PATCH libgpiod v4 09/18] dbus: add build files Precedence: bulk X-Mailing-List: linux-gpio@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20240807-dbus-v4-9-64ea80169e51@linaro.org> References: <20240807-dbus-v4-0-64ea80169e51@linaro.org> In-Reply-To: <20240807-dbus-v4-0-64ea80169e51@linaro.org> To: Linus Walleij , Kent Gibson , Erik Schilling , Phil Howard , Andy Shevchenko , Viresh Kumar , Dan Carpenter , Philip Withnall Cc: linux-gpio@vger.kernel.org, Bartosz Golaszewski , Alexander Sverdlin X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=9871; i=bartosz.golaszewski@linaro.org; h=from:subject:message-id; bh=xHp8/YOd8WNMHGZcr9P6Q0+fxlBiviRb8MGjLMP9bBs=; b=owEBbQKS/ZANAwAKARGnLqAUcddyAcsmYgBmszoRZN7z9e169L+nf6o4oa0xQrNIs0LQPk6oy iK5C6OiaeyJAjMEAAEKAB0WIQQWnetsC8PEYBPSx58Rpy6gFHHXcgUCZrM6EQAKCRARpy6gFHHX co68D/9HrWqRmyKhv7HbTD02Fpo9SRLZikRBl7223qMj6d+Mz85UxcqF4EAaGuCMH2FY/4JPFtN 4kC6G0L0ThpudAiJddzQwA7lhxL7hWYa5JvyDQ0G2XZwnZEGgLSduY42QcTrRnrFOQunFHnc7vy Wl30G+Gl9kfVwMrXPIUKUZhp0sbFj3dZGdeIo6aFY0H9OLaV82hYZKFsw3/JQxpLBh1goktb2Of 8pW5zbvparxUGvtB+HbFGCXadhpPOk8M65mj95zQQRK0inF9ko6tKcX8afCviFCiqMGVywNKQIk DDVzW8AJvxnxRzmAfjxI7xZ38Gk0n10h+3tcOor4b+qMAszOup5o7NUrKvz5lRF3wHlGGW8kIXp nITQ9R4jaZXUTz/7gMKijtus9J8imKLGiMLeQWcJv8mZwpC4jFRdzYIPtxN4AK7ViN9srpGnxTt 0CspicCfIUTRN7aUgSlXkZOgetyO/4jkNfBPAkrJd0oYHUCfkW7F1+e8gB00LDh/EXDr90UwMtF w9s8/CXyz8zjotEeHmqWZZrtsxYJlBqtgB+eswkP/yzcswiHngBK7viCKzl9yVyLIhZ38Q4HZnG d8pHLPbJodRZ+hUuk5wnmO5C3f4iSSu2JUv+mV42NJUnTOu+qs4p/aj0zhJklRNXjd1O+W6NkuC skhzVPKRKjxvc3Q== X-Developer-Key: i=bartosz.golaszewski@linaro.org; a=openpgp; fpr=169DEB6C0BC3C46013D2C79F11A72EA01471D772 From: Bartosz Golaszewski Add the directory structure and build files as well as changes to .gitignore for the DBus API. Tested-by: Alexander Sverdlin Signed-off-by: Bartosz Golaszewski --- Makefile.am | 7 +++++++ configure.ac | 45 +++++++++++++++++++++++++++++++++++++++++++++ dbus/Makefile.am | 10 ++++++++++ dbus/client/.gitignore | 4 ++++ dbus/client/Makefile.am | 31 +++++++++++++++++++++++++++++++ dbus/data/Makefile.am | 16 ++++++++++++++++ dbus/lib/Makefile.am | 29 +++++++++++++++++++++++++++++ dbus/manager/.gitignore | 4 ++++ dbus/manager/Makefile.am | 21 +++++++++++++++++++++ dbus/tests/.gitignore | 4 ++++ dbus/tests/Makefile.am | 25 +++++++++++++++++++++++++ 11 files changed, 196 insertions(+) diff --git a/Makefile.am b/Makefile.am index 2ace901..c824dc4 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-or-later # SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski +# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski ACLOCAL_AMFLAGS = -I m4 AUTOMAKE_OPTIONS = foreign @@ -37,6 +38,12 @@ endif # libgpiosim to be already present. SUBDIRS += bindings +if WITH_DBUS + +SUBDIRS += dbus + +endif + if HAS_DOXYGEN doc: Doxyfile diff --git a/configure.ac b/configure.ac index 31cb8d1..cbe9e13 100644 --- a/configure.ac +++ b/configure.ac @@ -280,6 +280,45 @@ m4_ifdef([GOBJECT_INTROSPECTION_CHECK], [GOBJECT_INTROSPECTION_CHECK([0.6.2])], [AM_CONDITIONAL(HAVE_INTROSPECTION, test "x$found_introspection" = "xyes")]) +# Depends on GLib bindings so must come after +AC_ARG_ENABLE([dbus], + [AS_HELP_STRING([--enable-dbus], [build dbus daemon [default=no]])], + [if test "x$enableval" == xyes; then with_dbus=true; fi], + [with_dbus=false]) +AM_CONDITIONAL([WITH_DBUS], [test "x$with_dbus" = xtrue]) + +AC_DEFUN([FUNC_NOT_FOUND_DBUS], + [ERR_NOT_FOUND([$1()], [dbus daemon])]) + +if test "x$with_dbus" = xtrue && test "x$with_bindings_glib" != xtrue +then + AC_MSG_ERROR([GLib bindings are required to build the dbus daemon - use --enable-bindings-glib]) +fi + +if test "x$with_dbus" = xtrue +then + AC_CHECK_FUNC([daemon], [], [FUNC_NOT_FOUND_DBUS([daemon])]) + AC_CHECK_FUNC([strverscmp], [], [FUNC_NOT_FOUND_DBUS([strverscmp])]) + PKG_CHECK_MODULES([GUDEV], [gudev-1.0 >= 230]) + AC_CHECK_PROG([has_gdbus_codegen], [gdbus-codegen], [true], [false]) + if test "x$has_gdbus_codegen" = xfalse + then + AC_MSG_ERROR([gdbus-codegen not found - needed to build dbus daemon]) + fi +fi + +AC_ARG_ENABLE([systemd], + [AS_HELP_STRING([--enable-systemd], [enable systemd support [default=no]])], + [if test "x$enableval" == xyes; then with_systemd=true; fi], + [with_systemd=false]) +AM_CONDITIONAL([WITH_SYSTEMD], [test "x$with_systemd" = xtrue]) + +if test "x$with_systemd" = xtrue +then + PKG_CHECK_VAR([systemdsystemunitdir], [systemd], [systemdsystemunitdir], [], + AC_MSG_ERROR([systemdsystemunitdir not found - needed to enable systemd support])) +fi + AC_CHECK_PROG([has_doxygen], [doxygen], [true], [false]) AM_CONDITIONAL([HAS_DOXYGEN], [test "x$has_doxygen" = xtrue]) if test "x$has_doxygen" = xfalse @@ -337,6 +376,12 @@ AC_CONFIG_FILES([Makefile bindings/rust/Makefile bindings/rust/gpiosim-sys/src/Makefile bindings/rust/gpiosim-sys/Makefile + dbus/Makefile + dbus/client/Makefile + dbus/data/Makefile + dbus/lib/Makefile + dbus/manager/Makefile + dbus/tests/Makefile man/Makefile]) AC_OUTPUT diff --git a/dbus/Makefile.am b/dbus/Makefile.am new file mode 100644 index 0000000..7868a96 --- /dev/null +++ b/dbus/Makefile.am @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# SPDX-FileCopyrightText: 2022-2023 Bartosz Golaszewski + +SUBDIRS = data lib manager client + +if WITH_TESTS + +SUBDIRS += tests + +endif diff --git a/dbus/client/.gitignore b/dbus/client/.gitignore new file mode 100644 index 0000000..08ec6c8 --- /dev/null +++ b/dbus/client/.gitignore @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: CC0-1.0 +# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski + +gpiocli diff --git a/dbus/client/Makefile.am b/dbus/client/Makefile.am new file mode 100644 index 0000000..1f99daf --- /dev/null +++ b/dbus/client/Makefile.am @@ -0,0 +1,31 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# SPDX-FileCopyrightText: 2022-2024 Bartosz Golaszewski + +AM_CFLAGS = -include $(top_builddir)/config.h +AM_CFLAGS += -I$(top_builddir)/dbus/lib/ -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 diff --git a/dbus/data/Makefile.am b/dbus/data/Makefile.am new file mode 100644 index 0000000..f3f7ba3 --- /dev/null +++ b/dbus/data/Makefile.am @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# SPDX-FileCopyrightText: 2022-2023 Bartosz Golaszewski + +EXTRA_DIST = gpio-manager.service + +dbusdir = $(sysconfdir)/dbus-1/system.d/ +dbus_DATA = io.gpiod1.conf + +if WITH_SYSTEMD + +systemdsystemunit_DATA = gpio-manager.service + +udevdir = $(libdir)/udev/rules.d/ +udev_DATA = 90-gpio.rules + +endif diff --git a/dbus/lib/Makefile.am b/dbus/lib/Makefile.am new file mode 100644 index 0000000..8e722ad --- /dev/null +++ b/dbus/lib/Makefile.am @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# SPDX-FileCopyrightText: 2022-2023 Bartosz Golaszewski + +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 \ + --glib-min-required 2.74.0 \ + $(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 diff --git a/dbus/manager/.gitignore b/dbus/manager/.gitignore new file mode 100644 index 0000000..5507c6d --- /dev/null +++ b/dbus/manager/.gitignore @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: CC0-1.0 +# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski + +gpio-manager diff --git a/dbus/manager/Makefile.am b/dbus/manager/Makefile.am new file mode 100644 index 0000000..d1cef8e --- /dev/null +++ b/dbus/manager/Makefile.am @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# SPDX-FileCopyrightText: 2022-2023 Bartosz Golaszewski + +AM_CFLAGS = -I$(top_srcdir)/bindings/glib/ -include $(top_builddir)/config.h +AM_CFLAGS += -Wall -Wextra -g +AM_CFLAGS += -I$(top_builddir)/dbus/lib/ -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 diff --git a/dbus/tests/.gitignore b/dbus/tests/.gitignore new file mode 100644 index 0000000..19f64af --- /dev/null +++ b/dbus/tests/.gitignore @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: CC0-1.0 +# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski + +gpiodbus-test diff --git a/dbus/tests/Makefile.am b/dbus/tests/Makefile.am new file mode 100644 index 0000000..ec4e26c --- /dev/null +++ b/dbus/tests/Makefile.am @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# SPDX-FileCopyrightText: 2022-2023 Bartosz Golaszewski + +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_builddir)/dbus/lib/ -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) From patchwork Wed Aug 7 09:10:41 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Bartosz Golaszewski X-Patchwork-Id: 818160 Received: from mail-wm1-f51.google.com (mail-wm1-f51.google.com [209.85.128.51]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 725091CB30E for ; Wed, 7 Aug 2024 09:11:22 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.51 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723021884; cv=none; b=pPahbKkZdRiY56pM20K6VhI9vn1H6TzlYuVthqiLZpaS5Bow4ci/Uo91l0YXXqpbJuMDvCAfaixYyLiJ843d5F87NONW6RyUWw5gmRr+ev3wg/YTpL4r5wD4RLiIwXPKWieJ20J3VGQYE7oUQY7vbesTF4ZReR9l79+3RpAeHWQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723021884; c=relaxed/simple; bh=gJSj7Y4BSXPPUUhXh90zjI+fkKXpwMQo1lpaZhjGAS8=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=nQPmuWbyMOUWUKiWdbM6xIH8l3mZA8XQQuHGD1AS2rATQ5E5h6kPvcOs3iCBes9Flbg0ELxLKktawjUDbB89t5vqLjo92l2m9kjeqFVaAXiIc7ziM5KeHir8fq0D4IMEC52imRRmO4Jq5/WECgDs1xS6/rJkB+Ou+ai8Xq8Ae3E= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=bgdev.pl; spf=none smtp.mailfrom=bgdev.pl; dkim=pass (2048-bit key) header.d=bgdev-pl.20230601.gappssmtp.com header.i=@bgdev-pl.20230601.gappssmtp.com header.b=RVPMB36I; arc=none smtp.client-ip=209.85.128.51 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=bgdev.pl Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=bgdev.pl Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=bgdev-pl.20230601.gappssmtp.com header.i=@bgdev-pl.20230601.gappssmtp.com header.b="RVPMB36I" Received: by mail-wm1-f51.google.com with SMTP id 5b1f17b1804b1-42819654737so10607185e9.1 for ; Wed, 07 Aug 2024 02:11:22 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bgdev-pl.20230601.gappssmtp.com; s=20230601; t=1723021881; x=1723626681; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=mkk0loRf4tUPf/AWLqTlqk0ORZVfkds+9375JLaGi5M=; b=RVPMB36IUxpNXoBcTsU+MwYJdTzDNpcDkiy9R6LtomXnqn12m15Q0GFm4VMCRyoSzl Z8MTxxH8dHjgFjtqTyy8Pk/rRgcD0PIG7CmGehfeojcrOraQvPSKGY5rWm+MoF3KHRj/ EPKuTImtAPiQ8IFqvxxmxt2mfX8EY1XoLCJIdnnwWp7mP2F5DkOonI2lD3xEZcpp/+O6 EVcEnxkBPBBXpuoXy/6ty/kSK5DinsWh0LPXPOboDtkkNHmw4ms9rhAarV06KyFLqI+Y zKC1nzN2R57EMwbXTtRIe3YXvCkRi9BsqJdDe37A1bJjaEcx29XoTQENpVxu8rl0XxeR zTfQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1723021881; x=1723626681; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=mkk0loRf4tUPf/AWLqTlqk0ORZVfkds+9375JLaGi5M=; b=UV/6ZnBY5txdeFxQwAd/0q7/GpxJV6l1oDHG4rNXQnIKfD5c8ybHuHCJSJ4DoGS7Il fmheTCzJKxXjpM7BTJhVJs9lS8KHf+FEcrqPGezpGGmlwm+ZRI3UxeuyLt1RGVN9OFLm NMd5vg6uwOXYITdoVGED0doT3wEYrkLGXpwwLAWukHqQkIfQLUZlgLq72l6On542K7G4 hUJ+NT+FKVnzbexydXuxH0Hz+5BqX02MemV6/qtK8Qw0EdYf+oAzIDM56JuCMGjikZFW a1CTplfWf8EgVg9P5ctR2j6Ze7oojUs7OQmBRD0i/+C6eCDLODfzzEosq4RBE6fI5IQ7 vmqg== X-Gm-Message-State: AOJu0YyYwM3GMf7vx1phTBVGbOwnLB1RH6UPy7m/6xGRm/BxceCOG3Y2 AoojaaIX/kh1HfGzBNrF3DNYyqBLmSWko8mxvD//z11jAJ4jij1tpSopNvy6DzM= X-Google-Smtp-Source: AGHT+IHErtdkhBtzlRdx11NM/Q7ltNHoDep33W1YJBDktGlT8bGI/QashNo1ooSTxBPupkgB+T+Wmg== X-Received: by 2002:a05:600c:578e:b0:425:5ec3:570b with SMTP id 5b1f17b1804b1-428f7668cfdmr55736445e9.35.1723021880650; Wed, 07 Aug 2024 02:11:20 -0700 (PDT) Received: from [127.0.1.1] ([2a01:cb1d:dc:7e00:a3e7:be71:fe4f:c360]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-429059cd2b3sm18936355e9.44.2024.08.07.02.11.19 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 07 Aug 2024 02:11:19 -0700 (PDT) From: Bartosz Golaszewski Date: Wed, 07 Aug 2024 11:10:41 +0200 Subject: [PATCH libgpiod v4 10/18] dbus: add the API definitions Precedence: bulk X-Mailing-List: linux-gpio@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20240807-dbus-v4-10-64ea80169e51@linaro.org> References: <20240807-dbus-v4-0-64ea80169e51@linaro.org> In-Reply-To: <20240807-dbus-v4-0-64ea80169e51@linaro.org> To: Linus Walleij , Kent Gibson , Erik Schilling , Phil Howard , Andy Shevchenko , Viresh Kumar , Dan Carpenter , Philip Withnall Cc: linux-gpio@vger.kernel.org, Bartosz Golaszewski , Alexander Sverdlin X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=10789; i=bartosz.golaszewski@linaro.org; h=from:subject:message-id; bh=NnP37xYr5zXDjZ3ZMCY3duYCzJkpDuifZLfirlvH5a8=; b=owEBbQKS/ZANAwAKARGnLqAUcddyAcsmYgBmszoRGD7zTQMziiIBIRg4jAHhouUYuJRdV57Qb SA1RfxubhSJAjMEAAEKAB0WIQQWnetsC8PEYBPSx58Rpy6gFHHXcgUCZrM6EQAKCRARpy6gFHHX clUPEAC0ynW8Go2XB+Qr/RNXlKIPBUZtaoM2VfEg2iPG0Urm0tewBbuNTqTnMD5tCLtBokjOifQ l/iZDG1+2ROAhkb+B0Xv/MHIdZTxsPzHRZ4p8D/7j2NEMq0fL8S4ovrlL3q1YlVgVFDvN2MNmkY S8PgY2mTKX5xARL6F0PguLVeGg55LfWEk2KGqyik0+LoU7Jr2d0++n47xxXXtJLXg6WXpSzh/dp /2wPakcdCtOQdBRSlUBeqs4gdTvTvQnSXeJsAe4ftya4HanVMBuccNqQsVGZXubGu1vKTyglmpr /gN1FQ6jHp57h9OIE1vkvs8JQa2Po9TH/2pQ7nkJUV/M1Z+Y0lEGmCsttkYAUheyIGHQcxM+llO CG0wR4J0smQ/FOmU4Yq+9xfrqYA6/QqYJ4V6lx9XyOCVzYt/3loSjoo9UCOdg38bf8iBb8m+aL+ GfIwsHmkcUtBGeO0Xyz4m/dCkCaosfYFsF7HauqYu5pjEdS4e42d8z1CNYSzCT5n26uMCfWgdNH sIfLypw6Zx1bVQ4Vjm+L1TWTabfWRQd1muztCZYaz7t9i0zMTC7aqeHw14lotNeALq/4FkhV1Lb /PXRVLAIka27A/ppFoyQS4i6wO/EaKEkF78sm/xusRa3uSe9QLXfSu8AwTRocBmX7rwC8zutDKk KgCKvUzk0vo5Reg== X-Developer-Key: i=bartosz.golaszewski@linaro.org; a=openpgp; fpr=169DEB6C0BC3C46013D2C79F11A72EA01471D772 From: Bartosz Golaszewski Add the XML file defining all the interfaces for the io.gpiod1 service. Tested-by: Alexander Sverdlin Signed-off-by: Bartosz Golaszewski --- dbus/lib/io.gpiod1.xml | 324 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 324 insertions(+) diff --git a/dbus/lib/io.gpiod1.xml b/dbus/lib/io.gpiod1.xml new file mode 100644 index 0000000..ace7d72 --- /dev/null +++ b/dbus/lib/io.gpiod1.xml @@ -0,0 +1,324 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From patchwork Wed Aug 7 09:10:42 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Bartosz Golaszewski X-Patchwork-Id: 817495 Received: from mail-wm1-f48.google.com (mail-wm1-f48.google.com [209.85.128.48]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 6A5811D173B for ; Wed, 7 Aug 2024 09:11:23 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.48 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723021884; cv=none; b=demf8kSgjFDclKZ4OaPJ/+PEEQKr5EviLa867rVrL+nwrLpRS09H8sDzAmVGwjm9eeUMJTKy+WQpY5cyY1vmi/P7BBqhTG5S2c3YqPkSadiKEB2VSwlurhxkVX1lCoY8XQo0xsa7ZSxmRcpa/7YpN3pbbLQ0HiJdWvlp3GaD9mE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723021884; c=relaxed/simple; bh=cKZGrYTO1B+5ZM1byDwrEV++EQVNLlUOyKBh1zx5B4I=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=eaTlafYM4DbeXyuD/yjA5Fx6EuWctJfeqVn2DhhIl8Ic9P7ACJyPCRLe6tNN3hydqwj5wnWG7qkpRLRxBTda5Fnyj/vazVfb4snOyJrKscveO1eUgO+c2UgSNbjIR+CVdsTwi8JZQm7/pjfY3M19O4dtMHDEeGNyLE1R3qAfRlU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=bgdev.pl; spf=none smtp.mailfrom=bgdev.pl; dkim=pass (2048-bit key) header.d=bgdev-pl.20230601.gappssmtp.com header.i=@bgdev-pl.20230601.gappssmtp.com header.b=XZceWyf1; arc=none smtp.client-ip=209.85.128.48 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=bgdev.pl Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=bgdev.pl Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=bgdev-pl.20230601.gappssmtp.com header.i=@bgdev-pl.20230601.gappssmtp.com header.b="XZceWyf1" Received: by mail-wm1-f48.google.com with SMTP id 5b1f17b1804b1-4266f3e0df8so10382555e9.2 for ; Wed, 07 Aug 2024 02:11:23 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bgdev-pl.20230601.gappssmtp.com; s=20230601; t=1723021882; x=1723626682; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=gUbQKwssYICDEm/Dx0g9d2o0je/YbN4psjkIGP6L5Fk=; b=XZceWyf1XI5+0Anp5WJPiHHB5cmZJYK9FlH18f9ELDKPTvgVOxFObs8YtwbANFG//x Gv7/pTTNphy7xKakMpejUEndlf2Q/j71kuHBGI0mEl7DP/pvQtHWktH0QLuU1ekjWb+T z6uO6efVnbjKguDXlcfCWn6mkr3nJmdsaa/SbfMvPvTg2wKDSSz/5pbIe7cbx1vZUYzc EXkEhbJOPvLB13NkjVeOqMWrRZWAUnQQ/LCO5FkWnR5pYWeuMC06P8vs7o6wh47fmwTQ TCLIlPev6xGPzitXBXsmyYHWp9BZ3judAWgn2DgEBB1SUkl92NuWLNQValJUcEX8k5pQ flSQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1723021882; x=1723626682; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=gUbQKwssYICDEm/Dx0g9d2o0je/YbN4psjkIGP6L5Fk=; b=MJcjnBlkD2uvoi6EzHGKtMpVmZCiuATui27HM06YsmVNMBogU5HEdUX9p9yM8o1nHu +ZT29gJ6+UIk3c2U+IX84KxYaEEkOZ0sD6L6Ii6QyfLehJC/O/eDIYThwaa5EZoa4Yj4 UZNnSI28Ye+hHVZHfL5PASKrHEUR7pnrbnhdSWAyktb7K5i1qh4SJePTcan5KxWtpQ6d /BM7sxu34Pz0iq1yqzE7Q8V93OTcLDl6fWMUd3fntJCbp33ARCSWhtCAep60LdHzLSvd bhZlxGozXgAGoq312LZMTXFw1BEprFUpgHuX28wNi3Yn8Hi678Kc+/W014EvFx8BmZpe tdlw== X-Gm-Message-State: AOJu0YySmf7Nod8G6WKQQwRAsAtAQEfkzVo7t6mXSgcAuDhN5wCJ6eYF Q6qeWf59lmqH+9TkFeasQxUiqUJl4RiaYRlcAXKFexvOqY4I2+Eg0i2cR1JXR9w= X-Google-Smtp-Source: AGHT+IFwoKwytJ3J4q9D8higgq7Nx59Scg5H6e8G/3LW6U6ZGhH9usaLQUDQ94NRPekpxgfJl+qEbQ== X-Received: by 2002:a05:600c:468c:b0:427:abed:3608 with SMTP id 5b1f17b1804b1-428e6af2fb9mr108849295e9.5.1723021881797; Wed, 07 Aug 2024 02:11:21 -0700 (PDT) Received: from [127.0.1.1] ([2a01:cb1d:dc:7e00:a3e7:be71:fe4f:c360]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-429059cd2b3sm18936355e9.44.2024.08.07.02.11.20 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 07 Aug 2024 02:11:21 -0700 (PDT) From: Bartosz Golaszewski Date: Wed, 07 Aug 2024 11:10:42 +0200 Subject: [PATCH libgpiod v4 11/18] dbus: add a wrapper around the gdbus-codegen generated header Precedence: bulk X-Mailing-List: linux-gpio@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20240807-dbus-v4-11-64ea80169e51@linaro.org> References: <20240807-dbus-v4-0-64ea80169e51@linaro.org> In-Reply-To: <20240807-dbus-v4-0-64ea80169e51@linaro.org> To: Linus Walleij , Kent Gibson , Erik Schilling , Phil Howard , Andy Shevchenko , Viresh Kumar , Dan Carpenter , Philip Withnall Cc: linux-gpio@vger.kernel.org, Bartosz Golaszewski , Alexander Sverdlin X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=773; i=bartosz.golaszewski@linaro.org; h=from:subject:message-id; bh=E7xfc0OoO8rCmrbuafNaSaM0N/X8NsOSUm8udR2uWxY=; b=owEBbQKS/ZANAwAKARGnLqAUcddyAcsmYgBmszoSVgsTRIzb9MCpTHhBjy4v/yVV8jM7DxO/d kSzKohcqbOJAjMEAAEKAB0WIQQWnetsC8PEYBPSx58Rpy6gFHHXcgUCZrM6EgAKCRARpy6gFHHX cuLGD/9ohzm3M7cRQk8xKjPE84GmCB1tNe7EHsyacf0qngb4yrfx2ysgrbzf2rjLjGsUgb3ZemQ iwAdkBG4yERVH/L5Wt8kLMSEHpvoXWBo3UWl1vP9uNXEGF/0KMq/Uq9PSCqTGUGwJcrY9kcejQx aDxmCBeWzQCwqDePoovbR+2cEv6fkppgqgg3UpzP/dZqah9+OKqd95fMenMcOlNtCoPy5TKGO6k 6C+7kaL93ZpbvChe5hdcqhCXpZdnMIeelolkDG1iAfpfcmJ5doh1H3H1WpAUEySXB91xyJn481V YA4revWYj+/deMHxzJJFh+LgeSUWRuUr4c6YeGbvadMORzl+NXKqZOFmNYMKJzSiO24peLxDlqN dp4EG8F1bC+bZ6t1A0Jimk5L3cAMG9JqXr73RgJhXaSr6jiIopWYEtD5dWFuwVmwTszL6FIku4P EZjRtkFrxIFI+0BLzT6PD9DeA70RhlcXvWlQN72DgAuI7O5BgVxcjH4XlD7mooOAdckYb4tE5bf uagZx8ukrba1Q8RF/QwzMMwSP8Gl0ucB5PYJv6K0EYDT2JVGVhOL0uz46wGRjoVDh9vVTbMJzV3 lqBRzuOTmoC3FRMwFTAydGoeZZZ9YlEYvNocxZ0KChUoTPhjl+M8pfXms6EMBxF7U06O/nlcfFa KTEElYLSznHJTrg== X-Developer-Key: i=bartosz.golaszewski@linaro.org; a=openpgp; fpr=169DEB6C0BC3C46013D2C79F11A72EA01471D772 From: Bartosz Golaszewski Add a header that will be included by all the code using the automatically generated GLib layer for the DBus interface. Tested-by: Alexander Sverdlin Signed-off-by: Bartosz Golaszewski --- dbus/lib/gpiodbus.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/dbus/lib/gpiodbus.h b/dbus/lib/gpiodbus.h new file mode 100644 index 0000000..69362f0 --- /dev/null +++ b/dbus/lib/gpiodbus.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* SPDX-FileCopyrightText: 2023 Bartosz Golaszewski */ + +#ifndef __GPIODBUS_H__ +#define __GPIODBUS_H__ + +#include "generated-gpiodbus.h" + +#endif /* __GPIODBUS_H__ */ From patchwork Wed Aug 7 09:10:43 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Bartosz Golaszewski X-Patchwork-Id: 818159 Received: from mail-lj1-f174.google.com (mail-lj1-f174.google.com [209.85.208.174]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 480911D173B for ; Wed, 7 Aug 2024 09:11:25 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.208.174 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723021886; cv=none; b=UJHrwHMNDMQJa2YzEuhDjrPlQOPEPQPxp3SqAkLxfoscvTheY4uTGzELCAfVSyKVEYvW6DF7JW57ZDzBtL62/Ln5srL4MVJ+Uc1AmdwV5Goir9usKUFdBcoelfMhPBjapemxVnpDzaLDV7si3I+YtIs+X16oa22syaBE2dcaFt4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723021886; c=relaxed/simple; bh=dbvjRGNmelcenKY6JKDsWpjzFkWOY/sdhX+4XylckwM=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=kXish+zhbPMaL+zycxPVvPgOU0ssaepFTw++2psk81dt1Cn/douOWGmhPFS2OvW12tDnWng8B5LYCvzLXbYDiXxVwOGqadC7wwgkcJokBTxzY9lKvNwP0uJj/eEB5/2vrL4f+qXGiHtgADpjd/ldIpKCqsHuL9XhOf9UXHIlt4Y= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=bgdev.pl; spf=none smtp.mailfrom=bgdev.pl; dkim=pass (2048-bit key) header.d=bgdev-pl.20230601.gappssmtp.com header.i=@bgdev-pl.20230601.gappssmtp.com header.b=FT1x3U8O; arc=none smtp.client-ip=209.85.208.174 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=bgdev.pl Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=bgdev.pl Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=bgdev-pl.20230601.gappssmtp.com header.i=@bgdev-pl.20230601.gappssmtp.com header.b="FT1x3U8O" Received: by mail-lj1-f174.google.com with SMTP id 38308e7fff4ca-2ef27bfd15bso15883691fa.2 for ; Wed, 07 Aug 2024 02:11:25 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bgdev-pl.20230601.gappssmtp.com; s=20230601; t=1723021883; x=1723626683; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=zmKu9lH9xWYQGAKSqvif1JN42oJCOxk7azckLgWclIY=; b=FT1x3U8OHocM3w5CNW3VO5GSncxb4UJtEvOXOO69ukrK+w0QWgWujXOaTpaxOkJb31 O/WXQ53YJE+HkRHN5b51vEvIY4ZKUkc47PPoPyH/BqvtQySoUYZSv8LYD9ffqCN1Rdsa 8jzaL4jghqTvTZt1kw+U6JpN1gdaTYBU4HnjoizyCvLLTsDvoMu1ihcHGo4bu55IUv3O z0XWEUxvDLTVHhNC1clC+o+YlXpEE+tT86Y5sCWg8EtGBlUKN1gM8w/cqslxWDAc7pAR Odip244uz9l4mHD7UG9aXt+Khpcw3KpPmmMkgxgmMyNDSHKDrGHrz1jhqujxooXlhpW6 B2bw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1723021883; x=1723626683; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=zmKu9lH9xWYQGAKSqvif1JN42oJCOxk7azckLgWclIY=; b=v22zdM9Jrlqa5yD+NHzfInSllF3cmTHlbSll9X2eWjeGcAqjKWOCOKUWsyy74uv6uE sSR2udqNTTFIolZHfooaPWJEVMWcOt9MjLk+X6S96fCo3yKGZG3/rYiLGSqX61m8ppIe 9Zs51iAEk3cyxqav8q2pmHsTXgTqEUFHuQdgO9tb890zKptpjHxqdAZ2zroHIziPnBln QHmqnmYtSCSrtkNyLyTVOh7yJs47jbW/Vx9yOoOEUk3Ne4AozS09ykTrt479sjyObzUn fHBCSUkNXT4KrnInkfmHE21eeYj4SeWynVhrKsQN8B+3QMziDySCQ7LUoc/zCQ4qRDKH YPnA== X-Gm-Message-State: AOJu0Yx3DyuSR/OD3mEI+JsKYfgF2OlYvFDcK1Eg7G5ZBJUwlKGYJJF+ piRZVpI5bu969schVMuhK16oXD8sATaU3vIqOJ+x9NUkkZNxxjnbus3pENMteEU= X-Google-Smtp-Source: AGHT+IGX9bAqVsKJKguRlPxnyNnkemaGaJCK+VXBTQESlX6bHuM4CF8uCTQr+YpymALroHWld5PDhg== X-Received: by 2002:a05:651c:95:b0:2ef:1db2:c02c with SMTP id 38308e7fff4ca-2f15aa83909mr119049191fa.10.1723021883475; Wed, 07 Aug 2024 02:11:23 -0700 (PDT) Received: from [127.0.1.1] ([2a01:cb1d:dc:7e00:a3e7:be71:fe4f:c360]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-429059cd2b3sm18936355e9.44.2024.08.07.02.11.21 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 07 Aug 2024 02:11:22 -0700 (PDT) From: Bartosz Golaszewski Date: Wed, 07 Aug 2024 11:10:43 +0200 Subject: [PATCH libgpiod v4 12/18] dbus: add data files Precedence: bulk X-Mailing-List: linux-gpio@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20240807-dbus-v4-12-64ea80169e51@linaro.org> References: <20240807-dbus-v4-0-64ea80169e51@linaro.org> In-Reply-To: <20240807-dbus-v4-0-64ea80169e51@linaro.org> To: Linus Walleij , Kent Gibson , Erik Schilling , Phil Howard , Andy Shevchenko , Viresh Kumar , Dan Carpenter , Philip Withnall Cc: linux-gpio@vger.kernel.org, Bartosz Golaszewski , Alexander Sverdlin X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=3920; i=bartosz.golaszewski@linaro.org; h=from:subject:message-id; bh=5uGtKWTlFCDU1nul8eXNrce8KwG7JiRpVs1b7fBHGLc=; b=owEBbQKS/ZANAwAKARGnLqAUcddyAcsmYgBmszoSw+4WBdDBLUaTb0gJwm/xTNy83BUfuRVYY f2r6zWNDOGJAjMEAAEKAB0WIQQWnetsC8PEYBPSx58Rpy6gFHHXcgUCZrM6EgAKCRARpy6gFHHX cgDvEACsj/dn+FnQ9CPkHhc03heaTWMoHDr3xRFyE5olWTG3/HgZ5syIjNRjMWhr7SsVaAHmEFp hn6f/vROmdRhcMqakQ3BZKJOU0bTUMQq+XgVI0Vgs+xmXAfJs2W/ZzvW742z8r8M2LSxFjUDU5m U9qZF990UOA3Olv7Qp+5hDzIjPkz/B8vVFEKXUIdHzrxdUAipYAzlB+x74Uvtl+bax8fkJPsPr3 BjXr4KI//JscgsvKR1I0bJvgBt80VTeX2Gd0GmBDODDoEwV0Ho2V32XbcAvISOVBWoTF4+hK/Wo uuIIn+ugyJH0Q4qVhkmXyD4ePJFWDDKS0XP7ca/bmOwyZ8P0GN/CaaOdnpvlBhrAjkvxT/aygW2 lCXnosSzioxhIihMpi/FfRKgGLKy1laBDwLjp7IPWiKjn5ya9z+mZeIj/OYKzLwb614k398dsZF OVOVhc+8CBEvtcLW6bjJEFZCS5tr8hz7YJk6C+xObgQK6VT5H19MddV7k180q5PDPUZzHi2KGFk tmnEtJxKveQv6RWuchzmkHAG7uXCP4zWcDhTUC2u57ckerhGlpOSjDyn944h1c1BbXHIuFcudVF LEYPlmfQy2DjMjCAonwdtzlExpNpQyd8PM2WJXrZ6D4gcUFkbVE9W4gyPdKmQWa/NGn5fv8CgcO PiSwPcoulU1bNdw== X-Developer-Key: i=bartosz.golaszewski@linaro.org; a=openpgp; fpr=169DEB6C0BC3C46013D2C79F11A72EA01471D772 From: Bartosz Golaszewski Add the default service configuration file for the DBus GPIO API and a systemd unit file that allows to start up the gpio-manager. Tested-by: Alexander Sverdlin Signed-off-by: Bartosz Golaszewski --- dbus/data/90-gpio.rules | 4 ++++ dbus/data/gpio-manager.service | 50 ++++++++++++++++++++++++++++++++++++++++++ dbus/data/io.gpiod1.conf | 41 ++++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+) diff --git a/dbus/data/90-gpio.rules b/dbus/data/90-gpio.rules new file mode 100644 index 0000000..ef27949 --- /dev/null +++ b/dbus/data/90-gpio.rules @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: CC0-1.0 +# SPDX-FileCopyrightText: 2023 Bartosz Golaszewski + +SUBSYSTEM=="gpio", KERNEL=="gpiochip[0-9]*", GROUP="gpio", MODE="0660" diff --git a/dbus/data/gpio-manager.service b/dbus/data/gpio-manager.service new file mode 100644 index 0000000..f93a6fa --- /dev/null +++ b/dbus/data/gpio-manager.service @@ -0,0 +1,50 @@ +# SPDX-License-Identifier: CC0-1.0 +# SPDX-FileCopyrightText: 2023-2024 Bartosz Golaszewski + +[Unit] +Description=Centralized GPIO manager daemon + +[Service] +Type=dbus +BusName=io.gpiod1 +ExecStart=/usr/bin/gpio-manager +Restart=always +User=gpio-manager + +CapabilityBoundingSet= +ReadOnlyDirectories=/ +NoNewPrivileges=yes +RemoveIPC=yes +PrivateTmp=yes +PrivateUsers=yes +ProtectControlGroups=yes +ProtectHome=yes +ProtectKernelModules=yes +ProtectKernelTunables=yes +ProtectSystem=strict +ProtectClock=yes +Delegate=no +IPAddressDeny=any +KeyringMode=private +LockPersonality=yes +MemoryDenyWriteExecute=yes +NotifyAccess=main +PrivateMounts=no +PrivateNetwork=no +ProtectHostname=yes +RestrictNamespaces=yes +RestrictRealtime=yes +RestrictSUIDSGID=yes +SystemCallFilter=~@clock +SystemCallFilter=~@cpu-emulation +SystemCallFilter=~@debug +SystemCallFilter=~@module +SystemCallFilter=~@mount +SystemCallFilter=~@obsolete +SystemCallFilter=~@privileged +SystemCallFilter=~@raw-io +SystemCallFilter=~@reboot +SystemCallFilter=~@swap + +[Install] +WantedBy=multi-user.target diff --git a/dbus/data/io.gpiod1.conf b/dbus/data/io.gpiod1.conf new file mode 100644 index 0000000..99b470f --- /dev/null +++ b/dbus/data/io.gpiod1.conf @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From patchwork Wed Aug 7 09:10:44 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Bartosz Golaszewski X-Patchwork-Id: 818158 Received: from mail-wm1-f44.google.com (mail-wm1-f44.google.com [209.85.128.44]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C99D71D173A for ; Wed, 7 Aug 2024 09:11:26 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.44 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723021890; cv=none; b=gwqcoAnbpzx8JERCS8WtSsDQLgTvDz9RwE4weI7c2xm9hY8NFIQybx6kJZj41PRq+xx8SdRpzQ8GEHjBjUlY9KFIJcxPB+LrVsUrVqYw0EIsTkRFOCG+WOuBbjR5bzhZyoIDTYkV/tNaFmfIe8CzzoPW1PFllH6qUoqesu9yR28= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723021890; c=relaxed/simple; bh=3TFqo13AgEuKldomFYatbNEwbrZT/6APEtn5z/hX7AY=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=n3ck/7zXeDpi47rD9l4bc2/Vnwwq9MHEuNI/sJdvp3XJtakK9d625qrZ1go30pfetMDrwZAsZa7GQ6GtyjeK6Pr9+5kqrNxpqo1oB9tc9N0y7WqantFS9/NydiTwqFVmIK41vyoZ1YVVVtYMXOTMBdFAWB7sMLnoEinb6OghK98= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=bgdev.pl; spf=none smtp.mailfrom=bgdev.pl; dkim=pass (2048-bit key) header.d=bgdev-pl.20230601.gappssmtp.com header.i=@bgdev-pl.20230601.gappssmtp.com header.b=fuHInaLX; arc=none smtp.client-ip=209.85.128.44 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=bgdev.pl Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=bgdev.pl Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=bgdev-pl.20230601.gappssmtp.com header.i=@bgdev-pl.20230601.gappssmtp.com header.b="fuHInaLX" Received: by mail-wm1-f44.google.com with SMTP id 5b1f17b1804b1-42817bee9e8so10570735e9.3 for ; Wed, 07 Aug 2024 02:11:26 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bgdev-pl.20230601.gappssmtp.com; s=20230601; t=1723021885; x=1723626685; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=kemVWrdbvs1kI5VlAwptc4EOYKaJ+AE0jFBkx7w5Ts8=; b=fuHInaLXiM8qwrKRqaRVWZZY+fkeotWxkVqfBnbYWnC71cVRE/uPpEHTBt4nKC6dhN gMgqDqB2rlf5GO0Ox776QfU71dQY0HNnTx842YA4bQTSfIzayX7nSiPHJ141GEWBxIjv bBJ8SGGfYFmRI1EUYI27ylWXteLTi1zQFXdkGb2Z9IfDY/Ogfus5QFLnVNIkYRXhd9RH FJUGW96m73XKGcrR472fFDllAS3UHaLfSVME5EzL6DkRJ1A92DzdQKpRWbMgVUGJNffN MNfXXhuKqBvqMPdy9V8wlY64GL1Nl5ttficABjZKysFujet4A183oofEuo9R5dSt4t1T wj/Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1723021885; x=1723626685; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=kemVWrdbvs1kI5VlAwptc4EOYKaJ+AE0jFBkx7w5Ts8=; b=w75xIBYAAiSfnm6XqVCa0j32CGBG2v3F49oELOJXlUXkSfoUYyH5TXMu5MmEgR5gRG 1MmpnnQQLHvdCQlNwrZBttyicT9+o/+ME2LJ7O/rArWBSdRKZa4V4stIFzSR7q+oLN6F A69GgVk2fdcEtPZeKQT0SY0czLLI2cNRTw/Ez6fWxzIvC6SMG3NLEc9WBpNhE5T8eCYJ 0yIrgsn0mQpOEx9IB/gPzjCFZsfGpTi0J+irhAXxS+r+4WTJQD6Zz9E2rhXph9TcSl26 0hSjhHM7CzlI9hiSSdOQMUcZsp8ZFORv7pyF9nn8aNTgwEMj1OFsaZsg0oK+D2v2H1Bf qVNQ== X-Gm-Message-State: AOJu0YzyzPIieH32oEVcdldv8BHSt4X3WZxsiKxmT4MjFtPT28hEq5fL MmnB+gY2vdJQ3itpY6IVlblmtGAHezpA/q+4D33/x0j0fTilqMxhRX+MoMtHnv4= X-Google-Smtp-Source: AGHT+IFf/WVOfZuqynfwaRBzl020tUNiI0wDl4S4zvIljuIv0SgnhPWK1gwUSqB9ucKNfKe6baFG1A== X-Received: by 2002:a05:600c:1f8e:b0:426:6308:e2f0 with SMTP id 5b1f17b1804b1-428e6b81f40mr121804675e9.26.1723021884706; Wed, 07 Aug 2024 02:11:24 -0700 (PDT) Received: from [127.0.1.1] ([2a01:cb1d:dc:7e00:a3e7:be71:fe4f:c360]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-429059cd2b3sm18936355e9.44.2024.08.07.02.11.23 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 07 Aug 2024 02:11:23 -0700 (PDT) From: Bartosz Golaszewski Date: Wed, 07 Aug 2024 11:10:44 +0200 Subject: [PATCH libgpiod v4 13/18] dbus: add gpio-manager code Precedence: bulk X-Mailing-List: linux-gpio@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20240807-dbus-v4-13-64ea80169e51@linaro.org> References: <20240807-dbus-v4-0-64ea80169e51@linaro.org> In-Reply-To: <20240807-dbus-v4-0-64ea80169e51@linaro.org> To: Linus Walleij , Kent Gibson , Erik Schilling , Phil Howard , Andy Shevchenko , Viresh Kumar , Dan Carpenter , Philip Withnall Cc: linux-gpio@vger.kernel.org, Bartosz Golaszewski , Alexander Sverdlin X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=47591; i=bartosz.golaszewski@linaro.org; h=from:subject:message-id; bh=RW1sY0D0WIxMYpE7Ks2Nsp+01rG4pJd+5KmbUv27kpA=; b=kA0DAAoBEacuoBRx13IByyZiAGazOhKhTTW+j1Xdr7lc20QQPq7wuugwJzEGpfkDIsBJuHlQj IkCMwQAAQoAHRYhBBad62wLw8RgE9LHnxGnLqAUcddyBQJmszoSAAoJEBGnLqAUcddyS1IQAKWd db3hJPN5TFSUBmB2vSoFXUErovrFDcWZ6E7Nqs7vkXk1NUvKrNca7ZwpyK7SxiU1wHJLX8Eky/E R5R0Mac0Xrz4eDtj97kXyj2eyGVQTGTm48y198fcUgb5GXZFsDfdxn9KzriaEtHSbV5fy5PJAQk zfdoMH6CAlZRSEgmVfuZ01ym7KwC27IRnDa7U5LPiP0sj7Gy9ez7mKrD9/KKeWHXW3NG8qCEKiW mZZ0/s2uqD7ajLesWq8nrZeyyqpNByrICrKmWfjwDhkMAhre/y84ok915PGtD4UOskIjcIQC8/I MpuZUPdPJDdUHUupUNEyvcrfc1/Dice0sLglRqAIE8dQ0P18b9WwW7H6xbSy8ISCFb3fWLA09fr /pM6JLw0Cz6CFgPZoVqY91T9FXsTZgSofoTDgreFhdK+fVfUmHZYcoXUynsAZGLVPq1gx+ZRFLW 8Dn2w2RyYPZG+YIKouV5m9aVQw74+E+Xd/O/ytNo+YI3BlmIGUZdtlQ3isO7SCAagKZDf6gGeRF mMfL//5YEryrVp4+Lo4gAz8ptuTHV3lHBs/Y2AMhZwcA79TqUhbKPKU0EZHjPoC+zb4Cq8r7fOv rZkz0K0GZltIUMW3zeHY/hhioVQ4CnDMaDJEFA0g6FnhlOc3DoW8mcRyXGywx6+IwNf1xTKcwni TSkkd X-Developer-Key: i=bartosz.golaszewski@linaro.org; a=openpgp; fpr=169DEB6C0BC3C46013D2C79F11A72EA01471D772 From: Bartosz Golaszewski Add the code for the gpio-manager - the actual implementation of the DBus API defined in io.gpiod1.xml. Tested-by: Alexander Sverdlin Signed-off-by: Bartosz Golaszewski --- dbus/manager/daemon.c | 821 ++++++++++++++++++++++++++++++++++++++++++++ dbus/manager/daemon.h | 22 ++ dbus/manager/gpio-manager.c | 173 ++++++++++ dbus/manager/helpers.c | 431 +++++++++++++++++++++++ dbus/manager/helpers.h | 26 ++ 5 files changed, 1473 insertions(+) diff --git a/dbus/manager/daemon.c b/dbus/manager/daemon.c new file mode 100644 index 0000000..d6eb4a5 --- /dev/null +++ b/dbus/manager/daemon.c @@ -0,0 +1,821 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2022-2024 Bartosz Golaszewski + +#include +#include +#include + +#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 { + GpiodglibChip *chip; + GpiodbusChip *dbus_chip; + GpiodbusDaemon *daemon; + GDBusObjectManagerServer *line_manager; + GHashTable *lines; +} GpiodbusDaemonChipData; + +typedef struct { + GpiodglibLineRequest *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 D-Bus 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(GpiodglibChip *chip G_GNUC_UNUSED, + GpiodglibInfoEvent *event, + gpointer data) +{ + GpiodbusDaemonChipData *chip_data = data; + g_autoptr(GpiodglibLineInfo) info = NULL; + GpiodbusDaemonLineData *line_data; + guint offset; + + info = gpiodglib_info_event_get_line_info(event); + offset = gpiodglib_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, + GpiodglibLineInfo *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 = gpiodglib_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(GpiodglibChipInfo) chip_info = NULL; + GpiodglibChip *chip = chip_data->chip; + g_autoptr(GError) err = NULL; + guint i, num_lines; + gint j; + + chip_info = gpiodglib_chip_get_info(chip, &err); + if (!chip_info) { + g_critical("failed to read chip info: %s", err->message); + return FALSE; + } + + num_lines = gpiodglib_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(GpiodglibLineInfo) linfo = NULL; + + linfo = gpiodglib_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--) + gpiodglib_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(GpiodglibLineConfig) 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 = gpiodglib_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 = gpiodglib_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 = gpiodglib_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 = gpiodglib_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(GpiodglibLineRequest *request G_GNUC_UNUSED, + GpiodglibEdgeEvent *event, gpointer user_data) +{ + GpiodbusDaemonRequestData *req_data = user_data; + GpiodbusDaemonLineData *line_data; + gulong line_seqno, global_seqno; + GpiodglibEdgeEventType edge; + guint64 timestamp; + guint offset; + gint val; + + edge = gpiodglib_edge_event_get_event_type(event); + offset = gpiodglib_edge_event_get_line_offset(event); + timestamp = gpiodglib_edge_event_get_timestamp_ns(event); + global_seqno = gpiodglib_edge_event_get_global_seqno(event); + line_seqno = gpiodglib_edge_event_get_line_seqno(event); + + val = edge == GPIODGLIB_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, + GpiodglibLineRequest *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(GpiodglibRequestConfig) req_cfg = NULL; + g_autoptr(GpiodglibLineRequest) request = NULL; + g_autoptr(GpiodglibLineConfig) 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 = gpiodglib_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(GpiodglibChip) chip = NULL; + g_autoptr(GHashTable) lines = NULL; + g_autofree gchar *obj_path = 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 = gpiodglib_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"); +} diff --git a/dbus/manager/daemon.h b/dbus/manager/daemon.h new file mode 100644 index 0000000..716396d --- /dev/null +++ b/dbus/manager/daemon.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* SPDX-FileCopyrightText: 2022-2024 Bartosz Golaszewski */ + +#ifndef __GPIODBUS_DAEMON_H__ +#define __GPIODBUS_DAEMON_H__ + +#include +#include +#include + +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__ */ diff --git a/dbus/manager/gpio-manager.c b/dbus/manager/gpio-manager.c new file mode 100644 index 0000000..e07641d --- /dev/null +++ b/dbus/manager/gpio-manager.c @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2022-2024 Bartosz Golaszewski + +#include +#include +#include +#include +#include + +#include "daemon.h" + +static const gchar *const debug_domains[] = { + "gpio-manager", + "gpiodglib", + NULL +}; + +static gboolean stop_main_loop_on_sig(gpointer data, const gchar *signame) +{ + GMainLoop *loop = data; + + g_debug("%s received", signame); + + g_main_loop_quit(loop); + + return G_SOURCE_REMOVE; +} + +static gboolean on_sigterm(gpointer data) +{ + return stop_main_loop_on_sig(data, "SIGTERM"); +} + +static gboolean on_sigint(gpointer data) +{ + return stop_main_loop_on_sig(data, "SIGINT"); +} + +static gboolean on_sighup(gpointer data G_GNUC_UNUSED) +{ + g_debug("SIGHUB received, ignoring"); + + return G_SOURCE_CONTINUE; +} + +static void on_bus_acquired(GDBusConnection *con, + const gchar *name G_GNUC_UNUSED, + gpointer data) +{ + GpiodbusDaemon *daemon = data; + + g_debug("D-Bus connection acquired"); + + gpiodbus_daemon_start(daemon, con); +} + +static void on_name_acquired(GDBusConnection *con G_GNUC_UNUSED, + const gchar *name, gpointer data G_GNUC_UNUSED) +{ + g_debug("D-Bus name acquired: '%s'", name); +} + +static void on_name_lost(GDBusConnection *con, + const gchar *name, gpointer data G_GNUC_UNUSED) +{ + g_debug("D-Bus name lost: '%s'", name); + + if (!con) + g_error("unable to make connection to the bus"); + + if (g_dbus_connection_is_closed(con)) + g_error("connection to the bus closed"); + + g_error("name '%s' lost on the bus", name); +} + +static void print_version_and_exit(void) +{ + g_print("%s (libgpiod) v%s\n", g_get_prgname(), gpiodglib_api_version()); + + exit(EXIT_SUCCESS); +} + +static void parse_opts(int argc, char **argv) +{ + gboolean ret, opt_debug = FALSE, opt_version = FALSE; + g_autoptr(GOptionContext) ctx = NULL; + g_auto(GStrv) remaining = NULL; + g_autoptr(GError) err = NULL; + + const GOptionEntry opts[] = { + { + .long_name = "debug", + .short_name = 'd', + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_NONE, + .arg_data = &opt_debug, + .description = "Emit additional debug log messages.", + }, + { + .long_name = "version", + .short_name = 'v', + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_NONE, + .arg_data = &opt_version, + .description = "Print version and exit.", + }, + { + .long_name = G_OPTION_REMAINING, + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_STRING_ARRAY, + .arg_data = &remaining, + }, + { } + }; + + ctx = g_option_context_new(NULL); + g_option_context_set_summary(ctx, "D-Bus daemon managing GPIOs."); + g_option_context_add_main_entries(ctx, opts, NULL); + + ret = g_option_context_parse(ctx, &argc, &argv, &err); + if (!ret) { + g_printerr("Option parsing failed: %s\n\nUse %s --help\n", + err->message, g_get_prgname()); + exit(EXIT_FAILURE); + } + + if (remaining) { + g_printerr("Option parsing failed: additional arguments are not allowed\n"); + exit(EXIT_FAILURE); + } + + if (opt_version) + print_version_and_exit(); + + if (opt_debug) + g_log_writer_default_set_debug_domains(debug_domains); +} + +int main(int argc, char **argv) +{ + g_autoptr(GpiodbusDaemon) daemon = NULL; + g_autofree gchar *basename = NULL; + g_autoptr(GMainLoop) loop = NULL; + guint bus_id; + + basename = g_path_get_basename(argv[0]); + g_set_prgname(basename); + parse_opts(argc, argv); + + g_message("initializing %s", g_get_prgname()); + + loop = g_main_loop_new(NULL, FALSE); + daemon = gpiodbus_daemon_new(); + + g_unix_signal_add(SIGTERM, on_sigterm, loop); + g_unix_signal_add(SIGINT, on_sigint, loop); + g_unix_signal_add(SIGHUP, on_sighup, NULL); /* Ignore SIGHUP. */ + + bus_id = g_bus_own_name(G_BUS_TYPE_SYSTEM, "io.gpiod1", + G_BUS_NAME_OWNER_FLAGS_NONE, on_bus_acquired, + on_name_acquired, on_name_lost, daemon, NULL); + + g_message("%s started", g_get_prgname()); + + g_main_loop_run(loop); + + g_bus_unown_name(bus_id); + + g_message("%s exiting", g_get_prgname()); + + return EXIT_SUCCESS; +} diff --git a/dbus/manager/helpers.c b/dbus/manager/helpers.c new file mode 100644 index 0000000..6e90460 --- /dev/null +++ b/dbus/manager/helpers.c @@ -0,0 +1,431 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2023-2024 Bartosz Golaszewski + +#include "helpers.h" + +gint gpiodbus_id_cmp(gconstpointer a, gconstpointer b, + gpointer user_data G_GNUC_UNUSED) +{ + const gint *id_a = a; + const gint *id_b = b; + + if (*id_a < *id_b) + return -1; + else if (*id_a > *id_b) + return 1; + + return 0; +} + +static gboolean find_lowest(gpointer key, gpointer value G_GNUC_UNUSED, + gpointer data) +{ + gint *lowest = data, *curr = key; + + if (*lowest == *curr) + (*lowest)++; + + return FALSE; +} + +gint gpiodbus_id_alloc(GTree *id_root) +{ + gint lowest = 0, *key; + + g_tree_foreach(id_root, find_lowest, &lowest); + + key = g_malloc(sizeof(*key)); + *key = lowest; + g_tree_insert(id_root, key, NULL); + + return lowest; +} + +void gpiodbus_id_free(GTree *id_root, gint id) +{ + g_assert(g_tree_remove(id_root, &id)); +} + +gboolean +gpiodbus_chip_set_props(GpiodbusChip *skeleton, GpiodglibChip *chip, + GError **err) +{ + g_autoptr(GpiodglibChipInfo) info = NULL; + g_autofree gchar *label = NULL; + g_autofree gchar *path = NULL; + g_autofree gchar *name = NULL; + + info = gpiodglib_chip_get_info(chip, err); + if (!info) + return FALSE; + + name = gpiodglib_chip_info_dup_name(info); + label = gpiodglib_chip_info_dup_label(info); + + gpiodbus_chip_set_name(skeleton, name); + gpiodbus_chip_set_label(skeleton, label); + gpiodbus_chip_set_num_lines(skeleton, + gpiodglib_chip_info_get_num_lines(info)); + path = gpiodglib_chip_dup_path(chip); + gpiodbus_chip_set_path(skeleton, path); + g_dbus_interface_skeleton_flush(G_DBUS_INTERFACE_SKELETON(skeleton)); + + return TRUE; +} + +static const gchar *map_direction(GpiodglibLineDirection direction) +{ + switch (direction) { + case GPIODGLIB_LINE_DIRECTION_INPUT: + return "input"; + case GPIODGLIB_LINE_DIRECTION_OUTPUT: + return "output"; + default: + g_error("invalid direction value returned by libgpiod-glib"); + } +} + +static const gchar *map_edge(GpiodglibLineEdge edge) +{ + switch (edge) { + case GPIODGLIB_LINE_EDGE_NONE: + return "none"; + case GPIODGLIB_LINE_EDGE_FALLING: + return "falling"; + case GPIODGLIB_LINE_EDGE_RISING: + return "rising"; + case GPIODGLIB_LINE_EDGE_BOTH: + return "both"; + default: + g_error("invalid edge value returned by libgpiod-glib"); + } +} + +static const gchar *map_bias(GpiodglibLineBias bias) +{ + switch (bias) { + case GPIODGLIB_LINE_BIAS_UNKNOWN: + return "unknown"; + case GPIODGLIB_LINE_BIAS_DISABLED: + return "disabled"; + case GPIODGLIB_LINE_BIAS_PULL_UP: + return "pull-up"; + case GPIODGLIB_LINE_BIAS_PULL_DOWN: + return "pull-down"; + default: + g_error("invalid bias value returned by libgpiod-glib"); + } +} + +static const gchar *map_drive(GpiodglibLineDrive drive) +{ + switch (drive) { + case GPIODGLIB_LINE_DRIVE_PUSH_PULL: + return "push-pull"; + case GPIODGLIB_LINE_DRIVE_OPEN_DRAIN: + return "open-drain"; + case GPIODGLIB_LINE_DRIVE_OPEN_SOURCE: + return "open-source"; + default: + g_error("invalid drive value returned by libgpiod-glib"); + } +} + +static const gchar *map_clock(GpiodglibLineClock event_clock) +{ + switch (event_clock) { + case GPIODGLIB_LINE_CLOCK_MONOTONIC: + return "monotonic"; + case GPIODGLIB_LINE_CLOCK_REALTIME: + return "realtime"; + case GPIODGLIB_LINE_CLOCK_HTE: + return "hte"; + default: + g_error("invalid event clock value returned by libgpiod-glib"); + } +} + +void gpiodbus_line_set_props(GpiodbusLine *skeleton, GpiodglibLineInfo *info) +{ + g_autofree gchar *consumer = gpiodglib_line_info_dup_consumer(info); + g_autofree gchar *name = gpiodglib_line_info_dup_name(info); + + gpiodbus_line_set_offset(skeleton, + gpiodglib_line_info_get_offset(info)); + gpiodbus_line_set_name(skeleton, name); + gpiodbus_line_set_used(skeleton, gpiodglib_line_info_is_used(info)); + gpiodbus_line_set_consumer(skeleton, consumer); + gpiodbus_line_set_direction(skeleton, + map_direction(gpiodglib_line_info_get_direction(info))); + gpiodbus_line_set_edge_detection(skeleton, + map_edge(gpiodglib_line_info_get_edge_detection(info))); + gpiodbus_line_set_bias(skeleton, + map_bias(gpiodglib_line_info_get_bias(info))); + gpiodbus_line_set_drive(skeleton, + map_drive(gpiodglib_line_info_get_drive(info))); + gpiodbus_line_set_active_low(skeleton, + gpiodglib_line_info_is_active_low(info)); + gpiodbus_line_set_debounced(skeleton, + gpiodglib_line_info_is_debounced(info)); + gpiodbus_line_set_debounce_period_us(skeleton, + gpiodglib_line_info_get_debounce_period_us(info)); + gpiodbus_line_set_event_clock(skeleton, + map_clock(gpiodglib_line_info_get_event_clock(info))); + g_dbus_interface_skeleton_flush(G_DBUS_INTERFACE_SKELETON(skeleton)); +} + +static gint line_offset_cmp(gconstpointer a, gconstpointer b) +{ + GpiodbusObject *line_obj = (GpiodbusObject *)a; + GpiodbusLine *line; + const guint *offset = b; + + line = gpiodbus_object_peek_line(line_obj); + + return gpiodbus_line_get_offset(line) != *offset; +} + +void gpiodbus_request_set_props(GpiodbusRequest *skeleton, + GpiodglibLineRequest *request, GpiodbusChip *chip, + GDBusObjectManager *line_manager) +{ + g_autolist(GpiodbusObject) line_objs = NULL; + g_autoptr(GStrvBuilder) builder = NULL; + g_autoptr(GArray) offsets = NULL; + g_auto(GStrv) paths = NULL; + GList *found; + guint i; + + offsets = gpiodglib_line_request_get_requested_offsets(request); + line_objs = g_dbus_object_manager_get_objects(line_manager); + builder = g_strv_builder_new(); + + for (i = 0; i < offsets->len; i++) { + found = g_list_find_custom(line_objs, + &g_array_index(offsets, guint, i), + line_offset_cmp); + if (found) + g_strv_builder_add(builder, + g_dbus_object_get_object_path( + G_DBUS_OBJECT(found->data))); + } + + paths = g_strv_builder_end(builder); + + gpiodbus_request_set_chip_path(skeleton, + g_dbus_interface_skeleton_get_object_path( + G_DBUS_INTERFACE_SKELETON(chip))); + gpiodbus_request_set_line_paths(skeleton, (const gchar *const *)paths); + g_dbus_interface_skeleton_flush(G_DBUS_INTERFACE_SKELETON(skeleton)); +} + +static gboolean +set_settings_from_variant(GpiodglibLineSettings *settings, const gchar *key, + GVariant *val) +{ + GpiodglibLineDirection direction; + GpiodglibLineClock event_clock; + GpiodglibLineDrive drive; + GpiodglibLineEdge edge; + GpiodglibLineBias bias; + const gchar *str; + + /* FIXME: Make it into a nice set of hashmaps and callbacks. */ + if (g_strcmp0(key, "direction") == 0) { + str = g_variant_get_string(val, NULL); + + if (g_strcmp0(str, "input") == 0) { + direction = GPIODGLIB_LINE_DIRECTION_INPUT; + } else if (g_strcmp0(str, "output") == 0) { + direction = GPIODGLIB_LINE_DIRECTION_OUTPUT; + } else if (g_strcmp0(str, "as-is") == 0) { + direction = GPIODGLIB_LINE_DIRECTION_AS_IS; + } else { + g_critical("invalid direction value received: '%s'", + str); + return FALSE; + } + + gpiodglib_line_settings_set_direction(settings, direction); + } else if (g_strcmp0(key, "edge") == 0) { + str = g_variant_get_string(val, NULL); + + if (g_strcmp0(str, "falling") == 0) { + edge = GPIODGLIB_LINE_EDGE_FALLING; + } else if (g_strcmp0(str, "rising") == 0) { + edge = GPIODGLIB_LINE_EDGE_RISING; + } else if (g_strcmp0(str, "both") == 0) { + edge = GPIODGLIB_LINE_EDGE_BOTH; + } else { + g_critical("invalid edge value received: '%s'", str); + return FALSE; + } + + gpiodglib_line_settings_set_edge_detection(settings, edge); + } else if (g_strcmp0(key, "active-low") == 0) { + if (g_variant_get_boolean(val)) + gpiodglib_line_settings_set_active_low(settings, TRUE); + } else if (g_strcmp0(key, "bias") == 0) { + str = g_variant_get_string(val, NULL); + + if (g_strcmp0(str, "as-is") == 0) { + bias = GPIODGLIB_LINE_BIAS_AS_IS; + } else if (g_strcmp0(str, "pull-up") == 0) { + bias = GPIODGLIB_LINE_BIAS_PULL_UP; + } else if (g_strcmp0(str, "pull-down") == 0) { + bias = GPIODGLIB_LINE_BIAS_PULL_DOWN; + } else if (g_strcmp0(str, "disabled") == 0) { + bias = GPIODGLIB_LINE_BIAS_DISABLED; + } else { + g_critical("invalid bias value received: '%s'", str); + return FALSE; + } + + gpiodglib_line_settings_set_bias(settings, bias); + } else if (g_strcmp0(key, "drive") == 0) { + str = g_variant_get_string(val, NULL); + + if (g_strcmp0(str, "push-pull") == 0) { + drive = GPIODGLIB_LINE_DRIVE_PUSH_PULL; + } else if (g_strcmp0(str, "open-drain") == 0) { + drive = GPIODGLIB_LINE_DRIVE_OPEN_DRAIN; + } else if (g_strcmp0(str, "open-source") == 0) { + drive = GPIODGLIB_LINE_DRIVE_OPEN_SOURCE; + } else { + g_critical("invalid drive value received: '%s'", str); + return FALSE; + } + + gpiodglib_line_settings_set_drive(settings, drive); + } else if (g_strcmp0(key, "debounce-period") == 0) { + gpiodglib_line_settings_set_debounce_period_us(settings, + g_variant_get_int64(val)); + } else if (g_strcmp0(key, "event-clock") == 0) { + str = g_variant_get_string(val, NULL); + + if (g_strcmp0(str, "monotonic") == 0) { + event_clock = GPIODGLIB_LINE_CLOCK_MONOTONIC; + } else if (g_strcmp0(str, "realtime") == 0) { + event_clock = GPIODGLIB_LINE_CLOCK_REALTIME; + } else if (g_strcmp0(str, "hte") == 0) { + event_clock = GPIODGLIB_LINE_CLOCK_HTE; + } else { + g_critical("invalid event clock value received: '%s'", + str); + return FALSE; + } + + gpiodglib_line_settings_set_event_clock(settings, event_clock); + } else { + g_critical("invalid config option received: '%s'", key); + return FALSE; + } + + return TRUE; +} + +GpiodglibLineConfig *gpiodbus_line_config_from_variant(GVariant *variant) +{ + g_autoptr(GpiodglibLineSettings) settings = NULL; + g_autoptr(GpiodglibLineConfig) config = NULL; + g_autoptr(GVariant) output_values_v = NULL; + g_autoptr(GVariant) line_configs_v = NULL; + g_autoptr(GArray) values = NULL; + g_autoptr(GError) err = NULL; + GVariantIter iter0, iter1; + guint offset; + gboolean ret; + GVariant *v; + gchar *k; + gint val; + + line_configs_v = g_variant_get_child_value(variant, 0); + output_values_v = g_variant_get_child_value(variant, 1); + + config = gpiodglib_line_config_new(); + settings = gpiodglib_line_settings_new(NULL); + + g_variant_iter_init(&iter0, line_configs_v); + while ((v = g_variant_iter_next_value(&iter0))) { + g_autoptr(GVariant) line_settings_v = NULL; + g_autoptr(GVariant) line_config_v = v; + g_autoptr(GVariant) offsets_v = NULL; + g_autoptr(GArray) offsets = NULL; + + offsets_v = g_variant_get_child_value(line_config_v, 0); + line_settings_v = g_variant_get_child_value(line_config_v, 1); + + gpiodglib_line_settings_reset(settings); + g_variant_iter_init(&iter1, line_settings_v); + while (g_variant_iter_next(&iter1, "{sv}", &k, &v)) { + g_autoptr(GVariant) val = v; + g_autofree gchar *key = k; + + ret = set_settings_from_variant(settings, key, val); + if (!ret) + return NULL; + } + + offsets = g_array_sized_new(FALSE, TRUE, sizeof(guint), + g_variant_n_children(offsets_v)); + g_variant_iter_init(&iter1, offsets_v); + while (g_variant_iter_next(&iter1, "u", &offset)) + g_array_append_val(offsets, offset); + + ret = gpiodglib_line_config_add_line_settings(config, offsets, + settings, &err); + if (!ret) { + g_critical("failed to add line settings: %s", + err->message); + return NULL; + } + } + + values = g_array_sized_new(FALSE, TRUE, sizeof(gint), + g_variant_n_children(output_values_v)); + g_variant_iter_init(&iter0, output_values_v); + while (g_variant_iter_next(&iter0, "i", &val)) + g_array_append_val(values, val); + + if (values->len > 0) { + ret = gpiodglib_line_config_set_output_values(config, values, + &err); + if (!ret) { + g_critical("failed to set output values: %s", + err->message); + return NULL; + } + } + + return g_object_ref(config); +} + +GpiodglibRequestConfig *gpiodbus_request_config_from_variant(GVariant *variant) +{ + g_autoptr(GpiodglibRequestConfig) config = NULL; + GVariantIter iter; + GVariant *v; + gchar *k; + + config = gpiodglib_request_config_new(NULL); + + g_variant_iter_init(&iter, variant); + while (g_variant_iter_next(&iter, "{sv}", &k, &v)) { + g_autoptr(GVariant) val = v; + g_autofree gchar *key = k; + + if (g_strcmp0(key, "consumer") == 0) { + gpiodglib_request_config_set_consumer(config, + g_variant_get_string(val, NULL)); + } else if (g_strcmp0(key, "event-buffer-size") == 0) { + gpiodglib_request_config_set_event_buffer_size(config, + g_variant_get_uint32(val)); + } else { + g_critical("invalid request config option received: '%s'", + key); + return NULL; + } + } + + return g_object_ref(config); +} diff --git a/dbus/manager/helpers.h b/dbus/manager/helpers.h new file mode 100644 index 0000000..6ad83bd --- /dev/null +++ b/dbus/manager/helpers.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* SPDX-FileCopyrightText: 2023-2024 Bartosz Golaszewski */ + +#ifndef __GPIODBUS_HELPERS_H__ +#define __GPIODBUS_HELPERS_H__ + +#include +#include +#include +#include + +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, GpiodglibChip *chip, + GError **err); +void gpiodbus_line_set_props(GpiodbusLine *skeleton, GpiodglibLineInfo *info); +void gpiodbus_request_set_props(GpiodbusRequest *skeleton, + GpiodglibLineRequest *request, + GpiodbusChip *chip, + GDBusObjectManager *line_manager); +GpiodglibLineConfig *gpiodbus_line_config_from_variant(GVariant *variant); +GpiodglibRequestConfig *gpiodbus_request_config_from_variant(GVariant *variant); + +#endif /* __GPIODBUS_HELPERS_H__ */ From patchwork Wed Aug 7 09:10:45 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Bartosz Golaszewski X-Patchwork-Id: 817494 Received: from mail-wm1-f50.google.com (mail-wm1-f50.google.com [209.85.128.50]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 542B71D173B for ; Wed, 7 Aug 2024 09:11:27 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.50 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723021889; cv=none; b=F/7f+5JloKFq5DdDZY5tMHQELet9Isxc5huOCKpyxrPDABcBgcBaA78zZXJ5A23QvI8vTQj8wrHRQvfI9OZK6Xo/1Hp28jE1V/jzstDVrZwebK6JwlWssSUs7LO6qOGY8YZXvME4LpR5xb3/IalIDb6HMoyt3S0TbbzWcjO4cHI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723021889; c=relaxed/simple; bh=ageDLTwEo+BmI3rPAdbaDhfHfFsSpDT9K7TZGbim0sE=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=hogT1KveN/IM9RIbYvKboPAXwdRVdsdFd2kqm3endlwJZlbzwJlIeezaeL1jOoWTeTcIMA48Jh1+uR62voy4a3P55SXrUocEuqvFSF4RxW9ISQ5k4+elUeAIlScxMfMpW8iMSf96MNwLp0i62OQ5jYRFPY0rC3vsaXPzkpxQtbQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=bgdev.pl; spf=none smtp.mailfrom=bgdev.pl; dkim=pass (2048-bit key) header.d=bgdev-pl.20230601.gappssmtp.com header.i=@bgdev-pl.20230601.gappssmtp.com header.b=VjqnKuuG; arc=none smtp.client-ip=209.85.128.50 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=bgdev.pl Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=bgdev.pl Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=bgdev-pl.20230601.gappssmtp.com header.i=@bgdev-pl.20230601.gappssmtp.com header.b="VjqnKuuG" Received: by mail-wm1-f50.google.com with SMTP id 5b1f17b1804b1-428fb103724so3676965e9.1 for ; Wed, 07 Aug 2024 02:11:27 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bgdev-pl.20230601.gappssmtp.com; s=20230601; t=1723021886; x=1723626686; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=T9XvZnmeUGmFbZbvV9oUCAGwukHfh77cEC8B1BTwJbg=; b=VjqnKuuGd02LoWrTrFdDJRp4VyZZOPLqqDNBI2JZYDmSDU9fi2foehZQgPQ8ElO9jU QY5DVZ9SQphCeuRUjXRNCyLIIiQAClJu+PsvDd46/AuAr9vughO8hxTjrt5rG75zI3g0 C3BIx23v+9CqOhAeu7HOgKHgpJI5sehKb7PD48YSGqi28OrAr0U6YDsY1IPLEXuV3016 GlTElVNely5ju6ujBXlxfWq6Qmh9+3dmdo0jdxKyatAaNW3ZCN9bH3XzUByJpShPlQtl RrRroDZWZQhXyii7oB7CftYABNe7DZCVWiGPBUc7ay7CGJpXy6mwLPQpyLhDnzLZfeBg DiJw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1723021886; x=1723626686; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=T9XvZnmeUGmFbZbvV9oUCAGwukHfh77cEC8B1BTwJbg=; b=izG0uKpN25nFC1WpU+dWV3TlbEoJGU0BfqIUCMxh3meRkUbxCM9pbLuIsW8BTpgxhD g4mTJnVtTCvafZ7rLnSNlc74jFaPEbHto1K7NFViKhJxN+ppNJozgfTdxjHED87l6UVq /uJEexknecleFST5MfGqD7IgZ15yfDpvz17p3LSiKZAG0+s4v6Y2rOqe3WFY9hheI0Nu oFpWdY7ZhZBU8RqlatX7tfcJZYBksUJAJNcMQZuQFpKbtcRv1itUkEIKlmtdjx0ZCVCa dv5Tal3btna0w/8X0XbZCVTd+pIIL0xjemYnNv1h5MwOBipB85nP/aJPSnNk1nCvIq+i mqCg== X-Gm-Message-State: AOJu0YzEjsFwiVGA4NkFzwIWjxfEKdn6SB7GqG51gZuKoSpaA96n9w4n KrwkQ6CaTOLht8k8PfYWyMNw0Mfm0VtgDa1TXLyNKGFqgCXemqfGZ5bD/ruQdtE= X-Google-Smtp-Source: AGHT+IHaPQcUplAJCG74zOgIsJkoDgIfKMv6P/6p9xdLqAwOLVcZ+KnA2ts9RMjNlblBnbZx2y3vrw== X-Received: by 2002:a05:600c:19c7:b0:426:6fb1:6b64 with SMTP id 5b1f17b1804b1-429050c2b96mr11147395e9.7.1723021885595; Wed, 07 Aug 2024 02:11:25 -0700 (PDT) Received: from [127.0.1.1] ([2a01:cb1d:dc:7e00:a3e7:be71:fe4f:c360]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-429059cd2b3sm18936355e9.44.2024.08.07.02.11.24 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 07 Aug 2024 02:11:25 -0700 (PDT) From: Bartosz Golaszewski Date: Wed, 07 Aug 2024 11:10:45 +0200 Subject: [PATCH libgpiod v4 14/18] dbus: add tests Precedence: bulk X-Mailing-List: linux-gpio@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20240807-dbus-v4-14-64ea80169e51@linaro.org> References: <20240807-dbus-v4-0-64ea80169e51@linaro.org> In-Reply-To: <20240807-dbus-v4-0-64ea80169e51@linaro.org> To: Linus Walleij , Kent Gibson , Erik Schilling , Phil Howard , Andy Shevchenko , Viresh Kumar , Dan Carpenter , Philip Withnall Cc: linux-gpio@vger.kernel.org, Bartosz Golaszewski , Alexander Sverdlin X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=29306; i=bartosz.golaszewski@linaro.org; h=from:subject:message-id; bh=O8kSYeVdijgogU40UUjjMH6YuwZ5QMa4Z46U5SrDdQ0=; b=owEBbQKS/ZANAwAKARGnLqAUcddyAcsmYgBmszoS12jOmc+tlHJUuP3V+OzrHODKWmOMCEPTe vBroZiutYSJAjMEAAEKAB0WIQQWnetsC8PEYBPSx58Rpy6gFHHXcgUCZrM6EgAKCRARpy6gFHHX cj/jEADfpNiBWGJ5qo7MiyYBgndIeji2lSYw99YfKJ0a98yVzdnyWXZUldD9EYxgPvJ7NERVq21 EcLHwsVc3mPPg5NZwWLAbd8nblQZqzk9rONqhFObsGyZ2XeFkcO102qCKbfjMlrr4XcHdGX7Wob D3JRqRNya0/uo7BieD9x2jKm63mDq+/n1+uKeDntSMvkF8Gj7KLXNM1CqaTJAjlvT7AwCWsqlHU A06IfdCNVxbo8g6MwAZdB1O03a7kP1OaowqsiKo93Tpc/H9OFly+IY2zdw1LgdDzXEtDi5FIG2W VgwwEB3OIiFHxqov7uUYDspeI3vnn5XPye4OqTgRS++H6Q/gDufYk9AqZ+Mid5QAYRfdOMg++17 xilMYMxJGyOwa8M2Dl2p9h8+inxyybkQeKEgSwyCA2VFJhYcVb/CsfhTNtj8w9K+4wMK8uQFMY3 zc5j9pH+OYUIywaqPglBuCiywb6RgkSTIy6Z+JCxt+NDUzr/7qF/JxTkp9cGA+Bqh7hUIU31HV5 zhrCpXmvJUYzAk+Xd8Rx9ZYRCV6BP6dwfuAJnWA80HoY15pLZNCnE5GgjH1VsV0IcYeqvoRd/or ANxWmMd0gnH17jTm4k7vur1T1GFarKvHLU4zk57TpRRYAdDi15Vz1/DYhrK0fkaqsWL8XQ4DHpE 55Z6aA/WrJpIQMQ== X-Developer-Key: i=bartosz.golaszewski@linaro.org; a=openpgp; fpr=169DEB6C0BC3C46013D2C79F11A72EA01471D772 From: Bartosz Golaszewski Add a set of test-cases verifying the functionality of the gpio-manager daemon. Tested-by: Alexander Sverdlin Signed-off-by: Bartosz Golaszewski --- dbus/tests/daemon-process.c | 129 +++++++++++++++++++++++++ dbus/tests/daemon-process.h | 20 ++++ dbus/tests/helpers.c | 107 ++++++++++++++++++++ dbus/tests/helpers.h | 114 ++++++++++++++++++++++ dbus/tests/tests-chip.c | 133 +++++++++++++++++++++++++ dbus/tests/tests-line.c | 231 ++++++++++++++++++++++++++++++++++++++++++++ dbus/tests/tests-request.c | 116 ++++++++++++++++++++++ 7 files changed, 850 insertions(+) diff --git a/dbus/tests/daemon-process.c b/dbus/tests/daemon-process.c new file mode 100644 index 0000000..e65183e --- /dev/null +++ b/dbus/tests/daemon-process.c @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2022-2023 Bartosz Golaszewski + +#include +#include + +#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); +} diff --git a/dbus/tests/daemon-process.h b/dbus/tests/daemon-process.h new file mode 100644 index 0000000..f5f453b --- /dev/null +++ b/dbus/tests/daemon-process.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* SPDX-FileCopyrightText: 2022-2023 Bartosz Golaszewski */ + +#ifndef __GPIODBUS_TEST_DAEMON_PROCESS_H__ +#define __GPIODBUS_TEST_DAEMON_PROCESS_H__ + +#include + +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__ */ diff --git a/dbus/tests/helpers.c b/dbus/tests/helpers.c new file mode 100644 index 0000000..f0089a0 --- /dev/null +++ b/dbus/tests/helpers.c @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2022-2023 Bartosz Golaszewski + +#include + +#include "helpers.h" + +GDBusConnection *gpiodbus_test_get_dbus_connection(void) +{ + g_autoptr(GDBusConnection) con = NULL; + g_autofree gchar *addr = NULL; + g_autoptr(GError) err = NULL; + + addr = g_dbus_address_get_for_bus_sync(G_BUS_TYPE_SYSTEM, NULL, &err); + if (!addr) + g_error("Failed to get address on the 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 system bus connection: %s", + err->message); + + return g_object_ref(con); +} + +typedef struct { + gboolean *added; + gchar *obj_path; +} OnObjectAddedData; + +static void on_object_added(GDBusObjectManager *manager G_GNUC_UNUSED, + GpiodbusObject *object, gpointer data) +{ + OnObjectAddedData *cb_data = data; + const gchar *path; + + path = g_dbus_object_get_object_path(G_DBUS_OBJECT(object)); + + if (g_strcmp0(path, cb_data->obj_path) == 0) + *cb_data->added = TRUE; +} + +static gboolean on_timeout(gpointer data G_GNUC_UNUSED) +{ + g_error("timeout reached waiting for the gpiochip interface to appear on the bus"); + + return G_SOURCE_REMOVE; +} + +void gpiodbus_test_wait_for_sim_intf(GPIOSimChip *sim) +{ + g_autoptr(GDBusObjectManager) manager = NULL; + g_autoptr(GDBusConnection) con = NULL; + g_autoptr(GpiodbusObject) obj = NULL; + g_autoptr(GError) err = NULL; + g_autofree gchar *obj_path; + OnObjectAddedData cb_data; + gboolean added = FALSE; + guint timeout_id; + + con = gpiodbus_test_get_dbus_connection(); + if (!con) + g_error("failed to obtain a bus connection: %s", err->message); + + obj_path = g_strdup_printf("/io/gpiod1/chips/%s", + g_gpiosim_chip_get_name(sim)); + + cb_data.added = &added; + cb_data.obj_path = obj_path; + + manager = gpiodbus_object_manager_client_new_sync(con, + G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE, + "io.gpiod1", "/io/gpiod1/chips", NULL, &err); + if (!manager) + g_error("failed to create the object manager client: %s", + err->message); + + g_signal_connect(manager, "object-added", G_CALLBACK(on_object_added), + &cb_data); + + obj = GPIODBUS_OBJECT(g_dbus_object_manager_get_object(manager, + obj_path)); + if (obj) { + if (g_strcmp0(g_dbus_object_get_object_path(G_DBUS_OBJECT(obj)), + obj_path) == 0) + added = TRUE; + } + + timeout_id = g_timeout_add_seconds(5, on_timeout, NULL); + + while (!added) + g_main_context_iteration(NULL, TRUE); + + g_source_remove(timeout_id); +} + +GVariant *gpiodbus_test_make_empty_request_config(void) +{ + GVariantBuilder builder; + + g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}")); + + return g_variant_ref_sink(g_variant_builder_end(&builder)); +} diff --git a/dbus/tests/helpers.h b/dbus/tests/helpers.h new file mode 100644 index 0000000..b0be279 --- /dev/null +++ b/dbus/tests/helpers.h @@ -0,0 +1,114 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* SPDX-FileCopyrightText: 2022-2023 Bartosz Golaszewski */ + +#ifndef __GPIODBUS_TEST_INTERNAL_H__ +#define __GPIODBUS_TEST_INTERNAL_H__ + +#include +#include +#include +#include + +#define __gpiodbus_test_check_gboolean_and_error(_ret, _err) \ + do { \ + g_assert_true(_ret); \ + g_assert_no_error(_err); \ + gpiod_test_return_if_failed(); \ + } while (0) + +#define __gpiodbus_test_check_nonnull_and_error(_ptr, _err) \ + do { \ + g_assert_nonnull(_ptr); \ + g_assert_no_error(_err); \ + gpiod_test_return_if_failed(); \ + } while (0) + +#define gpiodbus_test_get_chip_proxy_or_fail(_obj_path) \ + ({ \ + g_autoptr(GDBusConnection) _con = NULL; \ + g_autoptr(GError) _err = NULL; \ + g_autoptr(GpiodbusChip) _chip = NULL; \ + _con = gpiodbus_test_get_dbus_connection(); \ + _chip = gpiodbus_chip_proxy_new_sync(_con, \ + G_DBUS_PROXY_FLAGS_NONE, \ + "io.gpiod1", _obj_path, \ + NULL, &_err); \ + __gpiodbus_test_check_nonnull_and_error(_chip, _err); \ + g_object_ref(_chip); \ + }) + +#define gpiodbus_test_get_line_proxy_or_fail(_obj_path) \ + ({ \ + g_autoptr(GDBusConnection) _con = NULL; \ + g_autoptr(GError) _err = NULL; \ + g_autoptr(GpiodbusLine) _line = NULL; \ + _con = gpiodbus_test_get_dbus_connection(); \ + _line = gpiodbus_line_proxy_new_sync(_con, \ + G_DBUS_PROXY_FLAGS_NONE, \ + "io.gpiod1", _obj_path, \ + NULL, &_err); \ + __gpiodbus_test_check_nonnull_and_error(_line, _err); \ + g_object_ref(_line); \ + }) + +#define gpiodbus_test_get_request_proxy_or_fail(_obj_path) \ + ({ \ + g_autoptr(GDBusConnection) _con = NULL; \ + g_autoptr(GError) _err = NULL; \ + g_autoptr(GpiodbusRequest) _req = NULL; \ + _con = gpiodbus_test_get_dbus_connection(); \ + _req = gpiodbus_request_proxy_new_sync(_con, \ + G_DBUS_PROXY_FLAGS_NONE, \ + "io.gpiod1", _obj_path, \ + NULL, &_err); \ + __gpiodbus_test_check_nonnull_and_error(_req, _err); \ + g_object_ref(_req); \ + }) + +#define gpiodbus_test_get_chip_object_manager_or_fail() \ + ({ \ + g_autoptr(GDBusObjectManager) _manager = NULL; \ + g_autoptr(GDBusConnection) _con = NULL; \ + g_autoptr(GError) _err = NULL; \ + _con = gpiodbus_test_get_dbus_connection(); \ + _manager = gpiodbus_object_manager_client_new_sync( \ + _con, \ + G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE, \ + "io.gpiod1", "/io/gpiod1/chips", NULL, \ + &_err); \ + __gpiodbus_test_check_nonnull_and_error(_manager, _err); \ + g_object_ref(_manager); \ + }) + +#define gpiodbus_test_chip_call_request_lines_sync_or_fail(_chip, \ + _line_config, \ + _request_config, \ + _request_path) \ + do { \ + g_autoptr(GError) _err = NULL; \ + gboolean _ret; \ + _ret = gpiodbus_chip_call_request_lines_sync( \ + _chip, _line_config, \ + _request_config, \ + G_DBUS_CALL_FLAGS_NONE, -1, \ + _request_path, NULL, &_err); \ + __gpiodbus_test_check_gboolean_and_error(_ret, _err); \ + } while (0) + +#define gpiodbus_test_request_call_release_sync_or_fail(_request) \ + do { \ + g_autoptr(GError) _err = NULL; \ + gboolean _ret; \ + _ret = gpiodbus_request_call_release_sync( \ + _request, \ + G_DBUS_CALL_FLAGS_NONE, \ + -1, NULL, &_err); \ + __gpiodbus_test_check_gboolean_and_error(_ret, _err); \ + } while (0) + +GDBusConnection *gpiodbus_test_get_dbus_connection(void); +void gpiodbus_test_wait_for_sim_intf(GPIOSimChip *sim); +GVariant *gpiodbus_test_make_empty_request_config(void); + +#endif /* __GPIODBUS_TEST_INTERNAL_H__ */ + diff --git a/dbus/tests/tests-chip.c b/dbus/tests/tests-chip.c new file mode 100644 index 0000000..bfb5e3c --- /dev/null +++ b/dbus/tests/tests-chip.c @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2022-2023 Bartosz Golaszewski + +#include +#include +#include +#include +#include +#include + +#include "daemon-process.h" +#include "helpers.h" + +#define GPIOD_TEST_GROUP "gpiodbus/chip" + +GPIOD_TEST_CASE(read_chip_info) +{ + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, + "label", "foobar", + NULL); + g_autoptr(GpiodbusDaemonProcess) mgr = NULL; + g_autoptr(GpiodbusChip) chip = NULL; + g_autofree gchar *obj_path = NULL; + + mgr = gpiodbus_daemon_process_new(); + gpiodbus_test_wait_for_sim_intf(sim); + + obj_path = g_strdup_printf("/io/gpiod1/chips/%s", + g_gpiosim_chip_get_name(sim)); + chip = gpiodbus_test_get_chip_proxy_or_fail(obj_path); + + g_assert_cmpstr(gpiodbus_chip_get_name(chip), ==, + g_gpiosim_chip_get_name(sim)); + g_assert_cmpstr(gpiodbus_chip_get_label(chip), ==, "foobar"); + g_assert_cmpuint(gpiodbus_chip_get_num_lines(chip), ==, 8); + g_assert_cmpstr(gpiodbus_chip_get_path(chip), ==, + g_gpiosim_chip_get_dev_path(sim)); +} + +static gboolean on_timeout(gpointer user_data) +{ + gboolean *timed_out = user_data; + + *timed_out = TRUE; + + return G_SOURCE_REMOVE; +} + +static void on_object_event(GDBusObjectManager *manager G_GNUC_UNUSED, + GpiodbusObject *object, gpointer user_data) +{ + gchar **obj_path = user_data; + + *obj_path = g_strdup(g_dbus_object_get_object_path( + G_DBUS_OBJECT(object))); +} + +GPIOD_TEST_CASE(chip_added) +{ + g_autoptr(GDBusObjectManager) manager = NULL; + g_autoptr(GpiodbusDaemonProcess) mgr = NULL; + g_autofree gchar *sim_obj_path = NULL; + g_autoptr(GPIOSimChip) sim = NULL; + g_autofree gchar *obj_path = NULL; + gboolean timed_out = FALSE; + guint timeout_id; + + mgr = gpiodbus_daemon_process_new(); + + manager = gpiodbus_test_get_chip_object_manager_or_fail(); + + g_signal_connect(manager, "object-added", G_CALLBACK(on_object_event), + &obj_path); + timeout_id = g_timeout_add_seconds(5, on_timeout, &timed_out); + + sim = g_gpiosim_chip_new(NULL); + + while (!obj_path && !timed_out) + g_main_context_iteration(NULL, TRUE); + + if (timed_out) { + g_test_fail_printf("timeout reached waiting for chip to be added"); + return; + } + + sim_obj_path = g_strdup_printf("/io/gpiod1/chips/%s", + g_gpiosim_chip_get_name(sim)); + + g_assert_cmpstr(sim_obj_path, ==, obj_path); + + g_source_remove(timeout_id); +} + +GPIOD_TEST_CASE(chip_removed) +{ + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new(NULL); + g_autoptr(GDBusObjectManager) manager = NULL; + g_autoptr(GpiodbusDaemonProcess) mgr = NULL; + g_autofree gchar *sim_obj_path = NULL; + g_autoptr(GpiodbusChip) chip = NULL; + g_autofree gchar *obj_path = NULL; + gboolean timed_out = FALSE; + guint timeout_id; + + sim_obj_path = g_strdup_printf("/io/gpiod1/chips/%s", + g_gpiosim_chip_get_name(sim)); + + mgr = gpiodbus_daemon_process_new(); + gpiodbus_test_wait_for_sim_intf(sim); + + obj_path = g_strdup_printf("/io/gpiod1/chips/%s", + g_gpiosim_chip_get_name(sim)); + chip = gpiodbus_test_get_chip_proxy_or_fail(obj_path); + manager = gpiodbus_test_get_chip_object_manager_or_fail(); + + g_signal_connect(manager, "object-removed", G_CALLBACK(on_object_event), + &obj_path); + timeout_id = g_timeout_add_seconds(5, on_timeout, &timed_out); + + g_clear_object(&sim); + + while (!obj_path && !timed_out) + g_main_context_iteration(NULL, TRUE); + + if (timed_out) { + g_test_fail_printf("timeout reached waiting for chip to be removed"); + return; + } + + g_assert_cmpstr(sim_obj_path, ==, obj_path); + + g_source_remove(timeout_id); +} diff --git a/dbus/tests/tests-line.c b/dbus/tests/tests-line.c new file mode 100644 index 0000000..309e6c4 --- /dev/null +++ b/dbus/tests/tests-line.c @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2023 Bartosz Golaszewski + +#include +#include +#include +#include +#include +#include + +#include "daemon-process.h" +#include "helpers.h" + +#define GPIOD_TEST_GROUP "gpiodbus/line" + +GPIOD_TEST_CASE(read_line_properties) +{ + static const GPIOSimLineName names[] = { + { .offset = 1, .name = "foo", }, + { .offset = 2, .name = "bar", }, + { .offset = 4, .name = "baz", }, + { .offset = 5, .name = "xyz", }, + { } + }; + + static const GPIOSimHog hogs[] = { + { + .offset = 3, + .name = "hog3", + .direction = G_GPIOSIM_DIRECTION_OUTPUT_HIGH, + }, + { + .offset = 4, + .name = "hog4", + .direction = G_GPIOSIM_DIRECTION_OUTPUT_LOW, + }, + { } + }; + + g_autoptr(GpiodbusDaemonProcess) mgr = NULL; + g_autoptr(GpiodbusLine) line4 = NULL; + g_autoptr(GpiodbusLine) line6 = NULL; + g_autofree gchar *obj_path_4 = NULL; + g_autofree gchar *obj_path_6 = NULL; + g_autoptr(GPIOSimChip) sim = NULL; + 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, + "line-names", vnames, + "hogs", vhogs, + NULL); + + mgr = gpiodbus_daemon_process_new(); + gpiodbus_test_wait_for_sim_intf(sim); + + obj_path_4 = g_strdup_printf("/io/gpiod1/chips/%s/line4", + g_gpiosim_chip_get_name(sim)); + line4 = gpiodbus_test_get_line_proxy_or_fail(obj_path_4); + + obj_path_6 = g_strdup_printf("/io/gpiod1/chips/%s/line6", + g_gpiosim_chip_get_name(sim)); + line6 = gpiodbus_test_get_line_proxy_or_fail(obj_path_6); + + g_assert_cmpuint(gpiodbus_line_get_offset(line4), ==, 4); + g_assert_cmpstr(gpiodbus_line_get_name(line4), ==, "baz"); + g_assert_cmpstr(gpiodbus_line_get_consumer(line4), ==, "hog4"); + g_assert_true(gpiodbus_line_get_used(line4)); + g_assert_false(gpiodbus_line_get_managed(line4)); + g_assert_cmpstr(gpiodbus_line_get_direction(line4), ==, "output"); + g_assert_cmpstr(gpiodbus_line_get_edge_detection(line4), ==, "none"); + g_assert_false(gpiodbus_line_get_active_low(line4)); + g_assert_cmpstr(gpiodbus_line_get_bias(line4), ==, "unknown"); + g_assert_cmpstr(gpiodbus_line_get_drive(line4), ==, "push-pull"); + g_assert_cmpstr(gpiodbus_line_get_event_clock(line4), ==, "monotonic"); + g_assert_false(gpiodbus_line_get_debounced(line4)); + g_assert_cmpuint(gpiodbus_line_get_debounce_period_us(line4), ==, 0); + + g_assert_cmpuint(gpiodbus_line_get_offset(line6), ==, 6); + g_assert_cmpstr(gpiodbus_line_get_name(line6), ==, ""); + g_assert_cmpstr(gpiodbus_line_get_consumer(line6), ==, ""); + g_assert_false(gpiodbus_line_get_used(line6)); +} + +static gboolean on_timeout(gpointer user_data) +{ + gboolean *timed_out = user_data; + + *timed_out = TRUE; + + return G_SOURCE_REMOVE; +} + +static void +on_properties_changed(GpiodbusLine *line G_GNUC_UNUSED, + GVariant *changed_properties, + GStrv invalidated_properties G_GNUC_UNUSED, + gpointer user_data) +{ + GHashTable *changed_props = user_data; + GVariantIter iter; + GVariant *variant; + gchar *str; + + g_variant_iter_init(&iter, changed_properties); + while (g_variant_iter_next(&iter, "{sv}", &str, &variant)) { + g_hash_table_insert(changed_props, str, NULL); + g_variant_unref(variant); + } +} + +static void check_props_requested(GHashTable *props) +{ + if (!g_hash_table_contains(props, "Direction") || + !g_hash_table_contains(props, "Consumer") || + !g_hash_table_contains(props, "Used") || + !g_hash_table_contains(props, "RequestPath") || + !g_hash_table_contains(props, "Managed")) + g_test_fail_printf("Not all expected properties have changed"); +} + +static void check_props_released(GHashTable *props) +{ + if (!g_hash_table_contains(props, "RequestPath") || + !g_hash_table_contains(props, "Consumer") || + !g_hash_table_contains(props, "Used") || + !g_hash_table_contains(props, "Managed")) + g_test_fail_printf("Not all expected properties have changed"); +} + +static GVariant *make_props_changed_line_config(void) +{ + 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; + + g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY); + g_variant_builder_add_value(&builder, g_variant_new_uint32(4)); + line_offsets = g_variant_builder_end(&builder); + + g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY); + g_variant_builder_add_value(&builder, + g_variant_new("{sv}", "direction", + g_variant_new_string("output"))); + 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); + + output_values = g_variant_new("ai", NULL); + + 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)); +} + +GPIOD_TEST_CASE(properties_changed) +{ + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL); + g_autoptr(GpiodbusDaemonProcess) mgr = NULL; + g_autoptr(GHashTable) changed_props = NULL; + g_autoptr(GpiodbusRequest) request = NULL; + g_autoptr(GVariant) request_config = NULL; + g_autoptr(GVariant) line_config = NULL; + g_autofree gchar *line_obj_path = NULL; + g_autofree gchar *chip_obj_path = NULL; + g_autofree gchar *request_path = NULL; + g_autoptr(GpiodbusChip) chip = NULL; + g_autoptr(GpiodbusLine) line = NULL; + gboolean timed_out = FALSE; + guint timeout_id; + + mgr = gpiodbus_daemon_process_new(); + gpiodbus_test_wait_for_sim_intf(sim); + + line_obj_path = g_strdup_printf("/io/gpiod1/chips/%s/line4", + g_gpiosim_chip_get_name(sim)); + line = gpiodbus_test_get_line_proxy_or_fail(line_obj_path); + + chip_obj_path = g_strdup_printf("/io/gpiod1/chips/%s", + g_gpiosim_chip_get_name(sim)); + chip = gpiodbus_test_get_chip_proxy_or_fail(chip_obj_path); + + changed_props = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, + NULL); + + g_signal_connect(line, "g-properties-changed", + G_CALLBACK(on_properties_changed), changed_props); + timeout_id = g_timeout_add_seconds(5, on_timeout, &timed_out); + + line_config = make_props_changed_line_config(); + request_config = gpiodbus_test_make_empty_request_config(); + + gpiodbus_test_chip_call_request_lines_sync_or_fail(chip, line_config, + request_config, + &request_path); + + while (g_hash_table_size(changed_props) < 5 && !timed_out) + g_main_context_iteration(NULL, TRUE); + + check_props_requested(changed_props); + + g_hash_table_destroy(g_hash_table_ref(changed_props)); + + request = gpiodbus_test_get_request_proxy_or_fail(request_path); + gpiodbus_test_request_call_release_sync_or_fail(request); + + while (g_hash_table_size(changed_props) < 4 && !timed_out) + g_main_context_iteration(NULL, TRUE); + + check_props_released(changed_props); + + if (timed_out) { + g_test_fail_printf("timeout reached waiting for line properties to change"); + return; + } + + g_source_remove(timeout_id); +} diff --git a/dbus/tests/tests-request.c b/dbus/tests/tests-request.c new file mode 100644 index 0000000..c84e528 --- /dev/null +++ b/dbus/tests/tests-request.c @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2022-2023 Bartosz Golaszewski + +#include +#include +#include +#include +#include +#include + +#include "daemon-process.h" +#include "helpers.h" + +#define GPIOD_TEST_GROUP "gpiodbus/request" + +static GVariant *make_empty_request_config(void) +{ + GVariantBuilder builder; + + g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}")); + + return g_variant_ref_sink(g_variant_builder_end(&builder)); +} + +static GVariant *make_input_lines_line_config(void) +{ + 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; + + g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY); + g_variant_builder_add_value(&builder, g_variant_new_uint32(3)); + g_variant_builder_add_value(&builder, g_variant_new_uint32(5)); + g_variant_builder_add_value(&builder, g_variant_new_uint32(7)); + line_offsets = g_variant_builder_end(&builder); + + g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY); + g_variant_builder_add_value(&builder, + g_variant_new("{sv}", "direction", + g_variant_new_string("input"))); + 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); + + output_values = g_variant_new("ai", NULL); + + 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)); +} + +GPIOD_TEST_CASE(request_input_lines) +{ + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL); + g_autoptr(GpiodbusDaemonProcess) mgr = NULL; + g_autoptr(GVariant) request_config = NULL; + g_autoptr(GVariant) line_config = NULL; + g_autofree gchar *request_path = NULL; + g_autoptr(GpiodbusChip) chip = NULL; + g_autofree gchar *obj_path = NULL; + + mgr = gpiodbus_daemon_process_new(); + gpiodbus_test_wait_for_sim_intf(sim); + + obj_path = g_strdup_printf("/io/gpiod1/chips/%s", + g_gpiosim_chip_get_name(sim)); + chip = gpiodbus_test_get_chip_proxy_or_fail(obj_path); + + line_config = make_input_lines_line_config(); + request_config = make_empty_request_config(); + + gpiodbus_test_chip_call_request_lines_sync_or_fail(chip, line_config, + request_config, + &request_path); +} + +GPIOD_TEST_CASE(release_request) +{ + g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL); + g_autoptr(GpiodbusDaemonProcess) mgr = NULL; + g_autoptr(GVariant) request_config = NULL; + g_autoptr(GpiodbusRequest) request = NULL; + g_autoptr(GVariant) line_config = NULL; + g_autofree gchar *request_path = NULL; + g_autoptr(GpiodbusChip) chip = NULL; + g_autofree gchar *obj_path = NULL; + + mgr = gpiodbus_daemon_process_new(); + gpiodbus_test_wait_for_sim_intf(sim); + + obj_path = g_strdup_printf("/io/gpiod1/chips/%s", + g_gpiosim_chip_get_name(sim)); + chip = gpiodbus_test_get_chip_proxy_or_fail(obj_path); + + line_config = make_input_lines_line_config(); + request_config = make_empty_request_config(); + + gpiodbus_test_chip_call_request_lines_sync_or_fail(chip, line_config, + request_config, + &request_path); + + request = gpiodbus_test_get_request_proxy_or_fail(request_path); + gpiodbus_test_request_call_release_sync_or_fail(request); +} From patchwork Wed Aug 7 09:10:46 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Bartosz Golaszewski X-Patchwork-Id: 817492 Received: from mail-lj1-f171.google.com (mail-lj1-f171.google.com [209.85.208.171]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2754B1D174E for ; Wed, 7 Aug 2024 09:11:29 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.208.171 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723021894; cv=none; b=oN2BpQGr6U8vYQXXX1NoV83J88jeyjED4FWPaqAxcHWbvQU7oSzfIYDeW3j12mLnclLXReeiVQhZiNaujXjRthknAYUdz36ZObVkTES/j6J9wVViRRdgY+XvG+FAdnRVEX7VkOv4QlzEjw6fiLOTUuF0WL9kr4qHRm/IP28KG4I= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723021894; c=relaxed/simple; bh=crhTucHEcX3UAcx4tJoyf6jwbSTmnXqUGD1VW2jGEIc=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=dYsLNoth3ZNSC5zu+sRzqk1UWXSLjCkiPF15yGn/XQBRbVzPW8GnfRygHTXNiox1xhBvCPwiAwIO2ZXqmeMf85QGhYgpvVUBw6sYL2aqzB/zh9GLivuGpIWOnvS37eQsrq1iU0bXGg7FgSOJww6fQlwkAu9+b8xdwX6s4MUmbzU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=bgdev.pl; spf=none smtp.mailfrom=bgdev.pl; dkim=pass (2048-bit key) header.d=bgdev-pl.20230601.gappssmtp.com header.i=@bgdev-pl.20230601.gappssmtp.com header.b=z9bx9979; arc=none smtp.client-ip=209.85.208.171 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=bgdev.pl Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=bgdev.pl Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=bgdev-pl.20230601.gappssmtp.com header.i=@bgdev-pl.20230601.gappssmtp.com header.b="z9bx9979" Received: by mail-lj1-f171.google.com with SMTP id 38308e7fff4ca-2ef2cce8be8so16468991fa.1 for ; Wed, 07 Aug 2024 02:11:29 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bgdev-pl.20230601.gappssmtp.com; s=20230601; t=1723021888; x=1723626688; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=wsc8TI9WHkCfdLfmy3xgn5VW6t8W+L1gvDWNX8MvR20=; b=z9bx9979fuqktA79pgQ/pjycx+OIyJQAXehynl3RqjLOYv0FfCYFllEoFk/OaeNvGh 0XL/diHi2aGeAQqSRBdPIOzJOxHkoQ6NU9MKP+wke8OxAx8/JiNm5/1Vmbnr6mzqCjPo fdo9ScvrpKiN7YI4kHVRS5r/N2VKPl9wuKAfEjTKLEcPrDznur9CHh6qg8FAYAdqWXwh c6cq7rzLU0X3bDQFA4oCfkhpxBiSl176rVg3E1EkD/qc6FmQdJlerIXex/kypMz9IVi9 EFbA003U65wAFXhbVluqOA/NxVLxtRosmL48x9z4UuF3Y5dMTOXNQMmeKAw5hicdbg0D TfuA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1723021888; x=1723626688; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=wsc8TI9WHkCfdLfmy3xgn5VW6t8W+L1gvDWNX8MvR20=; b=U7PL5J6cGLudPWWcfnE1aaSK8E9JfCIZG2FVbbbg+mq5w7P1WZ4gOULQDojzVabubZ PesbkIgPjz4LzPsZKBRjXtiqwts20LddXTLQoCDaTNEAjZZf1BZF6qQplVQmQ1EjzLMF +yMfPbrWgRkXNYaxg+NGntB0HR772A4MrOowchjrBfMflbmymdl8NXh1TK0LyqfQagAj XS3H1iAwhEpaHZG+XYX8IhSsmrf4sx1FgkrQfsJIYDmdFA1JSvTqOhQ06mDZDdUGIQj+ TfIGae+qgqvrDe4G6kHAuiRV7ofkRv3H00q2qdVEhU8IdbIDkdBOvRZ/PXe3o3GUw8p+ zWIw== X-Gm-Message-State: AOJu0YztlM5mLpU6X2nGSBI9nq9YCgl56IaMBZgl+14NZm2wM40DAD/E afedB21FKhtKcIu58tUXEWMP2fcaJwUYq1vsEIm0pT9ZarQRCVaIX4fXN9DMaMo= X-Google-Smtp-Source: AGHT+IG6hP8mMszIn1QrSHBYAJzYCXBOXjeab/HX57TowffG7I9OveFOWJOc2zK1DTfrnWjpAvRzAQ== X-Received: by 2002:a2e:3815:0:b0:2f0:1b87:9090 with SMTP id 38308e7fff4ca-2f15aac1b77mr130118021fa.29.1723021887787; Wed, 07 Aug 2024 02:11:27 -0700 (PDT) Received: from [127.0.1.1] ([2a01:cb1d:dc:7e00:a3e7:be71:fe4f:c360]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-429059cd2b3sm18936355e9.44.2024.08.07.02.11.25 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 07 Aug 2024 02:11:26 -0700 (PDT) From: Bartosz Golaszewski Date: Wed, 07 Aug 2024 11:10:46 +0200 Subject: [PATCH libgpiod v4 15/18] dbus: add a command-line client Precedence: bulk X-Mailing-List: linux-gpio@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20240807-dbus-v4-15-64ea80169e51@linaro.org> References: <20240807-dbus-v4-0-64ea80169e51@linaro.org> In-Reply-To: <20240807-dbus-v4-0-64ea80169e51@linaro.org> To: Linus Walleij , Kent Gibson , Erik Schilling , Phil Howard , Andy Shevchenko , Viresh Kumar , Dan Carpenter , Philip Withnall Cc: linux-gpio@vger.kernel.org, Bartosz Golaszewski , Alexander Sverdlin X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=86139; i=bartosz.golaszewski@linaro.org; h=from:subject:message-id; bh=I8z1qTxYPUPI2SChx/rUsN9Cpz9rR9vvTOPamkTIAZk=; b=owEBbQKS/ZANAwAKARGnLqAUcddyAcsmYgBmszoTiynN7Lf5GeD8MtVv2B/tk+Bx++5L2lesO Anmp1C1nmqJAjMEAAEKAB0WIQQWnetsC8PEYBPSx58Rpy6gFHHXcgUCZrM6EwAKCRARpy6gFHHX cgrVD/9DqTedq+QVEbrlL3ZsCXsDFkc+bBF1MsGs0uhpltNmO94rtLplNLtE3pCfp37GCzNK8MP VQiqWmd/liOFe6kzy2KkQrIeZYaIRMhP7Gn1e4GhfHf6ic24NgHropv8b/VGK+vfK5VOVs4a/U5 E1/G5IA4/SY9M5NDQiSw/vq3w5DJzDwPDZdknLO/lHCHGAYg8faWg387fVbn+sNqEHA2IbBO2bd VpwasvXneR8Ox2qTOBV33SiFybSogGs4ccvHJXu21RKcsEBbehwoDBkF7OWexjxpwfPCr+q9eDI u4hZUsudG7BiBlJWUXrjnAbRuVKdS94O0ig/4WZ9hvobqt6w2huqxeSocefopKj30KEcvgDyjBO ASHaW1xmXkInSV0ZHeBXrrBE1Uy6g0MHTaUsRqumUG+6ZaJMEz57ujk16MdzhmnIOiSWaUnFe4p J0UNA9DrhN1qSk9ZeTE0PqhZuMSvkBgvK8yOIuFEScX1d+azJlLpDqmrGnvcHdw+xRklcrzP2dy 6x1P6QQZNRwJM2DAzfXSiBKDx1LDR2qXMcv4pnOa9wzgOzkRvjX7x5Mz5Wtu/lwahoFuOu3kpLG 75LTP5QkdZRClBc2H1Fii+YNGVi40LL6xRxbqpZcx5BYZbTkW8tcl7APe2pDpbQx25NaT/8ihOG uNaY9xYwgtEn+Gw== X-Developer-Key: i=bartosz.golaszewski@linaro.org; a=openpgp; fpr=169DEB6C0BC3C46013D2C79F11A72EA01471D772 From: Bartosz Golaszewski Add code for gpiocli - the command-line client for controlling GPIOs using the gpio-manager daemon. Tested-by: Alexander Sverdlin Signed-off-by: Bartosz Golaszewski --- dbus/client/common.c | 646 ++++++++++++++++++++++++++++++++++++++++++++++ dbus/client/common.h | 203 +++++++++++++++ dbus/client/detect.c | 53 ++++ dbus/client/find.c | 66 +++++ dbus/client/get.c | 212 +++++++++++++++ dbus/client/gpiocli.c | 174 +++++++++++++ dbus/client/info.c | 184 +++++++++++++ dbus/client/monitor.c | 191 ++++++++++++++ dbus/client/notify.c | 295 +++++++++++++++++++++ dbus/client/reconfigure.c | 76 ++++++ dbus/client/release.c | 64 +++++ dbus/client/request.c | 250 ++++++++++++++++++ dbus/client/requests.c | 71 +++++ dbus/client/set.c | 173 +++++++++++++ dbus/client/wait.c | 188 ++++++++++++++ 15 files changed, 2846 insertions(+) diff --git a/dbus/client/common.c b/dbus/client/common.c new file mode 100644 index 0000000..912c1ad --- /dev/null +++ b/dbus/client/common.c @@ -0,0 +1,646 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2022-2023 Bartosz Golaszewski + +#include +#include +#include +#include +#include + +#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) +{ + g_autofree gchar *chip_path = make_chip_obj_path(chip_name); + + return get_chip_obj_by_path(chip_path); +} + +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)); +} diff --git a/dbus/client/common.h b/dbus/client/common.h new file mode 100644 index 0000000..772e94a --- /dev/null +++ b/dbus/client/common.h @@ -0,0 +1,203 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* SPDX-FileCopyrightText: 2022-2023 Bartosz Golaszewski */ + +#ifndef __GPIOCLI_COMMON_H__ +#define __GPIOCLI_COMMON_H__ + +#include +#include +#include + +void die(const gchar *fmt, ...) G_NORETURN G_GNUC_PRINTF(1, 2); +void +die_gerror(GError *err, const gchar *fmt, ...) G_NORETURN G_GNUC_PRINTF(2, 3); +void die_parsing_opts(const char *fmt, ...) G_NORETURN G_GNUC_PRINTF(1, 2); + +void parse_options(const GOptionEntry *opts, const gchar *summary, + const gchar *description, int *argc, char ***argv); +void check_manager(void); + +gboolean quit_main_loop_on_signal(gpointer user_data); +void die_on_name_vanished(GDBusConnection *con, const gchar *name, + gpointer user_data); + +GList *strv_to_gstring_list(GStrv lines); +gint output_value_from_str(const gchar *value_str); + +GDBusObjectManager *get_object_manager_client(const gchar *obj_path); +GpiodbusObject *get_chip_obj_by_path(const gchar *obj_path); +GpiodbusObject *get_chip_obj(const gchar *chip_name); +GList *get_chip_objs(GStrv chip_names); +gchar *make_request_obj_path(const gchar *request); +GpiodbusObject *get_request_obj(const gchar *request_name); +GList *get_request_objs(void); +GArray *get_request_offsets(GpiodbusRequest *request); +gboolean get_line_obj_by_name(const gchar *name, GpiodbusObject **line_obj, + GpiodbusObject **chip_obj); +GpiodbusObject * +get_line_obj_by_name_for_chip(GpiodbusObject *chip_obj, const gchar *name_line); +GList *get_all_line_objs_for_chip(GpiodbusObject *chip_obj); + +gchar *sanitize_object_path(const gchar *path); + +typedef struct { + gchar *name; + guint offset; + gboolean used; + gchar *consumer; + const gchar *direction; + const gchar *drive; + const gchar *bias; + gboolean active_low; + const gchar *edge; + gboolean debounced; + guint64 debounce_period; + const gchar *event_clock; + gboolean managed; + gchar *request_name; +} LineProperties; + +LineProperties *get_line_properties(GpiodbusLine *line); +void free_line_properties(LineProperties *props); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(LineProperties, free_line_properties); + +typedef struct { + gboolean input; + gboolean output; + gboolean active_low; + gboolean rising_edge; + gboolean falling_edge; + gboolean both_edges; + gboolean push_pull; + gboolean open_source; + gboolean open_drain; + gboolean pull_up; + gboolean pull_down; + gboolean bias_disabled; + gboolean clock_monotonic; + gboolean clock_realtime; + gboolean clock_hte; + GTimeSpan debounce_period; + GArray *output_values; +} LineConfigOpts; + +#define LINE_CONFIG_OPTIONS(opts) \ + { \ + .long_name = "input", \ + .flags = G_OPTION_FLAG_NONE, \ + .arg = G_OPTION_ARG_NONE, \ + .arg_data = &(opts)->input, \ + .description = "Set direction to input.", \ + }, \ + { \ + .long_name = "output", \ + .flags = G_OPTION_FLAG_NONE, \ + .arg = G_OPTION_ARG_NONE, \ + .arg_data = &(opts)->output, \ + .description = "Set direction to output.", \ + }, \ + { \ + .long_name = "rising-edge", \ + .flags = G_OPTION_FLAG_NONE, \ + .arg = G_OPTION_ARG_NONE, \ + .arg_data = &(opts)->rising_edge, \ + .description = "Monitor rising edges." \ + }, \ + { \ + .long_name = "falling-edge", \ + .flags = G_OPTION_FLAG_NONE, \ + .arg = G_OPTION_ARG_NONE, \ + .arg_data = &(opts)->falling_edge, \ + .description = "Monitor falling edges." \ + }, \ + { \ + .long_name = "both-edges", \ + .flags = G_OPTION_FLAG_NONE, \ + .arg = G_OPTION_ARG_NONE, \ + .arg_data = &(opts)->both_edges, \ + .description = "Monitor rising and falling edges." \ + }, \ + { \ + .long_name = "push-pull", \ + .flags = G_OPTION_FLAG_NONE, \ + .arg = G_OPTION_ARG_NONE, \ + .arg_data = &(opts)->push_pull, \ + .description = "Drive the line in push-pull mode.", \ + }, \ + { \ + .long_name = "open-drain", \ + .flags = G_OPTION_FLAG_NONE, \ + .arg = G_OPTION_ARG_NONE, \ + .arg_data = &(opts)->open_drain, \ + .description = "Drive the line in open-drain mode.", \ + }, \ + { \ + .long_name = "open-source", \ + .flags = G_OPTION_FLAG_NONE, \ + .arg = G_OPTION_ARG_NONE, \ + .arg_data = &(opts)->open_source, \ + .description = "Drive the line in open-source mode.", \ + }, \ + { \ + .long_name = "pull-up", \ + .flags = G_OPTION_FLAG_NONE, \ + .arg = G_OPTION_ARG_NONE, \ + .arg_data = &(opts)->pull_up, \ + .description = "Enable internal pull-up bias.", \ + }, \ + { \ + .long_name = "pull-down", \ + .flags = G_OPTION_FLAG_NONE, \ + .arg = G_OPTION_ARG_NONE, \ + .arg_data = &(opts)->pull_down, \ + .description = "Enable internal pull-down bias.", \ + }, \ + { \ + .long_name = "bias-disabled", \ + .flags = G_OPTION_FLAG_NONE, \ + .arg = G_OPTION_ARG_NONE, \ + .arg_data = &(opts)->bias_disabled, \ + .description = "Disable internal pull-up/down bias.", \ + }, \ + { \ + .long_name = "active-low", \ + .flags = G_OPTION_FLAG_NONE, \ + .arg = G_OPTION_ARG_NONE, \ + .arg_data = &(opts)->active_low, \ + .description = "Treat the lines as active low.", \ + }, \ + { \ + .long_name = "debounce-period", \ + .flags = G_OPTION_FLAG_NONE, \ + .arg = G_OPTION_ARG_INT64, \ + .arg_data = &(opts)->debounce_period, \ + .arg_description = "", \ + .description = "Enable debouncing and set the period", \ + }, \ + { \ + .long_name = "clock-monotonic", \ + .flags = G_OPTION_FLAG_NONE, \ + .arg = G_OPTION_ARG_NONE, \ + .arg_data = &(opts)->clock_monotonic, \ + .description = "Use monotonic clock for edge event timestamps", \ + }, \ + { \ + .long_name = "clock-realtime", \ + .flags = G_OPTION_FLAG_NONE, \ + .arg = G_OPTION_ARG_NONE, \ + .arg_data = &(opts)->clock_realtime, \ + .description = "Use realtime clock for edge event timestamps", \ + }, \ + { \ + .long_name = "clock-hte", \ + .flags = G_OPTION_FLAG_NONE, \ + .arg = G_OPTION_ARG_NONE, \ + .arg_data = &(opts)->clock_hte, \ + .description = "Use HTE clock (if available) for edge event timestamps", \ + } + +void validate_line_config_opts(LineConfigOpts *opts); +GVariant *make_line_config(GArray *offsets, LineConfigOpts *cfg_opts); + +#endif /* __GPIOCLI_COMMON_H__ */ diff --git a/dbus/client/detect.c b/dbus/client/detect.c new file mode 100644 index 0000000..a98c3d3 --- /dev/null +++ b/dbus/client/detect.c @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2022-2023 Bartosz Golaszewski + +#include + +#include "common.h" + +static void show_chip(gpointer elem, gpointer user_data G_GNUC_UNUSED) +{ + GpiodbusObject *chip_obj = elem; + GpiodbusChip *chip; + + chip = gpiodbus_object_peek_chip(chip_obj); + + g_print("%s [%s] (%u lines)\n", + gpiodbus_chip_get_name(chip), + gpiodbus_chip_get_label(chip), + gpiodbus_chip_get_num_lines(chip)); +} + +int gpiocli_detect_main(int argc, char **argv) +{ + static const gchar *const summary = +"List GPIO chips, print their labels and number of GPIO lines."; + + static const gchar *const description = +"Chips may be identified by name or number. e.g. '0' and 'gpiochip0' refer to\n" +"the same chip.\n" +"\n" +"If no chips are specified - display information for all chips in the system."; + + g_autolist(GpiodbusObject) chip_objs = NULL; + g_auto(GStrv) chip_names = NULL; + + const GOptionEntry opts[] = { + { + .long_name = G_OPTION_REMAINING, + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_STRING_ARRAY, + .arg_data = &chip_names, + .arg_description = "[chip]...", + }, + { } + }; + + parse_options(opts, summary, description, &argc, &argv); + check_manager(); + + chip_objs = get_chip_objs(chip_names); + g_list_foreach(chip_objs, show_chip, NULL); + + return EXIT_SUCCESS; +} diff --git a/dbus/client/find.c b/dbus/client/find.c new file mode 100644 index 0000000..9fe4c1a --- /dev/null +++ b/dbus/client/find.c @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2023 Bartosz Golaszewski + +#include + +#include "common.h" + +static void find_line_in_chip(gpointer elem, gpointer user_data) +{ + g_autoptr(GpiodbusObject) line_obj = NULL; + GpiodbusObject *chip_obj = elem; + const gchar *name = user_data; + GpiodbusChip *chip; + GpiodbusLine *line; + + line_obj = get_line_obj_by_name_for_chip(chip_obj, name); + if (!line_obj) + return; + + chip = gpiodbus_object_peek_chip(chip_obj); + line = gpiodbus_object_peek_line(line_obj); + + g_print("%s %u\n", + gpiodbus_chip_get_name(chip), + gpiodbus_line_get_offset(line)); + + exit(EXIT_SUCCESS); +} + +int gpiocli_find_main(int argc, char **argv) +{ + static const gchar *const summary = +"Gicen a line name, find the name of the parent chip and offset of the line within that chip."; + + static const gchar *const description = +"As line names are not guaranteed to be unique, this command finds the first line with given name."; + + g_autolist(GpiodbusObject) objs = NULL; + g_auto(GStrv) line_name = NULL; + + const GOptionEntry opts[] = { + { + .long_name = G_OPTION_REMAINING, + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_STRING_ARRAY, + .arg_data = &line_name, + .arg_description = "", + }, + { } + }; + + parse_options(opts, summary, description, &argc, &argv); + check_manager(); + + if (!line_name) + die_parsing_opts("line name must be specified"); + if (g_strv_length(line_name) != 1) + die_parsing_opts("only one line can be mapped"); + + objs = get_chip_objs(NULL); + g_list_foreach(objs, find_line_in_chip, line_name[0]); + + /* If we got here, the line was not found. */ + die("line '%s' not found", line_name[0]); + return EXIT_FAILURE; +} diff --git a/dbus/client/get.c b/dbus/client/get.c new file mode 100644 index 0000000..4ca6f3c --- /dev/null +++ b/dbus/client/get.c @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2022-2024 Bartosz Golaszewski + +#include + +#include "common.h" + +int gpiocli_get_main(int argc, char **argv) +{ + static const gchar *const summary = +"Get values of one or more GPIO lines."; + + static const gchar *const description = +"If -r/--request is specified then all the lines must belong to the same\n" +"request (and - by extension - the same chip).\n" +"\n" +"If no lines are specified but -r/--request was passed then all lines within\n" +"the request will be used."; + + const gchar *request_name = NULL, *chip_path, *req_path; + gboolean ret, unquoted = FALSE, numeric = FALSE; + g_autoptr(GpiodbusObject) chip_obj = NULL; + g_autoptr(GpiodbusObject) req_obj = NULL; + g_autoptr(GArray) offsets = NULL; + g_autoptr(GArray) values = NULL; + g_autoptr(GError) err = NULL; + g_auto(GStrv) lines = NULL; + GpiodbusRequest *request; + GVariantBuilder builder; + GpiodbusLine *line; + gsize num_lines, i; + GVariantIter iter; + guint offset; + gint value; + + const GOptionEntry opts[] = { + { + .long_name = "request", + .short_name = 'r', + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_STRING, + .arg_data = &request_name, + .description = "restrict scope to a particular request", + .arg_description = "", + }, + { + .long_name = "unquoted", + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_NONE, + .arg_data = &unquoted, + .description = "don't quote line names", + }, + { + .long_name = "numeric", + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_NONE, + .arg_data = &numeric, + .description = "display line values as '0' (inactive) or '1' (active)", + }, + { + .long_name = G_OPTION_REMAINING, + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_STRING_ARRAY, + .arg_data = &lines, + .arg_description = "[line0] [line1]...", + }, + { } + }; + + parse_options(opts, summary, description, &argc, &argv); + check_manager(); + + if (!lines && !request_name) + die_parsing_opts("either at least one line or the request must be specified"); + + offsets = g_array_new(FALSE, TRUE, sizeof(guint)); + num_lines = lines ? g_strv_length(lines) : 0; + + if (!request_name) { + /* + * TODO Limit the number of D-Bus calls by gathering the requests + * and their relevant lines into a container of some kind first. + */ + + values = g_array_sized_new(FALSE, TRUE, sizeof(gint), + num_lines); + + for (i = 0; i < num_lines; i++) { + g_autoptr(GpiodbusRequest) req_proxy = NULL; + g_autoptr(GpiodbusObject) line_obj = NULL; + g_autoptr(GVariant) arg_offsets = NULL; + g_autoptr(GVariant) arg_values = NULL; + + ret = get_line_obj_by_name(lines[i], &line_obj, NULL); + if (!ret) + die("Line not found: %s\n", lines[i]); + + line = gpiodbus_object_peek_line(line_obj); + req_path = gpiodbus_line_get_request_path(line); + + if (!gpiodbus_line_get_managed(line)) + die("Line '%s' not managed by gpio-manager, must be requested first", + lines[i]); + + req_proxy = gpiodbus_request_proxy_new_for_bus_sync( + G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + "io.gpiod1", req_path, + NULL, &err); + if (err) + die_gerror(err, + "Failed to get D-Bus proxy for '%s'", + req_path); + + offset = gpiodbus_line_get_offset(line); + g_array_append_val(offsets, offset); + + g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY); + g_variant_builder_add(&builder, "u", offset); + arg_offsets = g_variant_ref_sink( + g_variant_builder_end(&builder)); + + ret = gpiodbus_request_call_get_values_sync( + req_proxy, arg_offsets, + G_DBUS_CALL_FLAGS_NONE, + -1, &arg_values, NULL, + &err); + if (!ret) + die_gerror(err, "Failed to get line values"); + + g_variant_iter_init(&iter, arg_values); + while (g_variant_iter_next(&iter, "i", &value)) + g_array_append_val(values, value); + } + } else { + g_autoptr(GVariant) arg_offsets = NULL; + g_autoptr(GVariant) arg_values = NULL; + + req_obj = get_request_obj(request_name); + request = gpiodbus_object_peek_request(req_obj); + chip_path = gpiodbus_request_get_chip_path(request); + chip_obj = get_chip_obj_by_path(chip_path); + + if (lines) { + for (i = 0; i < num_lines; i++) { + g_autoptr(GpiodbusObject) line_obj = NULL; + + line_obj = get_line_obj_by_name_for_chip( + chip_obj, lines[i]); + if (!line_obj) + die("Line not found: %s\n", lines[i]); + + line = gpiodbus_object_peek_line(line_obj); + + if (!gpiodbus_line_get_managed(line)) + die("Line '%s' not managed by gpio-manager, must be requested first", + lines[i]); + + offset = gpiodbus_line_get_offset(line); + g_array_append_val(offsets, offset); + } + } else { + offsets = get_request_offsets(request); + num_lines = offsets->len; + } + + g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY); + for (i = 0; i < offsets->len; i++) + g_variant_builder_add(&builder, "u", + g_array_index(offsets, guint, i)); + arg_offsets = g_variant_ref_sink( + g_variant_builder_end(&builder)); + + ret = gpiodbus_request_call_get_values_sync( + request, arg_offsets, + G_DBUS_CALL_FLAGS_NONE, + -1, &arg_values, NULL, + &err); + if (!ret) + die_gerror(err, "Failed to get line values"); + + values = g_array_sized_new(FALSE, TRUE, sizeof(gint), + g_variant_n_children(arg_values)); + + g_variant_iter_init(&iter, arg_values); + while (g_variant_iter_next(&iter, "i", &value)) + g_array_append_val(values, value); + } + + for (i = 0; i < num_lines; i++) { + if (!unquoted) + g_print("\""); + + if (lines) + g_print("%s", lines[i]); + else + g_print("%u", g_array_index(offsets, guint, i)); + + if (!unquoted) + g_print("\""); + + g_print("=%s", g_array_index(values, guint, i) ? + numeric ? "1" : "active" : + numeric ? "0" : "inactive"); + + if (i != (num_lines - 1)) + g_print(" "); + } + g_print("\n"); + + return EXIT_SUCCESS; +} diff --git a/dbus/client/gpiocli.c b/dbus/client/gpiocli.c new file mode 100644 index 0000000..fbd1bbe --- /dev/null +++ b/dbus/client/gpiocli.c @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2022-2023 Bartosz Golaszewski + +#include + +#include "common.h" + +typedef struct { + gchar *name; + int (*main_func)(int argc, char **argv); + gchar *descr; +} GPIOCliCmd; + +int gpiocli_detect_main(int argc, char **argv); +int gpiocli_find_main(int argc, char **argv); +int gpiocli_info_main(int argc, char **argv); +int gpiocli_get_main(int argc, char **argv); +int gpiocli_monitor_main(int argc, char **argv); +int gpiocli_notify_main(int argc, char **argv); +int gpiocli_reconfigure_main(int argc, char **argv); +int gpiocli_release_main(int argc, char **argv); +int gpiocli_request_main(int argc, char **argv); +int gpiocli_requests_main(int argc, char **argv); +int gpiocli_set_main(int argc, char **argv); +int gpiocli_wait_main(int argc, char **argv); + +static const GPIOCliCmd cli_cmds[] = { + { + .name = "detect", + .main_func = gpiocli_detect_main, + .descr = "list GPIO chips and print their properties", + }, + { + .name = "find", + .main_func = gpiocli_find_main, + .descr = "take a line name and find its parent chip's name and offset within it", + }, + { + .name = "info", + .main_func = gpiocli_info_main, + .descr = "print information about GPIO lines", + }, + { + .name = "get", + .main_func = gpiocli_get_main, + .descr = "get values of GPIO lines", + }, + { + .name = "monitor", + .main_func = gpiocli_monitor_main, + .descr = "notify the user about edge events", + }, + { + .name = "notify", + .main_func = gpiocli_notify_main, + .descr = "notify the user about line property changes", + }, + { + .name = "reconfigure", + .main_func = gpiocli_reconfigure_main, + .descr = "change the line configuration for an existing request", + }, + { + .name = "release", + .main_func = gpiocli_release_main, + .descr = "release one of the line requests controlled by the manager", + }, + { + .name = "request", + .main_func = gpiocli_request_main, + .descr = "request a set of GPIO lines for exclusive usage by the manager", + }, + { + .name = "requests", + .main_func = gpiocli_requests_main, + .descr = "list all line requests controlled by the manager", + }, + { + .name = "set", + .main_func = gpiocli_set_main, + .descr = "set values of GPIO lines", + }, + { + .name = "wait", + .main_func = gpiocli_wait_main, + .descr = "wait for the gpio-manager interface to appear", + }, + { } +}; + +static GHashTable *make_cmd_table(void) +{ + GHashTable *cmd_table = g_hash_table_new_full(g_str_hash, g_str_equal, + NULL, NULL); + const GPIOCliCmd *cmd; + + for (cmd = &cli_cmds[0]; cmd->name; cmd++) + g_hash_table_insert(cmd_table, cmd->name, cmd->main_func); + + return cmd_table; +} + +static gchar *make_description(void) +{ + g_autoptr(GString) descr = g_string_new("Available commands:\n"); + const GPIOCliCmd *cmd; + + for (cmd = &cli_cmds[0]; cmd->name; cmd++) + g_string_append_printf(descr, " %s - %s\n", + cmd->name, cmd->descr); + + g_string_truncate(descr, descr->len - 1); + return g_strdup(descr->str); +} + +static void show_version_and_exit(void) +{ + g_print("gpiocli v%s\n", GPIOD_VERSION_STR); + + exit(EXIT_SUCCESS); +} + +int main(int argc, char **argv) +{ + static const gchar *const summary = +"Simple command-line client for controlling gpio-manager."; + + g_autoptr(GHashTable) cmd_table = make_cmd_table(); + g_autofree gchar *description = make_description(); + g_autofree gchar *basename = NULL; + g_autofree gchar *cmd_name = NULL; + gint (*cmd_func)(gint, gchar **); + g_auto(GStrv) cmd_args = NULL; + gboolean show_version = FALSE; + + const GOptionEntry opts[] = { + { + .long_name = "version", + .short_name = 'v', + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_NONE, + .arg_data = &show_version, + .description = "Show version and exit.", + }, + { + .long_name = G_OPTION_REMAINING, + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_STRING_ARRAY, + .arg_data = &cmd_args, + .arg_description = "CMD [ARGS?] ...", + }, + { } + }; + + basename = g_path_get_basename(argv[0]); + g_set_prgname(basename); + + parse_options(opts, summary, description, &argc, &argv); + + if (show_version) + show_version_and_exit(); + + if (!cmd_args) + die_parsing_opts("Command must be specified."); + + cmd_func = g_hash_table_lookup(cmd_table, cmd_args[0]); + if (!cmd_func) + die_parsing_opts("Unknown command: %s.", cmd_args[0]); + + cmd_name = g_strdup_printf("%s %s", basename, cmd_args[0]); + g_set_prgname(cmd_name); + + return cmd_func(g_strv_length(cmd_args), cmd_args); +} diff --git a/dbus/client/info.c b/dbus/client/info.c new file mode 100644 index 0000000..fa08a3f --- /dev/null +++ b/dbus/client/info.c @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2023 Bartosz Golaszewski + +#include +#include + +#include "common.h" + +static gchar *make_line_name(const gchar *name) +{ + if (!name) + return g_strdup("unnamed"); + + return g_strdup_printf("\"%s\"", name); +} + +static void do_print_line_info(GpiodbusObject *line_obj, + GpiodbusObject *chip_obj) +{ + g_autoptr(LineProperties) props = NULL; + g_autoptr(GString) attributes = NULL; + g_autofree gchar *line_name = NULL; + GpiodbusChip *chip; + + props = get_line_properties(gpiodbus_object_peek_line(line_obj)); + line_name = make_line_name(props->name); + + attributes = g_string_new("["); + + if (props->used) + g_string_append_printf(attributes, "used,consumer=\"%s\",", + props->consumer); + + if (props->managed) + g_string_append_printf(attributes, "managed=\"%s\",", + props->request_name); + + if (props->edge) { + g_string_append_printf(attributes, "edges=%s,event-clock=%s,", + props->edge, props->event_clock); + if (props->debounced) + g_string_append_printf(attributes, + "debounce-period=%lu,", + props->debounce_period); + } + + if (props->bias) + g_string_append_printf(attributes, "bias=%s,", props->bias); + + if (props->active_low) + attributes = g_string_append(attributes, "active-low,"); + + g_string_append_printf(attributes, "%s", props->direction); + + if (g_strcmp0(props->direction, "output") == 0) + g_string_append_printf(attributes, ",%s", props->drive); + + attributes = g_string_append(attributes, "]"); + + if (chip_obj) { + chip = gpiodbus_object_peek_chip(chip_obj); + g_print("%s ", gpiodbus_chip_get_name(chip)); + } else { + g_print("\tline "); + } + + g_print("%3u:\t%s\t\t%s\n", props->offset, line_name, attributes->str); +} + +static void print_line_info(gpointer elem, gpointer user_data G_GNUC_UNUSED) +{ + GpiodbusObject *line_obj = elem; + + do_print_line_info(line_obj, NULL); +} + +static void do_show_chip(GpiodbusObject *chip_obj) +{ + GpiodbusChip *chip = gpiodbus_object_peek_chip(chip_obj); + g_autolist(GpiodbusObject) line_objs = NULL; + + g_print("%s - %u lines:\n", + gpiodbus_chip_get_name(chip), + gpiodbus_chip_get_num_lines(chip)); + + line_objs = get_all_line_objs_for_chip(chip_obj); + g_list_foreach(line_objs, print_line_info, NULL); +} + +static void show_chip(gpointer elem, gpointer user_data G_GNUC_UNUSED) +{ + GpiodbusObject *chip_obj = elem; + + do_show_chip(chip_obj); +} + +static void show_line_with_chip(gpointer elem, gpointer user_data) +{ + g_autoptr(GpiodbusObject) line_obj = NULL; + GpiodbusObject *chip_obj = user_data; + g_autofree gchar *chip_name = NULL; + GString *line_name = elem; + + line_obj = get_line_obj_by_name_for_chip(chip_obj, line_name->str); + if (!line_obj) { + chip_name = g_path_get_basename( + g_dbus_object_get_object_path(G_DBUS_OBJECT(chip_obj))); + die("no line '%s' on chip '%s'", line_name->str, chip_name); + } + + do_print_line_info(line_obj, chip_obj); +} + +static void show_line(gpointer elem, gpointer user_data G_GNUC_UNUSED) +{ + g_autoptr(GpiodbusObject) line_obj = NULL; + g_autoptr(GpiodbusObject) chip_obj = NULL; + GString *line_name = elem; + gboolean ret; + + ret = get_line_obj_by_name(line_name->str, &line_obj, &chip_obj); + if (!ret) + die("line '%s' not found", line_name->str); + + do_print_line_info(line_obj, chip_obj); +} + +int gpiocli_info_main(int argc, char **argv) +{ + static const gchar *const summary = +"Print information about GPIO lines."; + + static const gchar *const description = +"Lines are specified by name, or optionally by offset if the chip option\n" +"is provided.\n"; + + g_autolist(GpiodbusObject) chip_objs = NULL; + g_autolist(GString) line_name_list = NULL; + g_autoptr(GpiodbusObject) chip_obj = NULL; + g_auto(GStrv) line_names = NULL; + const gchar *chip_name = NULL; + + const GOptionEntry opts[] = { + { + .long_name = "chip", + .short_name = 'c', + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_STRING, + .arg_data = &chip_name, + .description = "restrict scope to a particular chip", + .arg_description = "", + }, + { + .long_name = G_OPTION_REMAINING, + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_STRING_ARRAY, + .arg_data = &line_names, + .arg_description = "[line1] [line2] ...", + }, + { } + }; + + parse_options(opts, summary, description, &argc, &argv); + check_manager(); + + if (chip_name) + chip_obj = get_chip_obj(chip_name); + + if (line_names) { + line_name_list = strv_to_gstring_list(line_names); + if (chip_obj) + g_list_foreach(line_name_list, show_line_with_chip, + chip_obj); + else + g_list_foreach(line_name_list, show_line, NULL); + } else if (chip_obj) { + do_show_chip(chip_obj); + } else { + chip_objs = get_chip_objs(NULL); + g_list_foreach(chip_objs, show_chip, NULL); + } + + return EXIT_SUCCESS; +} diff --git a/dbus/client/monitor.c b/dbus/client/monitor.c new file mode 100644 index 0000000..292b2bf --- /dev/null +++ b/dbus/client/monitor.c @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2024 Bartosz Golaszewski + +#include +#include +#include +#include + +#include "common.h" + +typedef struct { + GList *lines; +} MonitorData; + +static void on_edge_event(GpiodbusLine *line, GVariant *args, + gpointer user_data G_GNUC_UNUSED) +{ + const char *name = gpiodbus_line_get_name(line); + gulong global_seqno, line_seqno; + guint64 timestamp; + gint edge; + + g_variant_get(args, "(ittt)", &edge, ×tamp, + &global_seqno, &line_seqno); + + g_print("%lu %s ", timestamp, edge ? "rising " : "falling"); + if (strlen(name)) + g_print("\"%s\"\n", name); + else + g_print("%u\n", gpiodbus_line_get_offset(line)); +} + +static void connect_edge_event(gpointer elem, gpointer user_data) +{ + GpiodbusObject *line_obj = elem; + MonitorData *data = user_data; + g_autoptr(GError) err = NULL; + const gchar *line_obj_path; + GpiodbusLine *line; + + line_obj_path = g_dbus_object_get_object_path(G_DBUS_OBJECT(line_obj)); + + line = gpiodbus_line_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + "io.gpiod1", line_obj_path, + NULL, &err); + if (err) + die_gerror(err, "Failed to get D-Bus proxy for '%s'", + line_obj_path); + + if (!gpiodbus_line_get_managed(line)) + die("Line must be managed by gpio-manager in order to be monitored"); + + if (g_strcmp0(gpiodbus_line_get_edge_detection(line), "none") == 0) + die("Edge detection must be enabled for monitored lines"); + + data->lines = g_list_append(data->lines, line); + + g_signal_connect(line, "edge-event", G_CALLBACK(on_edge_event), NULL); +} + +int gpiocli_monitor_main(int argc, char **argv) +{ + static const gchar *const summary = +"Get values of one or more GPIO lines."; + + static const gchar *const description = +"If -r/--request is specified then all the lines must belong to the same\n" +"request (and - by extension - the same chip).\n" +"\n" +"If no lines are specified but -r/--request was passed then all lines within\n" +"the request will be used."; + + g_autoptr(GDBusObjectManager) manager = NULL; + const gchar *request_name = NULL, *chip_path; + g_autolist(GpiodbusObject) line_objs = NULL; + g_autoptr(GpiodbusObject) chip_obj = NULL; + g_autoptr(GpiodbusObject) req_obj = NULL; + g_autoptr(GArray) offsets = NULL; + g_autoptr(GMainLoop) loop = NULL; + g_auto(GStrv) lines = NULL; + GpiodbusRequest *request; + MonitorData data = { }; + gsize num_lines, i; + guint watch_id; + gboolean ret; + + const GOptionEntry opts[] = { + { + .long_name = "request", + .short_name = 'r', + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_STRING, + .arg_data = &request_name, + .description = "restrict scope to a particular request", + .arg_description = "", + }, + { + .long_name = G_OPTION_REMAINING, + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_STRING_ARRAY, + .arg_data = &lines, + .arg_description = "[line0] [line1]...", + }, + { } + }; + + parse_options(opts, summary, description, &argc, &argv); + + watch_id = g_bus_watch_name(G_BUS_TYPE_SYSTEM, "io.gpiod1", + G_BUS_NAME_WATCHER_FLAGS_NONE, + NULL, die_on_name_vanished, NULL, NULL); + check_manager(); + + if (!lines && !request_name) + die_parsing_opts("either at least one line or the request must be specified"); + + if (request_name) { + req_obj = get_request_obj(request_name); + request = gpiodbus_object_peek_request(req_obj); + chip_path = gpiodbus_request_get_chip_path(request); + chip_obj = get_chip_obj_by_path(chip_path); + offsets = g_array_new(FALSE, TRUE, sizeof(guint)); + + if (lines) { + num_lines = g_strv_length(lines); + + for (i = 0; i < num_lines; i++) { + g_autoptr(GpiodbusObject) line_obj = NULL; + + line_obj = get_line_obj_by_name_for_chip( + chip_obj, lines[i]); + if (!line_obj) + die("Line not found: %s\n", lines[i]); + + line_objs = g_list_append(line_objs, + g_object_ref(line_obj)); + } + } else { + offsets = get_request_offsets(request); + manager = get_object_manager_client(chip_path); + + for (i = 0; i < offsets->len; i++) { + g_autoptr(GpiodbusObject) line_obj = NULL; + g_autofree char *obj_path = NULL; + + obj_path = g_strdup_printf("%s/line%u", + chip_path, + g_array_index( + offsets, + guint, i)); + + line_obj = GPIODBUS_OBJECT( + g_dbus_object_manager_get_object( + manager, + obj_path)); + if (!line_obj) + die("Line not found: %u\n", + g_array_index(offsets, guint, i)); + + line_objs = g_list_append(line_objs, + g_object_ref(line_obj)); + } + } + } else { + num_lines = g_strv_length(lines); + + for (i = 0; i < num_lines; i++) { + g_autoptr(GpiodbusObject) line_obj = NULL; + + ret = get_line_obj_by_name(lines[i], &line_obj, NULL); + if (!ret) + die("Line not found: %s\n", lines[i]); + + line_objs = g_list_append(line_objs, + g_object_ref(line_obj)); + } + } + + g_list_foreach(line_objs, connect_edge_event, &data); + + loop = g_main_loop_new(NULL, FALSE); + g_unix_signal_add(SIGTERM, quit_main_loop_on_signal, loop); + g_unix_signal_add(SIGINT, quit_main_loop_on_signal, loop); + + g_main_loop_run(loop); + + g_bus_unwatch_name(watch_id); + + return EXIT_SUCCESS; +} diff --git a/dbus/client/notify.c b/dbus/client/notify.c new file mode 100644 index 0000000..f5a8e5d --- /dev/null +++ b/dbus/client/notify.c @@ -0,0 +1,295 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2023 Bartosz Golaszewski + +#include +#include +#include + +#include "common.h" + +/* + * Used to keep line proxies and chip interfaces alive for the duration of the + * program, which is required for signals to work. + */ +typedef struct { + GList *lines; + GList *chips; + GpiodbusObject *scoped_chip; +} NotifyData; + +static void clear_notify_data(NotifyData *data) +{ + g_list_free_full(data->lines, g_object_unref); + g_list_free_full(data->chips, g_object_unref); + + if (data->scoped_chip) + g_clear_object(&data->scoped_chip); +} + +G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(NotifyData, clear_notify_data); + +static const gchar *bool_to_str(gboolean val) +{ + return val ? "True" : "False"; +} + +static const gchar *bool_variant_to_str(GVariant *val) +{ + return bool_to_str(g_variant_get_boolean(val)); +} + +static void +on_properties_changed(GpiodbusLine *line, GVariant *changed_properties, + GStrv invalidated_properties G_GNUC_UNUSED, + gpointer user_data) +{ + GpiodbusChip *chip = user_data; + g_autofree gchar *name = NULL; + const gchar *consumer, *tmp; + GVariantIter iter; + GVariant *v; + gsize len; + gchar *k; + + if (g_variant_n_children(changed_properties) == 0) + return; + + tmp = gpiodbus_line_get_name(line); + name = tmp ? g_strdup_printf("\"%s\"", tmp) : g_strdup("unnamed"); + + g_variant_iter_init(&iter, changed_properties); + while (g_variant_iter_next(&iter, "{sv}", &k, &v)) { + g_autoptr(GString) change = g_string_new(NULL); + g_autofree gchar *req_name = NULL; + g_autoptr(GVariant) val = v; + g_autofree gchar *key = k; + + if (g_strcmp0(key, "Consumer") == 0) { + consumer = g_variant_get_string(val, &len); + g_string_printf(change, "consumer=>\"%s\"", + len ? consumer : "unused"); + } else if (g_strcmp0(key, "Used") == 0) { + g_string_printf(change, "used=>%s", + bool_variant_to_str(val)); + } else if (g_strcmp0(key, "Debounced") == 0) { + g_string_printf(change, "debounced=>%s", + bool_variant_to_str(val)); + } else if (g_strcmp0(key, "ActiveLow") == 0) { + g_string_printf(change, "active-low=>%s", + bool_variant_to_str(val)); + } else if (g_strcmp0(key, "Direction") == 0) { + g_string_printf(change, "direction=>%s", + g_variant_get_string(val, NULL)); + } else if (g_strcmp0(key, "Drive") == 0) { + g_string_printf(change, "drive=>%s", + g_variant_get_string(val, NULL)); + } else if (g_strcmp0(key, "Bias") == 0) { + g_string_printf(change, "bias=>%s", + g_variant_get_string(val, NULL)); + } else if (g_strcmp0(key, "EdgeDetection") == 0) { + g_string_printf(change, "edge=>%s", + g_variant_get_string(val, NULL)); + } else if (g_strcmp0(key, "EventClock") == 0) { + g_string_printf(change, "event-clock=>%s", + g_variant_get_string(val, NULL)); + } else if (g_strcmp0(key, "DebouncePeriodUs") == 0) { + g_string_printf(change, "debounce-period=>%ld", + g_variant_get_uint64(val)); + } else if (g_strcmp0(key, "Managed") == 0) { + g_string_printf(change, "managed=>%s", + bool_variant_to_str(val)); + } else if (g_strcmp0(key, "RequestPath") == 0) { + req_name = sanitize_object_path( + g_variant_get_string(val, NULL)); + g_string_printf(change, "request=>%s", + req_name); + } else { + die("unexpected property update received from manager: '%s'", + key); + } + + g_print("%s - %u (%s): [%s]\n", gpiodbus_chip_get_name(chip), + gpiodbus_line_get_offset(line), name ?: "unnamed", + change->str); + } +} + +static void print_line_info(GpiodbusLine *line, GpiodbusChip *chip) +{ + g_autoptr(LineProperties) props = get_line_properties(line); + g_autoptr(GString) attrs = g_string_new(props->direction); + g_autofree gchar *name = NULL; + + if (props->used) + g_string_append(attrs, ",used"); + + if (props->consumer) + g_string_append_printf(attrs, ",consumer=\"%s\"", + props->consumer); + + if (props->drive && g_strcmp0(props->direction, "output") == 0) + g_string_append_printf(attrs, ",%s", props->drive); + + if (props->bias) { + if (g_strcmp0(props->bias, "disabled") == 0) + g_string_append(attrs, ",bias-disabled"); + else + g_string_append_printf(attrs, ",%s", props->bias); + } + + if (props->active_low) + g_string_append(attrs, ",active-low"); + + if (props->edge) { + if (g_strcmp0(props->edge, "both") == 0) + g_string_append(attrs, ",both-edges"); + else + g_string_append_printf(attrs, ",%s-edge", props->edge); + + g_string_append_printf(attrs, ",%s-clock", props->event_clock); + + if (props->debounced) + g_string_append_printf(attrs, + "debounced,debounce-period=%lu", + props->debounce_period); + } + + if (props->managed) + g_string_append_printf(attrs, ",managed,request=\"%s\"", + props->request_name); + + name = props->name ? g_strdup_printf("\"%s\"", props->name) : + g_strdup("unnamed"); + + g_print("%s - %u (%s): [%s]\n", gpiodbus_chip_get_name(chip), + props->offset, name ?: "unnamed", attrs->str); +} + +static void connect_line(gpointer elem, gpointer user_data) +{ + g_autoptr(GpiodbusObject) line_obj = NULL; + g_autoptr(GpiodbusObject) chip_obj = NULL; + g_autoptr(GpiodbusLine) line = NULL; + g_autoptr(GpiodbusChip) chip = NULL; + g_autofree gchar *chip_name = NULL; + g_autoptr(GError) err = NULL; + NotifyData *data = user_data; + const gchar *line_obj_path; + GString *line_name = elem; + gboolean ret; + + if (data->scoped_chip) { + chip_obj = g_object_ref(data->scoped_chip); + line_obj = get_line_obj_by_name_for_chip(chip_obj, + line_name->str); + if (!line_obj) { + chip_name = g_path_get_basename( + g_dbus_object_get_object_path( + G_DBUS_OBJECT(chip_obj))); + die("no line '%s' on chip '%s'", + line_name->str, chip_name); + } + } else { + ret = get_line_obj_by_name(line_name->str, + &line_obj, &chip_obj); + if (!ret) + die("line '%s' not found", line_name->str); + } + + line_obj_path = g_dbus_object_get_object_path(G_DBUS_OBJECT(line_obj)); + + line = gpiodbus_line_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + "io.gpiod1", line_obj_path, + NULL, &err); + if (err) + die_gerror(err, "Failed to get D-Bus proxy for '%s'", + line_obj_path); + + data->lines = g_list_append(data->lines, g_object_ref(line)); + + if (data->scoped_chip) { + if (g_list_length(data->chips) == 0) { + chip = gpiodbus_object_get_chip(chip_obj); + data->chips = g_list_append(data->chips, + g_object_ref(chip)); + } else { + chip = g_list_first(data->chips)->data; + } + } else { + chip = gpiodbus_object_get_chip(chip_obj); + data->chips = g_list_append(data->chips, g_object_ref(chip)); + } + + print_line_info(line, chip); + + g_signal_connect(line, "g-properties-changed", + G_CALLBACK(on_properties_changed), chip); +} + +int gpiocli_notify_main(int argc, char **argv) +{ + static const gchar *const summary = +"Monitor a set of lines for property changes."; + + static const gchar *const description = +"Lines are specified by name, or optionally by offset if the chip option\n" +"is provided.\n"; + + g_autolist(GString) line_name_list = NULL; + g_autoptr(GMainLoop) loop = NULL; + g_auto(GStrv) line_names = NULL; + const gchar *chip_name = NULL; + /* + * FIXME: data internals must be freed but there's some issue with + * unrefing the GpiodbusObject here. For now it's leaking memory. + */ + NotifyData data = { }; + guint watch_id; + + const GOptionEntry opts[] = { + { + .long_name = "chip", + .short_name = 'c', + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_STRING, + .arg_data = &chip_name, + .description = "restrict scope to a particular chip", + .arg_description = "", + }, + { + .long_name = G_OPTION_REMAINING, + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_STRING_ARRAY, + .arg_data = &line_names, + .arg_description = " [line2] ...", + }, + { } + }; + + parse_options(opts, summary, description, &argc, &argv); + + watch_id = g_bus_watch_name(G_BUS_TYPE_SYSTEM, "io.gpiod1", + G_BUS_NAME_WATCHER_FLAGS_NONE, + NULL, die_on_name_vanished, NULL, NULL); + check_manager(); + + if (!line_names) + die_parsing_opts("at least one line must be specified"); + + if (chip_name) + data.scoped_chip = get_chip_obj(chip_name); + + line_name_list = strv_to_gstring_list(line_names); + g_list_foreach(line_name_list, connect_line, &data); + + loop = g_main_loop_new(NULL, FALSE); + g_unix_signal_add(SIGTERM, quit_main_loop_on_signal, loop); + g_unix_signal_add(SIGINT, quit_main_loop_on_signal, loop); + + g_main_loop_run(loop); + + g_bus_unwatch_name(watch_id); + + return EXIT_SUCCESS; +} diff --git a/dbus/client/reconfigure.c b/dbus/client/reconfigure.c new file mode 100644 index 0000000..cb22f58 --- /dev/null +++ b/dbus/client/reconfigure.c @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2024 Bartosz Golaszewski + +#include + +#include "common.h" + +int gpiocli_reconfigure_main(int argc, char **argv) +{ + static const gchar *const summary = +"Change the line configuration for an existing request."; + + g_autoptr(GpiodbusObject) req_obj = NULL; + g_autoptr(GVariant) line_config = NULL; + g_autoptr(GArray) output_values = NULL; + LineConfigOpts line_cfg_opts = { }; + g_autoptr(GArray) offsets = NULL; + g_auto(GStrv) remaining = NULL; + g_autoptr(GError) err = NULL; + GpiodbusRequest *request; + gsize num_values; + gboolean ret; + gint val; + guint i; + + const GOptionEntry opts[] = { + LINE_CONFIG_OPTIONS(&line_cfg_opts), + { + .long_name = G_OPTION_REMAINING, + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_STRING_ARRAY, + .arg_data = &remaining, + .arg_description = " [value1] [value2]...", + }, + { } + }; + + parse_options(opts, summary, NULL, &argc, &argv); + validate_line_config_opts(&line_cfg_opts); + + if (!remaining || g_strv_length(remaining) == 0) + die_parsing_opts("Exactly one request to reconfigure must be specified."); + + num_values = g_strv_length(remaining) - 1; + + check_manager(); + + req_obj = get_request_obj(remaining[0]); + request = gpiodbus_object_peek_request(req_obj); + offsets = get_request_offsets(request); + + if (num_values) { + if (num_values != offsets->len) + die_parsing_opts("The number of output values must correspond to the number of lines in the request"); + + output_values = g_array_sized_new(FALSE, TRUE, sizeof(gint), + num_values); + + for (i = 0; i < num_values; i++) { + val = output_value_from_str(remaining[i + 1]); + g_array_append_val(output_values, val); + } + } + + line_cfg_opts.output_values = output_values; + line_config = make_line_config(offsets, &line_cfg_opts); + + ret = gpiodbus_request_call_reconfigure_lines_sync( + request, line_config, + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, &err); + if (!ret) + die_gerror(err, "Failed to reconfigure lines"); + + return EXIT_SUCCESS; +} diff --git a/dbus/client/release.c b/dbus/client/release.c new file mode 100644 index 0000000..84e364f --- /dev/null +++ b/dbus/client/release.c @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2023 Bartosz Golaszewski + +#include + +#include "common.h" + +int gpiocli_release_main(int argc, char **argv) +{ + static const gchar *const summary = +"Release one of the line requests controlled by the manager."; + + g_autoptr(GDBusObjectManager) manager = NULL; + g_autoptr(GpiodbusObject) obj = NULL; + g_autofree gchar *obj_path = NULL; + g_auto(GStrv) remaining = NULL; + g_autoptr(GError) err = NULL; + const gchar *request_name; + GpiodbusRequest *request; + gboolean ret; + + const GOptionEntry opts[] = { + { + .long_name = G_OPTION_REMAINING, + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_STRING_ARRAY, + .arg_data = &remaining, + .arg_description = "", + }, + { } + }; + + parse_options(opts, summary, NULL, &argc, &argv); + + if (!remaining || g_strv_length(remaining) != 1) + die_parsing_opts("Exactly one request to release must be specified."); + + check_manager(); + + request_name = remaining[0]; + + obj_path = make_request_obj_path(request_name); + manager = get_object_manager_client("/io/gpiod1/requests"); + obj = GPIODBUS_OBJECT(g_dbus_object_manager_get_object(manager, + obj_path)); + if (!obj) + goto no_request; + + request = gpiodbus_object_peek_request(obj); + if (!request) + goto no_request; + + ret = gpiodbus_request_call_release_sync(request, + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, &err); + if (!ret) + die_gerror(err, "Failed to release request '%s': %s", + request_name, err->message); + + return EXIT_SUCCESS; + +no_request: + die("No such request: '%s'", request_name); +} diff --git a/dbus/client/request.c b/dbus/client/request.c new file mode 100644 index 0000000..f12d903 --- /dev/null +++ b/dbus/client/request.c @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2023-2024 Bartosz Golaszewski + +#include + +#include "common.h" + +typedef struct { + LineConfigOpts line_cfg_opts; + const gchar *consumer; +} RequestOpts; + +typedef struct { + const gchar *request_path; + gboolean done; +} RequestWaitData; + +static GVariant *make_request_config(RequestOpts *opts) +{ + GVariantBuilder builder; + + g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY); + g_variant_builder_add_value(&builder, + g_variant_new("{sv}", "consumer", + g_variant_new_string(opts->consumer))); + + return g_variant_ref_sink(g_variant_builder_end(&builder)); +} + +static gboolean on_timeout(gpointer user_data G_GNUC_UNUSED) +{ + die("wait for request to appear timed out!"); +} + +static void obj_match_request_path(GpiodbusObject *obj, RequestWaitData *data) +{ + if (g_strcmp0(g_dbus_object_get_object_path(G_DBUS_OBJECT(obj)), + data->request_path) == 0) + data->done = TRUE; +} + +static void match_request_path(gpointer elem, gpointer user_data) +{ + RequestWaitData *data = user_data; + GpiodbusObject *obj = elem; + + obj_match_request_path(obj, data); +} + +static void on_object_added(GDBusObjectManager *manager G_GNUC_UNUSED, + GpiodbusObject *obj, gpointer user_data) +{ + RequestWaitData *data = user_data; + + obj_match_request_path(GPIODBUS_OBJECT(obj), data); +} + +static void wait_for_request(const gchar *request_path) +{ + RequestWaitData data = { .request_path = request_path }; + g_autoptr(GDBusObjectManager) manager = NULL; + g_autolist(GpiodbusObject) objs = NULL; + + manager = get_object_manager_client("/io/gpiod1/requests"); + + g_signal_connect(manager, "object-added", + G_CALLBACK(on_object_added), &data); + + objs = g_dbus_object_manager_get_objects(manager); + g_list_foreach(objs, match_request_path, &data); + + g_timeout_add(5000, on_timeout, NULL); + + while (!data.done) + g_main_context_iteration(NULL, TRUE); +} + +static int +request_lines(GList *line_names, const gchar *chip_name, RequestOpts *req_opts) +{ + g_autoptr(GpiodbusObject) chip_obj = NULL; + g_autoptr(GVariant) request_config = NULL; + g_autoptr(GVariant) line_config = NULL; + g_autofree gchar *request_path = NULL; + g_autofree gchar *request_name = NULL; + g_autofree gchar *dyn_name = NULL; + g_autoptr(GArray) offsets = NULL; + g_autoptr(GError) err = NULL; + GpiodbusLine *line; + GpiodbusChip *chip; + GString *line_name; + guint i, *offset; + gboolean ret; + GList *pos; + gsize llen; + + llen = g_list_length(line_names); + offsets = g_array_sized_new(FALSE, TRUE, sizeof(guint), llen); + g_array_set_size(offsets, llen); + + if (chip_name) + chip_obj = get_chip_obj(chip_name); + + for (i = 0, pos = g_list_first(line_names); + i < llen; + i++, pos = g_list_next(pos)) { + g_autoptr(GpiodbusObject) line_obj = NULL; + + line_name = pos->data; + + if (chip_obj) { + line_obj = get_line_obj_by_name_for_chip(chip_obj, + line_name->str); + if (!line_obj) { + if (dyn_name) { + ret = get_line_obj_by_name( + line_name->str, + &line_obj, NULL); + if (ret) + /* + * This means the line exists + * but on a different chip. + */ + die("all requested lines must belong to the same chip"); + } + + die("no line '%s' on chip '%s'", + line_name->str, chip_name); + } + } else { + ret = get_line_obj_by_name(line_name->str, &line_obj, + &chip_obj); + if (!ret) + die("line '%s' not found", line_name->str); + + dyn_name = g_path_get_basename( + g_dbus_object_get_object_path( + G_DBUS_OBJECT(chip_obj))); + chip_name = dyn_name; + } + + line = gpiodbus_object_peek_line(line_obj); + offset = &g_array_index(offsets, guint, i); + *offset = gpiodbus_line_get_offset(line); + } + + chip = gpiodbus_object_peek_chip(chip_obj); + line_config = make_line_config(offsets, &req_opts->line_cfg_opts); + request_config = make_request_config(req_opts); + + ret = gpiodbus_chip_call_request_lines_sync(chip, line_config, + request_config, + G_DBUS_CALL_FLAGS_NONE, -1, + &request_path, NULL, &err); + if (err) + die_gerror(err, "failed to request lines from chip '%s'", + chip_name); + + wait_for_request(request_path); + + request_name = g_path_get_basename(request_path); + g_print("%s\n", request_name); + + return EXIT_SUCCESS; +} + +int gpiocli_request_main(int argc, char **argv) +{ + static const gchar *const summary = +"Request a set of GPIO lines for exclusive usage by the gpio-manager."; + + g_autoptr(GArray) output_values = NULL; + g_autolist(GString) line_names = NULL; + const gchar *chip_name = NULL; + g_auto(GStrv) lines = NULL; + RequestOpts req_opts = {}; + gsize llen; + gint val; + guint i; + + const GOptionEntry opts[] = { + { + .long_name = "chip", + .short_name = 'c', + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_STRING, + .arg_data = &chip_name, + .description = +"Explicitly specify the chip_name on which to resolve the lines which allows to use raw offsets instead of line names.", + .arg_description = "", + }, + { + .long_name = "consumer", + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_STRING, + .arg_data = &req_opts.consumer, + .description = "Consumer string (defaults to program name)", + .arg_description = "", + }, + { + .long_name = G_OPTION_REMAINING, + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_STRING_ARRAY, + .arg_data = &lines, + .arg_description = "[=value1] [line2[=value2]] ...", + }, + LINE_CONFIG_OPTIONS(&req_opts.line_cfg_opts), + { } + }; + + parse_options(opts, summary, NULL, &argc, &argv); + validate_line_config_opts(&req_opts.line_cfg_opts); + + if (!lines) + die_parsing_opts("At least one line must be specified"); + + if (!req_opts.consumer) + req_opts.consumer = "gpio-manager"; + + for (i = 0, llen = g_strv_length(lines); i < llen; i++) { + g_auto(GStrv) tokens = NULL; + + tokens = g_strsplit(lines[i], "=", 2); + line_names = g_list_append(line_names, g_string_new(tokens[0])); + if (g_strv_length(tokens) == 2) { + if (!req_opts.line_cfg_opts.output) + die_parsing_opts("Output values can only be set in output mode"); + + if (!output_values) + output_values = g_array_sized_new(FALSE, TRUE, + sizeof(gint), + llen); + val = output_value_from_str(tokens[1]); + g_array_append_val(output_values, val); + } + } + + if (output_values && req_opts.line_cfg_opts.input) + die_parsing_opts("cannot set output values in input mode"); + + if (output_values && + (g_list_length(line_names) != output_values->len)) + die_parsing_opts("if values are set, they must be set for all lines"); + + req_opts.line_cfg_opts.output_values = output_values; + + check_manager(); + + return request_lines(line_names, chip_name, &req_opts); +} diff --git a/dbus/client/requests.c b/dbus/client/requests.c new file mode 100644 index 0000000..be25823 --- /dev/null +++ b/dbus/client/requests.c @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2023 Bartosz Golaszewski + +#include + +#include "common.h" + +static void show_request(gpointer elem, gpointer user_data G_GNUC_UNUSED) +{ + g_autoptr(GDBusObjectManager) manager = NULL; + g_autofree gchar *request_name = NULL; + g_autofree gchar *offsets_str = NULL; + g_autoptr(GVariant) voffsets = NULL; + g_autofree gchar *chip_name = NULL; + g_autoptr(GArray) offsets = NULL; + GpiodbusObject *obj = elem; + GpiodbusRequest *request; + GVariantBuilder builder; + const gchar *chip_path; + gsize i; + + request_name = g_path_get_basename( + g_dbus_object_get_object_path(G_DBUS_OBJECT(obj))); + request = gpiodbus_object_peek_request(obj); + chip_path = gpiodbus_request_get_chip_path(request); + manager = get_object_manager_client(chip_path); + /* FIXME: Use chip proxy? */ + chip_name = g_path_get_basename(chip_path); + + offsets = get_request_offsets(request); + g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY); + for (i = 0; i < offsets->len; i++) + g_variant_builder_add(&builder, "u", + g_array_index(offsets, guint, i)); + voffsets = g_variant_ref_sink(g_variant_builder_end(&builder)); + offsets_str = g_variant_print(voffsets, FALSE); + + g_print("%s (%s) Offsets: %s\n", + request_name, chip_name, offsets_str); +} + +int gpiocli_requests_main(int argc, char **argv) +{ + static const gchar *const summary = +"List all line requests controlled by the manager."; + + g_autolist(GpiodbusObject) request_objs = NULL; + g_auto(GStrv) remaining = NULL; + + const GOptionEntry opts[] = { + { + .long_name = G_OPTION_REMAINING, + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_STRING_ARRAY, + .arg_data = &remaining, + .arg_description = NULL, + }, + { } + }; + + parse_options(opts, summary, NULL, &argc, &argv); + check_manager(); + + if (remaining) + die_parsing_opts("command doesn't take additional arguments"); + + request_objs = get_request_objs(); + g_list_foreach(request_objs, show_request, NULL); + + return EXIT_SUCCESS; +} diff --git a/dbus/client/set.c b/dbus/client/set.c new file mode 100644 index 0000000..6460dd5 --- /dev/null +++ b/dbus/client/set.c @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2024 Bartosz Golaszewski + +#include + +#include "common.h" + +static void free_str(gpointer data) +{ + GString *str = data; + + g_string_free(str, TRUE); +} + +int gpiocli_set_main(int argc, char **argv) +{ + static const gchar *const summary = +"Set values of one or more GPIO lines."; + + static const gchar *const description = +"If -r/--request is specified then all the lines must belong to the same\n" +"request (and - by extension - the same chip)."; + + const gchar *request_name = NULL, *chip_path, *req_path; + g_autoptr(GpiodbusObject) chip_obj = NULL; + g_autoptr(GpiodbusObject) req_obj = NULL; + g_autoptr(GPtrArray) line_names = NULL; + g_autoptr(GArray) values = NULL; + g_autoptr(GError) err = NULL; + g_auto(GStrv) lines = NULL; + GpiodbusRequest *request; + GVariantBuilder builder; + GpiodbusLine *line; + gsize num_lines, i; + GString *line_name; + gboolean ret; + guint offset; + gint val; + + const GOptionEntry opts[] = { + { + .long_name = "request", + .short_name = 'r', + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_STRING, + .arg_data = &request_name, + .description = "restrict scope to a particular request", + .arg_description = "", + }, + { + .long_name = G_OPTION_REMAINING, + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_STRING_ARRAY, + .arg_data = &lines, + .arg_description = " [line2=value2] ...", + }, + { } + }; + + parse_options(opts, summary, description, &argc, &argv); + + if (!lines) + die_parsing_opts("at least one line value must be specified"); + + num_lines = g_strv_length(lines); + line_names = g_ptr_array_new_full(num_lines, free_str); + values = g_array_sized_new(FALSE, TRUE, sizeof(gint), num_lines); + + for (i = 0; i < num_lines; i++) { + g_auto(GStrv) tokens = NULL; + + tokens = g_strsplit(lines[i], "=", 2); + if (g_strv_length(tokens) != 2) + die_parsing_opts("line must have a single value assigned"); + + g_ptr_array_add(line_names, g_string_new(tokens[0])); + val = output_value_from_str(tokens[1]); + g_array_append_val(values, val); + } + + check_manager(); + + if (request_name) { + g_autoptr(GVariant) arg_values = NULL; + g_autoptr(GArray) offsets = NULL; + + req_obj = get_request_obj(request_name); + request = gpiodbus_object_peek_request(req_obj); + chip_path = gpiodbus_request_get_chip_path(request); + chip_obj = get_chip_obj_by_path(chip_path); + offsets = g_array_sized_new(FALSE, TRUE, sizeof(guint), + num_lines); + + for (i = 0; i < num_lines; i++) { + g_autoptr(GpiodbusObject) line_obj = NULL; + + line_name = g_ptr_array_index(line_names, i); + + line_obj = get_line_obj_by_name_for_chip(chip_obj, + line_name->str); + if (!line_obj) + die("Line not found: %s\n", line_name->str); + + line = gpiodbus_object_peek_line(line_obj); + offset = gpiodbus_line_get_offset(line); + g_array_append_val(offsets, offset); + } + + g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY); + for (i = 0; i < num_lines; i++) { + g_variant_builder_add(&builder, "{ui}", + g_array_index(offsets, guint, i), + g_array_index(values, gint, i)); + } + + arg_values = g_variant_ref_sink( + g_variant_builder_end(&builder)); + + ret = gpiodbus_request_call_set_values_sync( + request, arg_values, + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, &err); + if (!ret) + die_gerror(err, "Failed to set line values"); + + return EXIT_SUCCESS; + } + + for (i = 0; i < num_lines; i++) { + g_autoptr(GpiodbusRequest) req_proxy = NULL; + g_autoptr(GpiodbusObject) line_obj = NULL; + g_autoptr(GVariant) arg_values = NULL; + + line_name = g_ptr_array_index(line_names, i); + + ret = get_line_obj_by_name(line_name->str, &line_obj, NULL); + if (!ret) + die("Line not found: %s\n", line_name->str); + + line = gpiodbus_object_peek_line(line_obj); + req_path = gpiodbus_line_get_request_path(line); + + if (!gpiodbus_line_get_managed(line)) + die("Line '%s' not managed by gpio-manager, must be requested first", + line_name->str); + + req_proxy = gpiodbus_request_proxy_new_for_bus_sync( + G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + "io.gpiod1", req_path, + NULL, &err); + if (err) + die_gerror(err, "Failed to get D-Bus proxy for '%s'", + req_path); + + offset = gpiodbus_line_get_offset(line); + + g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY); + g_variant_builder_add(&builder, "{ui}", offset, + g_array_index(values, gint, i)); + arg_values = g_variant_ref_sink( + g_variant_builder_end(&builder)); + + ret = gpiodbus_request_call_set_values_sync( + req_proxy, arg_values, + G_DBUS_CALL_FLAGS_NONE, -1, + NULL, &err); + if (!ret) + die_gerror(err, "Failed to set line values"); + } + + return EXIT_SUCCESS; +} diff --git a/dbus/client/wait.c b/dbus/client/wait.c new file mode 100644 index 0000000..d65c4e7 --- /dev/null +++ b/dbus/client/wait.c @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2023 Bartosz Golaszewski + +#include + +#include "common.h" + +typedef struct { + gboolean name_done; + gboolean chip_done; + const gchar *label; +} WaitData; + +static void obj_match_label(GpiodbusObject *chip_obj, WaitData *data) +{ + GpiodbusChip *chip = gpiodbus_object_peek_chip(chip_obj); + + if (g_strcmp0(gpiodbus_chip_get_label(chip), data->label) == 0) + data->chip_done = TRUE; +} + +static void check_label(gpointer elem, gpointer user_data) +{ + WaitData *data = user_data; + GpiodbusObject *obj = elem; + + obj_match_label(obj, data); +} + +static void on_object_added(GDBusObjectManager *manager G_GNUC_UNUSED, + GpiodbusObject *obj, gpointer user_data) +{ + WaitData *data = user_data; + + obj_match_label(GPIODBUS_OBJECT(obj), data); +} + +static void wait_for_chip(WaitData *data) +{ + g_autoptr(GDBusObjectManager) manager = NULL; + g_autolist(GpiodbusObject) objs = NULL; + + manager = get_object_manager_client("/io/gpiod1/chips"); + + g_signal_connect(manager, "object-added", + G_CALLBACK(on_object_added), data); + + objs = g_dbus_object_manager_get_objects(manager); + g_list_foreach(objs, check_label, data); + + while (!data->chip_done) + g_main_context_iteration(NULL, TRUE); +} + +static void on_name_appeared(GDBusConnection *con G_GNUC_UNUSED, + const gchar *name G_GNUC_UNUSED, + const gchar *name_owner G_GNUC_UNUSED, + gpointer user_data) +{ + WaitData *data = user_data; + + data->name_done = TRUE; +} + +static void on_name_vanished(GDBusConnection *con G_GNUC_UNUSED, + const gchar *name G_GNUC_UNUSED, + gpointer user_data) +{ + WaitData *data = user_data; + + if (data->label && data->chip_done) + die("gpio-manager vanished while waiting for chip"); +} + +static gboolean on_timeout(gpointer user_data G_GNUC_UNUSED) +{ + die("wait timed out!"); +} + +static guint schedule_timeout(const gchar *timeout) +{ + gint64 period, multiplier = 0; + gchar *end; + + period = g_ascii_strtoll(timeout, &end, 10); + + switch (*end) { + case 'm': + multiplier = 1; + end++; + break; + case 's': + multiplier = 1000; + break; + case '\0': + break; + default: + goto invalid_timeout; + } + + if (multiplier) { + if (*end != 's') + goto invalid_timeout; + + end++; + } else { + /* Default to miliseconds. */ + multiplier = 1; + } + + period *= multiplier; + if (period > G_MAXUINT) + die("timeout must not exceed %u miliseconds\n", G_MAXUINT); + + return g_timeout_add(period, on_timeout, NULL); + +invalid_timeout: + die("invalid timeout value: %s", timeout); +} + +int gpiocli_wait_main(int argc, char **argv) +{ + static const gchar *const summary = +"Wait for the gpio-manager interface to appear."; + + static const gchar *const description = +"Timeout period defaults to miliseconds but can be given in seconds or miliseconds\n" +"explicitly .e.g: --timeout=1000, --timeout=1000ms and --timeout=1s all specify\n" +"the same period."; + + const gchar *timeout_str = NULL; + guint watch_id, timeout_id = 0; + g_auto(GStrv) remaining = NULL; + WaitData data = {}; + + const GOptionEntry opts[] = { + { + .long_name = "chip", + .short_name = 'c', + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_STRING, + .arg_data = &data.label, + .description = "Wait for a specific chip to appear.", + .arg_description = "