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