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);
79static void	devinfo_free_dev(struct devinfo_i_dev *dd);
80
81TAILQ_HEAD(,devinfo_i_dev)	devinfo_dev;
82TAILQ_HEAD(,devinfo_i_rman)	devinfo_rman;
83TAILQ_HEAD(,devinfo_i_res)	devinfo_res;
84
85static int	devinfo_initted = 0;
86static 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 */
102int
103devinfo_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
169static int
170devinfo_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
268static int
269devinfo_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 */
375static void
376devinfo_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 */
389void
390devinfo_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 */
415struct devinfo_dev *
416devinfo_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 */
442struct devinfo_res *
443devinfo_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 */
456struct devinfo_rman *
457devinfo_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 */
471int
472devinfo_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 */
490int
491devinfo_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 */
509extern int
510devinfo_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 */
528extern int
529devinfo_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