xref: /illumos-gate/usr/src/lib/libi2c/common/libi2c_port.c (revision 32002227574cf0a435dc03de622191ca53724f0a)
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