41ec95c47f
have access to machines that are pushing 400 devices. When 1,000 was selected, it was rare to get even 40 or 50 devices. Bump the limit by 10x to keep up with the times. Sponsored by: Netflix
506 lines
13 KiB
C
506 lines
13 KiB
C
/*-
|
|
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
|
*
|
|
* Copyright (c) 2000 Michael Smith
|
|
* Copyright (c) 2000 BSDi
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
/*
|
|
* An interface to the FreeBSD kernel's bus/device information interface.
|
|
*
|
|
* This interface is implemented with the
|
|
*
|
|
* hw.bus
|
|
* hw.bus.devices
|
|
* hw.bus.rman
|
|
*
|
|
* sysctls. The interface is not meant for general user application
|
|
* consumption.
|
|
*
|
|
* Device information is obtained by scanning a linear list of all devices
|
|
* maintained by the kernel. The actual device structure pointers are
|
|
* handed out as opaque handles in order to allow reconstruction of the
|
|
* logical toplogy in user space.
|
|
*
|
|
* Resource information is obtained by scanning the kernel's resource
|
|
* managers and fetching their contents. Ownership of resources is
|
|
* tracked using the device's structure pointer again as a handle.
|
|
*
|
|
* In order to ensure coherency of the library's picture of the kernel,
|
|
* a generation count is maintained by the kernel. The initial generation
|
|
* count is obtained (along with the interface version) from the hw.bus
|
|
* sysctl, and must be passed in with every request. If the generation
|
|
* number supplied by the library does not match the kernel's current
|
|
* generation number, the request is failed and the library must discard
|
|
* the data it has received and rescan.
|
|
*
|
|
* The information obtained from the kernel is exported to consumers of
|
|
* this library through a variety of interfaces.
|
|
*/
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/types.h>
|
|
#include <sys/sysctl.h>
|
|
#include <err.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include "devinfo.h"
|
|
#include "devinfo_var.h"
|
|
|
|
static int devinfo_init_devices(int generation);
|
|
static int devinfo_init_resources(int generation);
|
|
|
|
TAILQ_HEAD(,devinfo_i_dev) devinfo_dev;
|
|
TAILQ_HEAD(,devinfo_i_rman) devinfo_rman;
|
|
TAILQ_HEAD(,devinfo_i_res) devinfo_res;
|
|
|
|
static int devinfo_initted = 0;
|
|
static int devinfo_generation = 0;
|
|
|
|
#if 0
|
|
# define debug(...) do { \
|
|
fprintf(stderr, "%s:", __func__); \
|
|
fprintf(stderr, __VA_ARGS__); \
|
|
fprintf(stderr, "\n"); \
|
|
} while (0)
|
|
#else
|
|
# define debug(...)
|
|
#endif
|
|
|
|
/*
|
|
* Initialise our local database with the contents of the kernel's
|
|
* tables.
|
|
*/
|
|
int
|
|
devinfo_init(void)
|
|
{
|
|
struct u_businfo ubus;
|
|
size_t ub_size;
|
|
int error, retries;
|
|
|
|
if (!devinfo_initted) {
|
|
TAILQ_INIT(&devinfo_dev);
|
|
TAILQ_INIT(&devinfo_rman);
|
|
TAILQ_INIT(&devinfo_res);
|
|
}
|
|
|
|
/*
|
|
* Get the generation count and interface version, verify that we
|
|
* are compatible with the kernel.
|
|
*/
|
|
for (retries = 0; retries < 10; retries++) {
|
|
debug("get interface version");
|
|
ub_size = sizeof(ubus);
|
|
if (sysctlbyname("hw.bus.info", &ubus,
|
|
&ub_size, NULL, 0) != 0) {
|
|
warn("sysctlbyname(\"hw.bus.info\", ...) failed");
|
|
return(EINVAL);
|
|
}
|
|
if ((ub_size != sizeof(ubus)) ||
|
|
(ubus.ub_version != BUS_USER_VERSION)) {
|
|
warn("kernel bus interface version mismatch");
|
|
return(EINVAL);
|
|
}
|
|
debug("generation count is %d", ubus.ub_generation);
|
|
|
|
/*
|
|
* Don't rescan if the generation count hasn't changed.
|
|
*/
|
|
if (ubus.ub_generation == devinfo_generation)
|
|
return(0);
|
|
|
|
/*
|
|
* Generation count changed, rescan
|
|
*/
|
|
devinfo_free();
|
|
devinfo_initted = 0;
|
|
devinfo_generation = 0;
|
|
|
|
if ((error = devinfo_init_devices(ubus.ub_generation)) != 0) {
|
|
devinfo_free();
|
|
if (error == EINVAL)
|
|
continue;
|
|
break;
|
|
}
|
|
if ((error = devinfo_init_resources(ubus.ub_generation)) != 0) {
|
|
devinfo_free();
|
|
if (error == EINVAL)
|
|
continue;
|
|
break;
|
|
}
|
|
devinfo_initted = 1;
|
|
devinfo_generation = ubus.ub_generation;
|
|
return(0);
|
|
}
|
|
debug("scan failed after %d retries", retries);
|
|
errno = error;
|
|
return(1);
|
|
}
|
|
|
|
static int
|
|
devinfo_init_devices(int generation)
|
|
{
|
|
struct u_device udev;
|
|
struct devinfo_i_dev *dd;
|
|
int dev_idx;
|
|
int dev_ptr;
|
|
int name2oid[2];
|
|
int oid[CTL_MAXNAME + 12];
|
|
size_t oidlen, rlen;
|
|
char *name;
|
|
int error;
|
|
|
|
/*
|
|
* Find the OID for the rman interface node.
|
|
* This is just the usual evil, undocumented sysctl juju.
|
|
*/
|
|
name2oid[0] = 0;
|
|
name2oid[1] = 3;
|
|
oidlen = sizeof(oid);
|
|
name = "hw.bus.devices";
|
|
error = sysctl(name2oid, 2, oid, &oidlen, name, strlen(name));
|
|
if (error < 0) {
|
|
warnx("can't find hw.bus.devices sysctl node");
|
|
return(ENOENT);
|
|
}
|
|
oidlen /= sizeof(int);
|
|
if (oidlen > CTL_MAXNAME) {
|
|
warnx("hw.bus.devices oid is too large");
|
|
return(EINVAL);
|
|
}
|
|
oid[oidlen++] = generation;
|
|
dev_ptr = oidlen++;
|
|
|
|
/*
|
|
* Scan devices.
|
|
*
|
|
* Stop after a fairly insane number to avoid death in the case
|
|
* of kernel corruption.
|
|
*/
|
|
for (dev_idx = 0; dev_idx < 10000; dev_idx++) {
|
|
|
|
/*
|
|
* Get the device information.
|
|
*/
|
|
oid[dev_ptr] = dev_idx;
|
|
rlen = sizeof(udev);
|
|
error = sysctl(oid, oidlen, &udev, &rlen, NULL, 0);
|
|
if (error < 0) {
|
|
if (errno == ENOENT) /* end of list */
|
|
break;
|
|
if (errno != EINVAL) /* gen count skip, restart */
|
|
warn("sysctl hw.bus.devices.%d", dev_idx);
|
|
return(errno);
|
|
}
|
|
if ((dd = malloc(sizeof(*dd))) == NULL)
|
|
return(ENOMEM);
|
|
dd->dd_dev.dd_handle = udev.dv_handle;
|
|
dd->dd_dev.dd_parent = udev.dv_parent;
|
|
snprintf(dd->dd_name, sizeof(dd->dd_name), "%s", udev.dv_name);
|
|
dd->dd_dev.dd_name = &dd->dd_name[0];
|
|
snprintf(dd->dd_desc, sizeof(dd->dd_desc), "%s", udev.dv_desc);
|
|
dd->dd_dev.dd_desc = &dd->dd_desc[0];
|
|
snprintf(dd->dd_drivername, sizeof(dd->dd_drivername), "%s",
|
|
udev.dv_drivername);
|
|
dd->dd_dev.dd_drivername = &dd->dd_drivername[0];
|
|
snprintf(dd->dd_pnpinfo, sizeof(dd->dd_pnpinfo), "%s",
|
|
udev.dv_pnpinfo);
|
|
dd->dd_dev.dd_pnpinfo = &dd->dd_pnpinfo[0];
|
|
snprintf(dd->dd_location, sizeof(dd->dd_location), "%s",
|
|
udev.dv_location);
|
|
dd->dd_dev.dd_location = &dd->dd_location[0];
|
|
dd->dd_dev.dd_devflags = udev.dv_devflags;
|
|
dd->dd_dev.dd_flags = udev.dv_flags;
|
|
dd->dd_dev.dd_state = udev.dv_state;
|
|
TAILQ_INSERT_TAIL(&devinfo_dev, dd, dd_link);
|
|
}
|
|
debug("fetched %d devices", dev_idx);
|
|
return(0);
|
|
}
|
|
|
|
static int
|
|
devinfo_init_resources(int generation)
|
|
{
|
|
struct u_rman urman;
|
|
struct devinfo_i_rman *dm;
|
|
struct u_resource ures;
|
|
struct devinfo_i_res *dr;
|
|
int rman_idx, res_idx;
|
|
int rman_ptr, res_ptr;
|
|
int name2oid[2];
|
|
int oid[CTL_MAXNAME + 12];
|
|
size_t oidlen, rlen;
|
|
char *name;
|
|
int error;
|
|
|
|
/*
|
|
* Find the OID for the rman interface node.
|
|
* This is just the usual evil, undocumented sysctl juju.
|
|
*/
|
|
name2oid[0] = 0;
|
|
name2oid[1] = 3;
|
|
oidlen = sizeof(oid);
|
|
name = "hw.bus.rman";
|
|
error = sysctl(name2oid, 2, oid, &oidlen, name, strlen(name));
|
|
if (error < 0) {
|
|
warnx("can't find hw.bus.rman sysctl node");
|
|
return(ENOENT);
|
|
}
|
|
oidlen /= sizeof(int);
|
|
if (oidlen > CTL_MAXNAME) {
|
|
warnx("hw.bus.rman oid is too large");
|
|
return(EINVAL);
|
|
}
|
|
oid[oidlen++] = generation;
|
|
rman_ptr = oidlen++;
|
|
res_ptr = oidlen++;
|
|
|
|
/*
|
|
* Scan resource managers.
|
|
*
|
|
* Stop after a fairly insane number to avoid death in the case
|
|
* of kernel corruption.
|
|
*/
|
|
for (rman_idx = 0; rman_idx < 255; rman_idx++) {
|
|
|
|
/*
|
|
* Get the resource manager information.
|
|
*/
|
|
oid[rman_ptr] = rman_idx;
|
|
oid[res_ptr] = -1;
|
|
rlen = sizeof(urman);
|
|
error = sysctl(oid, oidlen, &urman, &rlen, NULL, 0);
|
|
if (error < 0) {
|
|
if (errno == ENOENT) /* end of list */
|
|
break;
|
|
if (errno != EINVAL) /* gen count skip, restart */
|
|
warn("sysctl hw.bus.rman.%d", rman_idx);
|
|
return(errno);
|
|
}
|
|
if ((dm = malloc(sizeof(*dm))) == NULL)
|
|
return(ENOMEM);
|
|
dm->dm_rman.dm_handle = urman.rm_handle;
|
|
dm->dm_rman.dm_start = urman.rm_start;
|
|
dm->dm_rman.dm_size = urman.rm_size;
|
|
snprintf(dm->dm_desc, DEVINFO_STRLEN, "%s", urman.rm_descr);
|
|
dm->dm_rman.dm_desc = &dm->dm_desc[0];
|
|
TAILQ_INSERT_TAIL(&devinfo_rman, dm, dm_link);
|
|
|
|
/*
|
|
* Scan resources on this resource manager.
|
|
*
|
|
* Stop after a fairly insane number to avoid death in the case
|
|
* of kernel corruption.
|
|
*/
|
|
for (res_idx = 0; res_idx < 1000; res_idx++) {
|
|
/*
|
|
* Get the resource information.
|
|
*/
|
|
oid[res_ptr] = res_idx;
|
|
rlen = sizeof(ures);
|
|
error = sysctl(oid, oidlen, &ures, &rlen, NULL, 0);
|
|
if (error < 0) {
|
|
if (errno == ENOENT) /* end of list */
|
|
break;
|
|
if (errno != EINVAL) /* gen count skip */
|
|
warn("sysctl hw.bus.rman.%d.%d",
|
|
rman_idx, res_idx);
|
|
return(errno);
|
|
}
|
|
if ((dr = malloc(sizeof(*dr))) == NULL)
|
|
return(ENOMEM);
|
|
dr->dr_res.dr_handle = ures.r_handle;
|
|
dr->dr_res.dr_rman = ures.r_parent;
|
|
dr->dr_res.dr_device = ures.r_device;
|
|
dr->dr_res.dr_start = ures.r_start;
|
|
dr->dr_res.dr_size = ures.r_size;
|
|
TAILQ_INSERT_TAIL(&devinfo_res, dr, dr_link);
|
|
}
|
|
debug("fetched %d resources", res_idx);
|
|
}
|
|
debug("scanned %d resource managers", rman_idx);
|
|
return(0);
|
|
}
|
|
|
|
/*
|
|
* Free the list contents.
|
|
*/
|
|
void
|
|
devinfo_free(void)
|
|
{
|
|
struct devinfo_i_dev *dd;
|
|
struct devinfo_i_rman *dm;
|
|
struct devinfo_i_res *dr;
|
|
|
|
while ((dd = TAILQ_FIRST(&devinfo_dev)) != NULL) {
|
|
TAILQ_REMOVE(&devinfo_dev, dd, dd_link);
|
|
free(dd);
|
|
}
|
|
while ((dm = TAILQ_FIRST(&devinfo_rman)) != NULL) {
|
|
TAILQ_REMOVE(&devinfo_rman, dm, dm_link);
|
|
free(dm);
|
|
}
|
|
while ((dr = TAILQ_FIRST(&devinfo_res)) != NULL) {
|
|
TAILQ_REMOVE(&devinfo_res, dr, dr_link);
|
|
free(dr);
|
|
}
|
|
devinfo_initted = 0;
|
|
devinfo_generation = 0;
|
|
}
|
|
|
|
/*
|
|
* Find a device by its handle.
|
|
*/
|
|
struct devinfo_dev *
|
|
devinfo_handle_to_device(devinfo_handle_t handle)
|
|
{
|
|
struct devinfo_i_dev *dd;
|
|
|
|
/*
|
|
* Find the root device, whose parent is NULL
|
|
*/
|
|
if (handle == DEVINFO_ROOT_DEVICE) {
|
|
TAILQ_FOREACH(dd, &devinfo_dev, dd_link)
|
|
if (dd->dd_dev.dd_parent == DEVINFO_ROOT_DEVICE)
|
|
return(&dd->dd_dev);
|
|
return(NULL);
|
|
}
|
|
|
|
/*
|
|
* Scan for the device
|
|
*/
|
|
TAILQ_FOREACH(dd, &devinfo_dev, dd_link)
|
|
if (dd->dd_dev.dd_handle == handle)
|
|
return(&dd->dd_dev);
|
|
return(NULL);
|
|
}
|
|
|
|
/*
|
|
* Find a resource by its handle.
|
|
*/
|
|
struct devinfo_res *
|
|
devinfo_handle_to_resource(devinfo_handle_t handle)
|
|
{
|
|
struct devinfo_i_res *dr;
|
|
|
|
TAILQ_FOREACH(dr, &devinfo_res, dr_link)
|
|
if (dr->dr_res.dr_handle == handle)
|
|
return(&dr->dr_res);
|
|
return(NULL);
|
|
}
|
|
|
|
/*
|
|
* Find a resource manager by its handle.
|
|
*/
|
|
struct devinfo_rman *
|
|
devinfo_handle_to_rman(devinfo_handle_t handle)
|
|
{
|
|
struct devinfo_i_rman *dm;
|
|
|
|
TAILQ_FOREACH(dm, &devinfo_rman, dm_link)
|
|
if (dm->dm_rman.dm_handle == handle)
|
|
return(&dm->dm_rman);
|
|
return(NULL);
|
|
}
|
|
|
|
/*
|
|
* Iterate over the children of a device, calling (fn) on each. If
|
|
* (fn) returns nonzero, abort the scan and return.
|
|
*/
|
|
int
|
|
devinfo_foreach_device_child(struct devinfo_dev *parent,
|
|
int (* fn)(struct devinfo_dev *child, void *arg),
|
|
void *arg)
|
|
{
|
|
struct devinfo_i_dev *dd;
|
|
int error;
|
|
|
|
TAILQ_FOREACH(dd, &devinfo_dev, dd_link)
|
|
if (dd->dd_dev.dd_parent == parent->dd_handle)
|
|
if ((error = fn(&dd->dd_dev, arg)) != 0)
|
|
return(error);
|
|
return(0);
|
|
}
|
|
|
|
/*
|
|
* Iterate over all the resources owned by a device, calling (fn) on each.
|
|
* If (fn) returns nonzero, abort the scan and return.
|
|
*/
|
|
int
|
|
devinfo_foreach_device_resource(struct devinfo_dev *dev,
|
|
int (* fn)(struct devinfo_dev *dev, struct devinfo_res *res, void *arg),
|
|
void *arg)
|
|
{
|
|
struct devinfo_i_res *dr;
|
|
int error;
|
|
|
|
TAILQ_FOREACH(dr, &devinfo_res, dr_link)
|
|
if (dr->dr_res.dr_device == dev->dd_handle)
|
|
if ((error = fn(dev, &dr->dr_res, arg)) != 0)
|
|
return(error);
|
|
return(0);
|
|
}
|
|
|
|
/*
|
|
* Iterate over all the resources owned by a resource manager, calling (fn)
|
|
* on each. If (fn) returns nonzero, abort the scan and return.
|
|
*/
|
|
extern int
|
|
devinfo_foreach_rman_resource(struct devinfo_rman *rman,
|
|
int (* fn)(struct devinfo_res *res, void *arg),
|
|
void *arg)
|
|
{
|
|
struct devinfo_i_res *dr;
|
|
int error;
|
|
|
|
TAILQ_FOREACH(dr, &devinfo_res, dr_link)
|
|
if (dr->dr_res.dr_rman == rman->dm_handle)
|
|
if ((error = fn(&dr->dr_res, arg)) != 0)
|
|
return(error);
|
|
return(0);
|
|
}
|
|
|
|
/*
|
|
* Iterate over all the resource managers, calling (fn) on each. If (fn)
|
|
* returns nonzero, abort the scan and return.
|
|
*/
|
|
extern int
|
|
devinfo_foreach_rman(int (* fn)(struct devinfo_rman *rman, void *arg),
|
|
void *arg)
|
|
{
|
|
struct devinfo_i_rman *dm;
|
|
int error;
|
|
|
|
TAILQ_FOREACH(dm, &devinfo_rman, dm_link)
|
|
if ((error = fn(&dm->dm_rman, arg)) != 0)
|
|
return(error);
|
|
return(0);
|
|
}
|