#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
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);
}
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);
if (ubus.ub_generation == devinfo_generation)
return(0);
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;
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++;
for (dev_idx = 0; dev_idx < 1000; dev_idx++) {
oid[dev_ptr] = dev_idx;
rlen = sizeof(udev);
error = sysctl(oid, oidlen, &udev, &rlen, NULL, 0);
if (error < 0) {
if (errno == ENOENT)
break;
if (errno != EINVAL)
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;
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++;
for (rman_idx = 0; rman_idx < 255; rman_idx++) {
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)
break;
if (errno != EINVAL)
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];
dm->dm_rman.dm_cpuid = urman.rm_cpuid;
TAILQ_INSERT_TAIL(&devinfo_rman, dm, dm_link);
for (res_idx = 0; res_idx < 1000; res_idx++) {
oid[res_ptr] = res_idx;
rlen = sizeof(ures);
error = sysctl(oid, oidlen, &ures, &rlen, NULL, 0);
if (error < 0) {
if (errno == ENOENT)
break;
if (errno != EINVAL)
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);
}
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;
}
struct devinfo_dev *
devinfo_handle_to_device(devinfo_handle_t handle)
{
struct devinfo_i_dev *dd;
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);
}
TAILQ_FOREACH(dd, &devinfo_dev, dd_link)
if (dd->dd_dev.dd_handle == handle)
return(&dd->dd_dev);
return(NULL);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}