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