@@ -31,6 +31,7 @@ AC_SUBST(ABI_CXX_VERSION, [2.1.1])
# ABI version for libgpiomockup (we need this since it can be installed if we
# enable install-tests).
AC_SUBST(ABI_MOCKUP_VERSION, [0.1.0])
+AC_SUBST(ABI_GPIOSIM_VERSION, [0.1.0])
AC_CONFIG_AUX_DIR([autostuff])
AC_CONFIG_MACRO_DIRS([m4])
@@ -126,10 +127,11 @@ AC_DEFUN([FUNC_NOT_FOUND_TESTS],
if test "x$with_tests" = xtrue
then
- # For libgpiomockup
+ # For libgpiomockup & libgpiosim
AC_CHECK_FUNC([qsort], [], [FUNC_NOT_FOUND_TESTS([qsort])])
PKG_CHECK_MODULES([KMOD], [libkmod >= 18])
PKG_CHECK_MODULES([UDEV], [libudev >= 215])
+ PKG_CHECK_MODULES([MOUNT], [mount >= 2.33.1])
# For core library tests
PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.50])
@@ -224,6 +226,7 @@ AC_CONFIG_FILES([Makefile
tools/Makefile
tests/Makefile
tests/mockup/Makefile
+ tests/gpiosim/Makefile
bindings/cxx/libgpiodcxx.pc
bindings/Makefile
bindings/cxx/Makefile
@@ -1,7 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-or-later
# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
-SUBDIRS = mockup
+SUBDIRS = mockup gpiosim
AM_CFLAGS = -I$(top_srcdir)/include/ -I$(top_srcdir)/tests/mockup/
AM_CFLAGS += -include $(top_builddir)/config.h
new file mode 100644
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
+
+gpiosim-selftest
new file mode 100644
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
+
+lib_LTLIBRARIES = libgpiosim.la
+noinst_PROGRAMS = gpiosim-selftest
+
+AM_CFLAGS = -Wall -Wextra -g -fvisibility=hidden -std=gnu89
+AM_CFLAGS += -include $(top_builddir)/config.h
+
+libgpiosim_la_SOURCES = gpiosim.c gpiosim.h
+libgpiosim_la_CFLAGS = $(AM_CFLAGS) $(KMOD_CFLAGS) $(UDEV_CFLAGS) $(MOUNT_CFLAGS)
+libgpiosim_la_LDFLAGS = -version-info $(subst .,:,$(ABI_GPIOSIM_VERSION))
+libgpiosim_la_LDFLAGS += $(KMOD_LIBS) $(UDEV_LIBS) $(MOUNT_LIBS)
+
+gpiosim_selftest_SOURCES = gpiosim-selftest.c
+gpiosim_selftest_LDADD = libgpiosim.la
new file mode 100644
@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "gpiosim.h"
+
+#define UNUSED __attribute__((unused))
+
+static char *line_names[] = {
+ "foo",
+ "bar",
+ "foobar",
+ NULL,
+ "barfoo",
+};
+
+int main(int argc UNUSED, char **argv UNUSED)
+{
+ const char *devpath, *chip_name;
+ struct gpiosim_ctx *sim_ctx;
+ struct gpiosim_chip *chip;
+ int ret;
+
+ printf("Creating gpiosim context\n");
+
+ sim_ctx = gpiosim_ctx_new();
+ if (!sim_ctx) {
+ perror("unable to create the gpios-sim context");
+ return EXIT_FAILURE;
+ }
+
+ printf("Creating a chip with random name\n");
+
+ chip = gpiosim_chip_new(sim_ctx, NULL);
+ if (!chip) {
+ perror("Unable to create a chip with random name");
+ return EXIT_FAILURE;
+ }
+
+ printf("Setting the number of lines to 16\n");
+
+ ret = gpiosim_chip_set_num_lines(chip, 16);
+ if (ret) {
+ perror("Unable to set the number of lines");
+ return EXIT_FAILURE;
+ }
+
+ printf("Setting the chip label\n");
+
+ ret = gpiosim_chip_set_label(chip, "foobar");
+ if (ret) {
+ perror("Unable to set the chip label");
+ return EXIT_FAILURE;
+ }
+
+ printf("Setting the line names\n");
+
+ ret = gpiosim_chip_set_line_names(chip, 5, line_names);
+ if (ret) {
+ perror("Unable to set GPIO line names");
+ return EXIT_FAILURE;
+ }
+
+ printf("Committing the chip\n");
+
+ ret = gpiosim_chip_commit_sync(chip);
+ if (ret) {
+ perror("Unable to commit the chip");
+ return EXIT_FAILURE;
+ }
+
+ printf("Reading the device path\n");
+
+ devpath = gpiosim_chip_get_dev(chip);
+ if (!devpath) {
+ perror("Unable to read the device path");
+ return EXIT_FAILURE;
+ }
+
+ printf("The device path is: '%s'\n", devpath);
+ printf("Reading the chip name\n");
+
+ chip_name = gpiosim_chip_get_name(chip);
+ if (!chip_name) {
+ perror("Unable to read the chip name");
+ return EXIT_FAILURE;
+ }
+
+ printf("The chip name is: '%s'\n", chip_name);
+
+ ret = gpiosim_chip_uncommit(chip);
+ if (ret) {
+ perror("Unable to uncommit the chip");
+ return EXIT_FAILURE;
+ }
+
+ gpiosim_chip_unref(chip);
+ gpiosim_ctx_unref(sim_ctx);
+
+ return EXIT_SUCCESS;
+}
new file mode 100644
@@ -0,0 +1,743 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <errno.h>
+#include <libkmod.h>
+#include <libmount.h>
+#include <libudev.h>
+#include <linux/version.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/random.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/utsname.h>
+#include <unistd.h>
+
+#include "gpiosim.h"
+
+#define GPIOSIM_API __attribute__((visibility("default")))
+#define GPIOSIM_ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x)))
+/* FIXME Bump to 5.13 once released. */
+#define MIN_KERNEL_VERSION KERNEL_VERSION(5, 12, 0)
+
+struct gpiosim_ctx {
+ unsigned int refcnt;
+ int pending_dir_fd;
+ int live_dir_fd;
+ char *cfs_mnt_dir;
+};
+
+struct gpiosim_chip {
+ unsigned int refcnt;
+ struct gpiosim_ctx *ctx;
+ bool live;
+ char *item_name;
+ char *devname;
+ char *chipname;
+ char *devnode;
+ int configfs_dir_fd;
+ int sysfs_dir_fd;
+};
+
+static int check_kernel_version(void)
+{
+ unsigned int major, minor, release;
+ struct utsname un;
+ int rv;
+
+ rv = uname(&un);
+ if (rv)
+ return -1;
+
+ rv = sscanf(un.release, "%u.%u.%u", &major, &minor, &release);
+ if (rv != 3) {
+ errno = EFAULT;
+ return -1;
+ }
+
+ if (KERNEL_VERSION(major, minor, release) < MIN_KERNEL_VERSION) {
+ errno = EOPNOTSUPP;
+ return -1;
+ }
+
+ return 0;
+}
+
+static int check_gpiosim_module(void)
+{
+ struct kmod_module *module;
+ struct kmod_ctx *kmod;
+ const char *modpath;
+ int ret, initstate;
+
+ kmod = kmod_new(NULL, NULL);
+ if (!kmod)
+ return -1;
+
+ ret = kmod_module_new_from_name(kmod, "gpio-sim", &module);
+ if (ret)
+ goto out_unref_kmod;
+
+again:
+ /* First check if the module is already loaded or built-in. */
+ initstate = kmod_module_get_initstate(module);
+ if (initstate < 0) {
+ if (errno == ENOENT) {
+ /*
+ * It's not loaded, let's see if we can do it manually.
+ * See if we can find the module.
+ */
+ modpath = kmod_module_get_path(module);
+ if (!modpath) {
+ /* libkmod doesn't set errno. */
+ errno = ENOENT;
+ ret = -1;
+ goto out_unref_module;
+ }
+
+ ret = kmod_module_probe_insert_module(module,
+ KMOD_PROBE_IGNORE_LOADED,
+ NULL, NULL, NULL, NULL);
+ if (ret)
+ goto out_unref_module;
+
+ goto again;
+ } else {
+ if (errno == 0)
+ errno = EOPNOTSUPP;
+
+ goto out_unref_module;
+ }
+ }
+
+ if (initstate != KMOD_MODULE_BUILTIN &&
+ initstate != KMOD_MODULE_LIVE &&
+ initstate != KMOD_MODULE_COMING) {
+ errno = EPERM;
+ goto out_unref_module;
+ }
+
+ ret = 0;
+
+out_unref_module:
+ kmod_module_unref(module);
+out_unref_kmod:
+ kmod_unref(kmod);
+ return ret;
+}
+
+static int ctx_open_configfs_dirs(struct gpiosim_ctx *ctx, const char *cfs_path)
+{
+ int fd;
+
+ fd = open(cfs_path, O_RDONLY);
+ if (fd < 0)
+ return -1;
+
+ ctx->pending_dir_fd = openat(fd, "gpio-sim/pending", O_RDONLY);
+ if (ctx->pending_dir_fd < 0) {
+ close(fd);
+ return -1;
+ }
+
+ ctx->live_dir_fd = openat(fd, "gpio-sim/live", O_RDONLY);
+ close(fd);
+ if (ctx->live_dir_fd < 0) {
+ close(ctx->pending_dir_fd);
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * We don't need to check the configfs module as loading gpio-sim will pull it
+ * in but we need to find out if and where configfs was mounted. If it wasn't
+ * then as a last resort we'll try to mount it ourselves.
+ */
+static int ctx_get_configfs_fds(struct gpiosim_ctx *ctx)
+{
+ struct libmnt_context *mntctx;
+ struct libmnt_iter *iter;
+ struct libmnt_table *tb;
+ struct libmnt_fs *fs;
+ const char *type;
+ int ret;
+
+ /* Try to find out if and where configfs is mounted. */
+ mntctx = mnt_new_context();
+ if (!mntctx)
+ return -1;
+
+ ret = mnt_context_get_mtab(mntctx, &tb);
+ if (ret)
+ goto out_free_ctx;
+
+ iter = mnt_new_iter(MNT_ITER_FORWARD);
+ if (!iter)
+ goto out_free_ctx;
+
+ while (mnt_table_next_fs(tb, iter, &fs) == 0) {
+ type = mnt_fs_get_fstype(fs);
+
+ if (strcmp(type, "configfs") == 0) {
+ ret = ctx_open_configfs_dirs(ctx,
+ mnt_fs_get_target(fs));
+ if (ret)
+ goto out_free_iter;
+
+ ret = 0;
+ goto out_free_iter;
+ }
+ }
+
+ /* Didn't find any configfs mounts - let's try to do it ourselves. */
+ ctx->cfs_mnt_dir = strdup("/tmp/gpiosim-configfs-XXXXXX");
+ if (!ctx->cfs_mnt_dir)
+ goto out_free_iter;
+
+ ctx->cfs_mnt_dir = mkdtemp(ctx->cfs_mnt_dir);
+ if (!ctx->cfs_mnt_dir)
+ goto out_free_tmpdir;
+
+ ret = mount(NULL, ctx->cfs_mnt_dir, "configfs", MS_RELATIME, NULL);
+ if (ret)
+ goto out_rm_tmpdir;
+
+ ret = ctx_open_configfs_dirs(ctx, ctx->cfs_mnt_dir);
+ if (ret == 0)
+ /* Skip unmounting & deleting the tmp directory on success. */
+ goto out_free_iter;
+
+ umount(ctx->cfs_mnt_dir);
+out_rm_tmpdir:
+ rmdir(ctx->cfs_mnt_dir);
+out_free_tmpdir:
+ free(ctx->cfs_mnt_dir);
+ ctx->cfs_mnt_dir = NULL;
+out_free_iter:
+ mnt_free_iter(iter);
+out_free_ctx:
+ mnt_free_context(mntctx);
+
+ return ret;
+}
+
+GPIOSIM_API struct gpiosim_ctx *gpiosim_ctx_new(void)
+{
+ struct gpiosim_ctx *ctx;
+ int ret;
+
+ ret = check_kernel_version();
+ if (ret)
+ return NULL;
+
+ ret = check_gpiosim_module();
+ if (ret)
+ return NULL;
+
+ ctx = malloc(sizeof(*ctx));
+ if (!ctx)
+ return NULL;
+
+ memset(ctx, 0, sizeof(*ctx));
+ ctx->refcnt = 1;
+
+ ret = ctx_get_configfs_fds(ctx);
+ if (ret) {
+ free(ctx);
+ return NULL;
+ }
+
+ return ctx;
+}
+
+GPIOSIM_API struct gpiosim_ctx *gpiosim_ctx_ref(struct gpiosim_ctx *ctx)
+{
+ ctx->refcnt++;
+ return ctx;
+}
+
+GPIOSIM_API void gpiosim_ctx_unref(struct gpiosim_ctx *ctx)
+{
+ if (--ctx->refcnt == 0) {
+ close(ctx->pending_dir_fd);
+ close(ctx->live_dir_fd);
+
+ if (ctx->cfs_mnt_dir) {
+ umount(ctx->cfs_mnt_dir);
+ rmdir(ctx->cfs_mnt_dir);
+ free(ctx->cfs_mnt_dir);
+ }
+
+ free(ctx);
+ }
+}
+
+static int open_write_close(int base_fd, const char *where, const char *what)
+{
+ ssize_t written, size = strlen(what);
+ int fd;
+
+ fd = openat(base_fd, where, O_WRONLY);
+ if (fd < 0)
+ return -1;
+
+ written = write(fd, what, size);
+ close(fd);
+ if (written < 0) {
+ return -1;
+ } else if (written != size) {
+ errno = EIO;
+ return -1;
+ }
+
+ return 0;
+}
+
+static int open_read_close(int base_fd, const char *where,
+ char *buf, size_t bufsize)
+{
+ ssize_t rd;
+ int fd;
+
+ fd = openat(base_fd, where, O_RDONLY);
+ if (fd < 0)
+ return -1;
+
+ memset(buf, 0, bufsize);
+ rd = read(fd, buf, bufsize);
+ close(fd);
+ if (rd < 0)
+ return -1;
+
+ if (buf[rd - 1] == '\n')
+ buf[rd - 1] = '\0';
+
+ return 0;
+}
+
+/* We don't have mkdtempat()... :( */
+static char *make_random_dir_at(int at)
+{
+ static const char chars[] = "abcdefghijklmnoprstquvwxyz"
+ "ABCDEFGHIJKLMNOPRSTQUVWXYZ"
+ "0123456789";
+ static const unsigned int namelen = 16;
+
+ unsigned int idx, i;
+ char *name;
+ int ret;
+
+ name = malloc(namelen);
+ if (!name)
+ return NULL;
+
+again:
+ for (i = 0; i < namelen; i++) {
+ ret = getrandom(&idx, sizeof(idx), GRND_NONBLOCK);
+ if (ret != sizeof(idx)) {
+ if (ret >= 0)
+ errno = EAGAIN;
+
+ free(name);
+ return NULL;
+ }
+
+ name[i] = chars[idx % (GPIOSIM_ARRAY_SIZE(chars) - 1)];
+ }
+
+ ret = mkdirat(at, name, 0600);
+ if (ret) {
+ if (errno == EEXIST)
+ goto again;
+
+ free(name);
+ return NULL;
+ }
+
+ return name;
+}
+
+GPIOSIM_API struct gpiosim_chip *
+gpiosim_chip_new(struct gpiosim_ctx *ctx, const char *item_name)
+{
+ struct gpiosim_chip *chip;
+ int configfs_fd, ret;
+ char devname[128];
+ char *name;
+
+ if (item_name) {
+ name = strdup(item_name);
+ if (!name)
+ return NULL;
+
+ ret = mkdirat(ctx->pending_dir_fd, name, 0600);
+ if (ret)
+ goto err_free_name;
+ } else {
+ name = make_random_dir_at(ctx->pending_dir_fd);
+ if (!name)
+ return NULL;
+ }
+
+ configfs_fd = openat(ctx->pending_dir_fd, name, O_RDONLY);
+ if (configfs_fd < 0)
+ goto err_unlink;
+
+ chip = malloc(sizeof(*chip));
+ if (!chip)
+ goto err_close_fd;
+
+ ret = open_read_close(configfs_fd, "dev_name",
+ devname, sizeof(devname));
+ if (ret)
+ goto err_free_chip;
+
+ memset(chip, 0, sizeof(*chip));
+ chip->refcnt = 1;
+ chip->configfs_dir_fd = configfs_fd;
+ chip->sysfs_dir_fd = -1;
+ chip->item_name = name;
+
+ chip->devname = strdup(devname);
+ if (!chip->devname)
+ goto err_free_chip;
+
+ chip->ctx = gpiosim_ctx_ref(ctx);
+
+ return chip;
+
+err_free_chip:
+ free(chip);
+err_close_fd:
+ close(configfs_fd);
+err_unlink:
+ unlinkat(ctx->pending_dir_fd, name, AT_REMOVEDIR);
+err_free_name:
+ free(name);
+
+ return NULL;
+}
+
+GPIOSIM_API struct gpiosim_chip *gpiosim_chip_ref(struct gpiosim_chip *chip)
+{
+ chip->refcnt++;
+ return chip;
+}
+
+GPIOSIM_API void gpiosim_chip_unref(struct gpiosim_chip *chip)
+{
+ struct gpiosim_ctx *ctx = chip->ctx;
+
+ if (--chip->refcnt == 0) {
+ if (chip->live)
+ gpiosim_chip_uncommit(chip);
+
+ close(chip->configfs_dir_fd);
+ unlinkat(ctx->pending_dir_fd, chip->item_name, AT_REMOVEDIR);
+ free(chip->devname);
+ free(chip->item_name);
+ gpiosim_ctx_unref(ctx);
+ free(chip);
+ }
+}
+
+static bool chip_check_pending(struct gpiosim_chip *chip)
+{
+ if (chip->live)
+ errno = EBUSY;
+
+ return !chip->live;
+}
+
+static bool chip_check_live(struct gpiosim_chip *chip)
+{
+ if (!chip->live)
+ errno = ENODEV;
+
+ return chip->live;
+}
+
+GPIOSIM_API int gpiosim_chip_set_label(struct gpiosim_chip *chip,
+ const char *label)
+{
+ if (!chip_check_pending(chip))
+ return -1;
+
+ return open_write_close(chip->configfs_dir_fd, "label", label);
+}
+
+GPIOSIM_API int gpiosim_chip_set_num_lines(struct gpiosim_chip *chip,
+ unsigned int num_lines)
+{
+ char buf[32];
+
+ if (!chip_check_pending(chip))
+ return -1;
+
+ snprintf(buf, sizeof(buf), "%u", num_lines);
+
+ return open_write_close(chip->configfs_dir_fd, "num_lines", buf);
+}
+
+GPIOSIM_API int gpiosim_chip_set_line_names(struct gpiosim_chip *chip,
+ unsigned int num_names,
+ char **names)
+{
+ int ret, written = 0;
+ size_t size = 0, len;
+ unsigned int i;
+ char *buf;
+
+ if (!chip_check_pending(chip))
+ return -1;
+
+ if (!num_names)
+ return 0;
+
+ for (i = 0; i < num_names; i++) {
+ len = names[i] ? strlen(names[i]) : 0;
+ /* Length of the name + '"'x2 + ', '. */
+ size += len + 4;
+ }
+
+ buf = malloc(size);
+ if (!buf)
+ return -1;
+
+ memset(buf, 0, size);
+
+ for (i = 0; i < num_names; i++)
+ written += snprintf(buf + written, size - written,
+ "\"%s\", ", names[i] ?: "");
+ buf[size - 2] = '\0';
+
+ ret = open_write_close(chip->configfs_dir_fd, "line_names", buf);
+ free(buf);
+ return ret;
+}
+
+static int chip_open_sysfs_dir(struct gpiosim_chip *chip)
+{
+ int ret, fd;
+ char *path;
+
+ ret = asprintf(&path,
+ "/sys/devices/platform/%s/line-ctrl", chip->devname);
+ if (ret < 0)
+ return -1;
+
+ fd = open(path, O_RDONLY);
+ free(path);
+ if (fd < 0)
+ return -1;
+
+ return fd;
+}
+
+/* Check if this is the device we're waiting for. */
+static bool check_gpiosim_devpath(struct gpiosim_chip *chip,
+ const char *devpath, const char *sysname)
+{
+ char expected[128];
+
+ snprintf(expected, sizeof(expected),
+ "/devices/platform/%s/%s", chip->devname, sysname);
+
+ return !strcmp(devpath, expected);
+}
+
+/*
+ * This version is called _sync because it synchronously waits for the chip
+ * to appear in the system. There's no _async variant for now but using the
+ * suffix here will make it easier to add it if we ever need it.
+ */
+GPIOSIM_API int gpiosim_chip_commit_sync(struct gpiosim_chip *chip)
+{
+ const char *devpath, *devnode, *sysname, *action;
+ struct gpiosim_ctx *ctx = chip->ctx;
+ struct udev_monitor *udev_mon;
+ struct udev_device *dev;
+ unsigned int tries = 10;
+ struct udev *udev;
+ struct pollfd pfd;
+ int ret;
+
+ if (!chip_check_pending(chip))
+ return -1;
+
+ udev = udev_new();
+ if (!udev)
+ return -1;
+
+ udev_mon = udev_monitor_new_from_netlink(udev, "udev");
+ if (!udev_mon) {
+ ret = -1;
+ goto out_unref_udev;
+ }
+
+ ret = udev_monitor_filter_add_match_subsystem_devtype(udev_mon,
+ "gpio", NULL);
+ if (ret < 0)
+ goto out_unref_udev_mon;
+
+ ret = udev_monitor_enable_receiving(udev_mon);
+ if (ret < 0)
+ goto out_unref_udev_mon;
+
+ ret = renameat(ctx->pending_dir_fd, chip->item_name,
+ ctx->live_dir_fd, chip->item_name);
+ if (ret)
+ goto out_unref_udev_mon;
+
+ while (--tries) {
+ pfd.fd = udev_monitor_get_fd(udev_mon);
+ pfd.events = POLLIN | POLLPRI;
+
+ ret = poll(&pfd, 1, 5000);
+ if (ret <= 0) {
+ if (ret == 0)
+ errno = EAGAIN;
+ goto out_rename_back;
+ }
+
+ dev = udev_monitor_receive_device(udev_mon);
+ if (!dev) {
+ ret = -1;
+ goto out_rename_back;
+ }
+
+ 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 ||
+ !check_gpiosim_devpath(chip, devpath, sysname) ||
+ strcmp(action, "add") != 0) {
+ udev_device_unref(dev);
+ continue;
+ }
+
+ chip->devnode = strdup(devnode);
+ if (!chip->devnode) {
+ udev_device_unref(dev);
+ goto out_rename_back;
+ }
+
+ chip->chipname = strdup(sysname);
+ udev_device_unref(dev);
+ if (!chip->chipname) {
+ free(chip->devnode);
+ goto out_rename_back;
+ }
+
+ chip->sysfs_dir_fd = chip_open_sysfs_dir(chip);
+ if (chip->sysfs_dir_fd < 0) {
+ free(chip->chipname);
+ free(chip->devnode);
+ goto out_rename_back;
+ }
+
+ chip->live = true;
+ ret = 0;
+ goto out_unref_udev_mon;
+ }
+
+ errno = ENODEV;
+ ret = -1;
+
+out_rename_back:
+ renameat(ctx->live_dir_fd, chip->item_name,
+ ctx->pending_dir_fd, chip->item_name);
+out_unref_udev_mon:
+ udev_monitor_unref(udev_mon);
+out_unref_udev:
+ udev_unref(udev);
+
+ return ret;
+}
+
+GPIOSIM_API int gpiosim_chip_uncommit(struct gpiosim_chip *chip)
+{
+ int ret;
+
+ if (!chip_check_live(chip))
+ return -1;
+
+ ret = renameat(chip->ctx->live_dir_fd, chip->item_name,
+ chip->ctx->pending_dir_fd, chip->item_name);
+ if (ret)
+ return ret;
+
+ close(chip->sysfs_dir_fd);
+ free(chip->chipname);
+ free(chip->devnode);
+ chip->live = false;
+
+ return 0;
+}
+
+GPIOSIM_API const char *gpiosim_chip_get_dev(struct gpiosim_chip *chip)
+{
+ if (!chip_check_live(chip))
+ return NULL;
+
+ return chip->devnode;
+}
+
+GPIOSIM_API const char *gpiosim_chip_get_name(struct gpiosim_chip *chip)
+{
+ if (!chip_check_live(chip))
+ return NULL;
+
+ return chip->chipname;
+}
+
+GPIOSIM_API int gpiosim_chip_get_value(struct gpiosim_chip *chip,
+ unsigned int offset)
+{
+ char where[32], what[3];
+ int ret;
+
+ if (!chip_check_live(chip))
+ return -1;
+
+ snprintf(where, sizeof(where), "gpio%u", offset);
+
+ ret = open_read_close(chip->sysfs_dir_fd, where, what, sizeof(what));
+ if (ret)
+ return ret;
+
+ if (what[0] == '0')
+ return 0;
+ if (what[0] == '1')
+ return 1;
+
+ errno = EIO;
+ return -1;
+}
+
+GPIOSIM_API int gpiosim_chip_set_pull(struct gpiosim_chip *chip,
+ unsigned int offset, int value)
+{
+ char where[32], what[2];
+
+ if (!chip_check_live(chip))
+ return -1;
+
+ if (value != 0 && value != 1) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ snprintf(where, sizeof(where), "gpio%u", offset);
+ snprintf(what, sizeof(what), value ? "1" : "0");
+
+ return open_write_close(chip->sysfs_dir_fd, where, what);
+}
new file mode 100644
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/* SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl> */
+
+#ifndef __GPIOD_GPIOSIM_H__
+#define __GPIOD_GPIOSIM_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct gpiosim_ctx;
+struct gpiosim_chip;
+
+struct gpiosim_ctx *gpiosim_ctx_new(void);
+struct gpiosim_ctx *gpiosim_ctx_ref(struct gpiosim_ctx *ctx);
+void gpiosim_ctx_unref(struct gpiosim_ctx *ctx);
+
+struct gpiosim_chip *
+gpiosim_chip_new(struct gpiosim_ctx *ctx, const char *item_name);
+struct gpiosim_chip *gpiosim_chip_ref(struct gpiosim_chip *chip);
+void gpiosim_chip_unref(struct gpiosim_chip *chip);
+
+int gpiosim_chip_set_label(struct gpiosim_chip *chip, const char *label);
+int gpiosim_chip_set_num_lines(struct gpiosim_chip *chip,
+ unsigned int num_lines);
+int gpiosim_chip_set_line_names(struct gpiosim_chip *chip,
+ unsigned int num_names, char **names);
+
+int gpiosim_chip_commit_sync(struct gpiosim_chip *chip);
+int gpiosim_chip_uncommit(struct gpiosim_chip *chip);
+
+const char *gpiosim_chip_get_dev(struct gpiosim_chip *chip);
+const char *gpiosim_chip_get_name(struct gpiosim_chip *chip);
+int gpiosim_chip_get_value(struct gpiosim_chip *chip, unsigned int offset);
+int gpiosim_chip_set_pull(struct gpiosim_chip *chip,
+ unsigned int offset, int value);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* __GPIOD_GPIOSIM_H__ */
Add a C library for controlling the gpio-sim kernel module from various libgpiod test suites. This aims at replacing the old gpio-mockup module and its user-space library - libgpio-mockup - in the project's tree. Signed-off-by: Bartosz Golaszewski <brgl@bgdev.pl> --- configure.ac | 5 +- tests/Makefile.am | 2 +- tests/gpiosim/.gitignore | 4 + tests/gpiosim/Makefile.am | 16 + tests/gpiosim/gpiosim-selftest.c | 103 +++++ tests/gpiosim/gpiosim.c | 743 +++++++++++++++++++++++++++++++ tests/gpiosim/gpiosim.h | 42 ++ 7 files changed, 913 insertions(+), 2 deletions(-) create mode 100644 tests/gpiosim/.gitignore create mode 100644 tests/gpiosim/Makefile.am create mode 100644 tests/gpiosim/gpiosim-selftest.c create mode 100644 tests/gpiosim/gpiosim.c create mode 100644 tests/gpiosim/gpiosim.h