xref: /illumos-gate/usr/src/test/os-tests/tests/gpio/dpio_test.c (revision fd71220ba0fafcc9cf5ea0785db206f3f31336e7)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2022 Oxide Computer Company
14  */
15 
16 /*
17  * Perform basic validation around DPIO creation cases and that the resulting
18  * character devices properly honor the constraints put in them.
19  *
20  * The test starts by creating 4 DPIOs on GPIOs 0-3:
21  *
22  *  o GPIO 0: no read/write
23  *  o GPIO 1: read-only
24  *  o GPIO 2: read-write
25  *  o GPIO 3: read-write, kernel
26  *
27  * We then iterate on GPIOs 4/5 for other tests that should fail to create
28  * DPIOs.
29  */
30 
31 #include <sys/stat.h>
32 #include <sys/types.h>
33 #include <fcntl.h>
34 #include <err.h>
35 #include <stdlib.h>
36 #include <unistd.h>
37 #include <string.h>
38 #include <errno.h>
39 #include <stdbool.h>
40 #include <limits.h>
41 #include <sys/sysmacros.h>
42 #include <time.h>
43 
44 #include <sys/gpio/kgpio.h>
45 #include <sys/gpio/dpio.h>
46 
47 #define	SIM_NPINS	6
48 
49 /*
50  * For now we hardcode the path to our controller rather than discovering it
51  * through libdevinfo or libxpio to simplify the test implementation (and not
52  * have deps on libxpio for functionality below libxpio).
53  */
54 static const char *dpio_ctrl_path = "/devices/pseudo/kgpio@0:gpio_sim2";
55 static int dpio_exit = EXIT_SUCCESS;
56 
57 typedef struct {
58 	const char *dce_desc;
59 	uint32_t dce_gpio;
60 	const char *dce_name;
61 	kgpio_dpio_flags_t dce_flags;
62 	int dce_errno;
63 } dpio_create_err_t;
64 
65 const dpio_create_err_t dpio_create_errs[] = {
66 	{ "GPIO already a DPIO", 0, "WeIrDnAmE12345", 0, EBUSY },
67 	{ "Name already used", 5, "dpioTESTnone", 0, EEXIST },
68 	{ "bad pin", UINT32_MAX, "thispindoesnotexist23", 0, ENOENT },
69 	{ "Name already used", 5, "dpioTESTnone", 0, EEXIST },
70 	{ "bad flags", 5, "amaZINGflags12345", ~KGPIO_DPIO_F_WRITE, EINVAL },
71 	{ "bad name 1", 5, "12345!@#$%^", 0, EINVAL },
72 	{ "bad name 2", 5, "this-is-a-___test!", 0, EINVAL },
73 };
74 
75 /*
76  * Reuse the error struct, ignoring the error to create our defaults.
77  */
78 static const dpio_create_err_t dpio_default_dpios[] = {
79 	{ NULL, 0, "dpioTESTnone", 0, 0 },
80 	{ NULL, 1, "dpioTESTro", KGPIO_DPIO_F_READ, 0 },
81 	{ NULL, 2, "dpioTESTrw", KGPIO_DPIO_F_READ | KGPIO_DPIO_F_WRITE, 0 },
82 	{ NULL, 3, "dpioTESTrwK", KGPIO_DPIO_F_READ | KGPIO_DPIO_F_WRITE |
83 	    KGPIO_DPIO_F_KERNEL, 0 }
84 };
85 
86 static void
dpio_fail(const char * fmt,...)87 dpio_fail(const char *fmt, ...)
88 {
89 	va_list ap;
90 	dpio_exit = EXIT_FAILURE;
91 	(void) printf("TEST FAILED: ");
92 
93 	va_start(ap, fmt);
94 	vprintf(fmt, ap);
95 	va_end(ap);
96 
97 	(void) putchar('\n');
98 }
99 
100 static void
dpio_pass(const char * fmt,...)101 dpio_pass(const char *fmt, ...)
102 {
103 	va_list ap;
104 
105 	(void) printf("TEST PASSED: ");
106 	va_start(ap, fmt);
107 	vprintf(fmt, ap);
108 	va_end(ap);
109 
110 	(void) putchar('\n');
111 }
112 
113 static void
dpio_bad_create(int ctrl_fd,const dpio_create_err_t * test)114 dpio_bad_create(int ctrl_fd, const dpio_create_err_t *test)
115 {
116 	kgpio_dpio_create_t create;
117 
118 	(void) memset(&create, 0, sizeof (create));
119 	create.kdc_id = test->dce_gpio;
120 	create.kdc_flags = test->dce_flags;
121 	(void) strlcpy(create.kdc_name, test->dce_name,
122 	    sizeof (create.kdc_name));
123 
124 	if (ioctl(ctrl_fd, KGPIO_IOC_DPIO_CREATE, &create) == 0) {
125 		dpio_fail("DPIO create: %s: succeeded when we expected failure",
126 		    test->dce_desc);
127 	} else if (errno != test->dce_errno) {
128 		dpio_fail("DPIO create failed: %s: but got wrong errno. "
129 		    "Expected %d, found %d", test->dce_desc, test->dce_errno,
130 		    errno);
131 	} else {
132 		dpio_pass("DPIO create failed: %s", test->dce_desc);
133 	}
134 }
135 
136 static bool
dpio_default_create(int ctrl_fd,uint32_t gpio,const char * name,kgpio_dpio_flags_t flags)137 dpio_default_create(int ctrl_fd, uint32_t gpio, const char *name,
138     kgpio_dpio_flags_t flags)
139 {
140 	kgpio_dpio_create_t create;
141 
142 	(void) memset(&create, 0, sizeof (create));
143 	create.kdc_id = gpio;
144 	create.kdc_flags = flags;
145 	(void) strlcpy(create.kdc_name, name, sizeof (create.kdc_name));
146 
147 	if (ioctl(ctrl_fd, KGPIO_IOC_DPIO_CREATE, &create) != 0) {
148 		warn("failed to create bootstrap DPIO");
149 		return (false);
150 	}
151 
152 	return (true);
153 }
154 
155 /*
156  * As part of exiting, make sure that there are no DPIOs remaining on our
157  * controller. This should not be used for general tests as it ignores a number
158  * of errors.
159  */
160 static void
dpio_cleanup(int ctrl_fd)161 dpio_cleanup(int ctrl_fd)
162 {
163 	for (uint32_t i = 0; i < SIM_NPINS; i++) {
164 		kgpio_dpio_destroy_t destroy;
165 
166 		(void) memset(&destroy, 0, sizeof (destroy));
167 		destroy.kdd_id = i;
168 		if (ioctl(ctrl_fd, KGPIO_IOC_DPIO_DESTROY, &destroy) != 0) {
169 			if (errno != ENOENT) {
170 				dpio_fail("failed to cleanup DPIO on pin %u",
171 				    i);
172 			}
173 		}
174 	}
175 }
176 
177 /*
178  * Verify the various set of features around writing to a DPIO work:
179  *
180  *  o Getting the current output
181  *  o Advancing write timestamps
182  *  o Actually performing the write
183  *
184  * If any of these fail, we short-circuit and don't perform the rest.
185  */
186 static void
dpio_test_cbops_write(int fd,const char * path)187 dpio_test_cbops_write(int fd, const char *path)
188 {
189 	dpio_curout_t curout;
190 	dpio_timing_t pre, post;
191 	uint32_t val;
192 	ssize_t ret;
193 
194 	(void) memset(&curout, 0, sizeof (curout));
195 	if (ioctl(fd, DPIO_IOC_CUROUT, &curout) != 0) {
196 		dpio_fail("failed to get DPIO_IOC_CUROUT on %s: %s", path,
197 		    strerror(errno));
198 		return;
199 	}
200 	dpio_pass("DPIO_IOC_CUROUT successful on %s", path);
201 
202 	(void) memset(&pre, 0, sizeof (pre));
203 	(void) memset(&post, 0, sizeof (post));
204 	if (ioctl(fd, DPIO_IOC_TIMING, &pre) != 0) {
205 		dpio_fail("failed to get DPIO_IOC_TIMING on %s: %s", path,
206 		    strerror(errno));
207 		return;
208 	}
209 	dpio_pass("DPIO_IOC_TIMING successful on %s", path);
210 
211 	val = curout.dps_curout;
212 	ret = write(fd, &val, sizeof (val));
213 	if (ret == -1) {
214 		dpio_fail("write failed on %s: %s", path, strerror(ret));
215 		return;
216 	} else if (ret != 4) {
217 		dpio_fail("write to %s returned wrong number of bytes: %ld, "
218 		    "expected 4 bytes", path, ret);
219 		return;
220 	}
221 	dpio_pass("write successful on %s", path);
222 
223 	if (ioctl(fd, DPIO_IOC_TIMING, &post) != 0) {
224 		dpio_fail("failed to get post-write DPIO_IOC_TIMING on %s: %s",
225 		    path, strerror(errno));
226 		return;
227 	}
228 
229 	if (post.dpt_last_write > pre.dpt_last_write) {
230 		dpio_pass("write time advanced on %s", path);
231 	} else {
232 		dpio_fail("write time on %s did not advance, pre: 0x%lx, "
233 		    "post: 0x%lx", pre.dpt_last_write, post.dpt_last_write);
234 	}
235 }
236 
237 static void
dpio_test_cbops(const char * name,bool can_open,bool can_read,bool can_write)238 dpio_test_cbops(const char *name, bool can_open, bool can_read, bool can_write)
239 {
240 	uint32_t val;
241 	char path[PATH_MAX];
242 	dpio_curout_t curout;
243 	int fd;
244 	ssize_t ret;
245 
246 	(void) snprintf(path, sizeof (path), "/dev/dpio/%s", name);
247 	fd = open(path, O_RDWR);
248 	if (fd < 0) {
249 		if (!can_open) {
250 			dpio_pass("failed to open %s", path);
251 		} else {
252 			dpio_fail("failed to open %s, but expected to: %s",
253 			    path, strerror(errno));
254 		}
255 		return;
256 	} else {
257 		if (!can_open) {
258 			dpio_fail("TEST FAILED: accidentally was able to open "
259 			    "%s", path);
260 			return;
261 		} else {
262 			dpio_pass("successfully opened %s", path);
263 		}
264 	}
265 
266 	errno = 0;
267 	ret = read(fd, &val, sizeof (val));
268 	if (ret != 4) {
269 		if (!can_read && errno == ENOTSUP) {
270 			dpio_pass("successfully failed to read %s", path);
271 		} else if (!can_read) {
272 			dpio_fail("failed to read %s: %s, but expected ENOTSUP",
273 			    path, strerror(errno));
274 		} else {
275 			dpio_fail("failed to read %s: %s", path,
276 			    strerror(errno));
277 		}
278 	} else {
279 		if (!can_read) {
280 			dpio_fail("successfully read %s, but expected failure",
281 			    path);
282 		} else {
283 			dpio_pass("successfully read %s", path);
284 		}
285 	}
286 
287 	if (can_write) {
288 		dpio_test_cbops_write(fd, path);
289 		(void) close(fd);
290 		return;
291 	}
292 
293 	/*
294 	 * Test the can't write path case here. This means we expect that
295 	 * getting the current output will fail and that a write will fail. We
296 	 * won't bother checking the timing information in this case either.
297 	 * That is being done in the write check.
298 	 */
299 
300 	(void) memset(&curout, 0, sizeof (curout));
301 	if (ioctl(fd, DPIO_IOC_CUROUT, &curout) == 0) {
302 		dpio_fail("DPIO_IOC_CUROUT worked on %s, but expected failure",
303 		    path);
304 	} else if (errno != ENOTSUP) {
305 		dpio_fail("got unexpected errno from DPIO_IOC_CUROUT ioctl on "
306 		    "%s: %s, expected ENOTSUP", path, strerror(errno));
307 	} else {
308 		dpio_pass("DPIO_IOC_CUROUT failed on %s", path);
309 	}
310 
311 	val = 0;
312 	if (write(fd, &val, sizeof (val)) == 0) {
313 		dpio_fail("wrote to %s, but expected failure", path);
314 	} else if (errno != ENOTSUP) {
315 		dpio_fail("got unexpected errno writing to %s: %s, expected "
316 		    "ENOTSUP", path, strerror(errno));
317 	} else {
318 		dpio_pass("successfully failed to write %s", path);
319 	}
320 
321 	(void) close(fd);
322 }
323 
324 /*
325  * The /dev entries for a DPIO are created somewhat asynchronously from the
326  * minor node which is created synchronously in the ioctl. Poll in 10ms chunks
327  * for one of these to show up.
328  */
329 static void
dpio_dev_poll(void)330 dpio_dev_poll(void)
331 {
332 	struct timespec ts;
333 	size_t max;
334 
335 	ts.tv_sec = 0;
336 	ts.tv_nsec = MSEC2NSEC(10);
337 	max = SEC2NSEC(1) / ts.tv_nsec;
338 
339 	for (size_t i = 0; i < ARRAY_SIZE(dpio_default_dpios); i++) {
340 		char buf[PATH_MAX];
341 		bool found = false;
342 
343 		(void) snprintf(buf, sizeof (buf), "/dev/dpio/%s",
344 		    dpio_default_dpios[i].dce_name);
345 
346 		for (size_t i = 0; i < max; i++) {
347 			struct stat st;
348 
349 			if (stat(buf, &st) == 0) {
350 				found = true;
351 				break;
352 			}
353 
354 			(void) nanosleep(&ts, NULL);
355 		}
356 
357 		if (!found) {
358 			dpio_fail("timed out waiting for %s", buf);
359 		}
360 	}
361 }
362 
363 /*
364  * Verify that basic FEXCL behavior works.
365  */
366 static void
dpio_test_excl(void)367 dpio_test_excl(void)
368 {
369 	int exclfd, nonexcl;
370 	char path[PATH_MAX];
371 
372 	(void) snprintf(path, sizeof (path), "/dev/dpio/%s",
373 	    dpio_default_dpios[0].dce_name);
374 
375 	nonexcl = open(path, O_RDWR);
376 	if (nonexcl < 0) {
377 		dpio_fail("couldn't open base non-excl fd: %s",
378 		    strerror(errno));
379 		return;
380 	}
381 
382 	exclfd = open(path, O_RDWR | O_EXCL);
383 	if (exclfd >= 0) {
384 		dpio_fail("open O_EXCL worked, but dev was already open with "
385 		    "fd %d", nonexcl);
386 	} else if (errno != EBUSY) {
387 		dpio_fail("open O_EXCL if already open failed with unexpected "
388 		    "errno: %s, expected EBUSY", strerror(errno));
389 	} else {
390 		dpio_pass("open O_EXCL fails if already open");
391 	}
392 	(void) close(nonexcl);
393 
394 	exclfd = open(path, O_RDWR | O_EXCL);
395 	if (exclfd < 0) {
396 		dpio_fail("couldn't open bae excl fd: %s", strerror(errno));
397 		return;
398 	} else {
399 		dpio_pass("base O_EXCL open");
400 	}
401 
402 	nonexcl = open(path, O_RDWR);
403 	if (nonexcl >= 0) {
404 		dpio_fail("O_EXCL didn't block subsequent open of fd %d",
405 		    exclfd);
406 	} else if (errno != EBUSY) {
407 		dpio_fail("O_EXCL blocked other open, but with unexpected "
408 		    "errno: %s, expected EBUSY", strerror(errno));
409 	} else {
410 		dpio_pass("O_EXCL blocks subsequent open");
411 	}
412 
413 	(void) close(exclfd);
414 }
415 
416 /*
417  * Verify we can't destroy a DPIO if it's currently open.
418  */
419 static void
dpio_destroy_ebusy(int ctrl_fd)420 dpio_destroy_ebusy(int ctrl_fd)
421 {
422 	int fd;
423 	char path[PATH_MAX];
424 	kgpio_dpio_destroy_t destroy;
425 
426 	(void) snprintf(path, sizeof (path), "/dev/dpio/%s",
427 	    dpio_default_dpios[0].dce_name);
428 
429 	fd = open(path, O_RDWR);
430 	if (fd < 0) {
431 		dpio_fail("failed to open %s for destruction tests: %s", path,
432 		    strerror(errno));
433 		return;
434 	}
435 
436 	(void) memset(&destroy, 0, sizeof (destroy));
437 	destroy.kdd_id = dpio_default_dpios[0].dce_gpio;
438 
439 	if (ioctl(ctrl_fd, KGPIO_IOC_DPIO_DESTROY, &destroy) == 0) {
440 		dpio_fail("DPIO was destroyed despite open fd!!");
441 	} else if (errno != EBUSY) {
442 		dpio_fail("failed to destroy DPIO with open fd, but got wrong "
443 		    "errno: found %s, expected EBUSY", strerror(errno));
444 	} else {
445 		dpio_pass("failed to destroy DPIO with open fd");
446 	}
447 
448 	(void) close(fd);
449 }
450 
451 int
main(void)452 main(void)
453 {
454 	int ctrl_fd;
455 
456 	ctrl_fd = open(dpio_ctrl_path, O_RDWR);
457 	if (ctrl_fd < 0) {
458 		err(EXIT_FAILURE, "failed to open controller %s",
459 		    dpio_ctrl_path);
460 	}
461 
462 	/*
463 	 * We use somewhat gross names with the hope that we'll avoid anything
464 	 * actually created.
465 	 */
466 	for (size_t i = 0; i < ARRAY_SIZE(dpio_default_dpios); i++) {
467 		const dpio_create_err_t *t = &dpio_default_dpios[i];
468 		if (!dpio_default_create(ctrl_fd, t->dce_gpio, t->dce_name,
469 		    t->dce_flags)) {
470 			dpio_fail("failed to create initial DPIOs");
471 			goto cleanup;
472 		} else {
473 			dpio_pass("created bootstrap DPIO %u", i);
474 		}
475 	}
476 
477 	for (size_t i = 0; i < ARRAY_SIZE(dpio_create_errs); i++) {
478 		dpio_bad_create(ctrl_fd, &dpio_create_errs[i]);
479 	}
480 
481 	/*
482 	 * Make sure we get our links and then go through and test the various
483 	 * cbops work or don't work based on the actual values that we set.
484 	 */
485 	dpio_dev_poll();
486 
487 	dpio_test_cbops("dpioTESTnone", true, false, false);
488 	dpio_test_cbops("dpioTESTro", true, true, false);
489 	dpio_test_cbops("dpioTESTrw", true, true, true);
490 	dpio_test_cbops("dpioTESTrwK", false, false, false);
491 
492 	/*
493 	 * Verify a few particular behaviours around fds. In particular we want
494 	 * to make sure O_EXCL / FEXCL is honored properly in the device. We
495 	 * also want to make sure that you can't destroy something if the fd is
496 	 * open.
497 	 */
498 	dpio_test_excl();
499 	dpio_destroy_ebusy(ctrl_fd);
500 
501 cleanup:
502 	dpio_cleanup(ctrl_fd);
503 
504 	(void) close(ctrl_fd);
505 	if (dpio_exit == EXIT_SUCCESS) {
506 		(void) printf("All tests passed successfully!\n");
507 	}
508 	return (dpio_exit);
509 }
510