xref: /illumos-gate/usr/src/test/i2c-tests/tests/ioctl/i2c_ioctl_util.c (revision e8d3efa1c56e5f2b5368600a2baeb7b1d54a07f8)
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 2026 Oxide Computer Company
14  */
15 
16 /*
17  * Misc. utility functions for our ioctl tests.
18  */
19 
20 #include <libdevinfo.h>
21 #include <err.h>
22 #include <fts.h>
23 #include <limits.h>
24 #include <stdio.h>
25 #include <string.h>
26 #include <sys/stat.h>
27 #include <fcntl.h>
28 #include <sys/sysmacros.h>
29 
30 #include <sys/i2c/ioctl.h>
31 
32 #include "i2c_ioctl_util.h"
33 
34 static int i2c_test_devfd = -1;
35 const char *i2c_sim_dipath = "/pseudo/i2csim@0";
36 
37 /*
38  * Different forms of invalid I2C addresses.
39  */
40 const bad_addr_t bad_addrs[] = {
41 	{ I2C_ADDR_7BIT, 0x00, I2C_CORE_E_ADDR_RSVD },
42 	{ I2C_ADDR_7BIT, 0x7f, I2C_CORE_E_ADDR_RSVD },
43 	{ I2C_ADDR_7BIT, 0x80, I2C_CORE_E_BAD_ADDR },
44 	{ I2C_ADDR_7BIT, 0x7777, I2C_CORE_E_BAD_ADDR },
45 	{ I2C_ADDR_7BIT, INT16_MAX, I2C_CORE_E_BAD_ADDR },
46 	{ I2C_ADDR_7BIT, UINT16_MAX, I2C_CORE_E_BAD_ADDR },
47 	{ I2C_ADDR_10BIT, 0x42, I2C_CORE_E_UNSUP_ADDR_TYPE },
48 	{ I2C_ADDR_10BIT, 0x3ff, I2C_CORE_E_UNSUP_ADDR_TYPE },
49 	{ I2C_ADDR_10BIT, 0x7ff, I2C_CORE_E_BAD_ADDR },
50 	{ I2C_ADDR_10BIT, 0x7777, I2C_CORE_E_BAD_ADDR },
51 	{ 0x7777, 0x42, I2C_CORE_E_BAD_ADDR_TYPE },
52 	{ 0x2, 0x23, I2C_CORE_E_BAD_ADDR_TYPE },
53 	{ INT16_MAX, UINT16_MAX, I2C_CORE_E_BAD_ADDR_TYPE },
54 };
55 
56 const size_t nbad_addrs = ARRAY_SIZE(bad_addrs);
57 
58 /*
59  * The children of an i2c nexus are constructed on demand. A devfs lookup or
60  * readdir causes the nexus to configure the nodes at that level, and system
61  * observers such as devfsadmd asynchronously create (and remove) them in
62  * response to fabric changes. A devinfo snapshot performs no configuration
63  * itself, so the set of nodes it captures would otherwise depend on a race
64  * with those observers. Explicitly walk the simulator's /devices subtree
65  * before taking a snapshot to synchronously construct each level of the tree.
66  */
67 static void
68 i2c_ioctl_test_config(void)
69 {
70 	char path[PATH_MAX];
71 	char *const paths[] = { path, NULL };
72 	FTS *fts;
73 
74 	(void) snprintf(path, sizeof (path), "/devices%s", i2c_sim_dipath);
75 
76 	fts = fts_open(paths, FTS_PHYSICAL | FTS_NOCHDIR, NULL);
77 	if (fts == NULL)
78 		return;
79 	while (fts_read(fts) != NULL)
80 		;
81 	(void) fts_close(fts);
82 }
83 
84 static di_node_t
85 i2c_ioctl_test_init_devi(void)
86 {
87 	di_node_t root;
88 
89 	if (i2c_test_devfd == -1) {
90 		i2c_test_devfd = open("/devices", O_RDONLY | O_DIRECTORY);
91 		if (i2c_test_devfd < 0) {
92 			err(EXIT_FAILURE, "INTERNAL TEST FAILURE: failed "
93 			    "to open /devices");
94 		}
95 	}
96 
97 	i2c_ioctl_test_config();
98 
99 	/*
100 	 * We do not cache this as a global as some tests want to be able to
101 	 * find devices after they've created and made changes to the set of
102 	 * devices present. This makes some tests more expensive, but others
103 	 * correct!
104 	 */
105 	root = di_init(i2c_sim_dipath, DINFOCPYALL);
106 	if (root == DI_NODE_NIL) {
107 		err(EXIT_FAILURE, "INTERNAL TEST FAILURE: failed to "
108 		    "initialize devinfo library");
109 	}
110 
111 	return (root);
112 }
113 
114 static i2c_dev_t
115 i2c_node_type(di_node_t dn)
116 {
117 	const char *drv = di_driver_name(dn);
118 	char *strs;
119 	int nstrs;
120 
121 	nstrs = di_prop_lookup_strings(DDI_DEV_T_ANY, dn, "device_type",
122 	    &strs);
123 	if (nstrs == 1 && strcmp(strs, "i2c") == 0) {
124 		return (I2C_D_DEVICE);
125 	}
126 
127 	if (drv == NULL || strcmp(drv, "i2cnex") != 0) {
128 		return (I2C_D_OTHER);
129 	}
130 
131 	nstrs = di_prop_lookup_strings(DDI_DEV_T_ANY, dn, I2C_NEXUS_TYPE_PROP,
132 	    &strs);
133 	if (nstrs != 1) {
134 		return (I2C_D_OTHER);
135 	}
136 
137 	if (strcmp(strs, I2C_NEXUS_TYPE_PORT) == 0) {
138 		return (I2C_D_PORT);
139 	} else if (strcmp(strs, I2C_NEXUS_TYPE_CTRL) == 0) {
140 		return (I2C_D_CTRL);
141 	} else if (strcmp(strs, I2C_NEXUS_TYPE_MUX) == 0) {
142 		return (I2C_D_MUX);
143 	}
144 
145 	return (I2C_D_OTHER);
146 }
147 
148 static di_node_t
149 i2c_ioctl_test_find_by_addr(di_node_t dev, i2c_dev_t type, const char *targ)
150 {
151 	for (di_node_t dn = di_child_node(dev); dn != NULL;
152 	    dn = di_sibling_node(dn)) {
153 		if (i2c_node_type(dn) != type)
154 			continue;
155 
156 		const char *addr = di_bus_addr(dn);
157 		if (addr != NULL && strcmp(addr, targ) == 0) {
158 			return (dn);
159 		}
160 	}
161 	return (DI_NODE_NIL);
162 }
163 
164 /*
165  * muxes use the address of their driver instance. This is variable, so we can't
166  * rely on it; however, a device will only have a single mux so we take the
167  * first one we find.
168  */
169 static di_node_t
170 i2c_ioctl_test_find_mux(di_node_t dev)
171 {
172 	for (di_node_t dn = di_child_node(dev); dn != NULL;
173 	    dn = di_sibling_node(dn)) {
174 		if (i2c_node_type(dn) == I2C_D_MUX) {
175 			return (dn);
176 		}
177 	}
178 	return (DI_NODE_NIL);
179 }
180 
181 static di_node_t
182 i2c_ioctl_test_find_device(di_node_t dev, const char *addrstr)
183 {
184 	uint16_t addr;
185 	const char *err;
186 
187 	addr = (uint16_t)strtonumx(addrstr, 0, 0x7f, &err, 0);
188 	if (err != NULL) {
189 		errx(EXIT_FAILURE, "failed to parse %s as a 7-bit address: "
190 		    "value is %s", addrstr, err);
191 	}
192 
193 	for (di_node_t dn = di_child_node(dev); dn != NULL;
194 	    dn = di_sibling_node(dn)) {
195 		if (i2c_node_type(dn) != I2C_D_DEVICE)
196 			continue;
197 
198 		int *reg;
199 		int nreg = di_prop_lookup_ints(DDI_DEV_T_ANY, dn, "reg", &reg);
200 		if (nreg == 0 || (nreg % 2) != 0)
201 			continue;
202 
203 		if (reg[0] == I2C_ADDR_7BIT && reg[1] == addr) {
204 			return (dn);
205 		}
206 	}
207 	return (DI_NODE_NIL);
208 }
209 
210 static di_minor_t
211 i2c_ioctl_test_device_minor(di_node_t dn)
212 {
213 	int nreg, *reg;
214 	char name[32];
215 
216 	nreg = di_prop_lookup_ints(DDI_DEV_T_ANY, dn, "reg", &reg);
217 	if (nreg == 0 || (nreg % 2) != 0) {
218 		return (DI_MINOR_NIL);
219 	}
220 
221 	(void) snprintf(name, sizeof (name), "%x,%x", reg[0], reg[1]);
222 	dn = di_parent_node(dn);
223 	if (i2c_node_type(dn) != I2C_D_PORT) {
224 		return (DI_MINOR_NIL);
225 	}
226 
227 	for (di_minor_t m = di_minor_next(dn, DI_MINOR_NIL); m != DI_MINOR_NIL;
228 	    m = di_minor_next(dn, m)) {
229 		if (strcmp(di_minor_nodetype(m), DDI_NT_I2C_DEV) == 0 &&
230 		    strcmp(di_minor_name(m), name) == 0) {
231 			return (m);
232 		}
233 	}
234 
235 	return (DI_MINOR_NIL);
236 
237 	return (DI_MINOR_NIL);
238 }
239 
240 static di_minor_t
241 i2c_ioctl_test_minor(di_node_t dn, i2c_dev_t type)
242 {
243 	const char *nt;
244 
245 	switch (type) {
246 	case I2C_D_CTRL:
247 		nt = DDI_NT_I2C_CTRL;
248 		break;
249 	case I2C_D_PORT:
250 		nt = DDI_NT_I2C_PORT;
251 		break;
252 	case I2C_D_MUX:
253 		nt = DDI_NT_I2C_MUX;
254 		break;
255 	case I2C_D_DEVICE:
256 		return (i2c_ioctl_test_device_minor(dn));
257 	default:
258 		return (DI_MINOR_NIL);
259 	}
260 
261 	for (di_minor_t m = di_minor_next(dn, DI_MINOR_NIL); m != DI_MINOR_NIL;
262 	    m = di_minor_next(dn, m)) {
263 		if (strcmp(di_minor_nodetype(m), nt) == 0) {
264 			return (m);
265 		}
266 	}
267 
268 	return (DI_MINOR_NIL);
269 }
270 
271 /*
272  * This is a variant of our i2c path that explicitly includes the mux nodes with
273  * a string "mux" to make it easier for us to open and refer to it as a device
274  * node. As userland doesn't directly interact with these normally, these aren't
275  * in the path.
276  */
277 int
278 i2c_ioctl_test_get_fd(i2c_dev_t dev, const char *path, int flags)
279 {
280 	char *dup = strdup(path), *lasts;
281 	if (dup == NULL) {
282 		err(EXIT_FAILURE, "failed to allocate memory to duplicate path "
283 		    "%s", path);
284 	}
285 
286 	i2c_dev_t type = I2C_D_OTHER;
287 	di_node_t root = i2c_ioctl_test_init_devi();
288 	di_node_t devi = root;
289 	for (const char *ent = strtok_r(dup, "/", &lasts); ent != NULL;
290 	    ent = strtok_r(NULL, "/", &lasts)) {
291 		switch (type) {
292 		case I2C_D_OTHER:
293 			/*
294 			 * We're at the root of the i2csim0 tree. There should
295 			 * be an i2cnex child whose address matches our
296 			 * controller name.
297 			 */
298 			devi = i2c_ioctl_test_find_by_addr(devi, I2C_D_CTRL,
299 			    ent);
300 			if (devi == NULL) {
301 				errx(EXIT_FAILURE, "INTERNAL TEST FAILURE: "
302 				    "failed to find controller %s as part of "
303 				    "path %s", ent, path);
304 			}
305 			type = I2C_D_CTRL;
306 			break;
307 		case I2C_D_CTRL:
308 		case I2C_D_MUX:
309 			devi = i2c_ioctl_test_find_by_addr(devi, I2C_D_PORT,
310 			    ent);
311 			if (devi == NULL) {
312 				errx(EXIT_FAILURE, "INTERNAL TEST FAILURE: "
313 				    "failed to find port %s as part of "
314 				    "path %s", ent, path);
315 			}
316 			type = I2C_D_PORT;
317 			break;
318 		case I2C_D_PORT:
319 			devi = i2c_ioctl_test_find_device(devi, ent);
320 			if (devi == NULL) {
321 				errx(EXIT_FAILURE, "INTERNAL TEST FAILURE: "
322 				    "failed to find device %s as part of "
323 				    "path %s", ent, path);
324 			}
325 			type = I2C_D_DEVICE;
326 			break;
327 		case I2C_D_DEVICE:
328 			if (strcmp(ent, "mux") != 0) {
329 				errx(EXIT_FAILURE, "INTERNAL TEST FAILURE: "
330 				    "device cannot have something other than "
331 				    "mux under them, found %s", ent);
332 			}
333 
334 			devi = i2c_ioctl_test_find_mux(devi);
335 			if (devi == NULL) {
336 				errx(EXIT_FAILURE, "INTERNAL TEST FAILURE: "
337 				    "failed to find mux as part of path %s",
338 				    path);
339 			}
340 			type = I2C_D_MUX;
341 			break;
342 		}
343 	}
344 
345 	if (type != dev) {
346 		errx(EXIT_FAILURE, "INTERNAL TEST FAILURE: path %s ended at "
347 		    "type 0x%x, but expected 0x%x", path, type, dev);
348 	}
349 	free(dup);
350 
351 	di_minor_t minor = i2c_ioctl_test_minor(devi, type);
352 	if (minor == DI_MINOR_NIL) {
353 		errx(EXIT_FAILURE, "INTERNAL TEST FAILURE: failed to find "
354 		    "minor corresponding to path %s", path);
355 	}
356 
357 	char *minor_path = di_devfs_minor_path(minor);
358 	if (minor_path == NULL) {
359 		err(EXIT_FAILURE, "INTERNAL TEST FAILURE: failed to get minor "
360 		    "path for %s", path);
361 	}
362 
363 	int fd = openat(i2c_test_devfd, minor_path + 1, flags);
364 	if (fd < 0) {
365 		err(EXIT_FAILURE, "INTERNAL TEST FAILURE: failed to open "
366 		    "/devices%s", minor_path);
367 	}
368 	di_devfs_path_free(minor_path);
369 	di_fini(root);
370 	return (fd);
371 }
372