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 static void devinfo_free_dev(struct devinfo_i_dev *dd); 80 81 TAILQ_HEAD(,devinfo_i_dev) devinfo_dev; 82 TAILQ_HEAD(,devinfo_i_rman) devinfo_rman; 83 TAILQ_HEAD(,devinfo_i_res) devinfo_res; 84 85 static int devinfo_initted = 0; 86 static int devinfo_generation = 0; 87 88 #if 0 89 # define debug(...) do { \ 90 fprintf(stderr, "%s:", __func__); \ 91 fprintf(stderr, __VA_ARGS__); \ 92 fprintf(stderr, "\n"); \ 93 } while (0) 94 #else 95 # define debug(...) 96 #endif 97 98 /* 99 * Initialise our local database with the contents of the kernel's 100 * tables. 101 */ 102 int 103 devinfo_init(void) 104 { 105 struct u_businfo ubus; 106 size_t ub_size; 107 int error, retries; 108 109 if (!devinfo_initted) { 110 TAILQ_INIT(&devinfo_dev); 111 TAILQ_INIT(&devinfo_rman); 112 TAILQ_INIT(&devinfo_res); 113 } 114 115 /* 116 * Get the generation count and interface version, verify that we 117 * are compatible with the kernel. 118 */ 119 for (retries = 0; retries < 10; retries++) { 120 debug("get interface version"); 121 ub_size = sizeof(ubus); 122 if (sysctlbyname("hw.bus.info", &ubus, 123 &ub_size, NULL, 0) != 0) { 124 warn("sysctlbyname(\"hw.bus.info\", ...) failed"); 125 return(EINVAL); 126 } 127 if ((ub_size != sizeof(ubus)) || 128 (ubus.ub_version != BUS_USER_VERSION)) { 129 warnx("kernel bus interface version mismatch: kernel %d expected %d", 130 ubus.ub_version, BUS_USER_VERSION); 131 return(EINVAL); 132 } 133 debug("generation count is %d", ubus.ub_generation); 134 135 /* 136 * Don't rescan if the generation count hasn't changed. 137 */ 138 if (ubus.ub_generation == devinfo_generation) 139 return(0); 140 141 /* 142 * Generation count changed, rescan 143 */ 144 devinfo_free(); 145 devinfo_initted = 0; 146 devinfo_generation = 0; 147 148 if ((error = devinfo_init_devices(ubus.ub_generation)) != 0) { 149 devinfo_free(); 150 if (error == EINVAL) 151 continue; 152 break; 153 } 154 if ((error = devinfo_init_resources(ubus.ub_generation)) != 0) { 155 devinfo_free(); 156 if (error == EINVAL) 157 continue; 158 break; 159 } 160 devinfo_initted = 1; 161 devinfo_generation = ubus.ub_generation; 162 return(0); 163 } 164 debug("scan failed after %d retries", retries); 165 errno = error; 166 return(1); 167 } 168 169 static int 170 devinfo_init_devices(int generation) 171 { 172 struct u_device udev; 173 struct devinfo_i_dev *dd; 174 int dev_idx; 175 int dev_ptr; 176 int name2oid[2]; 177 int oid[CTL_MAXNAME + 12]; 178 size_t oidlen, rlen; 179 char *name, *walker, *ep; 180 int error; 181 182 /* 183 * Find the OID for the rman interface node. 184 * This is just the usual evil, undocumented sysctl juju. 185 */ 186 name2oid[0] = 0; 187 name2oid[1] = 3; 188 oidlen = sizeof(oid); 189 name = "hw.bus.devices"; 190 error = sysctl(name2oid, 2, oid, &oidlen, name, strlen(name)); 191 if (error < 0) { 192 warnx("can't find hw.bus.devices sysctl node"); 193 return(ENOENT); 194 } 195 oidlen /= sizeof(int); 196 if (oidlen > CTL_MAXNAME) { 197 warnx("hw.bus.devices oid is too large"); 198 return(EINVAL); 199 } 200 oid[oidlen++] = generation; 201 dev_ptr = oidlen++; 202 203 /* 204 * Scan devices. 205 * 206 * Stop after a fairly insane number to avoid death in the case 207 * of kernel corruption. 208 */ 209 for (dev_idx = 0; dev_idx < 10000; dev_idx++) { 210 211 /* 212 * Get the device information. 213 */ 214 oid[dev_ptr] = dev_idx; 215 rlen = sizeof(udev); 216 error = sysctl(oid, oidlen, &udev, &rlen, NULL, 0); 217 if (error < 0) { 218 if (errno == ENOENT) /* end of list */ 219 break; 220 if (errno != EINVAL) /* gen count skip, restart */ 221 warn("sysctl hw.bus.devices.%d", dev_idx); 222 return(errno); 223 } 224 if (rlen != sizeof(udev)) { 225 warnx("sysctl returned wrong data %zd bytes instead of %zd", 226 rlen, sizeof(udev)); 227 return (EINVAL); 228 } 229 if ((dd = calloc(1, sizeof(*dd))) == NULL) 230 return(ENOMEM); 231 dd->dd_dev.dd_handle = udev.dv_handle; 232 dd->dd_dev.dd_parent = udev.dv_parent; 233 dd->dd_dev.dd_devflags = udev.dv_devflags; 234 dd->dd_dev.dd_flags = udev.dv_flags; 235 dd->dd_dev.dd_state = udev.dv_state; 236 237 walker = udev.dv_fields; 238 ep = walker + sizeof(udev.dv_fields); 239 dd->dd_name = NULL; 240 dd->dd_desc = NULL; 241 dd->dd_drivername = NULL; 242 dd->dd_pnpinfo = NULL; 243 dd->dd_location = NULL; 244 #define UNPACK(x) \ 245 dd->dd_dev.x = dd->x = strdup(walker); \ 246 if (dd->x == NULL) { \ 247 devinfo_free_dev(dd); \ 248 return(ENOMEM); \ 249 } \ 250 if (walker + strnlen(walker, ep - walker) >= ep) { \ 251 devinfo_free_dev(dd); \ 252 return(EINVAL); \ 253 } \ 254 walker += strlen(walker) + 1; 255 256 UNPACK(dd_name); 257 UNPACK(dd_desc); 258 UNPACK(dd_drivername); 259 UNPACK(dd_pnpinfo); 260 UNPACK(dd_location); 261 #undef UNPACK 262 TAILQ_INSERT_TAIL(&devinfo_dev, dd, dd_link); 263 } 264 debug("fetched %d devices", dev_idx); 265 return(0); 266 } 267 268 static int 269 devinfo_init_resources(int generation) 270 { 271 struct u_rman urman; 272 struct devinfo_i_rman *dm; 273 struct u_resource ures; 274 struct devinfo_i_res *dr; 275 int rman_idx, res_idx; 276 int rman_ptr, res_ptr; 277 int name2oid[2]; 278 int oid[CTL_MAXNAME + 12]; 279 size_t oidlen, rlen; 280 char *name; 281 int error; 282 283 /* 284 * Find the OID for the rman interface node. 285 * This is just the usual evil, undocumented sysctl juju. 286 */ 287 name2oid[0] = 0; 288 name2oid[1] = 3; 289 oidlen = sizeof(oid); 290 name = "hw.bus.rman"; 291 error = sysctl(name2oid, 2, oid, &oidlen, name, strlen(name)); 292 if (error < 0) { 293 warnx("can't find hw.bus.rman sysctl node"); 294 return(ENOENT); 295 } 296 oidlen /= sizeof(int); 297 if (oidlen > CTL_MAXNAME) { 298 warnx("hw.bus.rman oid is too large"); 299 return(EINVAL); 300 } 301 oid[oidlen++] = generation; 302 rman_ptr = oidlen++; 303 res_ptr = oidlen++; 304 305 /* 306 * Scan resource managers. 307 * 308 * Stop after a fairly insane number to avoid death in the case 309 * of kernel corruption. 310 */ 311 for (rman_idx = 0; rman_idx < 255; rman_idx++) { 312 313 /* 314 * Get the resource manager information. 315 */ 316 oid[rman_ptr] = rman_idx; 317 oid[res_ptr] = -1; 318 rlen = sizeof(urman); 319 error = sysctl(oid, oidlen, &urman, &rlen, NULL, 0); 320 if (error < 0) { 321 if (errno == ENOENT) /* end of list */ 322 break; 323 if (errno != EINVAL) /* gen count skip, restart */ 324 warn("sysctl hw.bus.rman.%d", rman_idx); 325 return(errno); 326 } 327 if ((dm = malloc(sizeof(*dm))) == NULL) 328 return(ENOMEM); 329 dm->dm_rman.dm_handle = urman.rm_handle; 330 dm->dm_rman.dm_start = urman.rm_start; 331 dm->dm_rman.dm_size = urman.rm_size; 332 snprintf(dm->dm_desc, DEVINFO_STRLEN, "%s", urman.rm_descr); 333 dm->dm_rman.dm_desc = &dm->dm_desc[0]; 334 TAILQ_INSERT_TAIL(&devinfo_rman, dm, dm_link); 335 336 /* 337 * Scan resources on this resource manager. 338 * 339 * Stop after a fairly insane number to avoid death in the case 340 * of kernel corruption. 341 */ 342 for (res_idx = 0; res_idx < 1000; res_idx++) { 343 /* 344 * Get the resource information. 345 */ 346 oid[res_ptr] = res_idx; 347 rlen = sizeof(ures); 348 error = sysctl(oid, oidlen, &ures, &rlen, NULL, 0); 349 if (error < 0) { 350 if (errno == ENOENT) /* end of list */ 351 break; 352 if (errno != EINVAL) /* gen count skip */ 353 warn("sysctl hw.bus.rman.%d.%d", 354 rman_idx, res_idx); 355 return(errno); 356 } 357 if ((dr = malloc(sizeof(*dr))) == NULL) 358 return(ENOMEM); 359 dr->dr_res.dr_handle = ures.r_handle; 360 dr->dr_res.dr_rman = ures.r_parent; 361 dr->dr_res.dr_device = ures.r_device; 362 dr->dr_res.dr_start = ures.r_start; 363 dr->dr_res.dr_size = ures.r_size; 364 TAILQ_INSERT_TAIL(&devinfo_res, dr, dr_link); 365 } 366 debug("fetched %d resources", res_idx); 367 } 368 debug("scanned %d resource managers", rman_idx); 369 return(0); 370 } 371 372 /* 373 * Free an individual dev. 374 */ 375 static void 376 devinfo_free_dev(struct devinfo_i_dev *dd) 377 { 378 free(dd->dd_name); 379 free(dd->dd_desc); 380 free(dd->dd_drivername); 381 free(dd->dd_pnpinfo); 382 free(dd->dd_location); 383 free(dd); 384 } 385 386 /* 387 * Free the list contents. 388 */ 389 void 390 devinfo_free(void) 391 { 392 struct devinfo_i_dev *dd; 393 struct devinfo_i_rman *dm; 394 struct devinfo_i_res *dr; 395 396 while ((dd = TAILQ_FIRST(&devinfo_dev)) != NULL) { 397 TAILQ_REMOVE(&devinfo_dev, dd, dd_link); 398 devinfo_free_dev(dd); 399 } 400 while ((dm = TAILQ_FIRST(&devinfo_rman)) != NULL) { 401 TAILQ_REMOVE(&devinfo_rman, dm, dm_link); 402 free(dm); 403 } 404 while ((dr = TAILQ_FIRST(&devinfo_res)) != NULL) { 405 TAILQ_REMOVE(&devinfo_res, dr, dr_link); 406 free(dr); 407 } 408 devinfo_initted = 0; 409 devinfo_generation = 0; 410 } 411 412 /* 413 * Find a device by its handle. 414 */ 415 struct devinfo_dev * 416 devinfo_handle_to_device(devinfo_handle_t handle) 417 { 418 struct devinfo_i_dev *dd; 419 420 /* 421 * Find the root device, whose parent is NULL 422 */ 423 if (handle == DEVINFO_ROOT_DEVICE) { 424 TAILQ_FOREACH(dd, &devinfo_dev, dd_link) 425 if (dd->dd_dev.dd_parent == DEVINFO_ROOT_DEVICE) 426 return(&dd->dd_dev); 427 return(NULL); 428 } 429 430 /* 431 * Scan for the device 432 */ 433 TAILQ_FOREACH(dd, &devinfo_dev, dd_link) 434 if (dd->dd_dev.dd_handle == handle) 435 return(&dd->dd_dev); 436 return(NULL); 437 } 438 439 /* 440 * Find a resource by its handle. 441 */ 442 struct devinfo_res * 443 devinfo_handle_to_resource(devinfo_handle_t handle) 444 { 445 struct devinfo_i_res *dr; 446 447 TAILQ_FOREACH(dr, &devinfo_res, dr_link) 448 if (dr->dr_res.dr_handle == handle) 449 return(&dr->dr_res); 450 return(NULL); 451 } 452 453 /* 454 * Find a resource manager by its handle. 455 */ 456 struct devinfo_rman * 457 devinfo_handle_to_rman(devinfo_handle_t handle) 458 { 459 struct devinfo_i_rman *dm; 460 461 TAILQ_FOREACH(dm, &devinfo_rman, dm_link) 462 if (dm->dm_rman.dm_handle == handle) 463 return(&dm->dm_rman); 464 return(NULL); 465 } 466 467 /* 468 * Iterate over the children of a device, calling (fn) on each. If 469 * (fn) returns nonzero, abort the scan and return. 470 */ 471 int 472 devinfo_foreach_device_child(struct devinfo_dev *parent, 473 int (* fn)(struct devinfo_dev *child, void *arg), 474 void *arg) 475 { 476 struct devinfo_i_dev *dd; 477 int error; 478 479 TAILQ_FOREACH(dd, &devinfo_dev, dd_link) 480 if (dd->dd_dev.dd_parent == parent->dd_handle) 481 if ((error = fn(&dd->dd_dev, arg)) != 0) 482 return(error); 483 return(0); 484 } 485 486 /* 487 * Iterate over all the resources owned by a device, calling (fn) on each. 488 * If (fn) returns nonzero, abort the scan and return. 489 */ 490 int 491 devinfo_foreach_device_resource(struct devinfo_dev *dev, 492 int (* fn)(struct devinfo_dev *dev, struct devinfo_res *res, void *arg), 493 void *arg) 494 { 495 struct devinfo_i_res *dr; 496 int error; 497 498 TAILQ_FOREACH(dr, &devinfo_res, dr_link) 499 if (dr->dr_res.dr_device == dev->dd_handle) 500 if ((error = fn(dev, &dr->dr_res, arg)) != 0) 501 return(error); 502 return(0); 503 } 504 505 /* 506 * Iterate over all the resources owned by a resource manager, calling (fn) 507 * on each. If (fn) returns nonzero, abort the scan and return. 508 */ 509 extern int 510 devinfo_foreach_rman_resource(struct devinfo_rman *rman, 511 int (* fn)(struct devinfo_res *res, void *arg), 512 void *arg) 513 { 514 struct devinfo_i_res *dr; 515 int error; 516 517 TAILQ_FOREACH(dr, &devinfo_res, dr_link) 518 if (dr->dr_res.dr_rman == rman->dm_handle) 519 if ((error = fn(&dr->dr_res, arg)) != 0) 520 return(error); 521 return(0); 522 } 523 524 /* 525 * Iterate over all the resource managers, calling (fn) on each. If (fn) 526 * returns nonzero, abort the scan and return. 527 */ 528 extern int 529 devinfo_foreach_rman(int (* fn)(struct devinfo_rman *rman, void *arg), 530 void *arg) 531 { 532 struct devinfo_i_rman *dm; 533 int error; 534 535 TAILQ_FOREACH(dm, &devinfo_rman, dm_link) 536 if ((error = fn(&dm->dm_rman, arg)) != 0) 537 return(error); 538 return(0); 539 } 540