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