xref: /freebsd/lib/libdevinfo/devinfo.c (revision c98323078dede7579020518ec84cdcb478e5c142)
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 
28 #include <sys/cdefs.h>
29 __FBSDID("$FreeBSD$");
30 
31 /*
32  * An interface to the FreeBSD kernel's bus/devce 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(fmt, args...)	\
87 	fprintf(stderr, "%s:" fmt "\n", __FUNCTION__ , ##args)
88 #else
89 # define debug(fmt, args...)
90 #endif
91 
92 /*
93  * Initialise our local database with the contents of the kernel's
94  * tables.
95  */
96 int
97 devinfo_init(void)
98 {
99 	struct u_businfo	ubus;
100 	size_t		ub_size;
101 	int			error, retries;
102 
103 	if (!devinfo_initted) {
104 		TAILQ_INIT(&devinfo_dev);
105 		TAILQ_INIT(&devinfo_rman);
106 		TAILQ_INIT(&devinfo_res);
107 	}
108 
109 	/*
110 	 * Get the generation count and interface version, verify that we
111 	 * are compatible with the kernel.
112 	 */
113 	for (retries = 0; retries < 10; retries++) {
114 		debug("get interface version");
115 		ub_size = sizeof(ubus);
116 		if (sysctlbyname("hw.bus.info", &ubus,
117 		    &ub_size, NULL, 0) != 0) {
118 			warn("sysctlbyname(\"hw.bus.info\", ...) failed");
119 			return(EINVAL);
120 		}
121 		if ((ub_size != sizeof(ubus)) ||
122 		    (ubus.ub_version != BUS_USER_VERSION)) {
123 			warn("kernel bus interface version mismatch");
124 			return(EINVAL);
125 		}
126 		debug("generation count is %d", ubus.ub_generation);
127 
128 		/*
129 		 * Don't rescan if the generation count hasn't changed.
130 		 */
131 		if (ubus.ub_generation == devinfo_generation)
132 			return(0);
133 
134 		/*
135 		 * Generation count changed, rescan
136 		 */
137 		devinfo_free();
138 		devinfo_initted = 0;
139 		devinfo_generation = 0;
140 
141 		if ((error = devinfo_init_devices(ubus.ub_generation)) != 0) {
142 			devinfo_free();
143 			if (error == EINVAL)
144 				continue;
145 			break;
146 		}
147 		if ((error = devinfo_init_resources(ubus.ub_generation)) != 0) {
148 			devinfo_free();
149 			if (error == EINVAL)
150 				continue;
151 			break;
152 		}
153 		devinfo_initted = 1;
154 		devinfo_generation = ubus.ub_generation;
155 		return(0);
156 	}
157 	debug("scan failed after %d retries", retries);
158 	errno = error;
159 	return(1);
160 }
161 
162 static int
163 devinfo_init_devices(int generation)
164 {
165 	struct u_device		udev;
166 	struct devinfo_i_dev	*dd;
167 	int				dev_idx;
168 	int				dev_ptr;
169 	int				name2oid[2];
170 	int				oid[CTL_MAXNAME + 12];
171 	size_t			oidlen, rlen;
172 	char			*name, *np, *fmt;
173 	int				error, hexmode;
174 
175 	/*
176 	 * Find the OID for the rman interface node.
177 	 * This is just the usual evil, undocumented sysctl juju.
178 	 */
179 	name2oid[0] = 0;
180 	name2oid[1] = 3;
181 	oidlen = sizeof(oid);
182 	name = "hw.bus.devices";
183 	error = sysctl(name2oid, 2, oid, &oidlen, name, strlen(name));
184 	if (error < 0) {
185 		warnx("can't find hw.bus.devices sysctl node");
186 		return(ENOENT);
187 	}
188 	oidlen /= sizeof(int);
189 	if (oidlen > CTL_MAXNAME) {
190 		warnx("hw.bus.devices oid is too large");
191 		return(EINVAL);
192 	}
193 	oid[oidlen++] = generation;
194 	dev_ptr = oidlen++;
195 
196 	/*
197 	 * Scan devices.
198 	 *
199 	 * Stop after a fairly insane number to avoid death in the case
200 	 * of kernel corruption.
201 	 */
202 	for (dev_idx = 0; dev_idx < 1000; dev_idx++) {
203 
204 		/*
205 		 * Get the device information.
206 		 */
207 		oid[dev_ptr] = dev_idx;
208 		rlen = sizeof(udev);
209 		error = sysctl(oid, oidlen, &udev, &rlen, NULL, 0);
210 		if (error < 0) {
211 			if (errno == ENOENT)	/* end of list */
212 				break;
213 			if (errno != EINVAL)	/* gen count skip, restart */
214 				warn("sysctl hw.bus.devices.%d", dev_idx);
215 			return(errno);
216 		}
217 		if ((dd = malloc(sizeof(*dd))) == NULL)
218 			return(ENOMEM);
219 		dd->dd_dev.dd_handle = udev.dv_handle;
220 		dd->dd_dev.dd_parent = udev.dv_parent;
221 		snprintf(dd->dd_name, sizeof(dd->dd_name), "%s", udev.dv_name);
222 		dd->dd_dev.dd_name = &dd->dd_name[0];
223 		snprintf(dd->dd_desc, sizeof(dd->dd_desc), "%s", udev.dv_desc);
224 		dd->dd_dev.dd_desc = &dd->dd_desc[0];
225 		snprintf(dd->dd_drivername, sizeof(dd->dd_drivername), "%s",
226 		    udev.dv_drivername);
227 		dd->dd_dev.dd_drivername = &dd->dd_drivername[0];
228 		snprintf(dd->dd_pnpinfo, sizeof(dd->dd_pnpinfo), "%s",
229 		    udev.dv_pnpinfo);
230 		dd->dd_dev.dd_pnpinfo = &dd->dd_pnpinfo[0];
231 		snprintf(dd->dd_location, sizeof(dd->dd_location), "%s",
232 		    udev.dv_location);
233 		dd->dd_dev.dd_location = &dd->dd_location[0];
234 		dd->dd_dev.dd_devflags = udev.dv_devflags;
235 		dd->dd_dev.dd_flags = udev.dv_flags;
236 		dd->dd_dev.dd_state = udev.dv_state;
237 		TAILQ_INSERT_TAIL(&devinfo_dev, dd, dd_link);
238 	}
239 	debug("fetched %d devices", dev_idx);
240 	return(0);
241 }
242 
243 static int
244 devinfo_init_resources(int generation)
245 {
246 	struct u_rman		urman;
247 	struct devinfo_i_rman	*dm;
248 	struct u_resource		ures;
249 	struct devinfo_i_res	*dr;
250 	int				rman_idx, res_idx;
251 	int				rman_ptr, res_ptr;
252 	int				name2oid[2];
253 	int				oid[CTL_MAXNAME + 12];
254 	size_t			oidlen, rlen;
255 	char			*name, *np, *fmt;
256 	int				error, hexmode;
257 
258 	/*
259 	 * Find the OID for the rman interface node.
260 	 * This is just the usual evil, undocumented sysctl juju.
261 	 */
262 	name2oid[0] = 0;
263 	name2oid[1] = 3;
264 	oidlen = sizeof(oid);
265 	name = "hw.bus.rman";
266 	error = sysctl(name2oid, 2, oid, &oidlen, name, strlen(name));
267 	if (error < 0) {
268 		warnx("can't find hw.bus.rman sysctl node");
269 		return(ENOENT);
270 	}
271 	oidlen /= sizeof(int);
272 	if (oidlen > CTL_MAXNAME) {
273 		warnx("hw.bus.rman oid is too large");
274 		return(EINVAL);
275 	}
276 	oid[oidlen++] = generation;
277 	rman_ptr = oidlen++;
278 	res_ptr = oidlen++;
279 
280 	/*
281 	 * Scan resource managers.
282 	 *
283 	 * Stop after a fairly insane number to avoid death in the case
284 	 * of kernel corruption.
285 	 */
286 	for (rman_idx = 0; rman_idx < 255; rman_idx++) {
287 
288 		/*
289 		 * Get the resource manager information.
290 		 */
291 		oid[rman_ptr] = rman_idx;
292 		oid[res_ptr] = -1;
293 		rlen = sizeof(urman);
294 		error = sysctl(oid, oidlen, &urman, &rlen, NULL, 0);
295 		if (error < 0) {
296 			if (errno == ENOENT)	/* end of list */
297 				break;
298 			if (errno != EINVAL)	/* gen count skip, restart */
299 				warn("sysctl hw.bus.rman.%d", rman_idx);
300 			return(errno);
301 		}
302 		if ((dm = malloc(sizeof(*dm))) == NULL)
303 			return(ENOMEM);
304 		dm->dm_rman.dm_handle = urman.rm_handle;
305 		dm->dm_rman.dm_start = urman.rm_start;
306 		dm->dm_rman.dm_size = urman.rm_size;
307 		snprintf(dm->dm_desc, DEVINFO_STRLEN, "%s", urman.rm_descr);
308 		dm->dm_rman.dm_desc = &dm->dm_desc[0];
309 		TAILQ_INSERT_TAIL(&devinfo_rman, dm, dm_link);
310 
311 		/*
312 		 * Scan resources on this resource manager.
313 		 *
314 		 * Stop after a fairly insane number to avoid death in the case
315 		 * of kernel corruption.
316 		 */
317 		for (res_idx = 0; res_idx < 1000; res_idx++) {
318 			/*
319 			 * Get the resource information.
320 			 */
321 			oid[res_ptr] = res_idx;
322 			rlen = sizeof(ures);
323 			error = sysctl(oid, oidlen, &ures, &rlen, NULL, 0);
324 			if (error < 0) {
325 				if (errno == ENOENT)	/* end of list */
326 					break;
327 				if (errno != EINVAL)	/* gen count skip */
328 					warn("sysctl hw.bus.rman.%d.%d",
329 					    rman_idx, res_idx);
330 				return(errno);
331 			}
332 			if ((dr = malloc(sizeof(*dr))) == NULL)
333 				return(ENOMEM);
334 			dr->dr_res.dr_handle = ures.r_handle;
335 			dr->dr_res.dr_rman = ures.r_parent;
336 			dr->dr_res.dr_device = ures.r_device;
337 			dr->dr_res.dr_start = ures.r_start;
338 			dr->dr_res.dr_size = ures.r_size;
339 			TAILQ_INSERT_TAIL(&devinfo_res, dr, dr_link);
340 		}
341 		debug("fetched %d resources", res_idx);
342 	}
343 	debug("scanned %d resource managers", rman_idx);
344 	return(0);
345 }
346 
347 /*
348  * Free the list contents.
349  */
350 void
351 devinfo_free(void)
352 {
353 	struct devinfo_i_dev	*dd;
354 	struct devinfo_i_rman	*dm;
355 	struct devinfo_i_res	*dr;
356 
357 	while ((dd = TAILQ_FIRST(&devinfo_dev)) != NULL) {
358 		TAILQ_REMOVE(&devinfo_dev, dd, dd_link);
359 		free(dd);
360 	}
361 	while ((dm = TAILQ_FIRST(&devinfo_rman)) != NULL) {
362 		TAILQ_REMOVE(&devinfo_rman, dm, dm_link);
363 		free(dm);
364 	}
365 	while ((dr = TAILQ_FIRST(&devinfo_res)) != NULL) {
366 		TAILQ_REMOVE(&devinfo_res, dr, dr_link);
367 		free(dr);
368 	}
369 	devinfo_initted = 0;
370 }
371 
372 /*
373  * Find a device by its handle.
374  */
375 struct devinfo_dev *
376 devinfo_handle_to_device(devinfo_handle_t handle)
377 {
378 	struct devinfo_i_dev	*dd;
379 
380 	/*
381 	 * Find the root device, whose parent is NULL
382 	 */
383 	if (handle == DEVINFO_ROOT_DEVICE) {
384 		TAILQ_FOREACH(dd, &devinfo_dev, dd_link)
385 		    if (dd->dd_dev.dd_parent == DEVINFO_ROOT_DEVICE)
386 			    return(&dd->dd_dev);
387 		return(NULL);
388 	}
389 
390 	/*
391 	 * Scan for the device
392 	 */
393 	TAILQ_FOREACH(dd, &devinfo_dev, dd_link)
394 	    if (dd->dd_dev.dd_handle == handle)
395 		    return(&dd->dd_dev);
396 	return(NULL);
397 }
398 
399 /*
400  * Find a resource by its handle.
401  */
402 struct devinfo_res *
403 devinfo_handle_to_resource(devinfo_handle_t handle)
404 {
405 	struct devinfo_i_res	*dr;
406 
407 	TAILQ_FOREACH(dr, &devinfo_res, dr_link)
408 	    if (dr->dr_res.dr_handle == handle)
409 		    return(&dr->dr_res);
410 	return(NULL);
411 }
412 
413 /*
414  * Find a resource manager by its handle.
415  */
416 struct devinfo_rman *
417 devinfo_handle_to_rman(devinfo_handle_t handle)
418 {
419 	struct devinfo_i_rman	*dm;
420 
421 	TAILQ_FOREACH(dm, &devinfo_rman, dm_link)
422 	    if (dm->dm_rman.dm_handle == handle)
423 		    return(&dm->dm_rman);
424 	return(NULL);
425 }
426 
427 /*
428  * Iterate over the children of a device, calling (fn) on each.  If
429  * (fn) returns nonzero, abort the scan and return.
430  */
431 int
432 devinfo_foreach_device_child(struct devinfo_dev *parent,
433     int (* fn)(struct devinfo_dev *child, void *arg),
434     void *arg)
435 {
436 	struct devinfo_i_dev	*dd;
437 	int				error;
438 
439 	TAILQ_FOREACH(dd, &devinfo_dev, dd_link)
440 	    if (dd->dd_dev.dd_parent == parent->dd_handle)
441 		    if ((error = fn(&dd->dd_dev, arg)) != 0)
442 			    return(error);
443 	return(0);
444 }
445 
446 /*
447  * Iterate over all the resources owned by a device, calling (fn) on each.
448  * If (fn) returns nonzero, abort the scan and return.
449  */
450 int
451 devinfo_foreach_device_resource(struct devinfo_dev *dev,
452     int (* fn)(struct devinfo_dev *dev, struct devinfo_res *res, void *arg),
453     void *arg)
454 {
455 	struct devinfo_i_res	*dr;
456 	int				error;
457 
458 	TAILQ_FOREACH(dr, &devinfo_res, dr_link)
459 	    if (dr->dr_res.dr_device == dev->dd_handle)
460 		    if ((error = fn(dev, &dr->dr_res, arg)) != 0)
461 			    return(error);
462 	return(0);
463 }
464 
465 /*
466  * Iterate over all the resources owned by a resource manager, calling (fn)
467  * on each.  If (fn) returns nonzero, abort the scan and return.
468  */
469 extern int
470 devinfo_foreach_rman_resource(struct devinfo_rman *rman,
471     int (* fn)(struct devinfo_res *res, void *arg),
472     void *arg)
473 {
474 	struct devinfo_i_res	*dr;
475 	int				error;
476 
477 	TAILQ_FOREACH(dr, &devinfo_res, dr_link)
478 	    if (dr->dr_res.dr_rman == rman->dm_handle)
479 		    if ((error = fn(&dr->dr_res, arg)) != 0)
480 			    return(error);
481 	return(0);
482 }
483 
484 /*
485  * Iterate over all the resource managers, calling (fn) on each.  If (fn)
486  * returns nonzero, abort the scan and return.
487  */
488 extern int
489 devinfo_foreach_rman(int (* fn)(struct devinfo_rman *rman, void *arg),
490     void *arg)
491 {
492     struct devinfo_i_rman	*dm;
493     int				error;
494 
495     TAILQ_FOREACH(dm, &devinfo_rman, dm_link)
496 	if ((error = fn(&dm->dm_rman, arg)) != 0)
497 	    return(error);
498     return(0);
499 }
500