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
devinfo_init(void)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
devinfo_init_devices(int generation)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
devinfo_init_resources(int generation)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
devinfo_free_dev(struct devinfo_i_dev * dd)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
devinfo_free(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 *
devinfo_handle_to_device(devinfo_handle_t handle)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 *
devinfo_handle_to_resource(devinfo_handle_t handle)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 *
devinfo_handle_to_rman(devinfo_handle_t handle)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
devinfo_foreach_device_child(struct devinfo_dev * parent,int (* fn)(struct devinfo_dev * child,void * arg),void * arg)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
devinfo_foreach_device_resource(struct devinfo_dev * dev,int (* fn)(struct devinfo_dev * dev,struct devinfo_res * res,void * arg),void * arg)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
devinfo_foreach_rman_resource(struct devinfo_rman * rman,int (* fn)(struct devinfo_res * res,void * arg),void * arg)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
devinfo_foreach_rman(int (* fn)(struct devinfo_rman * rman,void * arg),void * arg)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