blob: fa27bd74413e17469721e4158e89923c0c22eb43 [file] [log] [blame]
/* 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 <linux/version.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")))
/*
* The gpio-mockup features (including the debugfs interface) we're using
* in this library have first been released in the linux kernel version below.
*/
#define MIN_KERNEL_VERSION KERNEL_VERSION(5, 1, 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 (KERNEL_VERSION(major, minor, release) < MIN_KERNEL_VERSION) {
errno = EOPNOTSUPP;
return false;
}
return true;
}
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,
KMOD_PROBE_IGNORE_LOADED,
"gpio_mockup_ranges=-1,4",
NULL, NULL, NULL);
if (rv)
goto err_unref_module;
/* We need to check that the gpio-mockup debugfs directory exists. */
rv = access("/sys/kernel/debug/gpio-mockup", R_OK | W_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,
const 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,
const 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;
ctx->num_chips = num_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 to 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;
}
static bool index_valid(struct gpio_mockup *ctx, unsigned int idx)
{
if (!ctx->chips) {
errno = ENODEV;
return false;
}
if (idx >= ctx->num_chips) {
errno = EINVAL;
return false;
}
return true;
}
EXPORT const char *
gpio_mockup_chip_name(struct gpio_mockup *ctx, unsigned int idx)
{
if (!index_valid(ctx, idx))
return NULL;
return ctx->chips[idx]->name;
}
EXPORT const char *
gpio_mockup_chip_path(struct gpio_mockup *ctx, unsigned int idx)
{
if (!index_valid(ctx, idx))
return NULL;
return ctx->chips[idx]->path;
}
EXPORT int gpio_mockup_chip_num(struct gpio_mockup *ctx, unsigned int idx)
{
if (!index_valid(ctx, idx))
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 (!index_valid(ctx, chip_idx))
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 (!index_valid(ctx, chip_idx))
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;
}