324 lines
6.5 KiB
C
324 lines
6.5 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* GPIO chardev test helper
|
|
*
|
|
* Copyright (C) 2016 Bamvor Jian Zhang
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <fcntl.h>
|
|
#include <getopt.h>
|
|
#include <sys/ioctl.h>
|
|
#include <libmount.h>
|
|
#include <err.h>
|
|
#include <dirent.h>
|
|
#include <linux/gpio.h>
|
|
#include "../../../gpio/gpio-utils.h"
|
|
|
|
#define CONSUMER "gpio-selftest"
|
|
#define GC_NUM 10
|
|
enum direction {
|
|
OUT,
|
|
IN
|
|
};
|
|
|
|
static int get_debugfs(char **path)
|
|
{
|
|
struct libmnt_context *cxt;
|
|
struct libmnt_table *tb;
|
|
struct libmnt_iter *itr = NULL;
|
|
struct libmnt_fs *fs;
|
|
int found = 0, ret;
|
|
|
|
cxt = mnt_new_context();
|
|
if (!cxt)
|
|
err(EXIT_FAILURE, "libmount context allocation failed");
|
|
|
|
itr = mnt_new_iter(MNT_ITER_FORWARD);
|
|
if (!itr)
|
|
err(EXIT_FAILURE, "failed to initialize libmount iterator");
|
|
|
|
if (mnt_context_get_mtab(cxt, &tb))
|
|
err(EXIT_FAILURE, "failed to read mtab");
|
|
|
|
while (mnt_table_next_fs(tb, itr, &fs) == 0) {
|
|
const char *type = mnt_fs_get_fstype(fs);
|
|
|
|
if (!strcmp(type, "debugfs")) {
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (found) {
|
|
ret = asprintf(path, "%s/gpio", mnt_fs_get_target(fs));
|
|
if (ret < 0)
|
|
err(EXIT_FAILURE, "failed to format string");
|
|
}
|
|
|
|
mnt_free_iter(itr);
|
|
mnt_free_context(cxt);
|
|
|
|
if (!found)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gpio_debugfs_get(const char *consumer, int *dir, int *value)
|
|
{
|
|
char *debugfs;
|
|
FILE *f;
|
|
char *line = NULL;
|
|
size_t len = 0;
|
|
char *cur;
|
|
int found = 0;
|
|
|
|
if (get_debugfs(&debugfs) != 0)
|
|
err(EXIT_FAILURE, "debugfs is not mounted");
|
|
|
|
f = fopen(debugfs, "r");
|
|
if (!f)
|
|
err(EXIT_FAILURE, "read from gpio debugfs failed");
|
|
|
|
/*
|
|
* gpio-2 ( |gpio-selftest ) in lo
|
|
*/
|
|
while (getline(&line, &len, f) != -1) {
|
|
cur = strstr(line, consumer);
|
|
if (cur == NULL)
|
|
continue;
|
|
|
|
cur = strchr(line, ')');
|
|
if (!cur)
|
|
continue;
|
|
|
|
cur += 2;
|
|
if (!strncmp(cur, "out", 3)) {
|
|
*dir = OUT;
|
|
cur += 4;
|
|
} else if (!strncmp(cur, "in", 2)) {
|
|
*dir = IN;
|
|
cur += 4;
|
|
}
|
|
|
|
if (!strncmp(cur, "hi", 2))
|
|
*value = 1;
|
|
else if (!strncmp(cur, "lo", 2))
|
|
*value = 0;
|
|
|
|
found = 1;
|
|
break;
|
|
}
|
|
free(debugfs);
|
|
fclose(f);
|
|
free(line);
|
|
|
|
if (!found)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct gpiochip_info *list_gpiochip(const char *gpiochip_name, int *ret)
|
|
{
|
|
struct gpiochip_info *cinfo;
|
|
struct gpiochip_info *current;
|
|
const struct dirent *ent;
|
|
DIR *dp;
|
|
char *chrdev_name;
|
|
int fd;
|
|
int i = 0;
|
|
|
|
cinfo = calloc(sizeof(struct gpiochip_info) * 4, GC_NUM + 1);
|
|
if (!cinfo)
|
|
err(EXIT_FAILURE, "gpiochip_info allocation failed");
|
|
|
|
current = cinfo;
|
|
dp = opendir("/dev");
|
|
if (!dp) {
|
|
*ret = -errno;
|
|
goto error_out;
|
|
} else {
|
|
*ret = 0;
|
|
}
|
|
|
|
while (ent = readdir(dp), ent) {
|
|
if (check_prefix(ent->d_name, "gpiochip")) {
|
|
*ret = asprintf(&chrdev_name, "/dev/%s", ent->d_name);
|
|
if (*ret < 0)
|
|
goto error_out;
|
|
|
|
fd = open(chrdev_name, 0);
|
|
if (fd == -1) {
|
|
*ret = -errno;
|
|
fprintf(stderr, "Failed to open %s\n",
|
|
chrdev_name);
|
|
goto error_close_dir;
|
|
}
|
|
*ret = ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, current);
|
|
if (*ret == -1) {
|
|
perror("Failed to issue CHIPINFO IOCTL\n");
|
|
goto error_close_dir;
|
|
}
|
|
close(fd);
|
|
if (strcmp(current->label, gpiochip_name) == 0
|
|
|| check_prefix(current->label, gpiochip_name)) {
|
|
*ret = 0;
|
|
current++;
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((!*ret && i == 0) || *ret < 0) {
|
|
free(cinfo);
|
|
cinfo = NULL;
|
|
}
|
|
if (!*ret && i > 0) {
|
|
cinfo = realloc(cinfo, sizeof(struct gpiochip_info) * 4 * i);
|
|
*ret = i;
|
|
}
|
|
|
|
error_close_dir:
|
|
closedir(dp);
|
|
error_out:
|
|
if (*ret < 0)
|
|
err(EXIT_FAILURE, "list gpiochip failed: %s", strerror(*ret));
|
|
|
|
return cinfo;
|
|
}
|
|
|
|
int gpio_pin_test(struct gpiochip_info *cinfo, int line, int flag, int value)
|
|
{
|
|
struct gpiohandle_data data;
|
|
unsigned int lines[] = {line};
|
|
int fd;
|
|
int debugfs_dir = IN;
|
|
int debugfs_value = 0;
|
|
int ret;
|
|
|
|
data.values[0] = value;
|
|
ret = gpiotools_request_linehandle(cinfo->name, lines, 1, flag, &data,
|
|
CONSUMER);
|
|
if (ret < 0)
|
|
goto fail_out;
|
|
else
|
|
fd = ret;
|
|
|
|
ret = gpio_debugfs_get(CONSUMER, &debugfs_dir, &debugfs_value);
|
|
if (ret) {
|
|
ret = -EINVAL;
|
|
goto fail_out;
|
|
}
|
|
if (flag & GPIOHANDLE_REQUEST_INPUT) {
|
|
if (debugfs_dir != IN) {
|
|
errno = -EINVAL;
|
|
ret = -errno;
|
|
}
|
|
} else if (flag & GPIOHANDLE_REQUEST_OUTPUT) {
|
|
if (flag & GPIOHANDLE_REQUEST_ACTIVE_LOW)
|
|
debugfs_value = !debugfs_value;
|
|
|
|
if (!(debugfs_dir == OUT && value == debugfs_value)) {
|
|
errno = -EINVAL;
|
|
ret = -errno;
|
|
}
|
|
}
|
|
gpiotools_release_linehandle(fd);
|
|
|
|
fail_out:
|
|
if (ret)
|
|
err(EXIT_FAILURE, "gpio<%s> line<%d> test flag<0x%x> value<%d>",
|
|
cinfo->name, line, flag, value);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void gpio_pin_tests(struct gpiochip_info *cinfo, unsigned int line)
|
|
{
|
|
printf("line<%d>", line);
|
|
gpio_pin_test(cinfo, line, GPIOHANDLE_REQUEST_OUTPUT, 0);
|
|
printf(".");
|
|
gpio_pin_test(cinfo, line, GPIOHANDLE_REQUEST_OUTPUT, 1);
|
|
printf(".");
|
|
gpio_pin_test(cinfo, line,
|
|
GPIOHANDLE_REQUEST_OUTPUT | GPIOHANDLE_REQUEST_ACTIVE_LOW,
|
|
0);
|
|
printf(".");
|
|
gpio_pin_test(cinfo, line,
|
|
GPIOHANDLE_REQUEST_OUTPUT | GPIOHANDLE_REQUEST_ACTIVE_LOW,
|
|
1);
|
|
printf(".");
|
|
gpio_pin_test(cinfo, line, GPIOHANDLE_REQUEST_INPUT, 0);
|
|
printf(".");
|
|
}
|
|
|
|
/*
|
|
* ./gpio-mockup-chardev gpio_chip_name_prefix is_valid_gpio_chip
|
|
* Return 0 if successful or exit with EXIT_FAILURE if test failed.
|
|
* gpio_chip_name_prefix: The prefix of gpiochip you want to test. E.g.
|
|
* gpio-mockup
|
|
* is_valid_gpio_chip: Whether the gpio_chip is valid. 1 means valid,
|
|
* 0 means invalid which could not be found by
|
|
* list_gpiochip.
|
|
*/
|
|
int main(int argc, char *argv[])
|
|
{
|
|
char *prefix;
|
|
int valid;
|
|
struct gpiochip_info *cinfo;
|
|
struct gpiochip_info *current;
|
|
int i;
|
|
int ret;
|
|
|
|
if (argc < 3) {
|
|
printf("Usage: %s prefix is_valid", argv[0]);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
prefix = argv[1];
|
|
valid = strcmp(argv[2], "true") == 0 ? 1 : 0;
|
|
|
|
printf("Test gpiochip %s: ", prefix);
|
|
cinfo = list_gpiochip(prefix, &ret);
|
|
if (!cinfo) {
|
|
if (!valid && ret == 0) {
|
|
printf("Invalid test successful\n");
|
|
ret = 0;
|
|
goto out;
|
|
} else {
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
} else if (cinfo && !valid) {
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
current = cinfo;
|
|
for (i = 0; i < ret; i++) {
|
|
gpio_pin_tests(current, 0);
|
|
gpio_pin_tests(current, current->lines - 1);
|
|
gpio_pin_tests(current, random() % current->lines);
|
|
current++;
|
|
}
|
|
ret = 0;
|
|
printf("successful\n");
|
|
|
|
out:
|
|
if (ret)
|
|
fprintf(stderr, "gpio<%s> test failed\n", prefix);
|
|
|
|
if (cinfo)
|
|
free(cinfo);
|
|
|
|
if (ret)
|
|
exit(EXIT_FAILURE);
|
|
|
|
return ret;
|
|
}
|