1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * GPIO chardev test helper
4 *
5 * Copyright (C) 2016 Bamvor Jian Zhang
6 */
7
8#define _GNU_SOURCE
9#include <unistd.h>
10#include <stdio.h>
11#include <stdlib.h>
12#include <errno.h>
13#include <string.h>
14#include <fcntl.h>
15#include <getopt.h>
16#include <sys/ioctl.h>
17#include <libmount.h>
18#include <err.h>
19#include <dirent.h>
20#include <linux/gpio.h>
21#include "../../../gpio/gpio-utils.h"
22
23#define CONSUMER	"gpio-selftest"
24#define	GC_NUM		10
25enum direction {
26	OUT,
27	IN
28};
29
30static int get_debugfs(char **path)
31{
32	struct libmnt_context *cxt;
33	struct libmnt_table *tb;
34	struct libmnt_iter *itr = NULL;
35	struct libmnt_fs *fs;
36	int found = 0, ret;
37
38	cxt = mnt_new_context();
39	if (!cxt)
40		err(EXIT_FAILURE, "libmount context allocation failed");
41
42	itr = mnt_new_iter(MNT_ITER_FORWARD);
43	if (!itr)
44		err(EXIT_FAILURE, "failed to initialize libmount iterator");
45
46	if (mnt_context_get_mtab(cxt, &tb))
47		err(EXIT_FAILURE, "failed to read mtab");
48
49	while (mnt_table_next_fs(tb, itr, &fs) == 0) {
50		const char *type = mnt_fs_get_fstype(fs);
51
52		if (!strcmp(type, "debugfs")) {
53			found = 1;
54			break;
55		}
56	}
57	if (found) {
58		ret = asprintf(path, "%s/gpio", mnt_fs_get_target(fs));
59		if (ret < 0)
60			err(EXIT_FAILURE, "failed to format string");
61	}
62
63	mnt_free_iter(itr);
64	mnt_free_context(cxt);
65
66	if (!found)
67		return -1;
68
69	return 0;
70}
71
72static int gpio_debugfs_get(const char *consumer, int *dir, int *value)
73{
74	char *debugfs;
75	FILE *f;
76	char *line = NULL;
77	size_t len = 0;
78	char *cur;
79	int found = 0;
80
81	if (get_debugfs(&debugfs) != 0)
82		err(EXIT_FAILURE, "debugfs is not mounted");
83
84	f = fopen(debugfs, "r");
85	if (!f)
86		err(EXIT_FAILURE, "read from gpio debugfs failed");
87
88	/*
89	 * gpio-2   (                    |gpio-selftest               ) in  lo
90	 */
91	while (getline(&line, &len, f) != -1) {
92		cur = strstr(line, consumer);
93		if (cur == NULL)
94			continue;
95
96		cur = strchr(line, ')');
97		if (!cur)
98			continue;
99
100		cur += 2;
101		if (!strncmp(cur, "out", 3)) {
102			*dir = OUT;
103			cur += 4;
104		} else if (!strncmp(cur, "in", 2)) {
105			*dir = IN;
106			cur += 4;
107		}
108
109		if (!strncmp(cur, "hi", 2))
110			*value = 1;
111		else if (!strncmp(cur, "lo", 2))
112			*value = 0;
113
114		found = 1;
115		break;
116	}
117	free(debugfs);
118	fclose(f);
119	free(line);
120
121	if (!found)
122		return -1;
123
124	return 0;
125}
126
127static struct gpiochip_info *list_gpiochip(const char *gpiochip_name, int *ret)
128{
129	struct gpiochip_info *cinfo;
130	struct gpiochip_info *current;
131	const struct dirent *ent;
132	DIR *dp;
133	char *chrdev_name;
134	int fd;
135	int i = 0;
136
137	cinfo = calloc(sizeof(struct gpiochip_info) * 4, GC_NUM + 1);
138	if (!cinfo)
139		err(EXIT_FAILURE, "gpiochip_info allocation failed");
140
141	current = cinfo;
142	dp = opendir("/dev");
143	if (!dp) {
144		*ret = -errno;
145		goto error_out;
146	} else {
147		*ret = 0;
148	}
149
150	while (ent = readdir(dp), ent) {
151		if (check_prefix(ent->d_name, "gpiochip")) {
152			*ret = asprintf(&chrdev_name, "/dev/%s", ent->d_name);
153			if (*ret < 0)
154				goto error_out;
155
156			fd = open(chrdev_name, 0);
157			if (fd == -1) {
158				*ret = -errno;
159				fprintf(stderr, "Failed to open %s\n",
160					chrdev_name);
161				goto error_close_dir;
162			}
163			*ret = ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, current);
164			if (*ret == -1) {
165				perror("Failed to issue CHIPINFO IOCTL\n");
166				goto error_close_dir;
167			}
168			close(fd);
169			if (strcmp(current->label, gpiochip_name) == 0
170			    || check_prefix(current->label, gpiochip_name)) {
171				*ret = 0;
172				current++;
173				i++;
174			}
175		}
176	}
177
178	if ((!*ret && i == 0) || *ret < 0) {
179		free(cinfo);
180		cinfo = NULL;
181	}
182	if (!*ret && i > 0) {
183		cinfo = realloc(cinfo, sizeof(struct gpiochip_info) * 4 * i);
184		*ret = i;
185	}
186
187error_close_dir:
188	closedir(dp);
189error_out:
190	if (*ret < 0)
191		err(EXIT_FAILURE, "list gpiochip failed: %s", strerror(*ret));
192
193	return cinfo;
194}
195
196int gpio_pin_test(struct gpiochip_info *cinfo, int line, int flag, int value)
197{
198	struct gpiohandle_data data;
199	unsigned int lines[] = {line};
200	int fd;
201	int debugfs_dir = IN;
202	int debugfs_value = 0;
203	int ret;
204
205	data.values[0] = value;
206	ret = gpiotools_request_linehandle(cinfo->name, lines, 1, flag, &data,
207					   CONSUMER);
208	if (ret < 0)
209		goto fail_out;
210	else
211		fd = ret;
212
213	ret = gpio_debugfs_get(CONSUMER, &debugfs_dir, &debugfs_value);
214	if (ret) {
215		ret = -EINVAL;
216		goto fail_out;
217	}
218	if (flag & GPIOHANDLE_REQUEST_INPUT) {
219		if (debugfs_dir != IN) {
220			errno = -EINVAL;
221			ret = -errno;
222		}
223	} else if (flag & GPIOHANDLE_REQUEST_OUTPUT) {
224		if (flag & GPIOHANDLE_REQUEST_ACTIVE_LOW)
225			debugfs_value = !debugfs_value;
226
227		if (!(debugfs_dir == OUT && value == debugfs_value)) {
228			errno = -EINVAL;
229			ret = -errno;
230		}
231	}
232	gpiotools_release_linehandle(fd);
233
234fail_out:
235	if (ret)
236		err(EXIT_FAILURE, "gpio<%s> line<%d> test flag<0x%x> value<%d>",
237		    cinfo->name, line, flag, value);
238
239	return ret;
240}
241
242void gpio_pin_tests(struct gpiochip_info *cinfo, unsigned int line)
243{
244	printf("line<%d>", line);
245	gpio_pin_test(cinfo, line, GPIOHANDLE_REQUEST_OUTPUT, 0);
246	printf(".");
247	gpio_pin_test(cinfo, line, GPIOHANDLE_REQUEST_OUTPUT, 1);
248	printf(".");
249	gpio_pin_test(cinfo, line,
250		      GPIOHANDLE_REQUEST_OUTPUT | GPIOHANDLE_REQUEST_ACTIVE_LOW,
251		      0);
252	printf(".");
253	gpio_pin_test(cinfo, line,
254		      GPIOHANDLE_REQUEST_OUTPUT | GPIOHANDLE_REQUEST_ACTIVE_LOW,
255		      1);
256	printf(".");
257	gpio_pin_test(cinfo, line, GPIOHANDLE_REQUEST_INPUT, 0);
258	printf(".");
259}
260
261/*
262 * ./gpio-mockup-chardev gpio_chip_name_prefix is_valid_gpio_chip
263 * Return 0 if successful or exit with EXIT_FAILURE if test failed.
264 * gpio_chip_name_prefix: The prefix of gpiochip you want to test. E.g.
265 *			  gpio-mockup
266 * is_valid_gpio_chip:	  Whether the gpio_chip is valid. 1 means valid,
267 *			  0 means invalid which could not be found by
268 *			  list_gpiochip.
269 */
270int main(int argc, char *argv[])
271{
272	char *prefix;
273	int valid;
274	struct gpiochip_info *cinfo;
275	struct gpiochip_info *current;
276	int i;
277	int ret;
278
279	if (argc < 3) {
280		printf("Usage: %s prefix is_valid", argv[0]);
281		exit(EXIT_FAILURE);
282	}
283
284	prefix = argv[1];
285	valid = strcmp(argv[2], "true") == 0 ? 1 : 0;
286
287	printf("Test gpiochip %s: ", prefix);
288	cinfo = list_gpiochip(prefix, &ret);
289	if (!cinfo) {
290		if (!valid && ret == 0) {
291			printf("Invalid test successful\n");
292			ret = 0;
293			goto out;
294		} else {
295			ret = -EINVAL;
296			goto out;
297		}
298	} else if (cinfo && !valid) {
299		ret = -EINVAL;
300		goto out;
301	}
302	current = cinfo;
303	for (i = 0; i < ret; i++) {
304		gpio_pin_tests(current, 0);
305		gpio_pin_tests(current, current->lines - 1);
306		gpio_pin_tests(current, random() % current->lines);
307		current++;
308	}
309	ret = 0;
310	printf("successful\n");
311
312out:
313	if (ret)
314		fprintf(stderr, "gpio<%s> test failed\n", prefix);
315
316	if (cinfo)
317		free(cinfo);
318
319	if (ret)
320		exit(EXIT_FAILURE);
321
322	return ret;
323}
324