// SPDX-License-Identifier: LGPL-2.1-or-later /* * This file is part of libgpiod. * * Copyright (C) 2019 Bartosz Golaszewski */ #include #include #include #include #include #include #include #include "gpiod-test.h" #define MIN_KERNEL_MAJOR 5 #define MIN_KERNEL_MINOR 5 #define MIN_KERNEL_RELEASE 0 #define MIN_KERNEL_VERSION KERNEL_VERSION(MIN_KERNEL_MAJOR, \ MIN_KERNEL_MINOR, \ MIN_KERNEL_RELEASE) struct gpiod_test_event_thread { GThread *id; GMutex lock; GCond cond; gboolean should_stop; guint chip_index; guint line_offset; guint freq; }; static struct { GList *tests; struct gpio_mockup *mockup; } globals; static void check_kernel(void) { guint major, minor, release; struct utsname un; gint ret; g_debug("checking linux kernel version"); ret = uname(&un); if (ret) g_error("unable to read the kernel release version: %s", g_strerror(errno)); ret = sscanf(un.release, "%u.%u.%u", &major, &minor, &release); if (ret != 3) g_error("error reading kernel release version"); if (KERNEL_VERSION(major, minor, release) < MIN_KERNEL_VERSION) g_error("linux kernel version must be at least v%u.%u.%u - got v%u.%u.%u", MIN_KERNEL_MAJOR, MIN_KERNEL_MINOR, MIN_KERNEL_RELEASE, major, minor, release); g_debug("kernel release is v%u.%u.%u - ok to run tests", major, minor, release); return; } static void test_func_wrapper(gconstpointer data) { const _GpiodTestCase *test = data; gint ret, flags = 0; if (test->flags & GPIOD_TEST_FLAG_NAMED_LINES) flags |= GPIO_MOCKUP_FLAG_NAMED_LINES; ret = gpio_mockup_probe(globals.mockup, test->num_chips, test->chip_sizes, flags); if (ret) g_error("unable to probe gpio-mockup: %s", g_strerror(errno)); test->func(); ret = gpio_mockup_remove(globals.mockup); if (ret) g_error("unable to remove gpio_mockup: %s", g_strerror(errno)); } static void unref_mockup(void) { gpio_mockup_unref(globals.mockup); } static void add_test_from_list(gpointer element, gpointer data G_GNUC_UNUSED) { _GpiodTestCase *test = element; g_test_add_data_func(test->path, test, test_func_wrapper); } int main(gint argc, gchar **argv) { g_test_init(&argc, &argv, NULL); g_test_set_nonfatal_assertions(); g_debug("running libgpiod test suite"); g_debug("%u tests registered", g_list_length(globals.tests)); /* * Setup libgpiomockup first so that it runs its own kernel version * check before we tell the user our local requirements are met as * well. */ globals.mockup = gpio_mockup_new(); if (!globals.mockup) g_error("unable to initialize gpio-mockup library: %s", g_strerror(errno)); atexit(unref_mockup); check_kernel(); g_list_foreach(globals.tests, add_test_from_list, NULL); g_list_free(globals.tests); return g_test_run(); } void _gpiod_test_register(_GpiodTestCase *test) { globals.tests = g_list_append(globals.tests, test); } const gchar *gpiod_test_chip_path(guint idx) { const gchar *path; path = gpio_mockup_chip_path(globals.mockup, idx); if (!path) g_error("unable to retrieve the chip path: %s", g_strerror(errno)); return path; } const gchar *gpiod_test_chip_name(guint idx) { const gchar *name; name = gpio_mockup_chip_name(globals.mockup, idx); if (!name) g_error("unable to retrieve the chip name: %s", g_strerror(errno)); return name; } gint gpiod_test_chip_num(unsigned int idx) { gint num; num = gpio_mockup_chip_num(globals.mockup, idx); if (num < 0) g_error("unable to retrieve the chip number: %s", g_strerror(errno)); return num; } gint gpiod_test_chip_get_value(guint chip_index, guint line_offset) { gint ret; ret = gpio_mockup_get_value(globals.mockup, chip_index, line_offset); if (ret < 0) g_error("unable to read line value from gpio-mockup: %s", g_strerror(errno)); return ret; } void gpiod_test_chip_set_pull(guint chip_index, guint line_offset, gint pull) { gint ret; ret = gpio_mockup_set_pull(globals.mockup, chip_index, line_offset, pull); if (ret) g_error("unable to set line pull in gpio-mockup: %s", g_strerror(errno)); } static gpointer event_worker_func(gpointer data) { GpiodTestEventThread *thread = data; gboolean signalled; gint64 end_time; gint i; for (i = 0;; i++) { g_mutex_lock(&thread->lock); if (thread->should_stop) { g_mutex_unlock(&thread->lock); break; } end_time = g_get_monotonic_time() + thread->freq * 1000; signalled = g_cond_wait_until(&thread->cond, &thread->lock, end_time); if (!signalled) gpiod_test_chip_set_pull(thread->chip_index, thread->line_offset, i % 2); g_mutex_unlock(&thread->lock); } return NULL; } GpiodTestEventThread * gpiod_test_start_event_thread(guint chip_index, guint line_offset, guint freq) { GpiodTestEventThread *thread = g_malloc0(sizeof(*thread)); g_mutex_init(&thread->lock); g_cond_init(&thread->cond); thread->chip_index = chip_index; thread->line_offset = line_offset; thread->freq = freq; thread->id = g_thread_new("event-worker", event_worker_func, thread); return thread; } void gpiod_test_stop_event_thread(GpiodTestEventThread *thread) { g_mutex_lock(&thread->lock); thread->should_stop = TRUE; g_cond_broadcast(&thread->cond); g_mutex_unlock(&thread->lock); (void)g_thread_join(thread->id); g_mutex_clear(&thread->lock); g_cond_clear(&thread->cond); g_free(thread); }