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