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