xref: /freebsd/lib/libdevinfo/devinfo.c (revision b52b9d56d4e96089873a75f9e29062eec19fabba)
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, DEVINFO_STRLEN, "%s", udev.dv_name);
222 		dd->dd_dev.dd_name = &dd->dd_name[0];
223 		snprintf(dd->dd_desc, DEVINFO_STRLEN, "%s", udev.dv_desc);
224 		dd->dd_dev.dd_desc = &dd->dd_desc[0];
225 		snprintf(dd->dd_drivername, DEVINFO_STRLEN, "%s",
226 		    udev.dv_drivername);
227 		dd->dd_dev.dd_drivername = &dd->dd_drivername[0];
228 		TAILQ_INSERT_TAIL(&devinfo_dev, dd, dd_link);
229 	}
230 	debug("fetched %d devices", dev_idx);
231 	return(0);
232 }
233 
234 static int
235 devinfo_init_resources(int generation)
236 {
237 	struct u_rman		urman;
238 	struct devinfo_i_rman	*dm;
239 	struct u_resource		ures;
240 	struct devinfo_i_res	*dr;
241 	int				rman_idx, res_idx;
242 	int				rman_ptr, res_ptr;
243 	int				name2oid[2];
244 	int				oid[CTL_MAXNAME + 12];
245 	size_t			oidlen, rlen;
246 	char			*name, *np, *fmt;
247 	int				error, hexmode;
248 
249 	/*
250 	 * Find the OID for the rman interface node.
251 	 * This is just the usual evil, undocumented sysctl juju.
252 	 */
253 	name2oid[0] = 0;
254 	name2oid[1] = 3;
255 	oidlen = sizeof(oid);
256 	name = "hw.bus.rman";
257 	error = sysctl(name2oid, 2, oid, &oidlen, name, strlen(name));
258 	if (error < 0) {
259 		warnx("can't find hw.bus.rman sysctl node");
260 		return(ENOENT);
261 	}
262 	oidlen /= sizeof(int);
263 	if (oidlen > CTL_MAXNAME) {
264 		warnx("hw.bus.rman oid is too large");
265 		return(EINVAL);
266 	}
267 	oid[oidlen++] = generation;
268 	rman_ptr = oidlen++;
269 	res_ptr = oidlen++;
270 
271 	/*
272 	 * Scan resource managers.
273 	 *
274 	 * Stop after a fairly insane number to avoid death in the case
275 	 * of kernel corruption.
276 	 */
277 	for (rman_idx = 0; rman_idx < 255; rman_idx++) {
278 
279 		/*
280 		 * Get the resource manager information.
281 		 */
282 		oid[rman_ptr] = rman_idx;
283 		oid[res_ptr] = -1;
284 		rlen = sizeof(urman);
285 		error = sysctl(oid, oidlen, &urman, &rlen, NULL, 0);
286 		if (error < 0) {
287 			if (errno == ENOENT)	/* end of list */
288 				break;
289 			if (errno != EINVAL)	/* gen count skip, restart */
290 				warn("sysctl hw.bus.rman.%d", rman_idx);
291 			return(errno);
292 		}
293 		if ((dm = malloc(sizeof(*dm))) == NULL)
294 			return(ENOMEM);
295 		dm->dm_rman.dm_handle = urman.rm_handle;
296 		dm->dm_rman.dm_start = urman.rm_start;
297 		dm->dm_rman.dm_size = urman.rm_size;
298 		snprintf(dm->dm_desc, DEVINFO_STRLEN, "%s", urman.rm_descr);
299 		dm->dm_rman.dm_desc = &dm->dm_desc[0];
300 		TAILQ_INSERT_TAIL(&devinfo_rman, dm, dm_link);
301 
302 		/*
303 		 * Scan resources on this resource manager.
304 		 *
305 		 * Stop after a fairly insane number to avoid death in the case
306 		 * of kernel corruption.
307 		 */
308 		for (res_idx = 0; res_idx < 1000; res_idx++) {
309 			/*
310 			 * Get the resource information.
311 			 */
312 			oid[res_ptr] = res_idx;
313 			rlen = sizeof(ures);
314 			error = sysctl(oid, oidlen, &ures, &rlen, NULL, 0);
315 			if (error < 0) {
316 				if (errno == ENOENT)	/* end of list */
317 					break;
318 				if (errno != EINVAL)	/* gen count skip */
319 					warn("sysctl hw.bus.rman.%d.%d",
320 					    rman_idx, res_idx);
321 				return(errno);
322 			}
323 			if ((dr = malloc(sizeof(*dr))) == NULL)
324 				return(ENOMEM);
325 			dr->dr_res.dr_handle = ures.r_handle;
326 			dr->dr_res.dr_rman = ures.r_parent;
327 			dr->dr_res.dr_device = ures.r_device;
328 			dr->dr_res.dr_start = ures.r_start;
329 			dr->dr_res.dr_size = ures.r_size;
330 			TAILQ_INSERT_TAIL(&devinfo_res, dr, dr_link);
331 		}
332 		debug("fetched %d resources", res_idx);
333 	}
334 	debug("scanned %d resource managers", rman_idx);
335 	return(0);
336 }
337 
338 /*
339  * Free the list contents.
340  */
341 void
342 devinfo_free(void)
343 {
344 	struct devinfo_i_dev	*dd;
345 	struct devinfo_i_rman	*dm;
346 	struct devinfo_i_res	*dr;
347 
348 	while ((dd = TAILQ_FIRST(&devinfo_dev)) != NULL) {
349 		TAILQ_REMOVE(&devinfo_dev, dd, dd_link);
350 		free(dd);
351 	}
352 	while ((dm = TAILQ_FIRST(&devinfo_rman)) != NULL) {
353 		TAILQ_REMOVE(&devinfo_rman, dm, dm_link);
354 		free(dm);
355 	}
356 	while ((dr = TAILQ_FIRST(&devinfo_res)) != NULL) {
357 		TAILQ_REMOVE(&devinfo_res, dr, dr_link);
358 		free(dr);
359 	}
360 	devinfo_initted = 0;
361 }
362 
363 /*
364  * Find a device by its handle.
365  */
366 struct devinfo_dev *
367 devinfo_handle_to_device(devinfo_handle_t handle)
368 {
369 	struct devinfo_i_dev	*dd;
370 
371 	/*
372 	 * Find the root device, whose parent is NULL
373 	 */
374 	if (handle == DEVINFO_ROOT_DEVICE) {
375 		TAILQ_FOREACH(dd, &devinfo_dev, dd_link)
376 		    if (dd->dd_dev.dd_parent == DEVINFO_ROOT_DEVICE)
377 			    return(&dd->dd_dev);
378 		return(NULL);
379 	}
380 
381 	/*
382 	 * Scan for the device
383 	 */
384 	TAILQ_FOREACH(dd, &devinfo_dev, dd_link)
385 	    if (dd->dd_dev.dd_handle == handle)
386 		    return(&dd->dd_dev);
387 	return(NULL);
388 }
389 
390 /*
391  * Find a resource by its handle.
392  */
393 struct devinfo_res *
394 devinfo_handle_to_resource(devinfo_handle_t handle)
395 {
396 	struct devinfo_i_res	*dr;
397 
398 	TAILQ_FOREACH(dr, &devinfo_res, dr_link)
399 	    if (dr->dr_res.dr_handle == handle)
400 		    return(&dr->dr_res);
401 	return(NULL);
402 }
403 
404 /*
405  * Find a resource manager by its handle.
406  */
407 struct devinfo_rman *
408 devinfo_handle_to_rman(devinfo_handle_t handle)
409 {
410 	struct devinfo_i_rman	*dm;
411 
412 	TAILQ_FOREACH(dm, &devinfo_rman, dm_link)
413 	    if (dm->dm_rman.dm_handle == handle)
414 		    return(&dm->dm_rman);
415 	return(NULL);
416 }
417 
418 /*
419  * Iterate over the children of a device, calling (fn) on each.  If
420  * (fn) returns nonzero, abort the scan and return.
421  */
422 int
423 devinfo_foreach_device_child(struct devinfo_dev *parent,
424     int (* fn)(struct devinfo_dev *child, void *arg),
425     void *arg)
426 {
427 	struct devinfo_i_dev	*dd;
428 	int				error;
429 
430 	TAILQ_FOREACH(dd, &devinfo_dev, dd_link)
431 	    if (dd->dd_dev.dd_parent == parent->dd_handle)
432 		    if ((error = fn(&dd->dd_dev, arg)) != 0)
433 			    return(error);
434 	return(0);
435 }
436 
437 /*
438  * Iterate over all the resources owned by a device, calling (fn) on each.
439  * If (fn) returns nonzero, abort the scan and return.
440  */
441 int
442 devinfo_foreach_device_resource(struct devinfo_dev *dev,
443     int (* fn)(struct devinfo_dev *dev, struct devinfo_res *res, void *arg),
444     void *arg)
445 {
446 	struct devinfo_i_res	*dr;
447 	int				error;
448 
449 	TAILQ_FOREACH(dr, &devinfo_res, dr_link)
450 	    if (dr->dr_res.dr_device == dev->dd_handle)
451 		    if ((error = fn(dev, &dr->dr_res, arg)) != 0)
452 			    return(error);
453 	return(0);
454 }
455 
456 /*
457  * Iterate over all the resources owned by a resource manager, calling (fn)
458  * on each.  If (fn) returns nonzero, abort the scan and return.
459  */
460 extern int
461 devinfo_foreach_rman_resource(struct devinfo_rman *rman,
462     int (* fn)(struct devinfo_res *res, void *arg),
463     void *arg)
464 {
465 	struct devinfo_i_res	*dr;
466 	int				error;
467 
468 	TAILQ_FOREACH(dr, &devinfo_res, dr_link)
469 	    if (dr->dr_res.dr_rman == rman->dm_handle)
470 		    if ((error = fn(&dr->dr_res, arg)) != 0)
471 			    return(error);
472 	return(0);
473 }
474 
475 /*
476  * Iterate over all the resource managers, calling (fn) on each.  If (fn)
477  * returns nonzero, abort the scan and return.
478  */
479 extern int
480 devinfo_foreach_rman(int (* fn)(struct devinfo_rman *rman, void *arg),
481     void *arg)
482 {
483     struct devinfo_i_rman	*dm;
484     int				error;
485 
486     TAILQ_FOREACH(dm, &devinfo_rman, dm_link)
487 	if ((error = fn(&dm->dm_rman, arg)) != 0)
488 	    return(error);
489     return(0);
490 }
491