| /* | |
| * GPIO chardev test helper | |
| * | |
| * Copyright (C) 2016 Bamvor Jian Zhang | |
| * | |
| * This program is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 as published by | |
| * the Free Software Foundation. | |
| */ | |
| #define _GNU_SOURCE | |
| #include <unistd.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <stdio.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; | |
| } |