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