devinfo.c revision 330897
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: stable/11/lib/libdevinfo/devinfo.c 330897 2018-03-14 03:19:51Z eadler $");
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
77static int	devinfo_init_devices(int generation);
78static int	devinfo_init_resources(int generation);
79
80TAILQ_HEAD(,devinfo_i_dev)	devinfo_dev;
81TAILQ_HEAD(,devinfo_i_rman)	devinfo_rman;
82TAILQ_HEAD(,devinfo_i_res)	devinfo_res;
83
84static int	devinfo_initted = 0;
85static 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 */
101int
102devinfo_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			warn("kernel bus interface version mismatch");
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
167static int
168devinfo_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;
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 < 1000; 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 ((dd = malloc(sizeof(*dd))) == NULL)
223			return(ENOMEM);
224		dd->dd_dev.dd_handle = udev.dv_handle;
225		dd->dd_dev.dd_parent = udev.dv_parent;
226		snprintf(dd->dd_name, sizeof(dd->dd_name), "%s", udev.dv_name);
227		dd->dd_dev.dd_name = &dd->dd_name[0];
228		snprintf(dd->dd_desc, sizeof(dd->dd_desc), "%s", udev.dv_desc);
229		dd->dd_dev.dd_desc = &dd->dd_desc[0];
230		snprintf(dd->dd_drivername, sizeof(dd->dd_drivername), "%s",
231		    udev.dv_drivername);
232		dd->dd_dev.dd_drivername = &dd->dd_drivername[0];
233		snprintf(dd->dd_pnpinfo, sizeof(dd->dd_pnpinfo), "%s",
234		    udev.dv_pnpinfo);
235		dd->dd_dev.dd_pnpinfo = &dd->dd_pnpinfo[0];
236		snprintf(dd->dd_location, sizeof(dd->dd_location), "%s",
237		    udev.dv_location);
238		dd->dd_dev.dd_location = &dd->dd_location[0];
239		dd->dd_dev.dd_devflags = udev.dv_devflags;
240		dd->dd_dev.dd_flags = udev.dv_flags;
241		dd->dd_dev.dd_state = udev.dv_state;
242		TAILQ_INSERT_TAIL(&devinfo_dev, dd, dd_link);
243	}
244	debug("fetched %d devices", dev_idx);
245	return(0);
246}
247
248static int
249devinfo_init_resources(int generation)
250{
251	struct u_rman		urman;
252	struct devinfo_i_rman	*dm;
253	struct u_resource	ures;
254	struct devinfo_i_res	*dr;
255	int			rman_idx, res_idx;
256	int			rman_ptr, res_ptr;
257	int			name2oid[2];
258	int			oid[CTL_MAXNAME + 12];
259	size_t			oidlen, rlen;
260	char			*name;
261	int			error;
262
263	/*
264	 * Find the OID for the rman interface node.
265	 * This is just the usual evil, undocumented sysctl juju.
266	 */
267	name2oid[0] = 0;
268	name2oid[1] = 3;
269	oidlen = sizeof(oid);
270	name = "hw.bus.rman";
271	error = sysctl(name2oid, 2, oid, &oidlen, name, strlen(name));
272	if (error < 0) {
273		warnx("can't find hw.bus.rman sysctl node");
274		return(ENOENT);
275	}
276	oidlen /= sizeof(int);
277	if (oidlen > CTL_MAXNAME) {
278		warnx("hw.bus.rman oid is too large");
279		return(EINVAL);
280	}
281	oid[oidlen++] = generation;
282	rman_ptr = oidlen++;
283	res_ptr = oidlen++;
284
285	/*
286	 * Scan resource managers.
287	 *
288	 * Stop after a fairly insane number to avoid death in the case
289	 * of kernel corruption.
290	 */
291	for (rman_idx = 0; rman_idx < 255; rman_idx++) {
292
293		/*
294		 * Get the resource manager information.
295		 */
296		oid[rman_ptr] = rman_idx;
297		oid[res_ptr] = -1;
298		rlen = sizeof(urman);
299		error = sysctl(oid, oidlen, &urman, &rlen, NULL, 0);
300		if (error < 0) {
301			if (errno == ENOENT)	/* end of list */
302				break;
303			if (errno != EINVAL)	/* gen count skip, restart */
304				warn("sysctl hw.bus.rman.%d", rman_idx);
305			return(errno);
306		}
307		if ((dm = malloc(sizeof(*dm))) == NULL)
308			return(ENOMEM);
309		dm->dm_rman.dm_handle = urman.rm_handle;
310		dm->dm_rman.dm_start = urman.rm_start;
311		dm->dm_rman.dm_size = urman.rm_size;
312		snprintf(dm->dm_desc, DEVINFO_STRLEN, "%s", urman.rm_descr);
313		dm->dm_rman.dm_desc = &dm->dm_desc[0];
314		TAILQ_INSERT_TAIL(&devinfo_rman, dm, dm_link);
315
316		/*
317		 * Scan resources on this resource manager.
318		 *
319		 * Stop after a fairly insane number to avoid death in the case
320		 * of kernel corruption.
321		 */
322		for (res_idx = 0; res_idx < 1000; res_idx++) {
323			/*
324			 * Get the resource information.
325			 */
326			oid[res_ptr] = res_idx;
327			rlen = sizeof(ures);
328			error = sysctl(oid, oidlen, &ures, &rlen, NULL, 0);
329			if (error < 0) {
330				if (errno == ENOENT)	/* end of list */
331					break;
332				if (errno != EINVAL)	/* gen count skip */
333					warn("sysctl hw.bus.rman.%d.%d",
334					    rman_idx, res_idx);
335				return(errno);
336			}
337			if ((dr = malloc(sizeof(*dr))) == NULL)
338				return(ENOMEM);
339			dr->dr_res.dr_handle = ures.r_handle;
340			dr->dr_res.dr_rman = ures.r_parent;
341			dr->dr_res.dr_device = ures.r_device;
342			dr->dr_res.dr_start = ures.r_start;
343			dr->dr_res.dr_size = ures.r_size;
344			TAILQ_INSERT_TAIL(&devinfo_res, dr, dr_link);
345		}
346		debug("fetched %d resources", res_idx);
347	}
348	debug("scanned %d resource managers", rman_idx);
349	return(0);
350}
351
352/*
353 * Free the list contents.
354 */
355void
356devinfo_free(void)
357{
358	struct devinfo_i_dev	*dd;
359	struct devinfo_i_rman	*dm;
360	struct devinfo_i_res	*dr;
361
362	while ((dd = TAILQ_FIRST(&devinfo_dev)) != NULL) {
363		TAILQ_REMOVE(&devinfo_dev, dd, dd_link);
364		free(dd);
365	}
366	while ((dm = TAILQ_FIRST(&devinfo_rman)) != NULL) {
367		TAILQ_REMOVE(&devinfo_rman, dm, dm_link);
368		free(dm);
369	}
370	while ((dr = TAILQ_FIRST(&devinfo_res)) != NULL) {
371		TAILQ_REMOVE(&devinfo_res, dr, dr_link);
372		free(dr);
373	}
374	devinfo_initted = 0;
375	devinfo_generation = 0;
376}
377
378/*
379 * Find a device by its handle.
380 */
381struct devinfo_dev *
382devinfo_handle_to_device(devinfo_handle_t handle)
383{
384	struct devinfo_i_dev	*dd;
385
386	/*
387	 * Find the root device, whose parent is NULL
388	 */
389	if (handle == DEVINFO_ROOT_DEVICE) {
390		TAILQ_FOREACH(dd, &devinfo_dev, dd_link)
391		    if (dd->dd_dev.dd_parent == DEVINFO_ROOT_DEVICE)
392			    return(&dd->dd_dev);
393		return(NULL);
394	}
395
396	/*
397	 * Scan for the device
398	 */
399	TAILQ_FOREACH(dd, &devinfo_dev, dd_link)
400	    if (dd->dd_dev.dd_handle == handle)
401		    return(&dd->dd_dev);
402	return(NULL);
403}
404
405/*
406 * Find a resource by its handle.
407 */
408struct devinfo_res *
409devinfo_handle_to_resource(devinfo_handle_t handle)
410{
411	struct devinfo_i_res	*dr;
412
413	TAILQ_FOREACH(dr, &devinfo_res, dr_link)
414	    if (dr->dr_res.dr_handle == handle)
415		    return(&dr->dr_res);
416	return(NULL);
417}
418
419/*
420 * Find a resource manager by its handle.
421 */
422struct devinfo_rman *
423devinfo_handle_to_rman(devinfo_handle_t handle)
424{
425	struct devinfo_i_rman	*dm;
426
427	TAILQ_FOREACH(dm, &devinfo_rman, dm_link)
428	    if (dm->dm_rman.dm_handle == handle)
429		    return(&dm->dm_rman);
430	return(NULL);
431}
432
433/*
434 * Iterate over the children of a device, calling (fn) on each.  If
435 * (fn) returns nonzero, abort the scan and return.
436 */
437int
438devinfo_foreach_device_child(struct devinfo_dev *parent,
439    int (* fn)(struct devinfo_dev *child, void *arg),
440    void *arg)
441{
442	struct devinfo_i_dev	*dd;
443	int				error;
444
445	TAILQ_FOREACH(dd, &devinfo_dev, dd_link)
446	    if (dd->dd_dev.dd_parent == parent->dd_handle)
447		    if ((error = fn(&dd->dd_dev, arg)) != 0)
448			    return(error);
449	return(0);
450}
451
452/*
453 * Iterate over all the resources owned by a device, calling (fn) on each.
454 * If (fn) returns nonzero, abort the scan and return.
455 */
456int
457devinfo_foreach_device_resource(struct devinfo_dev *dev,
458    int (* fn)(struct devinfo_dev *dev, struct devinfo_res *res, void *arg),
459    void *arg)
460{
461	struct devinfo_i_res	*dr;
462	int				error;
463
464	TAILQ_FOREACH(dr, &devinfo_res, dr_link)
465	    if (dr->dr_res.dr_device == dev->dd_handle)
466		    if ((error = fn(dev, &dr->dr_res, arg)) != 0)
467			    return(error);
468	return(0);
469}
470
471/*
472 * Iterate over all the resources owned by a resource manager, calling (fn)
473 * on each.  If (fn) returns nonzero, abort the scan and return.
474 */
475extern int
476devinfo_foreach_rman_resource(struct devinfo_rman *rman,
477    int (* fn)(struct devinfo_res *res, void *arg),
478    void *arg)
479{
480	struct devinfo_i_res	*dr;
481	int				error;
482
483	TAILQ_FOREACH(dr, &devinfo_res, dr_link)
484	    if (dr->dr_res.dr_rman == rman->dm_handle)
485		    if ((error = fn(&dr->dr_res, arg)) != 0)
486			    return(error);
487	return(0);
488}
489
490/*
491 * Iterate over all the resource managers, calling (fn) on each.  If (fn)
492 * returns nonzero, abort the scan and return.
493 */
494extern int
495devinfo_foreach_rman(int (* fn)(struct devinfo_rman *rman, void *arg),
496    void *arg)
497{
498    struct devinfo_i_rman	*dm;
499    int				error;
500
501    TAILQ_FOREACH(dm, &devinfo_rman, dm_link)
502	if ((error = fn(&dm->dm_rman, arg)) != 0)
503	    return(error);
504    return(0);
505}
506