xref: /dragonfly/lib/libdevinfo/devinfo.c (revision 86d7f5d305c6adaa56ff4582ece9859d73106103)
1 /*-
2  * Copyright (c) 2000 Michael Smith
3  * Copyright (c) 2000 BSDi
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  *
27  * $FreeBSD: src/lib/libdevinfo/devinfo.c,v 1.9 2006/07/17 09:33:24 stefanf Exp $
28  * $DragonFly: src/lib/libdevinfo/devinfo.c,v 1.1 2008/09/30 12:20:29 hasso Exp $
29  */
30 
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 
78 TAILQ_HEAD(,devinfo_i_dev)    devinfo_dev;
79 TAILQ_HEAD(,devinfo_i_rman)   devinfo_rman;
80 TAILQ_HEAD(,devinfo_i_res)    devinfo_res;
81 
82 static int          devinfo_initted = 0;
83 static int          devinfo_generation = 0;
84 
85 #if 0
86 # define debug(...) do { \
87           fprintf(stderr, "%s:", __func__); \
88           fprintf(stderr, __VA_ARGS__); \
89           fprintf(stderr, "\n"); \
90 } while (0)
91 #else
92 # define debug(...)
93 #endif
94 
95 /*
96  * Initialise our local database with the contents of the kernel's
97  * tables.
98  */
99 int
devinfo_init(void)100 devinfo_init(void)
101 {
102           struct u_businfo    ubus;
103           size_t              ub_size;
104           int                           error, retries;
105 
106           if (!devinfo_initted) {
107                     TAILQ_INIT(&devinfo_dev);
108                     TAILQ_INIT(&devinfo_rman);
109                     TAILQ_INIT(&devinfo_res);
110           }
111 
112           /*
113            * Get the generation count and interface version, verify that we
114            * are compatible with the kernel.
115            */
116           for (retries = 0; retries < 10; retries++) {
117                     debug("get interface version");
118                     ub_size = sizeof(ubus);
119                     if (sysctlbyname("hw.bus.info", &ubus,
120                         &ub_size, NULL, 0) != 0) {
121                               warn("sysctlbyname(\"hw.bus.info\", ...) failed");
122                               return(EINVAL);
123                     }
124                     if ((ub_size != sizeof(ubus)) ||
125                         (ubus.ub_version != BUS_USER_VERSION)) {
126                               warn("kernel bus interface version mismatch");
127                               return(EINVAL);
128                     }
129                     debug("generation count is %d", ubus.ub_generation);
130 
131                     /*
132                      * Don't rescan if the generation count hasn't changed.
133                      */
134                     if (ubus.ub_generation == devinfo_generation)
135                               return(0);
136 
137                     /*
138                      * Generation count changed, rescan
139                      */
140                     devinfo_free();
141                     devinfo_initted = 0;
142                     devinfo_generation = 0;
143 
144                     if ((error = devinfo_init_devices(ubus.ub_generation)) != 0) {
145                               devinfo_free();
146                               if (error == EINVAL)
147                                         continue;
148                               break;
149                     }
150                     if ((error = devinfo_init_resources(ubus.ub_generation)) != 0) {
151                               devinfo_free();
152                               if (error == EINVAL)
153                                         continue;
154                               break;
155                     }
156                     devinfo_initted = 1;
157                     devinfo_generation = ubus.ub_generation;
158                     return(0);
159           }
160           debug("scan failed after %d retries", retries);
161           errno = error;
162           return(1);
163 }
164 
165 static int
devinfo_init_devices(int generation)166 devinfo_init_devices(int generation)
167 {
168           struct u_device               udev;
169           struct devinfo_i_dev          *dd;
170           int                           dev_idx;
171           int                           dev_ptr;
172           int                           name2oid[2];
173           int                           oid[CTL_MAXNAME + 12];
174           size_t                        oidlen, rlen;
175           char                          *name;
176           int                           error;
177 
178           /*
179            * Find the OID for the rman interface node.
180            * This is just the usual evil, undocumented sysctl juju.
181            */
182           name2oid[0] = 0;
183           name2oid[1] = 3;
184           oidlen = sizeof(oid);
185           name = "hw.bus.devices";
186           error = sysctl(name2oid, 2, oid, &oidlen, name, strlen(name));
187           if (error < 0) {
188                     warnx("can't find hw.bus.devices sysctl node");
189                     return(ENOENT);
190           }
191           oidlen /= sizeof(int);
192           if (oidlen > CTL_MAXNAME) {
193                     warnx("hw.bus.devices oid is too large");
194                     return(EINVAL);
195           }
196           oid[oidlen++] = generation;
197           dev_ptr = oidlen++;
198 
199           /*
200            * Scan devices.
201            *
202            * Stop after a fairly insane number to avoid death in the case
203            * of kernel corruption.
204            */
205           for (dev_idx = 0; dev_idx < 1000; dev_idx++) {
206 
207                     /*
208                      * Get the device information.
209                      */
210                     oid[dev_ptr] = dev_idx;
211                     rlen = sizeof(udev);
212                     error = sysctl(oid, oidlen, &udev, &rlen, NULL, 0);
213                     if (error < 0) {
214                               if (errno == ENOENT)          /* end of list */
215                                         break;
216                               if (errno != EINVAL)          /* gen count skip, restart */
217                                         warn("sysctl hw.bus.devices.%d", dev_idx);
218                               return(errno);
219                     }
220                     if ((dd = malloc(sizeof(*dd))) == NULL)
221                               return(ENOMEM);
222                     dd->dd_dev.dd_handle = udev.dv_handle;
223                     dd->dd_dev.dd_parent = udev.dv_parent;
224                     snprintf(dd->dd_name, sizeof(dd->dd_name), "%s", udev.dv_name);
225                     dd->dd_dev.dd_name = &dd->dd_name[0];
226                     snprintf(dd->dd_desc, sizeof(dd->dd_desc), "%s", udev.dv_desc);
227                     dd->dd_dev.dd_desc = &dd->dd_desc[0];
228                     snprintf(dd->dd_drivername, sizeof(dd->dd_drivername), "%s",
229                         udev.dv_drivername);
230                     dd->dd_dev.dd_drivername = &dd->dd_drivername[0];
231                     snprintf(dd->dd_pnpinfo, sizeof(dd->dd_pnpinfo), "%s",
232                         udev.dv_pnpinfo);
233                     dd->dd_dev.dd_pnpinfo = &dd->dd_pnpinfo[0];
234                     snprintf(dd->dd_location, sizeof(dd->dd_location), "%s",
235                         udev.dv_location);
236                     dd->dd_dev.dd_location = &dd->dd_location[0];
237                     dd->dd_dev.dd_devflags = udev.dv_devflags;
238                     dd->dd_dev.dd_flags = udev.dv_flags;
239                     dd->dd_dev.dd_state = udev.dv_state;
240                     TAILQ_INSERT_TAIL(&devinfo_dev, dd, dd_link);
241           }
242           debug("fetched %d devices", dev_idx);
243           return(0);
244 }
245 
246 static int
devinfo_init_resources(int generation)247 devinfo_init_resources(int generation)
248 {
249           struct u_rman                 urman;
250           struct devinfo_i_rman         *dm;
251           struct u_resource   ures;
252           struct devinfo_i_res          *dr;
253           int                           rman_idx, res_idx;
254           int                           rman_ptr, res_ptr;
255           int                           name2oid[2];
256           int                           oid[CTL_MAXNAME + 12];
257           size_t                        oidlen, rlen;
258           char                          *name;
259           int                           error;
260 
261           /*
262            * Find the OID for the rman interface node.
263            * This is just the usual evil, undocumented sysctl juju.
264            */
265           name2oid[0] = 0;
266           name2oid[1] = 3;
267           oidlen = sizeof(oid);
268           name = "hw.bus.rman";
269           error = sysctl(name2oid, 2, oid, &oidlen, name, strlen(name));
270           if (error < 0) {
271                     warnx("can't find hw.bus.rman sysctl node");
272                     return(ENOENT);
273           }
274           oidlen /= sizeof(int);
275           if (oidlen > CTL_MAXNAME) {
276                     warnx("hw.bus.rman oid is too large");
277                     return(EINVAL);
278           }
279           oid[oidlen++] = generation;
280           rman_ptr = oidlen++;
281           res_ptr = oidlen++;
282 
283           /*
284            * Scan resource managers.
285            *
286            * Stop after a fairly insane number to avoid death in the case
287            * of kernel corruption.
288            */
289           for (rman_idx = 0; rman_idx < 255; rman_idx++) {
290 
291                     /*
292                      * Get the resource manager information.
293                      */
294                     oid[rman_ptr] = rman_idx;
295                     oid[res_ptr] = -1;
296                     rlen = sizeof(urman);
297                     error = sysctl(oid, oidlen, &urman, &rlen, NULL, 0);
298                     if (error < 0) {
299                               if (errno == ENOENT)          /* end of list */
300                                         break;
301                               if (errno != EINVAL)          /* gen count skip, restart */
302                                         warn("sysctl hw.bus.rman.%d", rman_idx);
303                               return(errno);
304                     }
305                     if ((dm = malloc(sizeof(*dm))) == NULL)
306                               return(ENOMEM);
307                     dm->dm_rman.dm_handle = urman.rm_handle;
308                     dm->dm_rman.dm_start = urman.rm_start;
309                     dm->dm_rman.dm_size = urman.rm_size;
310                     snprintf(dm->dm_desc, DEVINFO_STRLEN, "%s", urman.rm_descr);
311                     dm->dm_rman.dm_desc = &dm->dm_desc[0];
312                     TAILQ_INSERT_TAIL(&devinfo_rman, dm, dm_link);
313 
314                     /*
315                      * Scan resources on this resource manager.
316                      *
317                      * Stop after a fairly insane number to avoid death in the case
318                      * of kernel corruption.
319                      */
320                     for (res_idx = 0; res_idx < 1000; res_idx++) {
321                               /*
322                                * Get the resource information.
323                                */
324                               oid[res_ptr] = res_idx;
325                               rlen = sizeof(ures);
326                               error = sysctl(oid, oidlen, &ures, &rlen, NULL, 0);
327                               if (error < 0) {
328                                         if (errno == ENOENT)          /* end of list */
329                                                   break;
330                                         if (errno != EINVAL)          /* gen count skip */
331                                                   warn("sysctl hw.bus.rman.%d.%d",
332                                                       rman_idx, res_idx);
333                                         return(errno);
334                               }
335                               if ((dr = malloc(sizeof(*dr))) == NULL)
336                                         return(ENOMEM);
337                               dr->dr_res.dr_handle = ures.r_handle;
338                               dr->dr_res.dr_rman = ures.r_parent;
339                               dr->dr_res.dr_device = ures.r_device;
340                               dr->dr_res.dr_start = ures.r_start;
341                               dr->dr_res.dr_size = ures.r_size;
342                               TAILQ_INSERT_TAIL(&devinfo_res, dr, dr_link);
343                     }
344                     debug("fetched %d resources", res_idx);
345           }
346           debug("scanned %d resource managers", rman_idx);
347           return(0);
348 }
349 
350 /*
351  * Free the list contents.
352  */
353 void
devinfo_free(void)354 devinfo_free(void)
355 {
356           struct devinfo_i_dev          *dd;
357           struct devinfo_i_rman         *dm;
358           struct devinfo_i_res          *dr;
359 
360           while ((dd = TAILQ_FIRST(&devinfo_dev)) != NULL) {
361                     TAILQ_REMOVE(&devinfo_dev, dd, dd_link);
362                     free(dd);
363           }
364           while ((dm = TAILQ_FIRST(&devinfo_rman)) != NULL) {
365                     TAILQ_REMOVE(&devinfo_rman, dm, dm_link);
366                     free(dm);
367           }
368           while ((dr = TAILQ_FIRST(&devinfo_res)) != NULL) {
369                     TAILQ_REMOVE(&devinfo_res, dr, dr_link);
370                     free(dr);
371           }
372           devinfo_initted = 0;
373           devinfo_generation = 0;
374 }
375 
376 /*
377  * Find a device by its handle.
378  */
379 struct devinfo_dev *
devinfo_handle_to_device(devinfo_handle_t handle)380 devinfo_handle_to_device(devinfo_handle_t handle)
381 {
382           struct devinfo_i_dev          *dd;
383 
384           /*
385            * Find the root device, whose parent is NULL
386            */
387           if (handle == DEVINFO_ROOT_DEVICE) {
388                     TAILQ_FOREACH(dd, &devinfo_dev, dd_link)
389                         if (dd->dd_dev.dd_parent == DEVINFO_ROOT_DEVICE)
390                                   return(&dd->dd_dev);
391                     return(NULL);
392           }
393 
394           /*
395            * Scan for the device
396            */
397           TAILQ_FOREACH(dd, &devinfo_dev, dd_link)
398               if (dd->dd_dev.dd_handle == handle)
399                         return(&dd->dd_dev);
400           return(NULL);
401 }
402 
403 /*
404  * Find a resource by its handle.
405  */
406 struct devinfo_res *
devinfo_handle_to_resource(devinfo_handle_t handle)407 devinfo_handle_to_resource(devinfo_handle_t handle)
408 {
409           struct devinfo_i_res          *dr;
410 
411           TAILQ_FOREACH(dr, &devinfo_res, dr_link)
412               if (dr->dr_res.dr_handle == handle)
413                         return(&dr->dr_res);
414           return(NULL);
415 }
416 
417 /*
418  * Find a resource manager by its handle.
419  */
420 struct devinfo_rman *
devinfo_handle_to_rman(devinfo_handle_t handle)421 devinfo_handle_to_rman(devinfo_handle_t handle)
422 {
423           struct devinfo_i_rman         *dm;
424 
425           TAILQ_FOREACH(dm, &devinfo_rman, dm_link)
426               if (dm->dm_rman.dm_handle == handle)
427                         return(&dm->dm_rman);
428           return(NULL);
429 }
430 
431 /*
432  * Iterate over the children of a device, calling (fn) on each.  If
433  * (fn) returns nonzero, abort the scan and return.
434  */
435 int
devinfo_foreach_device_child(struct devinfo_dev * parent,int (* fn)(struct devinfo_dev * child,void * arg),void * arg)436 devinfo_foreach_device_child(struct devinfo_dev *parent,
437     int (* fn)(struct devinfo_dev *child, void *arg),
438     void *arg)
439 {
440           struct devinfo_i_dev          *dd;
441           int                                     error;
442 
443           TAILQ_FOREACH(dd, &devinfo_dev, dd_link)
444               if (dd->dd_dev.dd_parent == parent->dd_handle)
445                         if ((error = fn(&dd->dd_dev, arg)) != 0)
446                                   return(error);
447           return(0);
448 }
449 
450 /*
451  * Iterate over all the resources owned by a device, calling (fn) on each.
452  * If (fn) returns nonzero, abort the scan and return.
453  */
454 int
devinfo_foreach_device_resource(struct devinfo_dev * dev,int (* fn)(struct devinfo_dev * dev,struct devinfo_res * res,void * arg),void * arg)455 devinfo_foreach_device_resource(struct devinfo_dev *dev,
456     int (* fn)(struct devinfo_dev *dev, struct devinfo_res *res, void *arg),
457     void *arg)
458 {
459           struct devinfo_i_res          *dr;
460           int                                     error;
461 
462           TAILQ_FOREACH(dr, &devinfo_res, dr_link)
463               if (dr->dr_res.dr_device == dev->dd_handle)
464                         if ((error = fn(dev, &dr->dr_res, arg)) != 0)
465                                   return(error);
466           return(0);
467 }
468 
469 /*
470  * Iterate over all the resources owned by a resource manager, calling (fn)
471  * on each.  If (fn) returns nonzero, abort the scan and return.
472  */
473 extern int
devinfo_foreach_rman_resource(struct devinfo_rman * rman,int (* fn)(struct devinfo_res * res,void * arg),void * arg)474 devinfo_foreach_rman_resource(struct devinfo_rman *rman,
475     int (* fn)(struct devinfo_res *res, void *arg),
476     void *arg)
477 {
478           struct devinfo_i_res          *dr;
479           int                                     error;
480 
481           TAILQ_FOREACH(dr, &devinfo_res, dr_link)
482               if (dr->dr_res.dr_rman == rman->dm_handle)
483                         if ((error = fn(&dr->dr_res, arg)) != 0)
484                                   return(error);
485           return(0);
486 }
487 
488 /*
489  * Iterate over all the resource managers, calling (fn) on each.  If (fn)
490  * returns nonzero, abort the scan and return.
491  */
492 extern int
devinfo_foreach_rman(int (* fn)(struct devinfo_rman * rman,void * arg),void * arg)493 devinfo_foreach_rman(int (* fn)(struct devinfo_rman *rman, void *arg),
494     void *arg)
495 {
496     struct devinfo_i_rman     *dm;
497     int                                 error;
498 
499     TAILQ_FOREACH(dm, &devinfo_rman, dm_link)
500           if ((error = fn(&dm->dm_rman, arg)) != 0)
501               return(error);
502     return(0);
503 }
504