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