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
i2c_port_discover_fini(i2c_port_iter_t * iter)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
i2c_port_discover_step(i2c_port_iter_t * iter,const i2c_port_disc_t ** discp)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
i2c_port_sort_node(di_node_t l,di_node_t r)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
i2c_port_sort_info(di_node_t dn,node_info_t * info)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
i2c_port_sort(const void * left,const void * right)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
i2c_port_discover_init(i2c_hdl_t * hdl,i2c_port_iter_t ** iterp)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
i2c_port_discover(i2c_hdl_t * hdl,i2c_port_disc_f func,void * arg)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
i2c_port_disc_devi(const i2c_port_disc_t * disc)252 i2c_port_disc_devi(const i2c_port_disc_t *disc)
253 {
254 return (disc->pd_devi);
255 }
256
257 const char *
i2c_port_disc_path(const i2c_port_disc_t * disc)258 i2c_port_disc_path(const i2c_port_disc_t *disc)
259 {
260 return (disc->pd_path);
261 }
262
263 void
i2c_port_fini(i2c_port_t * port)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
i2c_port_init(i2c_hdl_t * hdl,di_node_t di,i2c_port_t ** portp)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
i2c_port_init_by_path(i2c_hdl_t * hdl,const char * path,i2c_port_t ** portp)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 *
i2c_port_name(i2c_port_t * port)427 i2c_port_name(i2c_port_t *port)
428 {
429 return (port->port_name);
430 }
431
432 const char *
i2c_port_path(i2c_port_t * port)433 i2c_port_path(i2c_port_t *port)
434 {
435 return (port->port_path);
436 }
437
438 uint32_t
i2c_port_portno(i2c_port_t * port)439 i2c_port_portno(i2c_port_t *port)
440 {
441 return (port->port_info.upo_portno);
442 }
443
444 i2c_port_type_t
i2c_port_type(i2c_port_t * port)445 i2c_port_type(i2c_port_t *port)
446 {
447 return (port->port_type);
448 }
449
450 void
i2c_port_map_free(i2c_port_map_t * map)451 i2c_port_map_free(i2c_port_map_t *map)
452 {
453 free(map);
454 }
455
456 bool
i2c_port_map_snap(i2c_port_t * port,i2c_port_map_t ** mapp)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
i2c_port_map_ndevs(const i2c_port_map_t * map,uint32_t * local,uint32_t * ds)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
i2c_port_map_addr_info(const i2c_port_map_t * map,const i2c_addr_t * addr,uint32_t * devsp,bool * dsp,major_t * majorp)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