xref: /illumos-gate/usr/src/test/i2c-tests/tests/libi2c/discovery.c (revision 0cbe48189888d02563dba9c90132ac391ba233b6)
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 2025 Oxide Computer Company
14  */
15 
16 /*
17  * Go through our various discovery APIs and make sure that we can find all of
18  * the different devices that we expect. This test is designed to operate
19  * against the full device complement. Specifically we go through and perform
20  * discovery to get the devi, open devices by their devi and then come back and
21  * use a path open and verify that we get the same sorts of things.
22  */
23 
24 #include <stdlib.h>
25 #include <err.h>
26 #include <string.h>
27 #include <sys/sysmacros.h>
28 
29 #include "libi2c_test_util.h"
30 
31 typedef struct {
32 	bool cc_fail;
33 	i2c_ctrl_t *cc_i2c;
34 	i2c_ctrl_t *cc_smbus;
35 } ctrl_cb_t;
36 
37 static bool
disc_ctrl_cb(i2c_hdl_t * hdl,const i2c_ctrl_disc_t * disc,void * arg)38 disc_ctrl_cb(i2c_hdl_t *hdl, const i2c_ctrl_disc_t *disc, void *arg)
39 {
40 	ctrl_cb_t *cb = arg;
41 	di_node_t di = i2c_ctrl_disc_devi(disc);
42 	const char *name = di_bus_addr(di);
43 
44 	if (strcmp(name, "i2csim0") == 0) {
45 		if (cb->cc_i2c != NULL) {
46 			warnx("TEST FAILED: discovered i2csim0 a second time!");
47 			cb->cc_fail = true;
48 		}
49 
50 		if (!i2c_ctrl_init(hdl, di, &cb->cc_i2c)) {
51 			libi2c_test_warn(hdl, "TEST FAILED: failed to "
52 			    "initialize i2c_ctrl_t for i2csim0");
53 			cb->cc_fail = true;
54 		}
55 	}
56 
57 	if (strcmp(name, "smbussim1") == 0) {
58 		if (cb->cc_smbus != NULL) {
59 			warnx("TEST FAILED: discovered smbussim1 a second "
60 			    "time!");
61 			cb->cc_fail = true;
62 		}
63 
64 		if (!i2c_ctrl_init(hdl, di, &cb->cc_smbus)) {
65 			libi2c_test_warn(hdl, "TEST FAILED: failed to "
66 			    "initialize i2c_ctrl_t for smbussim1");
67 			cb->cc_fail = true;
68 		}
69 	}
70 
71 	return (true);
72 }
73 
74 static bool
disc_ctrl_path(i2c_hdl_t * hdl,i2c_ctrl_t * ctrl,const char * path)75 disc_ctrl_path(i2c_hdl_t *hdl, i2c_ctrl_t *ctrl, const char *path)
76 {
77 	i2c_ctrl_t *alt;
78 	bool ret = true;
79 
80 	if (!i2c_ctrl_init_by_path(hdl, path, &alt)) {
81 		libi2c_test_warn(hdl, "TEST FAILED: failed to initialize "
82 		    "controller by path %s", path);
83 		return (false);
84 	}
85 
86 	if (strcmp(i2c_ctrl_name(ctrl), i2c_ctrl_name(alt)) != 0) {
87 		warnx("TEST FAILED: name mismatch on %s: %s vs. %s", path,
88 		    i2c_ctrl_name(ctrl), i2c_ctrl_name(alt));
89 		ret = false;
90 	}
91 
92 	if (strcmp(i2c_ctrl_path(ctrl), i2c_ctrl_path(alt)) != 0) {
93 		warnx("TEST FAILED: path mismatch on %s: %s vs. %s", path,
94 		    i2c_ctrl_path(ctrl), i2c_ctrl_path(alt));
95 		ret = false;
96 	}
97 
98 	if (i2c_ctrl_instance(ctrl) != i2c_ctrl_instance(alt)) {
99 		warnx("TEST FAILED: instance mismatch on %s: %d vs. %d", path,
100 		    i2c_ctrl_instance(ctrl), i2c_ctrl_instance(alt));
101 		ret = false;
102 	}
103 
104 	if (i2c_ctrl_nprops(ctrl) != i2c_ctrl_nprops(alt)) {
105 		warnx("TEST FAILED: nprops mismatch on %s: %u vs. %u", path,
106 		    i2c_ctrl_nprops(ctrl), i2c_ctrl_nprops(alt));
107 		ret = false;
108 	}
109 
110 	if (ret) {
111 		(void) printf("TEST PASSED: Controller %s has the same "
112 		    "properties when discovered by devi and path\n", path);
113 	}
114 	i2c_ctrl_fini(alt);
115 	return (ret);
116 }
117 
118 /*
119  * Start by finding and opening the controller for i2csim0 and smbussim1 by
120  * their devi based upon controller discovery. Once we have, that come back and
121  * use the path based and compare the results.
122  */
123 static bool
disc_ctrls(i2c_hdl_t * hdl)124 disc_ctrls(i2c_hdl_t *hdl)
125 {
126 	bool ret = true;
127 	ctrl_cb_t ctrl_cb;
128 
129 	(void) memset(&ctrl_cb, 0, sizeof (ctrl_cb));
130 	if (!i2c_ctrl_discover(hdl, disc_ctrl_cb, &ctrl_cb)) {
131 		libi2c_test_warn(hdl, "TEST FAILED: failed to discover "
132 		    "controllers");
133 	}
134 
135 	if (ctrl_cb.cc_fail) {
136 		ret = false;
137 	}
138 
139 	if (ctrl_cb.cc_i2c != NULL) {
140 		if (!disc_ctrl_path(hdl, ctrl_cb.cc_i2c, "i2csim0"))
141 			ret = false;
142 		i2c_ctrl_fini(ctrl_cb.cc_i2c);
143 	} else {
144 		warnx("TEST FAILED: failed to discover i2csim0 controller");
145 		ret = false;
146 	}
147 
148 	if (ctrl_cb.cc_smbus != NULL) {
149 		if (!disc_ctrl_path(hdl, ctrl_cb.cc_smbus, "smbussim1"))
150 			ret = false;
151 		i2c_ctrl_fini(ctrl_cb.cc_smbus);
152 	} else {
153 		warnx("TEST FAILED: failed to discover smbussim1 controller");
154 		ret = false;
155 	}
156 
157 	const char *bad_ctrls[] = { "i2csim0/0", "smbussim1/1", "", "foobar",
158 	    "i2csim0/0/0x20", "0x7777" };
159 	for (size_t i = 0; i < ARRAY_SIZE(bad_ctrls); i++) {
160 		i2c_ctrl_t *ctrl;
161 
162 		if (i2c_ctrl_init_by_path(hdl, bad_ctrls[i], &ctrl)) {
163 			warnx("TEST FAILED: expected path %s to not result "
164 			    "in a controller, but we found one!", bad_ctrls[i]);
165 			ret = false;
166 			i2c_ctrl_fini(ctrl);
167 		} else if (i2c_err(hdl) != I2C_ERR_BAD_CONTROLLER) {
168 			i2c_err_t e = i2c_err(hdl);
169 			warnx("TEST FAILED: bad controller %s resulted in "
170 			    "error %s (0x%x), but expected "
171 			    "I2C_ERR_BAD_CONTROLLER (0x%x)", bad_ctrls[i],
172 			    i2c_errtostr(hdl, e), e, I2C_ERR_BAD_CONTROLLER);
173 			ret = false;
174 		} else {
175 			(void) printf("TEST PASSED: successfully failed to "
176 			    "open bad controller '%s'\n", bad_ctrls[i]);
177 		}
178 	}
179 
180 	return (ret);
181 }
182 
183 /*
184  * Round trip from the devi to a port and get its path and make sure it matches
185  * the discovery path.
186  */
187 static bool
disc_port_cb(i2c_hdl_t * hdl,const i2c_port_disc_t * disc,void * arg)188 disc_port_cb(i2c_hdl_t *hdl, const i2c_port_disc_t *disc, void *arg)
189 {
190 	bool *retp = arg;
191 	i2c_port_t *port;
192 	const char *dpath = i2c_port_disc_path(disc);
193 
194 	if (strstr(dpath, "i2csim") == NULL && strstr(dpath, "smbussim") ==
195 	    NULL) {
196 		return (true);
197 	}
198 
199 	if (!i2c_port_init(hdl, i2c_port_disc_devi(disc), &port)) {
200 		libi2c_test_warn(hdl, "failed to open port by devi %s", dpath);
201 		*retp = false;
202 		return (true);
203 	}
204 
205 	const char *alt_path = i2c_port_path(port);
206 	if (strcmp(alt_path, dpath) == 0) {
207 		(void) printf("TEST PASSED: port %s has same discovery and "
208 		    "i2c_port_t path\n", dpath);
209 	} else {
210 		warnx("TEST FAILED: port %s has different discovery path %s "
211 		    "and i2c_port_t path %s", dpath, dpath, alt_path);
212 		*retp = false;
213 	}
214 
215 	i2c_port_fini(port);
216 	return (true);
217 }
218 
219 static bool
disc_port_path(i2c_hdl_t * hdl,const char * path,uint32_t portno,i2c_port_type_t type)220 disc_port_path(i2c_hdl_t *hdl, const char *path, uint32_t portno,
221     i2c_port_type_t type)
222 {
223 	bool ret = true;
224 	i2c_port_t *port;
225 
226 	if (i2c_port_init_by_path(hdl, path, &port)) {
227 		if (i2c_port_portno(port) != portno) {
228 			warnx("TEST FAILED: port %s has port number 0x%x, "
229 			    "expected 0x%x", path, i2c_port_portno(port),
230 			    portno);
231 			ret = false;
232 		}
233 
234 		if (i2c_port_type(port) != type) {
235 			warnx("TEST FAILED: port %s has type 0x%x, expected "
236 			    "0x%x", path, i2c_port_type(port), type);
237 			ret = false;
238 		}
239 
240 		if (ret) {
241 			(void) printf("TEST PASSED: port %s has expected "
242 			    "properties\n", path);
243 		}
244 		i2c_port_fini(port);
245 	} else {
246 		libi2c_test_warn(hdl, "TEST FAILED: failed to open port %s",
247 		    path);
248 		ret = false;
249 	}
250 
251 	return (ret);
252 }
253 
254 static bool
disc_ports(i2c_hdl_t * hdl)255 disc_ports(i2c_hdl_t *hdl)
256 {
257 	bool ret = true;
258 
259 	if (!i2c_port_discover(hdl, disc_port_cb, &ret)) {
260 		libi2c_test_warn(hdl, "TEST FAILED: failed to walk I2C ports");
261 		ret = false;
262 	}
263 
264 	if (!disc_port_path(hdl, "i2csim0/0", 0, I2C_PORT_TYPE_CTRL)) {
265 		ret = false;
266 	}
267 
268 	if (!disc_port_path(hdl, "smbussim1/1", 1, I2C_PORT_TYPE_CTRL)) {
269 		ret = false;
270 	}
271 
272 	if (!disc_port_path(hdl, "i2csim0/0/0x70/4", 4, I2C_PORT_TYPE_MUX)) {
273 		ret = false;
274 	}
275 
276 	if (!disc_port_path(hdl, "i2csim0/0/0x70/0/0x71/3", 3,
277 	    I2C_PORT_TYPE_MUX)) {
278 		ret = false;
279 	}
280 
281 	const char *bad_ports[] = { "i2csim0", "i2csim0/0/0x20",
282 	    "this-does-not-exist", "/", "", "smbussim1/2",
283 	    "i2csim0/0/0x70/23" };
284 	for (size_t i = 0; i < ARRAY_SIZE(bad_ports); i++) {
285 		i2c_port_t *port;
286 
287 		if (i2c_port_init_by_path(hdl, bad_ports[i], &port)) {
288 			warnx("TEST FAILED: expected path %s to not result "
289 			    "in a port, but we found one!", bad_ports[i]);
290 			ret = false;
291 			i2c_port_fini(port);
292 		} else if (i2c_err(hdl) != I2C_ERR_BAD_PORT) {
293 			i2c_err_t e = i2c_err(hdl);
294 			warnx("TEST FAILED: bad port %s resulted in error "
295 			    "%s (0x%x), but expected I2C_ERR_BAD_PORT (0x%x)",
296 			    bad_ports[i], i2c_errtostr(hdl, e), e,
297 			    I2C_ERR_BAD_PORT);
298 			ret = false;
299 		} else {
300 			(void) printf("TEST PASSED: successfully failed to "
301 			    "open bad port '%s'\n", bad_ports[i]);
302 		}
303 	}
304 
305 	return (ret);
306 }
307 
308 /*
309  * Perform basic verification of the i2csim0 muxes which are named after the
310  * driver.
311  */
312 static bool
disc_mux_cb(i2c_hdl_t * hdl,const i2c_mux_disc_t * disc,void * arg)313 disc_mux_cb(i2c_hdl_t *hdl, const i2c_mux_disc_t *disc, void *arg)
314 {
315 	int *retp = arg;
316 	const char *path = i2c_mux_disc_path(disc);
317 	bool valid = true;
318 
319 	if (strstr(path, "i2csim0/0/0x70") == NULL)
320 		return (true);
321 
322 	if (i2c_mux_disc_nports(disc) != 8) {
323 		warnx("TEST FAILED: mux %s has %u ports, not the expected 8",
324 		    path, i2c_mux_disc_nports(disc));
325 		valid = false;
326 	}
327 
328 	const char *driver = "pca954x";
329 	const char *name = i2c_mux_disc_name(disc);
330 	if (strncmp(driver, name, strlen(driver)) != 0) {
331 		warnx("TEST FAILED: mux %s has name %s that doesn't start "
332 		    "with %s", path, i2c_mux_disc_name(disc), driver);
333 		valid = false;
334 	}
335 
336 	if (valid) {
337 		(void) printf("TEST PASSED: i2csim mux %s has expected "
338 		    "discovery information\n", path);
339 	} else {
340 		*retp = EXIT_FAILURE;
341 	}
342 
343 	return (true);
344 }
345 
346 typedef struct disc_dev {
347 	const char *dd_path;
348 	const char *dd_name;
349 	const char *dd_driver;
350 	uint8_t dd_pri;
351 } disc_dev_t;
352 
353 /*
354  * This is a subset of the devices that we expect to exist that we're going to
355  * validate against.
356  */
357 static const disc_dev_t disc_devtab[] = {
358 	{ "i2csim0/0/0x10", "at24c32", "at24c", 0x10 },
359 	{ "i2csim0/0/0x70", "pca9548", "pca954x", 0x70 },
360 	{ "i2csim0/0/0x70/0/0x71", "pca9548", "pca954x", 0x71 },
361 	{ "i2csim0/0/0x70/2/0x71", "ts5111", "ts511x", 0x71 },
362 	{ "i2csim0/0/0x70/2/0x72", "ts5111", "ts511x", 0x72 },
363 	{ "i2csim0/0/0x70/3/0x71", "ts5111", "ts511x", 0x71 },
364 	{ "i2csim0/0/0x70/3/0x72", "ts5111", "ts511x", 0x72 },
365 	{ "i2csim0/0/0x70/0/0x71/7/0x72", "at24c32", "at24c", 0x72 },
366 };
367 
368 typedef struct {
369 	bool dc_err;
370 	bool dc_found[ARRAY_SIZE(disc_devtab)];
371 } disc_cb_t;
372 
373 static bool
disc_devs_cb(i2c_hdl_t * hdl,const i2c_dev_disc_t * disc,void * arg)374 disc_devs_cb(i2c_hdl_t *hdl, const i2c_dev_disc_t *disc, void *arg)
375 {
376 	const disc_dev_t *dd = NULL;
377 	const char *path = i2c_device_disc_path(disc);
378 	disc_cb_t *cb = arg;
379 	size_t idx;
380 
381 	for (size_t i = 0; i < ARRAY_SIZE(disc_devtab); i++) {
382 		if (strcmp(disc_devtab[i].dd_path, path) == 0) {
383 			dd = &disc_devtab[i];
384 			idx = i;
385 			break;
386 		}
387 	}
388 
389 	if (dd == NULL) {
390 		return (true);
391 	}
392 
393 	if (cb->dc_found[idx]) {
394 		warnx("TEST FAILED: discovered device %s twice", dd->dd_path);
395 		cb->dc_err = true;
396 		return (true);
397 	}
398 
399 	cb->dc_found[idx] = true;
400 	bool valid = true;
401 	i2c_dev_info_t *info;
402 
403 	if (!i2c_device_info_snap(hdl, i2c_device_disc_devi(disc), &info)) {
404 		libi2c_test_warn(hdl, "TEST FAILED: failed to get device info "
405 		    "for %s", dd->dd_path);
406 		cb->dc_err = true;
407 		return (true);
408 	}
409 
410 	if (strcmp(i2c_device_info_path(info), dd->dd_path) != 0) {
411 		warnx("TEST FAILED: device %s has path %s, expected %s",
412 		    dd->dd_path, i2c_device_info_path(info), dd->dd_path);
413 		cb->dc_err = true;
414 		valid = false;
415 	}
416 
417 	if (strcmp(i2c_device_info_name(info), dd->dd_name) != 0) {
418 		warnx("TEST FAILED: device %s has name %s, expected %s",
419 		    dd->dd_path, i2c_device_info_name(info), dd->dd_name);
420 		cb->dc_err = true;
421 		valid = false;
422 	}
423 
424 	if (strcmp(i2c_device_info_driver(info), dd->dd_driver) != 0) {
425 		warnx("TEST FAILED: device %s has driver %s, expected %s",
426 		    dd->dd_path, i2c_device_info_driver(info), dd->dd_driver);
427 		cb->dc_err = true;
428 		valid = false;
429 	}
430 
431 	if (i2c_device_info_addr_source(info, 0) != I2C_ADDR_SOURCE_REG) {
432 		warnx("TEST FAILED: device %s has address source 0x%x, "
433 		    "expected 0x%x", dd->dd_path,
434 		    i2c_device_info_addr_source(info, 0), I2C_ADDR_SOURCE_REG);
435 		cb->dc_err = true;
436 		cb->dc_err = true;
437 		valid = false;
438 	}
439 
440 	const i2c_addr_t *addr = i2c_device_info_addr_primary(info);
441 	if (addr->ia_type != I2C_ADDR_7BIT || addr->ia_addr != dd->dd_pri) {
442 		warnx("TEST FAILED: device %s has address 0x%x,0x%x, "
443 		    "expected 0x%x,0x%x", dd->dd_path, addr->ia_type,
444 		    addr->ia_addr, I2C_ADDR_7BIT, dd->dd_pri);
445 		cb->dc_err = true;
446 		valid = false;
447 	}
448 
449 	if (valid) {
450 		(void) printf("TEST PASSED: device %s information matches "
451 		    "table\n", dd->dd_path);
452 	}
453 	i2c_device_info_free(info);
454 	return (true);
455 }
456 
457 static bool
disc_devs(i2c_hdl_t * hdl)458 disc_devs(i2c_hdl_t *hdl)
459 {
460 	bool ret = true;
461 
462 	disc_cb_t cb;
463 	(void) memset(&cb, 0, sizeof (cb));
464 
465 	if (!i2c_device_discover(hdl, disc_devs_cb, &cb)) {
466 		libi2c_test_warn(hdl, "TEST FAILED: failed to iterate devices");
467 		ret = false;
468 	}
469 
470 	if (cb.dc_err) {
471 		ret = false;
472 	}
473 
474 	for (size_t i = 0; i < ARRAY_SIZE(cb.dc_found); i++) {
475 		if (!cb.dc_found[i]) {
476 			warnx("TEST FAILED: device discovery did not find "
477 			    "%s", disc_devtab[i].dd_path);
478 			ret = false;
479 		}
480 	}
481 
482 	const char *bad_devs[] = { "i2csim0", "", "i2csim0/0", "i2csim0/0/",
483 	    "i2csim0/0/0x702", "i2csim0/0/foobar", "i2csim0/0/0x70/0" };
484 	for (size_t i = 0; i < ARRAY_SIZE(bad_devs); i++) {
485 		i2c_port_t *port;
486 		i2c_dev_info_t *info;
487 
488 		if (i2c_port_dev_init_by_path(hdl, bad_devs[i], false, &port,
489 		    &info)) {
490 			warnx("TEST FAILED: opened bad device path %s",
491 			    bad_devs[i]);
492 			i2c_port_fini(port);
493 			i2c_device_info_free(info);
494 			ret = false;
495 		} else if (i2c_err(hdl) != I2C_ERR_BAD_DEVICE) {
496 			warnx("TEST FAILED: bad device %s resulted in error "
497 			    "%s (0x%x), but expected I2C_ERR_BAD_DEVICE (0x%x)",
498 			    bad_devs[i], i2c_errtostr(hdl, i2c_err(hdl)),
499 			    i2c_err(hdl), I2C_ERR_BAD_DEVICE);
500 			ret = false;
501 		} else {
502 			(void) printf("TEST PASSED: failed to open bad device "
503 			    "%s\n", bad_devs[i]);
504 		}
505 	}
506 
507 	return (ret);
508 }
509 
510 int
main(void)511 main(void)
512 {
513 	int ret = EXIT_SUCCESS;
514 	i2c_hdl_t *hdl = i2c_init();
515 	if (hdl == NULL) {
516 		err(EXIT_FAILURE, "INTERNAL TEST FAILURE: failed to create "
517 		    "libi2c handle");
518 	}
519 
520 	if (!disc_ctrls(hdl)) {
521 		ret = EXIT_FAILURE;
522 	}
523 
524 	if (!disc_ports(hdl)) {
525 		ret = EXIT_FAILURE;
526 	}
527 
528 	if (!i2c_mux_discover(hdl, disc_mux_cb, &ret)) {
529 		libi2c_test_warn(hdl, "TEST FAILURE: failed to iterate "
530 		    "muxes");
531 		ret = EXIT_FAILURE;
532 	}
533 
534 	if (!disc_devs(hdl)) {
535 		ret = EXIT_FAILURE;
536 	}
537 
538 	if (ret == EXIT_SUCCESS) {
539 		(void) printf("All tests passed successfully\n");
540 	}
541 	i2c_fini(hdl);
542 	return (ret);
543 }
544