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