]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - lib/libdevinfo/devinfo.c
Move SYSCTL_ADD_PROC() to unlocked context in if_ure to avoid lock order reversal.
[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 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
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
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
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
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
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 *
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 *
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 *
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
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
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
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
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 }