1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (c) 2000 Michael Smith 5 * Copyright (c) 2000 BSDi 6 * All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 */ 29 30 #include <sys/cdefs.h> 31 __FBSDID("$FreeBSD$"); 32 33 /* 34 * An interface to the FreeBSD kernel's bus/device information interface. 35 * 36 * This interface is implemented with the 37 * 38 * hw.bus 39 * hw.bus.devices 40 * hw.bus.rman 41 * 42 * sysctls. The interface is not meant for general user application 43 * consumption. 44 * 45 * Device information is obtained by scanning a linear list of all devices 46 * maintained by the kernel. The actual device structure pointers are 47 * handed out as opaque handles in order to allow reconstruction of the 48 * logical toplogy in user space. 49 * 50 * Resource information is obtained by scanning the kernel's resource 51 * managers and fetching their contents. Ownership of resources is 52 * tracked using the device's structure pointer again as a handle. 53 * 54 * In order to ensure coherency of the library's picture of the kernel, 55 * a generation count is maintained by the kernel. The initial generation 56 * count is obtained (along with the interface version) from the hw.bus 57 * sysctl, and must be passed in with every request. If the generation 58 * number supplied by the library does not match the kernel's current 59 * generation number, the request is failed and the library must discard 60 * the data it has received and rescan. 61 * 62 * The information obtained from the kernel is exported to consumers of 63 * this library through a variety of interfaces. 64 */ 65 66 #include <sys/param.h> 67 #include <sys/types.h> 68 #include <sys/sysctl.h> 69 #include <err.h> 70 #include <errno.h> 71 #include <stdio.h> 72 #include <stdlib.h> 73 #include <string.h> 74 #include "devinfo.h" 75 #include "devinfo_var.h" 76 77 static int devinfo_init_devices(int generation); 78 static int devinfo_init_resources(int generation); 79 80 TAILQ_HEAD(,devinfo_i_dev) devinfo_dev; 81 TAILQ_HEAD(,devinfo_i_rman) devinfo_rman; 82 TAILQ_HEAD(,devinfo_i_res) devinfo_res; 83 84 static int devinfo_initted = 0; 85 static int devinfo_generation = 0; 86 87 #if 0 88 # define debug(...) do { \ 89 fprintf(stderr, "%s:", __func__); \ 90 fprintf(stderr, __VA_ARGS__); \ 91 fprintf(stderr, "\n"); \ 92 } while (0) 93 #else 94 # define debug(...) 95 #endif 96 97 /* 98 * Initialise our local database with the contents of the kernel's 99 * tables. 100 */ 101 int 102 devinfo_init(void) 103 { 104 struct u_businfo ubus; 105 size_t ub_size; 106 int error, retries; 107 108 if (!devinfo_initted) { 109 TAILQ_INIT(&devinfo_dev); 110 TAILQ_INIT(&devinfo_rman); 111 TAILQ_INIT(&devinfo_res); 112 } 113 114 /* 115 * Get the generation count and interface version, verify that we 116 * are compatible with the kernel. 117 */ 118 for (retries = 0; retries < 10; retries++) { 119 debug("get interface version"); 120 ub_size = sizeof(ubus); 121 if (sysctlbyname("hw.bus.info", &ubus, 122 &ub_size, NULL, 0) != 0) { 123 warn("sysctlbyname(\"hw.bus.info\", ...) failed"); 124 return(EINVAL); 125 } 126 if ((ub_size != sizeof(ubus)) || 127 (ubus.ub_version != BUS_USER_VERSION)) { 128 warnx("kernel bus interface version mismatch: kernel %d expected %d", 129 ubus.ub_version, BUS_USER_VERSION); 130 return(EINVAL); 131 } 132 debug("generation count is %d", ubus.ub_generation); 133 134 /* 135 * Don't rescan if the generation count hasn't changed. 136 */ 137 if (ubus.ub_generation == devinfo_generation) 138 return(0); 139 140 /* 141 * Generation count changed, rescan 142 */ 143 devinfo_free(); 144 devinfo_initted = 0; 145 devinfo_generation = 0; 146 147 if ((error = devinfo_init_devices(ubus.ub_generation)) != 0) { 148 devinfo_free(); 149 if (error == EINVAL) 150 continue; 151 break; 152 } 153 if ((error = devinfo_init_resources(ubus.ub_generation)) != 0) { 154 devinfo_free(); 155 if (error == EINVAL) 156 continue; 157 break; 158 } 159 devinfo_initted = 1; 160 devinfo_generation = ubus.ub_generation; 161 return(0); 162 } 163 debug("scan failed after %d retries", retries); 164 errno = error; 165 return(1); 166 } 167 168 static int 169 devinfo_init_devices(int generation) 170 { 171 struct u_device udev; 172 struct devinfo_i_dev *dd; 173 int dev_idx; 174 int dev_ptr; 175 int name2oid[2]; 176 int oid[CTL_MAXNAME + 12]; 177 size_t oidlen, rlen; 178 char *name, *walker, *ep; 179 int error; 180 181 /* 182 * Find the OID for the rman interface node. 183 * This is just the usual evil, undocumented sysctl juju. 184 */ 185 name2oid[0] = 0; 186 name2oid[1] = 3; 187 oidlen = sizeof(oid); 188 name = "hw.bus.devices"; 189 error = sysctl(name2oid, 2, oid, &oidlen, name, strlen(name)); 190 if (error < 0) { 191 warnx("can't find hw.bus.devices sysctl node"); 192 return(ENOENT); 193 } 194 oidlen /= sizeof(int); 195 if (oidlen > CTL_MAXNAME) { 196 warnx("hw.bus.devices oid is too large"); 197 return(EINVAL); 198 } 199 oid[oidlen++] = generation; 200 dev_ptr = oidlen++; 201 202 /* 203 * Scan devices. 204 * 205 * Stop after a fairly insane number to avoid death in the case 206 * of kernel corruption. 207 */ 208 for (dev_idx = 0; dev_idx < 10000; dev_idx++) { 209 210 /* 211 * Get the device information. 212 */ 213 oid[dev_ptr] = dev_idx; 214 rlen = sizeof(udev); 215 error = sysctl(oid, oidlen, &udev, &rlen, NULL, 0); 216 if (error < 0) { 217 if (errno == ENOENT) /* end of list */ 218 break; 219 if (errno != EINVAL) /* gen count skip, restart */ 220 warn("sysctl hw.bus.devices.%d", dev_idx); 221 return(errno); 222 } 223 if (rlen != sizeof(udev)) { 224 warnx("sysctl returned wrong data %zd bytes instead of %zd", 225 rlen, sizeof(udev)); 226 return (EINVAL); 227 } 228 if ((dd = malloc(sizeof(*dd))) == NULL) 229 return(ENOMEM); 230 dd->dd_dev.dd_handle = udev.dv_handle; 231 dd->dd_dev.dd_parent = udev.dv_parent; 232 dd->dd_dev.dd_devflags = udev.dv_devflags; 233 dd->dd_dev.dd_flags = udev.dv_flags; 234 dd->dd_dev.dd_state = udev.dv_state; 235 236 walker = udev.dv_fields; 237 ep = walker + sizeof(udev.dv_fields); 238 dd->dd_name = NULL; 239 dd->dd_desc = NULL; 240 dd->dd_drivername = NULL; 241 dd->dd_pnpinfo = NULL; 242 dd->dd_location = NULL; 243 #define UNPACK(x) \ 244 dd->dd_dev.x = dd->x = strdup(walker); \ 245 if (dd->x == NULL) \ 246 return(ENOMEM); \ 247 if (walker + strnlen(walker, ep - walker) >= ep) \ 248 return(EINVAL); \ 249 walker += strlen(walker) + 1; 250 251 UNPACK(dd_name); 252 UNPACK(dd_desc); 253 UNPACK(dd_drivername); 254 UNPACK(dd_pnpinfo); 255 UNPACK(dd_location); 256 #undef UNPACK 257 TAILQ_INSERT_TAIL(&devinfo_dev, dd, dd_link); 258 } 259 debug("fetched %d devices", dev_idx); 260 return(0); 261 } 262 263 static int 264 devinfo_init_resources(int generation) 265 { 266 struct u_rman urman; 267 struct devinfo_i_rman *dm; 268 struct u_resource ures; 269 struct devinfo_i_res *dr; 270 int rman_idx, res_idx; 271 int rman_ptr, res_ptr; 272 int name2oid[2]; 273 int oid[CTL_MAXNAME + 12]; 274 size_t oidlen, rlen; 275 char *name; 276 int error; 277 278 /* 279 * Find the OID for the rman interface node. 280 * This is just the usual evil, undocumented sysctl juju. 281 */ 282 name2oid[0] = 0; 283 name2oid[1] = 3; 284 oidlen = sizeof(oid); 285 name = "hw.bus.rman"; 286 error = sysctl(name2oid, 2, oid, &oidlen, name, strlen(name)); 287 if (error < 0) { 288 warnx("can't find hw.bus.rman sysctl node"); 289 return(ENOENT); 290 } 291 oidlen /= sizeof(int); 292 if (oidlen > CTL_MAXNAME) { 293 warnx("hw.bus.rman oid is too large"); 294 return(EINVAL); 295 } 296 oid[oidlen++] = generation; 297 rman_ptr = oidlen++; 298 res_ptr = oidlen++; 299 300 /* 301 * Scan resource managers. 302 * 303 * Stop after a fairly insane number to avoid death in the case 304 * of kernel corruption. 305 */ 306 for (rman_idx = 0; rman_idx < 255; rman_idx++) { 307 308 /* 309 * Get the resource manager information. 310 */ 311 oid[rman_ptr] = rman_idx; 312 oid[res_ptr] = -1; 313 rlen = sizeof(urman); 314 error = sysctl(oid, oidlen, &urman, &rlen, NULL, 0); 315 if (error < 0) { 316 if (errno == ENOENT) /* end of list */ 317 break; 318 if (errno != EINVAL) /* gen count skip, restart */ 319 warn("sysctl hw.bus.rman.%d", rman_idx); 320 return(errno); 321 } 322 if ((dm = malloc(sizeof(*dm))) == NULL) 323 return(ENOMEM); 324 dm->dm_rman.dm_handle = urman.rm_handle; 325 dm->dm_rman.dm_start = urman.rm_start; 326 dm->dm_rman.dm_size = urman.rm_size; 327 snprintf(dm->dm_desc, DEVINFO_STRLEN, "%s", urman.rm_descr); 328 dm->dm_rman.dm_desc = &dm->dm_desc[0]; 329 TAILQ_INSERT_TAIL(&devinfo_rman, dm, dm_link); 330 331 /* 332 * Scan resources on this resource manager. 333 * 334 * Stop after a fairly insane number to avoid death in the case 335 * of kernel corruption. 336 */ 337 for (res_idx = 0; res_idx < 1000; res_idx++) { 338 /* 339 * Get the resource information. 340 */ 341 oid[res_ptr] = res_idx; 342 rlen = sizeof(ures); 343 error = sysctl(oid, oidlen, &ures, &rlen, NULL, 0); 344 if (error < 0) { 345 if (errno == ENOENT) /* end of list */ 346 break; 347 if (errno != EINVAL) /* gen count skip */ 348 warn("sysctl hw.bus.rman.%d.%d", 349 rman_idx, res_idx); 350 return(errno); 351 } 352 if ((dr = malloc(sizeof(*dr))) == NULL) 353 return(ENOMEM); 354 dr->dr_res.dr_handle = ures.r_handle; 355 dr->dr_res.dr_rman = ures.r_parent; 356 dr->dr_res.dr_device = ures.r_device; 357 dr->dr_res.dr_start = ures.r_start; 358 dr->dr_res.dr_size = ures.r_size; 359 TAILQ_INSERT_TAIL(&devinfo_res, dr, dr_link); 360 } 361 debug("fetched %d resources", res_idx); 362 } 363 debug("scanned %d resource managers", rman_idx); 364 return(0); 365 } 366 367 /* 368 * Free the list contents. 369 */ 370 void 371 devinfo_free(void) 372 { 373 struct devinfo_i_dev *dd; 374 struct devinfo_i_rman *dm; 375 struct devinfo_i_res *dr; 376 377 while ((dd = TAILQ_FIRST(&devinfo_dev)) != NULL) { 378 TAILQ_REMOVE(&devinfo_dev, dd, dd_link); 379 free(dd->dd_name); 380 free(dd->dd_desc); 381 free(dd->dd_drivername); 382 free(dd->dd_pnpinfo); 383 free(dd->dd_location); 384 free(dd); 385 } 386 while ((dm = TAILQ_FIRST(&devinfo_rman)) != NULL) { 387 TAILQ_REMOVE(&devinfo_rman, dm, dm_link); 388 free(dm); 389 } 390 while ((dr = TAILQ_FIRST(&devinfo_res)) != NULL) { 391 TAILQ_REMOVE(&devinfo_res, dr, dr_link); 392 free(dr); 393 } 394 devinfo_initted = 0; 395 devinfo_generation = 0; 396 } 397 398 /* 399 * Find a device by its handle. 400 */ 401 struct devinfo_dev * 402 devinfo_handle_to_device(devinfo_handle_t handle) 403 { 404 struct devinfo_i_dev *dd; 405 406 /* 407 * Find the root device, whose parent is NULL 408 */ 409 if (handle == DEVINFO_ROOT_DEVICE) { 410 TAILQ_FOREACH(dd, &devinfo_dev, dd_link) 411 if (dd->dd_dev.dd_parent == DEVINFO_ROOT_DEVICE) 412 return(&dd->dd_dev); 413 return(NULL); 414 } 415 416 /* 417 * Scan for the device 418 */ 419 TAILQ_FOREACH(dd, &devinfo_dev, dd_link) 420 if (dd->dd_dev.dd_handle == handle) 421 return(&dd->dd_dev); 422 return(NULL); 423 } 424 425 /* 426 * Find a resource by its handle. 427 */ 428 struct devinfo_res * 429 devinfo_handle_to_resource(devinfo_handle_t handle) 430 { 431 struct devinfo_i_res *dr; 432 433 TAILQ_FOREACH(dr, &devinfo_res, dr_link) 434 if (dr->dr_res.dr_handle == handle) 435 return(&dr->dr_res); 436 return(NULL); 437 } 438 439 /* 440 * Find a resource manager by its handle. 441 */ 442 struct devinfo_rman * 443 devinfo_handle_to_rman(devinfo_handle_t handle) 444 { 445 struct devinfo_i_rman *dm; 446 447 TAILQ_FOREACH(dm, &devinfo_rman, dm_link) 448 if (dm->dm_rman.dm_handle == handle) 449 return(&dm->dm_rman); 450 return(NULL); 451 } 452 453 /* 454 * Iterate over the children of a device, calling (fn) on each. If 455 * (fn) returns nonzero, abort the scan and return. 456 */ 457 int 458 devinfo_foreach_device_child(struct devinfo_dev *parent, 459 int (* fn)(struct devinfo_dev *child, void *arg), 460 void *arg) 461 { 462 struct devinfo_i_dev *dd; 463 int error; 464 465 TAILQ_FOREACH(dd, &devinfo_dev, dd_link) 466 if (dd->dd_dev.dd_parent == parent->dd_handle) 467 if ((error = fn(&dd->dd_dev, arg)) != 0) 468 return(error); 469 return(0); 470 } 471 472 /* 473 * Iterate over all the resources owned by a device, calling (fn) on each. 474 * If (fn) returns nonzero, abort the scan and return. 475 */ 476 int 477 devinfo_foreach_device_resource(struct devinfo_dev *dev, 478 int (* fn)(struct devinfo_dev *dev, struct devinfo_res *res, void *arg), 479 void *arg) 480 { 481 struct devinfo_i_res *dr; 482 int error; 483 484 TAILQ_FOREACH(dr, &devinfo_res, dr_link) 485 if (dr->dr_res.dr_device == dev->dd_handle) 486 if ((error = fn(dev, &dr->dr_res, arg)) != 0) 487 return(error); 488 return(0); 489 } 490 491 /* 492 * Iterate over all the resources owned by a resource manager, calling (fn) 493 * on each. If (fn) returns nonzero, abort the scan and return. 494 */ 495 extern int 496 devinfo_foreach_rman_resource(struct devinfo_rman *rman, 497 int (* fn)(struct devinfo_res *res, void *arg), 498 void *arg) 499 { 500 struct devinfo_i_res *dr; 501 int error; 502 503 TAILQ_FOREACH(dr, &devinfo_res, dr_link) 504 if (dr->dr_res.dr_rman == rman->dm_handle) 505 if ((error = fn(&dr->dr_res, arg)) != 0) 506 return(error); 507 return(0); 508 } 509 510 /* 511 * Iterate over all the resource managers, calling (fn) on each. If (fn) 512 * returns nonzero, abort the scan and return. 513 */ 514 extern int 515 devinfo_foreach_rman(int (* fn)(struct devinfo_rman *rman, void *arg), 516 void *arg) 517 { 518 struct devinfo_i_rman *dm; 519 int error; 520 521 TAILQ_FOREACH(dm, &devinfo_rman, dm_link) 522 if ((error = fn(&dm->dm_rman, arg)) != 0) 523 return(error); 524 return(0); 525 } 526