]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - lib/libdevinfo/devinfo.c
Add UPDATING entries and bump version.
[FreeBSD/FreeBSD.git] / lib / libdevinfo / devinfo.c
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
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
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
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
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 *
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 *
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 *
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
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
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
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
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 }