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
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			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
168static int
169devinfo_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
263static int
264devinfo_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 */
370void
371devinfo_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 */
401struct devinfo_dev *
402devinfo_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 */
428struct devinfo_res *
429devinfo_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 */
442struct devinfo_rman *
443devinfo_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 */
457int
458devinfo_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 */
476int
477devinfo_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 */
495extern int
496devinfo_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 */
514extern int
515devinfo_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