tests: mockup: add a library for controlling the gpio-mockup module
authorBartosz Golaszewski <bgolaszewski@baylibre.com>
Fri, 10 May 2019 09:43:22 +0000 (11:43 +0200)
committerBartosz Golaszewski <bgolaszewski@baylibre.com>
Wed, 5 Jun 2019 09:39:53 +0000 (11:39 +0200)
In order to both switch to glib unit testing framework for core
libgpiod tests as well as to cover the bindings in other languages
with proper testing while avoiding unnecessary code duplication, let's
factor out the code responsible for interaction with the gpio-mockup
module into a separate shared library.

Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>
configure.ac
tests/Makefile.am
tests/mockup/Makefile.am [new file with mode: 0644]
tests/mockup/gpio-mockup.c [new file with mode: 0644]
tests/mockup/gpio-mockup.h [new file with mode: 0644]

index bf779ca2a484610948c0ac54f9f4f7d4921b4fd2..8613881d1851a1401c093ce3afb90252b2233d51 100644 (file)
@@ -126,6 +126,10 @@ then
        PKG_CHECK_MODULES([UDEV], [libudev >= 215])
 fi
 
+# ABI version for libgpiomockup (we need this since it can be installed if we
+# enable install-tests).
+AC_SUBST(ABI_MOCKUP_VERSION, [0.0.0])
+
 AC_ARG_ENABLE([bindings-cxx],
        [AC_HELP_STRING([--enable-bindings-cxx],
                [enable C++ bindings [default=no]])],
@@ -180,6 +184,7 @@ AC_CONFIG_FILES([libgpiod.pc
                 lib/Makefile
                 tools/Makefile
                 tests/Makefile
+                tests/mockup/Makefile
                 bindings/cxx/libgpiodcxx.pc
                 bindings/Makefile
                 bindings/cxx/Makefile
index 40164087d66bcf6e866d6d6d8882d32f54929584..14366f690bedbab591ea3f7188b868cd231b8da9 100644 (file)
@@ -6,6 +6,8 @@
 # Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
 #
 
+SUBDIRS = mockup
+
 AM_CFLAGS = -I$(top_srcdir)/include/ -include $(top_builddir)/config.h
 AM_CFLAGS += -Wall -Wextra -g $(KMOD_CFLAGS) $(UDEV_CFLAGS)
 AM_LDFLAGS = -pthread
diff --git a/tests/mockup/Makefile.am b/tests/mockup/Makefile.am
new file mode 100644 (file)
index 0000000..03ce608
--- /dev/null
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+#
+# This file is part of libgpiod.
+#
+# Copyright (C) 2019 Bartosz Golaszewski <bgolaszewski@baylibre.com>
+#
+
+lib_LTLIBRARIES = libgpiomockup.la
+
+libgpiomockup_la_SOURCES = gpio-mockup.c gpio-mockup.h
+libgpiomockup_la_CFLAGS = -Wall -Wextra -g -fvisibility=hidden
+libgpiomockup_la_CFLAGS += -include $(top_builddir)/config.h
+libgpiomockup_la_CFLAGS += $(KMOD_CFLAGS) $(UDEV_CFLAGS)
+libgpiomockup_la_LDFLAGS = -version-info $(subst .,:,$(ABI_MOCKUP_VERSION))
+libgpiomockup_la_LDFLAGS += $(KMOD_LIBS) $(UDEV_LIBS)
diff --git a/tests/mockup/gpio-mockup.c b/tests/mockup/gpio-mockup.c
new file mode 100644 (file)
index 0000000..2af3c31
--- /dev/null
@@ -0,0 +1,513 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2019 Bartosz Golaszewski <bgolaszewski@baylibre.com>
+ */
+
+#include <errno.h>
+#include <libkmod.h>
+#include <libudev.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/utsname.h>
+#include <unistd.h>
+
+#include "gpio-mockup.h"
+
+#define EXPORT                 __attribute__((visibility("default")))
+
+static const unsigned int min_kern_major = 5;
+static const unsigned int min_kern_minor = 1;
+static const unsigned int min_kern_release = 0;
+
+struct gpio_mockup_chip {
+       char *name;
+       char *path;
+       unsigned int num;
+};
+
+struct gpio_mockup {
+       struct gpio_mockup_chip **chips;
+       unsigned int num_chips;
+       struct kmod_ctx *kmod;
+       struct kmod_module *module;
+       int refcount;
+};
+
+static void free_chip(struct gpio_mockup_chip *chip)
+{
+       free(chip->name);
+       free(chip->path);
+       free(chip);
+}
+
+static bool check_kernel_version(void)
+{
+       unsigned int major, minor, release;
+       struct utsname un;
+       int rv;
+
+       rv = uname(&un);
+       if (rv)
+               return false;
+
+       rv = sscanf(un.release, "%u.%u.%u", &major, &minor, &release);
+       if (rv != 3) {
+               errno = EFAULT;
+               return false;
+       }
+
+       if (major < min_kern_major) {
+               goto bad_version;
+       } else if (major > min_kern_major) {
+               goto good_version;
+       } else {
+               if (minor < min_kern_minor) {
+                       goto bad_version;
+               } else if (minor > min_kern_minor) {
+                       goto good_version;
+               } else {
+                       if (release < min_kern_release)
+                               goto bad_version;
+                       else
+                               goto good_version;
+               }
+       }
+
+good_version:
+       return true;
+
+bad_version:
+       errno = EOPNOTSUPP;
+       return false;
+}
+
+EXPORT struct gpio_mockup *gpio_mockup_new(void)
+{
+       struct gpio_mockup *ctx;
+       const char *modpath;
+       int rv;
+
+       if (!check_kernel_version())
+               goto err_out;
+
+       ctx = malloc(sizeof(*ctx));
+       if (!ctx)
+               goto err_out;
+
+       memset(ctx, 0, sizeof(*ctx));
+       ctx->refcount = 1;
+
+       ctx->kmod = kmod_new(NULL, NULL);
+       if (!ctx->kmod)
+               goto err_free_kmod;
+
+       rv = kmod_module_new_from_name(ctx->kmod, "gpio-mockup", &ctx->module);
+       if (rv)
+               goto err_unref_module;
+
+       /* First see if we can find the module. */
+       modpath = kmod_module_get_path(ctx->module);
+       if (!modpath) {
+               errno = ENOENT;
+               goto err_unref_module;
+       }
+
+       /*
+        * Then see if we can freely load and unload it. If it's already
+        * loaded - no problem, we'll remove it next anyway.
+        */
+       rv = kmod_module_probe_insert_module(ctx->module, 0,
+                                            "gpio_mockup_ranges=-1,4",
+                                            NULL, NULL, NULL);
+       if (rv)
+               goto err_unref_module;
+
+       /* We need to chech that the gpio-mockup debugfs directory exists. */
+       rv = access("/sys/kernel/debug/gpio-mockup", R_OK);
+       if (rv)
+               goto err_unref_module;
+
+       rv = kmod_module_remove_module(ctx->module, 0);
+       if (rv)
+               goto err_unref_module;
+
+       return ctx;
+
+err_unref_module:
+       kmod_unref(ctx->kmod);
+err_free_kmod:
+       free(ctx);
+err_out:
+       return NULL;
+}
+
+EXPORT void gpio_mockup_ref(struct gpio_mockup *ctx)
+{
+       ctx->refcount++;
+}
+
+EXPORT void gpio_mockup_unref(struct gpio_mockup *ctx)
+{
+       ctx->refcount--;
+
+       if (ctx->refcount == 0) {
+               if (ctx->chips)
+                       gpio_mockup_remove(ctx);
+
+               kmod_module_unref(ctx->module);
+               kmod_unref(ctx->kmod);
+               free(ctx);
+       }
+}
+
+static char *make_module_param_string(unsigned int num_chips,
+                                     unsigned int *num_lines, int flags)
+{
+       char *params, *new;
+       unsigned int i;
+       int rv;
+
+       params = strdup("gpio_mockup_ranges=");
+       if (!params)
+               return NULL;
+
+       for (i = 0; i < num_chips; i++) {
+               rv = asprintf(&new, "%s-1,%u,", params, num_lines[i]);
+               free(params);
+               if (rv < 0)
+                       return NULL;
+
+               params = new;
+       }
+       params[strlen(params) - 1] = '\0'; /* Remove the last comma. */
+
+       if (flags & GPIO_MOCKUP_FLAG_NAMED_LINES) {
+               rv = asprintf(&new, "%s gpio_mockup_named_lines", params);
+               free(params);
+               if (rv < 0)
+                       return NULL;
+
+               params = new;
+       }
+
+       return params;
+}
+
+static bool devpath_is_mockup(const char *devpath)
+{
+       static const char mockup_devpath[] = "/devices/platform/gpio-mockup";
+
+       return !strncmp(devpath, mockup_devpath, sizeof(mockup_devpath) - 1);
+}
+
+static int chipcmp(const void *c1, const void *c2)
+{
+       const struct gpio_mockup_chip *chip1, *chip2;
+
+       chip1 = *(const struct gpio_mockup_chip **)c1;
+       chip2 = *(const struct gpio_mockup_chip **)c2;
+
+       return chip1->num > chip2->num;
+}
+
+static struct gpio_mockup_chip *make_chip(const char *sysname,
+                                         const char *devnode)
+{
+       struct gpio_mockup_chip *chip;
+       int rv;
+
+       chip = malloc(sizeof(*chip));
+       if (!chip)
+               return NULL;
+
+       chip->name = strdup(sysname);
+       if (!chip->name) {
+               free(chip);
+               return NULL;
+       }
+
+       chip->path = strdup(devnode);
+       if (!chip->path) {
+               free(chip->name);
+               free(chip);
+               return NULL;
+       }
+
+       rv = sscanf(sysname, "gpiochip%u", &chip->num);
+       if (rv != 1) {
+               errno = EINVAL;
+               free(chip->path);
+               free(chip->name);
+               free(chip);
+               return NULL;
+       }
+
+       return chip;
+}
+
+EXPORT int gpio_mockup_probe(struct gpio_mockup *ctx, unsigned int num_chips,
+                            unsigned int *chip_sizes, int flags)
+{
+       const char *devpath, *devnode, *sysname, *action;
+       struct gpio_mockup_chip *chip;
+       struct udev_monitor *monitor;
+       unsigned int i, detected = 0;
+       struct udev_device *dev;
+       struct udev *udev_ctx;
+       struct pollfd pfd;
+       char *params;
+       int rv;
+
+       if (ctx->chips) {
+               errno = EBUSY;
+               goto err_out;
+       }
+
+       if (num_chips < 1) {
+               errno = EINVAL;
+               goto err_out;
+       }
+
+       udev_ctx = udev_new();
+       if (!udev_ctx)
+               goto err_out;
+
+       monitor = udev_monitor_new_from_netlink(udev_ctx, "udev");
+       if (!monitor)
+               goto err_unref_udev;
+
+       rv = udev_monitor_filter_add_match_subsystem_devtype(monitor,
+                                                            "gpio", NULL);
+       if (rv < 0)
+               goto err_unref_monitor;
+
+       rv = udev_monitor_enable_receiving(monitor);
+       if (rv < 0)
+               goto err_unref_monitor;
+
+       params = make_module_param_string(num_chips, chip_sizes, flags);
+       if (!params)
+               goto err_unref_monitor;
+
+       rv = kmod_module_probe_insert_module(ctx->module,
+                                            KMOD_PROBE_FAIL_ON_LOADED,
+                                            params, NULL, NULL, NULL);
+       free(params);
+       if (rv)
+               goto err_unref_monitor;
+
+       ctx->chips = calloc(num_chips, sizeof(struct gpio_mockup_chip *));
+       if (!ctx->chips)
+               goto err_remove_module;
+
+       for (i = 0; i < num_chips; i++) {
+               ctx->chips[i] = malloc(sizeof(struct gpio_mockup_chip));
+               if (!ctx->chips[i])
+                       goto err_free_chips;
+       }
+
+       pfd.fd = udev_monitor_get_fd(monitor);
+       pfd.events = POLLIN | POLLPRI;
+
+       while (num_chips > detected) {
+               rv = poll(&pfd, 1, 5000);
+               if (rv < 0) {
+                       goto err_free_chips;
+               } if (rv == 0) {
+                       errno = EAGAIN;
+                       goto err_free_chips;
+               }
+
+               dev = udev_monitor_receive_device(monitor);
+               if (!dev)
+                       goto err_free_chips;
+
+               devpath = udev_device_get_devpath(dev);
+               devnode = udev_device_get_devnode(dev);
+               sysname = udev_device_get_sysname(dev);
+               action = udev_device_get_action(dev);
+
+               if (!devpath || !devnode || !sysname ||
+                   !devpath_is_mockup(devpath) || strcmp(action, "add") != 0) {
+                       udev_device_unref(dev);
+                       continue;
+               }
+
+               chip = make_chip(sysname, devnode);
+               if (!chip)
+                       goto err_free_chips;
+
+               ctx->chips[detected++] = chip;
+               udev_device_unref(dev);
+       }
+
+       udev_monitor_unref(monitor);
+       udev_unref(udev_ctx);
+
+       /*
+        * We can't assume that the order in which the mockup gpiochip
+        * devices are created will be deterministic, yet we want the
+        * index passed to the test_chip_*() functions to correspond with the
+        * order in which the chips were defined in the TEST_DEFINE()
+        * macro.
+        *
+        * Once all gpiochips are there, sort them by chip number.
+        */
+       qsort(ctx->chips, ctx->num_chips, sizeof(*ctx->chips), chipcmp);
+
+       return 0;
+
+err_free_chips:
+       for (i = 0; i < detected; i++)
+               free_chip(ctx->chips[i]);
+       free(ctx->chips);
+err_remove_module:
+       kmod_module_remove_module(ctx->module, 0);
+err_unref_monitor:
+       udev_monitor_unref(monitor);
+err_unref_udev:
+       udev_unref(udev_ctx);
+err_out:
+       return -1;
+}
+
+EXPORT int gpio_mockup_remove(struct gpio_mockup *ctx)
+{
+       unsigned int i;
+       int rv;
+
+       if (!ctx->chips) {
+               errno = ENODEV;
+               return -1;
+       }
+
+       rv = kmod_module_remove_module(ctx->module, 0);
+       if (rv)
+               return -1;
+
+       for (i = 0; i < ctx->num_chips; i++)
+               free_chip(ctx->chips[i]);
+       free(ctx->chips);
+       ctx->chips = NULL;
+       ctx->num_chips = 0;
+
+       return 0;
+}
+
+EXPORT const char *
+gpio_mockup_chip_name(struct gpio_mockup *ctx, unsigned int idx)
+{
+       if (!ctx->chips) {
+               errno = ENODEV;
+               return NULL;
+       }
+
+       return ctx->chips[idx]->name;
+}
+
+EXPORT const char *
+gpio_mockup_chip_path(struct gpio_mockup *ctx, unsigned int idx)
+{
+       if (!ctx->chips) {
+               errno = ENODEV;
+               return NULL;
+       }
+
+       return ctx->chips[idx]->path;
+}
+
+EXPORT int gpio_mockup_chip_num(struct gpio_mockup *ctx, unsigned int idx)
+{
+       if (!ctx->chips) {
+               errno = ENODEV;
+               return -1;
+       }
+
+       return ctx->chips[idx]->num;
+}
+
+static int debugfs_open(unsigned int chip_num,
+                       unsigned int line_offset, int flags)
+{
+       char *path;
+       int fd, rv;
+
+       rv = asprintf(&path, "/sys/kernel/debug/gpio-mockup/gpiochip%u/%u",
+                     chip_num, line_offset);
+       if (rv < 0)
+               return -1;
+
+       fd = open(path, flags);
+       free(path);
+
+       return fd;
+}
+
+EXPORT int gpio_mockup_get_value(struct gpio_mockup *ctx,
+                                unsigned int chip_idx,
+                                unsigned int line_offset)
+{
+       ssize_t rd;
+       char buf;
+       int fd;
+
+       if (!ctx->chips) {
+               errno = ENODEV;
+               return -1;
+       }
+
+       fd = debugfs_open(ctx->chips[chip_idx]->num, line_offset, O_RDONLY);
+       if (fd < 0)
+               return fd;
+
+       rd = read(fd, &buf, 1);
+       close(fd);
+       if (rd < 0)
+               return rd;
+       if (rd != 1) {
+               errno = ENOTTY;
+               return -1;
+       }
+       if (buf != '0' && buf != '1') {
+               errno = EIO;
+               return -1;
+       }
+
+       return buf == '0' ? 0 : 1;
+}
+
+EXPORT int gpio_mockup_set_pull(struct gpio_mockup *ctx,
+                               unsigned int chip_idx,
+                               unsigned int line_offset, int pull)
+{
+       char buf[2];
+       ssize_t wr;
+       int fd;
+
+       if (!ctx->chips) {
+               errno = ENODEV;
+               return -1;
+       }
+
+       fd = debugfs_open(ctx->chips[chip_idx]->num, line_offset, O_WRONLY);
+       if (fd < 0)
+               return fd;
+
+       buf[0] = pull ? '1' : '0';
+       buf[1] = '\n';
+
+       wr = write(fd, &buf, sizeof(buf));
+       close(fd);
+       if (wr < 0)
+               return wr;
+       if (wr != sizeof(buf)) {
+               errno = EAGAIN;
+               return -1;
+       }
+
+       return 0;
+}
diff --git a/tests/mockup/gpio-mockup.h b/tests/mockup/gpio-mockup.h
new file mode 100644 (file)
index 0000000..a4f3058
--- /dev/null
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2019 Bartosz Golaszewski <bgolaszewski@baylibre.com>
+ */
+
+#ifndef __GPIO_MOCKUP_H__
+#define __GPIO_MOCKUP_H__
+
+struct gpio_mockup;
+
+#define GPIO_MOCKUP_FLAG_NAMED_LINES   (1 << 0)
+
+struct gpio_mockup *gpio_mockup_new(void);
+void gpio_mockup_ref(struct gpio_mockup *ctx);
+void gpio_mockup_unref(struct gpio_mockup *ctx);
+
+int gpio_mockup_probe(struct gpio_mockup *ctx, unsigned int num_chips,
+                     unsigned int *chip_sizes, int flags);
+int gpio_mockup_remove(struct gpio_mockup *ctx);
+
+const char *gpio_mockup_chip_name(struct gpio_mockup *ctx, unsigned int idx);
+const char *gpio_mockup_chip_path(struct gpio_mockup *ctx, unsigned int idx);
+int gpio_mockup_chip_num(struct gpio_mockup *ctx, unsigned int idx);
+
+int gpio_mockup_get_value(struct gpio_mockup *ctx,
+                         unsigned int chip_idx, unsigned int line_offset);
+int gpio_mockup_set_pull(struct gpio_mockup *ctx, unsigned int chip_idx,
+                        unsigned int line_offset, int pull);
+
+#endif /* __GPIO_MOCKUP_H__ */