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