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
75static int	devinfo_init_devices(int generation);
76static int	devinfo_init_resources(int generation);
77
78TAILQ_HEAD(,devinfo_i_dev)	devinfo_dev;
79TAILQ_HEAD(,devinfo_i_rman)	devinfo_rman;
80TAILQ_HEAD(,devinfo_i_res)	devinfo_res;
81
82static int	devinfo_initted = 0;
83static 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 */
99int
100devinfo_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
165static int
166devinfo_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
246static int
247devinfo_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 */
353void
354devinfo_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 */
379struct devinfo_dev *
380devinfo_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 */
406struct devinfo_res *
407devinfo_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 */
420struct devinfo_rman *
421devinfo_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 */
435int
436devinfo_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 */
454int
455devinfo_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 */
473extern int
474devinfo_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 */
492extern int
493devinfo_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