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 * I2C port discovery and initialization. 18 */ 19 20 #include <stdlib.h> 21 #include <string.h> 22 #include <unistd.h> 23 #include <sys/types.h> 24 #include <sys/stat.h> 25 #include <fcntl.h> 26 27 #include "libi2c_impl.h" 28 29 void 30 i2c_port_discover_fini(i2c_port_iter_t *iter) 31 { 32 if (iter == NULL) { 33 return; 34 } 35 36 free(iter->pi_ports); 37 di_fini(iter->pi_root); 38 free(iter); 39 } 40 41 i2c_iter_t 42 i2c_port_discover_step(i2c_port_iter_t *iter, const i2c_port_disc_t **discp) 43 { 44 for (;;) { 45 if (iter->pi_curport == iter->pi_nports) { 46 return (I2C_ITER_DONE); 47 } 48 49 iter->pi_disc.pd_devi = iter->pi_ports[iter->pi_curport]; 50 iter->pi_curport++; 51 52 if (!i2c_node_to_path(iter->pi_hdl, iter->pi_disc.pd_devi, 53 iter->pi_disc.pd_path, sizeof (iter->pi_disc.pd_path))) { 54 continue; 55 } 56 57 *discp = &iter->pi_disc; 58 return (I2C_ITER_VALID); 59 } 60 } 61 62 /* 63 * We have two nodes that are not the same. They are at the same point in the 64 * tree though. We expect all devices at a given layer in the tree to have the 65 * same type. We start with the node name and if they're the same fall back to 66 * the bus address which must be unique at this point. 67 */ 68 static int 69 i2c_port_sort_node(di_node_t l, di_node_t r) 70 { 71 const char *nl = di_node_name(l); 72 const char *nr = di_node_name(r); 73 int ret; 74 75 if ((ret = strcmp(nl, nr)) == 0) { 76 nl = di_bus_addr(l); 77 nr = di_bus_addr(r); 78 ret = strcmp(nl, nr); 79 } 80 81 return (ret); 82 } 83 84 typedef struct { 85 di_node_t ni_ctrl; 86 di_node_t ni_parent; 87 uint32_t ni_height; 88 } node_info_t; 89 90 static void 91 i2c_port_sort_info(di_node_t dn, node_info_t *info) 92 { 93 (void) memset(info, 0, sizeof (node_info_t)); 94 95 info->ni_parent = di_parent_node(dn); 96 for (;;) { 97 i2c_node_type_t type = i2c_node_type(dn); 98 99 if (type == I2C_NODE_T_CTRL) { 100 info->ni_ctrl = dn; 101 return; 102 } 103 104 info->ni_height++; 105 dn = di_parent_node(dn); 106 if (dn == DI_NODE_NIL) { 107 return; 108 } 109 } 110 } 111 112 static int 113 i2c_port_sort(const void *left, const void *right) 114 { 115 di_node_t l = *(di_node_t *)left; 116 di_node_t r = *(di_node_t *)right; 117 node_info_t li, ri; 118 119 if (l == r) { 120 return (0); 121 } 122 123 /* 124 * We have two nodes that are different points in the tree. We basically 125 * want to ask: 126 * 127 * - What controller do they point to? 128 * - What are the relative heights in the tree? 129 * 130 * If they belong to different controllers, we sort based on the 131 * controller's name. If they belong to the same controller, then we use 132 * the height in the tree. If they have the same height, we then see if 133 * they have the same parent. If they don't, we sort on the parent (like 134 * the controller). If they do, we use their name directly. 135 */ 136 i2c_port_sort_info(l, &li); 137 i2c_port_sort_info(r, &ri); 138 139 if (li.ni_ctrl != ri.ni_ctrl) { 140 return (i2c_port_sort_node(li.ni_ctrl, ri.ni_ctrl)); 141 } 142 143 if (li.ni_height != ri.ni_height) { 144 return (li.ni_height < ri.ni_height ? -1 : 1); 145 } 146 147 if (li.ni_parent != ri.ni_parent) { 148 return (i2c_port_sort_node(li.ni_parent, ri.ni_parent)); 149 } 150 151 return (i2c_port_sort_node(l, r)); 152 } 153 bool 154 i2c_port_discover_init(i2c_hdl_t *hdl, i2c_port_iter_t **iterp) 155 { 156 i2c_port_iter_t *iter; 157 158 if (iterp == NULL) { 159 return (i2c_error(hdl, I2C_ERR_BAD_PTR, 0, "encountered " 160 "invalid i2c_port_iter_t output pointer: %p", iterp)); 161 } 162 163 iter = calloc(1, sizeof (i2c_port_iter_t)); 164 if (iter == NULL) { 165 int e = errno; 166 return (i2c_error(hdl, I2C_ERR_NO_MEM, e, "failed to allocate " 167 "memory for a new i2c_port_iter_t")); 168 } 169 170 iter->pi_root = di_init("/", DINFOCPYALL); 171 if (iter->pi_root == NULL) { 172 int e = errno; 173 i2c_port_discover_fini(iter); 174 return (i2c_error(hdl, I2C_ERR_LIBDEVINFO, e, "failed to " 175 "initialize devinfo snapshot: %s", strerrordesc_np(e))); 176 } 177 iter->pi_done = false; 178 179 /* 180 * We want port discovery to have a reasonably stable and meaningful 181 * order. This is going to be built on top of for devices. We want to 182 * treat this in a bit of a depth-search sense. We don't want to do the 183 * children of one controller's port, then switch to a different 184 * controller, and then come back to say a mux. To facilitate this, note 185 * all the ports first, sort them, and then come back to it. 186 */ 187 for (di_node_t dn = di_drv_first_node(I2C_NEX_DRV, iter->pi_root); 188 dn != NULL; dn = di_drv_next_node(dn)) { 189 if (!i2c_node_is_type(dn, I2C_NODE_T_PORT)) { 190 continue; 191 } 192 193 if (iter->pi_nalloc == iter->pi_nports) { 194 di_node_t *new; 195 uint32_t toalloc = iter->pi_nalloc + 16; 196 197 new = recallocarray(iter->pi_ports, iter->pi_nports, 198 toalloc, sizeof (di_node_t)); 199 if (new == NULL) { 200 int e = errno; 201 i2c_port_discover_fini(iter); 202 return (i2c_error(hdl, I2C_ERR_NO_MEM, e, 203 "failed to allocate memory for a %u " 204 "element di_node_t array", 205 toalloc)); 206 } 207 iter->pi_ports = new; 208 iter->pi_nalloc = toalloc; 209 } 210 211 iter->pi_ports[iter->pi_nports] = dn; 212 iter->pi_nports++; 213 } 214 215 qsort(iter->pi_ports, iter->pi_nports, sizeof (di_node_t), 216 i2c_port_sort); 217 218 *iterp = iter; 219 return (i2c_success(hdl)); 220 } 221 222 bool 223 i2c_port_discover(i2c_hdl_t *hdl, i2c_port_disc_f func, void *arg) 224 { 225 i2c_port_iter_t *iter; 226 const i2c_port_disc_t *disc; 227 i2c_iter_t ret; 228 229 if (func == NULL) { 230 return (i2c_error(hdl, I2C_ERR_BAD_PTR, 0, "encountered " 231 "invalid i2c_port_disc_f function pointer: %p", func)); 232 } 233 234 if (!i2c_port_discover_init(hdl, &iter)) { 235 return (false); 236 } 237 238 while ((ret = i2c_port_discover_step(iter, &disc)) == I2C_ITER_VALID) { 239 if (!func(hdl, disc, arg)) 240 break; 241 } 242 243 i2c_port_discover_fini(iter); 244 if (ret == I2C_ITER_ERROR) { 245 return (false); 246 } 247 248 return (i2c_success(hdl)); 249 } 250 251 di_node_t 252 i2c_port_disc_devi(const i2c_port_disc_t *disc) 253 { 254 return (disc->pd_devi); 255 } 256 257 const char * 258 i2c_port_disc_path(const i2c_port_disc_t *disc) 259 { 260 return (disc->pd_path); 261 } 262 263 void 264 i2c_port_fini(i2c_port_t *port) 265 { 266 if (port == NULL) { 267 return; 268 } 269 270 if (port->port_fd >= 0) { 271 (void) close(port->port_fd); 272 } 273 274 di_devfs_path_free(port->port_minor); 275 free(port->port_name); 276 free(port); 277 } 278 279 bool 280 i2c_port_init(i2c_hdl_t *hdl, di_node_t di, i2c_port_t **portp) 281 { 282 di_minor_t minor; 283 di_node_t parent; 284 i2c_node_type_t ptype; 285 286 if (di == NULL) { 287 return (i2c_error(hdl, I2C_ERR_BAD_PTR, 0, "encountered " 288 "invalid di_node_t: %p", di)); 289 } 290 291 if (portp == NULL) { 292 return (i2c_error(hdl, I2C_ERR_BAD_PTR, 0, "encountered " 293 "invalid i2c_port_t output pointer: %p", portp)); 294 } 295 296 /* 297 * We've verified that we were given an i2cnex instance, make sure this 298 * corresponds to a port. 299 */ 300 if (!i2c_node_is_type(di, I2C_NODE_T_PORT)) { 301 return (i2c_error(hdl, I2C_ERR_BAD_DEVI, 0, "devi %s@%s is " 302 "not an i2c port", di_node_name(di), di_bus_addr(di))); 303 } 304 305 minor = i2c_node_minor(di); 306 if (minor == DI_MINOR_NIL) { 307 return (i2c_error(hdl, I2C_ERR_BAD_DEVI, 0, "devi %s@%s is " 308 "not an i2c port: failed to find port minor", 309 di_node_name(di), di_bus_addr(di))); 310 } 311 312 i2c_port_t *port = calloc(1, sizeof (i2c_port_t)); 313 if (port == NULL) { 314 int e = errno; 315 return (i2c_error(hdl, I2C_ERR_NO_MEM, e, "failed to allocate " 316 "memory for a new i2c_port_t")); 317 } 318 319 port->port_fd = -1; 320 port->port_hdl = hdl; 321 port->port_inst = di_instance(di); 322 port->port_name = strdup(di_bus_addr(di)); 323 if (port->port_name == NULL) { 324 int e = errno; 325 i2c_port_fini(port); 326 return (i2c_error(hdl, I2C_ERR_NO_MEM, e, "failed to duplicate " 327 "port bus address")); 328 } 329 330 parent = di_parent_node(di); 331 ptype = i2c_node_type(parent); 332 if (ptype == I2C_NODE_T_CTRL) { 333 port->port_type = I2C_PORT_TYPE_CTRL; 334 } else if (ptype == I2C_NODE_T_MUX) { 335 port->port_type = I2C_PORT_TYPE_MUX; 336 } else { 337 i2c_port_fini(port); 338 return (i2c_error(hdl, I2C_ERR_BAD_DEVI, 0, "devi %s@%s is not " 339 "an i2c port: found wrong parent", di_node_name(di), 340 di_bus_addr(di))); 341 } 342 343 if (!i2c_node_to_path(hdl, di, port->port_path, 344 sizeof (port->port_path))) { 345 i2c_port_fini(port); 346 return (false); 347 } 348 349 port->port_minor = di_devfs_minor_path(minor); 350 if (port->port_minor == NULL) { 351 int e = errno; 352 i2c_port_fini(port); 353 return (i2c_error(hdl, I2C_ERR_LIBDEVINFO, e, "failed to " 354 "obtain ports's devfs path: %s", strerrordesc_np(e))); 355 } 356 357 port->port_fd = openat(hdl->ih_devfd, port->port_minor + 1, O_RDWR); 358 if (port->port_fd < 0) { 359 int e = errno; 360 (void) i2c_error(hdl, I2C_ERR_OPEN_DEV, e, "failed to open " 361 "device path /devices%s: %s", port->port_minor, 362 strerrordesc_np(e)); 363 i2c_port_fini(port); 364 return (false); 365 } 366 367 if (ioctl(port->port_fd, UI2C_IOCTL_PORT_INFO, &port->port_info) != 0) { 368 int e = errno; 369 i2c_port_fini(port); 370 return (i2c_ioctl_syserror(hdl, e, "port information request")); 371 } 372 373 if (port->port_info.upo_error.i2c_error != I2C_CORE_E_OK) { 374 return (i2c_ioctl_error(hdl, &port->port_info.upo_error, 375 "port information request")); 376 } 377 378 *portp = port; 379 return (i2c_success(hdl)); 380 } 381 382 /* 383 * Initialize a port based on the passed in name. This name may be a top-level 384 * port for the controller or it may be a port on a mux. We end up walking the 385 * path, tokenizing and parsing it to try to find something here. 386 */ 387 bool 388 i2c_port_init_by_path(i2c_hdl_t *hdl, const char *path, i2c_port_t **portp) 389 { 390 i2c_node_type_t type; 391 di_node_t dn, root; 392 393 if (path == NULL) { 394 return (i2c_error(hdl, I2C_ERR_BAD_PTR, 0, "encountered " 395 "invalid i2c port path: %p", path)); 396 } 397 398 if (portp == NULL) { 399 return (i2c_error(hdl, I2C_ERR_BAD_PTR, 0, "encountered " 400 "invalid i2c_port_t output pointer: %p", portp)); 401 } 402 403 root = di_init("/", DINFOCPYALL); 404 if (root == DI_NODE_NIL) { 405 int e = errno; 406 return (i2c_error(hdl, I2C_ERR_LIBDEVINFO, e, "failed to " 407 "initialize devinfo snapshot: %s", strerrordesc_np(e))); 408 } 409 410 if (!i2c_path_parse(hdl, path, root, &dn, &type, I2C_ERR_BAD_PORT)) { 411 di_fini(root); 412 return (false); 413 } 414 415 if (type != I2C_NODE_T_PORT) { 416 di_fini(root); 417 return (i2c_error(hdl, I2C_ERR_BAD_PORT, 0, "parsed I2C path " 418 "%s did not end at a port", path)); 419 } 420 421 bool ret = i2c_port_init(hdl, dn, portp); 422 di_fini(root); 423 return (ret); 424 } 425 426 const char * 427 i2c_port_name(i2c_port_t *port) 428 { 429 return (port->port_name); 430 } 431 432 const char * 433 i2c_port_path(i2c_port_t *port) 434 { 435 return (port->port_path); 436 } 437 438 uint32_t 439 i2c_port_portno(i2c_port_t *port) 440 { 441 return (port->port_info.upo_portno); 442 } 443 444 i2c_port_type_t 445 i2c_port_type(i2c_port_t *port) 446 { 447 return (port->port_type); 448 } 449 450 void 451 i2c_port_map_free(i2c_port_map_t *map) 452 { 453 free(map); 454 } 455 456 bool 457 i2c_port_map_snap(i2c_port_t *port, i2c_port_map_t **mapp) 458 { 459 i2c_port_map_t *map; 460 i2c_hdl_t *hdl = port->port_hdl; 461 462 if (mapp == NULL) { 463 return (i2c_error(hdl, I2C_ERR_BAD_PTR, 0, "encountered " 464 "invalid i2c_port_map_t output pointer: %p", mapp)); 465 } 466 467 map = calloc(1, sizeof (i2c_port_map_t)); 468 if (map == NULL) { 469 int e = errno; 470 return (i2c_error(hdl, I2C_ERR_NO_MEM, e, "failed to allocate " 471 "memory for a new i2c_port_map_t")); 472 } 473 map->pm_hdl = hdl; 474 475 if (ioctl(port->port_fd, UI2C_IOCTL_PORT_INFO, &map->pm_info) != 0) { 476 int e = errno; 477 i2c_port_fini(port); 478 return (i2c_ioctl_syserror(hdl, e, "port maprmation request")); 479 } 480 481 if (map->pm_info.upo_error.i2c_error != I2C_CORE_E_OK) { 482 return (i2c_ioctl_error(hdl, &map->pm_info.upo_error, 483 "port information request")); 484 } 485 486 *mapp = map; 487 return (i2c_success(hdl)); 488 } 489 490 void 491 i2c_port_map_ndevs(const i2c_port_map_t *map, uint32_t *local, uint32_t *ds) 492 { 493 if (local != NULL) { 494 *local = map->pm_info.upo_ndevs; 495 } 496 497 if (ds != NULL) { 498 *ds = map->pm_info.upo_ndevs_ds; 499 } 500 } 501 502 bool 503 i2c_port_map_addr_info(const i2c_port_map_t *map, const i2c_addr_t *addr, 504 uint32_t *devsp, bool *dsp, major_t *majorp) 505 { 506 if (!i2c_addr_validate(map->pm_hdl, addr)) { 507 return (false); 508 } 509 510 if (addr->ia_type != I2C_ADDR_7BIT) { 511 (void) i2c_error(map->pm_hdl, I2C_ERR_UNSUP_ADDR_TYPE, 0, 512 "port map information is not available for this address " 513 "type"); 514 return (false); 515 } 516 517 const ui2c_port_addr_info_t *info = &map->pm_info.upo_7b[addr->ia_addr]; 518 519 if (devsp != NULL) { 520 *devsp = info->pai_ndevs; 521 } 522 523 if (dsp != NULL) { 524 *dsp = info->pai_downstream; 525 } 526 527 if (majorp != NULL) { 528 *majorp = info->pai_major; 529 } 530 531 532 return (i2c_success(map->pm_hdl)); 533 } 534