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 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 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 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 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 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 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 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 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 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 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