xref: /freebsd/lib/libdevinfo/devinfo.c (revision 2b15cb3d0922bd70ea592f0da9b4a5b167f4d53f)
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/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
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
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
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
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 *
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 *
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 *
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
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
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
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
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