forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
323 lines
6.5 KiB
323 lines
6.5 KiB
// 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; |
|
}
|
|
|