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", ®); 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", ®); 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