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