#include <sys/types.h>
#include <sys/pathname.h>
#include <sys/debug.h>
#include <sys/autoconf.h>
#include <sys/vmsystm.h>
#include <sys/conf.h>
#include <sys/file.h>
#include <sys/kmem.h>
#include <sys/modctl.h>
#include <sys/stat.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/sunldi_impl.h>
#include <sys/sunndi.h>
#include <sys/esunddi.h>
#include <sys/sunmdi.h>
#include <sys/ddi_impldefs.h>
#include <sys/ndi_impldefs.h>
#include <sys/mdi_impldefs.h>
#include <sys/devinfo_impl.h>
#include <sys/thread.h>
#include <sys/modhash.h>
#include <sys/bitmap.h>
#include <util/qsort.h>
#include <sys/disp.h>
#include <sys/kobj.h>
#include <sys/crc32.h>
#include <sys/ddi_hp.h>
#include <sys/ddi_hp_impl.h>
#include <sys/sysmacros.h>
#include <sys/list.h>
#ifdef DEBUG
static int di_debug;
#define dcmn_err(args) if (di_debug >= 1) cmn_err args
#define dcmn_err2(args) if (di_debug >= 2) cmn_err args
#define dcmn_err3(args) if (di_debug >= 3) cmn_err args
#else
#define dcmn_err(args)
#define dcmn_err2(args)
#define dcmn_err3(args)
#endif
static int di_max_opens = 32;
static int di_prop_dyn = 1;
#define DI_FULL_PARENT 0
#define DI_READONLY_PARENT 1
#define DI_NODE_SPECIES 2
#define DI_UNPRIVILEGED_NODE(x) (((x) % 2) != 0)
#define IOC_IDLE 0
#define IOC_SNAP 1
#define IOC_DONE 2
#define IOC_COPY 3
#define DI_ALIGN(addr) ((addr + 7l) & ~7l)
struct di_mem {
struct di_mem *next;
char *buf;
size_t buf_size;
devmap_cookie_t cook;
};
struct di_stack {
void *offset[MAX_TREE_DEPTH];
struct dev_info *dip[MAX_TREE_DEPTH];
int depth;
};
#define TOP_OFFSET(stack) \
((di_off_t *)(stack)->offset[(stack)->depth - 1])
#define TOP_NODE(stack) \
((stack)->dip[(stack)->depth - 1])
#define PARENT_OFFSET(stack) \
((di_off_t *)(stack)->offset[(stack)->depth - 2])
#define EMPTY_STACK(stack) ((stack)->depth == 0)
#define POP_STACK(stack) { \
ndi_devi_exit((dev_info_t *)TOP_NODE(stack)); \
((stack)->depth--); \
}
#define PUSH_STACK(stack, node, off_p) { \
ASSERT(node != NULL); \
ndi_devi_enter((dev_info_t *)node); \
(stack)->dip[(stack)->depth] = (node); \
(stack)->offset[(stack)->depth] = (void *)(off_p); \
((stack)->depth)++; \
}
#define DI_ALL_PTR(s) DI_ALL(di_mem_addr((s), 0))
enum di_ktype {
DI_DKEY = 1,
DI_PKEY = 2
};
struct di_dkey {
dev_info_t *dk_dip;
major_t dk_major;
int dk_inst;
pnode_t dk_nodeid;
};
struct di_pkey {
mdi_pathinfo_t *pk_pip;
char *pk_path_addr;
dev_info_t *pk_client;
dev_info_t *pk_phci;
};
struct di_key {
enum di_ktype k_type;
union {
struct di_dkey dkey;
struct di_pkey pkey;
} k_u;
};
struct i_lnode;
typedef struct i_link {
di_off_t self;
int spec_type;
struct i_lnode *src_lnode;
struct i_lnode *tgt_lnode;
struct i_link *src_link_next;
struct i_link *tgt_link_next;
} i_link_t;
typedef struct i_lnode {
di_off_t self;
int modid;
struct di_node *di_node;
dev_t devt;
i_link_t *link_in;
i_link_t *link_out;
} i_lnode_t;
typedef struct i_hp {
di_off_t hp_off;
dev_info_t *hp_child;
list_node_t hp_link;
} i_hp_t;
static struct di_state {
di_off_t mem_size;
struct di_mem *memlist;
uint_t command;
int di_iocstate;
mod_hash_t *reg_dip_hash;
mod_hash_t *reg_pip_hash;
int lnode_count;
int link_count;
mod_hash_t *lnode_hash;
mod_hash_t *link_hash;
list_t hp_list;
} **di_states;
static kmutex_t di_lock;
typedef enum {
DI_QUIET = 0,
DI_ERR,
DI_INFO,
DI_TRACE,
DI_TRACE1,
DI_TRACE2
} di_cache_debug_t;
static uint_t di_chunk = 32;
#define DI_CACHE_LOCK(c) (mutex_enter(&(c).cache_lock))
#define DI_CACHE_UNLOCK(c) (mutex_exit(&(c).cache_lock))
#define DI_CACHE_LOCKED(c) (mutex_owned(&(c).cache_lock))
#define DEVICES_FILES_CLEANABLE(st) \
(((st)->command & DINFOSUBTREE) && ((st)->command & DINFOFORCE) && \
strcmp(DI_ALL_PTR(st)->root_path, "/") == 0)
#define CACHE_DEBUG(args) \
{ if (di_cache_debug != DI_QUIET) di_cache_print args; }
typedef struct phci_walk_arg {
di_off_t off;
struct di_state *st;
} phci_walk_arg_t;
static int di_open(dev_t *, int, int, cred_t *);
static int di_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
static int di_close(dev_t, int, int, cred_t *);
static int di_info(dev_info_t *, ddi_info_cmd_t, void *, void **);
static int di_attach(dev_info_t *, ddi_attach_cmd_t);
static int di_detach(dev_info_t *, ddi_detach_cmd_t);
static di_off_t di_copyformat(di_off_t, struct di_state *, intptr_t, int);
static di_off_t di_snapshot_and_clean(struct di_state *);
static di_off_t di_copydevnm(di_off_t *, struct di_state *);
static di_off_t di_copytree(struct dev_info *, di_off_t *, struct di_state *);
static di_off_t di_copynode(struct dev_info *, struct di_stack *,
struct di_state *);
static di_off_t di_getmdata(struct ddi_minor_data *, di_off_t *, di_off_t,
struct di_state *);
static di_off_t di_getppdata(struct dev_info *, di_off_t *, struct di_state *);
static di_off_t di_getdpdata(struct dev_info *, di_off_t *, struct di_state *);
static di_off_t di_gethpdata(ddi_hp_cn_handle_t *, di_off_t *,
struct di_state *);
static di_off_t di_getprop(int, struct ddi_prop **, di_off_t *,
struct di_state *, struct dev_info *);
static void di_allocmem(struct di_state *, size_t);
static void di_freemem(struct di_state *);
static void di_copymem(struct di_state *st, caddr_t buf, size_t bufsiz);
static di_off_t di_checkmem(struct di_state *, di_off_t, size_t);
static void *di_mem_addr(struct di_state *, di_off_t);
static int di_setstate(struct di_state *, int);
static void di_register_dip(struct di_state *, dev_info_t *, di_off_t);
static void di_register_pip(struct di_state *, mdi_pathinfo_t *, di_off_t);
static di_off_t di_getpath_data(dev_info_t *, di_off_t *, di_off_t,
struct di_state *, int);
static di_off_t di_getlink_data(di_off_t, struct di_state *);
static int di_dip_find(struct di_state *st, dev_info_t *node, di_off_t *off_p);
static int cache_args_valid(struct di_state *st, int *error);
static int snapshot_is_cacheable(struct di_state *st);
static int di_cache_lookup(struct di_state *st);
static int di_cache_update(struct di_state *st);
static void di_cache_print(di_cache_debug_t msglevel, char *fmt, ...);
static int build_vhci_list(dev_info_t *vh_devinfo, void *arg);
static int build_phci_list(dev_info_t *ph_devinfo, void *arg);
static void di_hotplug_children(struct di_state *st);
extern int modrootloaded;
extern void mdi_walk_vhcis(int (*)(dev_info_t *, void *), void *);
extern void mdi_vhci_walk_phcis(dev_info_t *,
int (*)(dev_info_t *, void *), void *);
static struct cb_ops di_cb_ops = {
di_open,
di_close,
nodev,
nodev,
nodev,
nodev,
nodev,
di_ioctl,
nodev,
nodev,
nodev,
nochpoll,
ddi_prop_op,
NULL,
D_NEW | D_MP
};
static struct dev_ops di_ops = {
DEVO_REV,
0,
di_info,
nulldev,
nulldev,
di_attach,
di_detach,
nodev,
&di_cb_ops,
NULL
};
static struct modldrv modldrv = {
&mod_driverops,
"DEVINFO Driver",
&di_ops
};
static struct modlinkage modlinkage = {
MODREV_1,
&modldrv,
NULL
};
int
_init(void)
{
int error;
mutex_init(&di_lock, NULL, MUTEX_DRIVER, NULL);
error = mod_install(&modlinkage);
if (error != 0) {
mutex_destroy(&di_lock);
return (error);
}
return (0);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
int
_fini(void)
{
int error;
error = mod_remove(&modlinkage);
if (error != 0) {
return (error);
}
mutex_destroy(&di_lock);
return (0);
}
static dev_info_t *di_dip;
static int
di_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
{
int error = DDI_FAILURE;
switch (infocmd) {
case DDI_INFO_DEVT2DEVINFO:
*result = (void *)di_dip;
error = DDI_SUCCESS;
break;
case DDI_INFO_DEVT2INSTANCE:
*result = (void *)0;
error = DDI_SUCCESS;
break;
default:
break;
}
return (error);
}
static int
di_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
int error = DDI_FAILURE;
switch (cmd) {
case DDI_ATTACH:
di_states = kmem_zalloc(
di_max_opens * sizeof (struct di_state *), KM_SLEEP);
if (ddi_create_minor_node(dip, "devinfo", S_IFCHR,
DI_FULL_PARENT, DDI_PSEUDO, 0) == DDI_FAILURE ||
ddi_create_minor_node(dip, "devinfo,ro", S_IFCHR,
DI_READONLY_PARENT, DDI_PSEUDO, 0) == DDI_FAILURE) {
kmem_free(di_states,
di_max_opens * sizeof (struct di_state *));
ddi_remove_minor_node(dip, NULL);
error = DDI_FAILURE;
} else {
di_dip = dip;
ddi_report_dev(dip);
error = DDI_SUCCESS;
}
break;
default:
error = DDI_FAILURE;
break;
}
return (error);
}
static int
di_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
int error = DDI_FAILURE;
switch (cmd) {
case DDI_DETACH:
ddi_remove_minor_node(dip, NULL);
di_dip = NULL;
kmem_free(di_states, di_max_opens * sizeof (struct di_state *));
error = DDI_SUCCESS;
break;
default:
error = DDI_FAILURE;
break;
}
return (error);
}
static int
di_open(dev_t *devp, int flag, int otyp, cred_t *credp)
{
int m;
minor_t minor_parent = getminor(*devp);
if (minor_parent != DI_FULL_PARENT &&
minor_parent != DI_READONLY_PARENT)
return (ENXIO);
mutex_enter(&di_lock);
for (m = minor_parent; m < di_max_opens; m += DI_NODE_SPECIES) {
if (di_states[m] != NULL)
continue;
di_states[m] = kmem_zalloc(sizeof (struct di_state), KM_SLEEP);
break;
}
if (m >= di_max_opens) {
mutex_exit(&di_lock);
dcmn_err((CE_WARN, "devinfo: maximum devinfo open reached"));
return (EAGAIN);
}
mutex_exit(&di_lock);
ASSERT(m < di_max_opens);
*devp = makedevice(getmajor(*devp), (minor_t)(m + DI_NODE_SPECIES));
dcmn_err((CE_CONT, "di_open: thread = %p, assigned minor = %d\n",
(void *)curthread, m + DI_NODE_SPECIES));
return (0);
}
static int
di_close(dev_t dev, int flag, int otype, cred_t *cred_p)
{
struct di_state *st;
int m = (int)getminor(dev) - DI_NODE_SPECIES;
if (m < 0) {
cmn_err(CE_WARN, "closing non-existent devinfo minor %d",
m + DI_NODE_SPECIES);
return (ENXIO);
}
st = di_states[m];
ASSERT(m < di_max_opens && st != NULL);
di_freemem(st);
kmem_free(st, sizeof (struct di_state));
mutex_enter(&di_lock);
di_states[m] = NULL;
dcmn_err((CE_CONT, "di_close: thread = %p, assigned minor = %d\n",
(void *)curthread, m + DI_NODE_SPECIES));
mutex_exit(&di_lock);
return (0);
}
static int
di_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp)
{
int rv, error;
di_off_t off;
struct di_all *all;
struct di_state *st;
int m = (int)getminor(dev) - DI_NODE_SPECIES;
major_t i;
char *drv_name;
size_t map_size, size;
struct di_mem *dcp;
int ndi_flags;
if (m < 0 || m >= di_max_opens) {
return (ENXIO);
}
st = di_states[m];
ASSERT(st != NULL);
dcmn_err2((CE_CONT, "di_ioctl: mode = %x, cmd = %x\n", mode, cmd));
switch (cmd) {
case DINFOIDENT:
*rvalp = DI_MAGIC;
return (0);
case DINFOLODRV:
if (DI_UNPRIVILEGED_NODE(m)) {
return (EACCES);
}
drv_name = kmem_alloc(MAXNAMELEN, KM_SLEEP);
if (ddi_copyin((void *)arg, drv_name, MAXNAMELEN, mode) != 0) {
kmem_free(drv_name, MAXNAMELEN);
return (EFAULT);
}
i = ddi_name_to_major(drv_name);
if (ddi_hold_driver(i) == NULL) {
kmem_free(drv_name, MAXNAMELEN);
return (ENXIO);
}
ndi_flags = NDI_DEVI_PERSIST | NDI_CONFIG | NDI_NO_EVENT;
modunload_disable();
(void) i_ddi_load_drvconf(i);
(void) ndi_devi_config_driver(ddi_root_node(), ndi_flags, i);
kmem_free(drv_name, MAXNAMELEN);
ddi_rele_driver(i);
rv = i_ddi_devs_attached(i);
modunload_enable();
i_ddi_di_cache_invalidate();
return ((rv == DDI_SUCCESS)? 0 : ENXIO);
case DINFOUSRLD:
if (di_setstate(st, IOC_COPY) == -1)
return (EBUSY);
map_size = DI_ALL_PTR(st)->map_size;
if (map_size == 0) {
(void) di_setstate(st, IOC_DONE);
return (EFAULT);
}
map_size = (map_size + PAGEOFFSET) & PAGEMASK;
*rvalp = (int)map_size;
off = 0;
dcp = st->memlist;
while (map_size) {
size = dcp->buf_size;
if (map_size <= size) {
size = map_size;
}
if (ddi_copyout(di_mem_addr(st, off),
(void *)(arg + off), size, mode) != 0) {
(void) di_setstate(st, IOC_DONE);
return (EFAULT);
}
map_size -= size;
off += size;
dcp = dcp->next;
}
di_freemem(st);
(void) di_setstate(st, IOC_IDLE);
return (0);
default:
if ((cmd & ~DIIOC_MASK) != DIIOC) {
return (ENOTTY);
}
st->command = cmd & DIIOC_MASK;
}
if (di_setstate(st, IOC_SNAP) == -1)
return (EBUSY);
size = sizeof (struct di_all) +
sizeof (((struct dinfo_io *)(NULL))->root_path);
if (size < PAGESIZE)
size = PAGESIZE;
off = di_checkmem(st, 0, size);
all = DI_ALL_PTR(st);
off += sizeof (struct di_all);
all->devcnt = devcnt;
all->command = st->command;
all->version = DI_SNAPSHOT_VERSION;
all->top_vhci_devinfo = 0;
#if defined(_LITTLE_ENDIAN)
all->endianness = DI_LITTLE_ENDIAN;
#else
all->endianness = DI_BIG_ENDIAN;
#endif
if (copyinstr((void *)arg, all->req_path,
sizeof (((struct dinfo_io *)(NULL))->root_path), &size) != 0) {
di_freemem(st);
(void) di_setstate(st, IOC_IDLE);
return (EFAULT);
}
(void) strcpy(all->root_path, all->req_path);
off += size;
if ((st->command & DINFOCLEANUP) && !DEVICES_FILES_CLEANABLE(st)) {
di_freemem(st);
(void) di_setstate(st, IOC_IDLE);
return (EINVAL);
}
error = 0;
if ((st->command & DINFOCACHE) && !cache_args_valid(st, &error)) {
di_freemem(st);
(void) di_setstate(st, IOC_IDLE);
return (error);
}
if ((st->command & (DINFOPRIVDATA | DINFOFORCE)) != 0 &&
DI_UNPRIVILEGED_NODE(m)) {
di_freemem(st);
(void) di_setstate(st, IOC_IDLE);
return (EACCES);
}
if (st->command & DINFOPRIVDATA) {
arg += sizeof (((struct dinfo_io *)(NULL))->root_path);
#ifdef _MULTI_DATAMODEL
switch (ddi_model_convert_from(mode & FMODELS)) {
case DDI_MODEL_ILP32: {
di_freemem(st);
(void) di_setstate(st, IOC_IDLE);
return (EINVAL);
}
case DDI_MODEL_NONE:
if ((off = di_copyformat(off, st, arg, mode)) == 0) {
di_freemem(st);
(void) di_setstate(st, IOC_IDLE);
return (EFAULT);
}
break;
}
#else
if ((off = di_copyformat(off, st, arg, mode)) == 0) {
di_freemem(st);
(void) di_setstate(st, IOC_IDLE);
return (EFAULT);
}
#endif
}
all->top_devinfo = DI_ALIGN(off);
all = NULL;
if (st->command & DINFOCACHE) {
*rvalp = di_cache_lookup(st);
} else if (snapshot_is_cacheable(st)) {
DI_CACHE_LOCK(di_cache);
*rvalp = di_cache_update(st);
DI_CACHE_UNLOCK(di_cache);
} else
*rvalp = di_snapshot_and_clean(st);
if (*rvalp) {
DI_ALL_PTR(st)->map_size = *rvalp;
(void) di_setstate(st, IOC_DONE);
} else {
di_freemem(st);
(void) di_setstate(st, IOC_IDLE);
}
return (0);
}
static void
di_allocmem(struct di_state *st, size_t size)
{
struct di_mem *mem = kmem_zalloc(sizeof (struct di_mem), KM_SLEEP);
size_t tmp = 1;
while (tmp < size) {
tmp <<= 1;
}
size = (tmp > st->mem_size) ? tmp : st->mem_size;
mem->buf = ddi_umem_alloc(size, DDI_UMEM_SLEEP, &mem->cook);
mem->buf_size = size;
dcmn_err2((CE_CONT, "di_allocmem: mem_size=%x\n", st->mem_size));
if (st->mem_size == 0) {
st->memlist = mem;
} else {
struct di_mem *dcp = st->memlist;
while (dcp->next != NULL) {
dcp = dcp->next;
}
dcp->next = mem;
}
st->mem_size += size;
}
static void
di_copymem(struct di_state *st, caddr_t buf, size_t bufsiz)
{
struct di_mem *dcp;
size_t copysz;
if (st->mem_size == 0) {
ASSERT(st->memlist == NULL);
return;
}
copysz = 0;
for (dcp = st->memlist; dcp; dcp = dcp->next) {
ASSERT(bufsiz > 0);
if (bufsiz <= dcp->buf_size)
copysz = bufsiz;
else
copysz = dcp->buf_size;
bcopy(dcp->buf, buf, copysz);
buf += copysz;
bufsiz -= copysz;
if (bufsiz == 0)
break;
}
}
static void
di_freemem(struct di_state *st)
{
struct di_mem *dcp, *tmp;
dcmn_err2((CE_CONT, "di_freemem\n"));
if (st->mem_size) {
dcp = st->memlist;
while (dcp) {
tmp = dcp;
dcp = dcp->next;
ddi_umem_free(tmp->cook);
kmem_free(tmp, sizeof (struct di_mem));
}
st->mem_size = 0;
st->memlist = NULL;
}
ASSERT(st->mem_size == 0);
ASSERT(st->memlist == NULL);
}
static int
di_cache2mem(struct di_cache *cache, struct di_state *st)
{
caddr_t pa;
ASSERT(st->mem_size == 0);
ASSERT(st->memlist == NULL);
ASSERT(!servicing_interrupt());
ASSERT(DI_CACHE_LOCKED(*cache));
if (cache->cache_size == 0) {
ASSERT(cache->cache_data == NULL);
CACHE_DEBUG((DI_ERR, "Empty cache. Skipping copy"));
return (0);
}
ASSERT(cache->cache_data);
di_allocmem(st, cache->cache_size);
pa = di_mem_addr(st, 0);
ASSERT(pa);
ASSERT(st->memlist != NULL);
ASSERT(st->memlist->next == NULL);
bcopy(cache->cache_data, pa, cache->cache_size);
return (cache->cache_size);
}
static size_t
di_mem2cache(struct di_state *st, struct di_cache *cache)
{
size_t map_size;
ASSERT(cache->cache_size == 0);
ASSERT(cache->cache_data == NULL);
ASSERT(!servicing_interrupt());
ASSERT(DI_CACHE_LOCKED(*cache));
if (st->mem_size == 0) {
ASSERT(st->memlist == NULL);
CACHE_DEBUG((DI_ERR, "Empty memlist. Skipping copy"));
return (0);
}
ASSERT(st->memlist);
map_size = DI_ALL_PTR(st)->map_size;
if (map_size == 0 || map_size < sizeof (struct di_all) ||
map_size > st->mem_size) {
CACHE_DEBUG((DI_ERR, "cannot cache: bad size: 0x%x", map_size));
return (0);
}
cache->cache_data = kmem_alloc(map_size, KM_SLEEP);
cache->cache_size = map_size;
di_copymem(st, cache->cache_data, cache->cache_size);
return (map_size);
}
static di_off_t
di_checkmem(struct di_state *st, di_off_t off, size_t size)
{
dcmn_err3((CE_CONT, "di_checkmem: off=%x size=%x\n",
off, (int)size));
if (size == 0) {
dcmn_err((CE_WARN, "di_checkmem: invalid zero size used"));
size = 1;
}
off = DI_ALIGN(off);
if ((st->mem_size - off) < size) {
off = st->mem_size;
di_allocmem(st, size);
}
ASSERT(off == DI_ALIGN(off));
return (off);
}
static di_off_t
di_copyformat(di_off_t off, struct di_state *st, intptr_t arg, int mode)
{
di_off_t size;
struct di_priv_data *priv;
struct di_all *all = DI_ALL_PTR(st);
dcmn_err2((CE_CONT, "di_copyformat: off=%x, arg=%p mode=%x\n",
off, (void *)arg, mode));
priv = kmem_alloc(sizeof (struct di_priv_data), KM_SLEEP);
if ((ddi_copyin((void *)arg, priv, sizeof (struct di_priv_data),
mode) != 0) || (priv->version != DI_PRIVDATA_VERSION_0)) {
kmem_free(priv, sizeof (struct di_priv_data));
return (0);
}
all->pd_version = priv->version;
all->n_ppdata = priv->n_parent;
all->n_dpdata = priv->n_driver;
if (all->n_ppdata) {
size = all->n_ppdata * sizeof (struct di_priv_format);
all->ppdata_format = off = di_checkmem(st, off, size);
if (ddi_copyin(priv->parent, di_mem_addr(st, off), size,
mode) != 0) {
kmem_free(priv, sizeof (struct di_priv_data));
return (0);
}
off += size;
}
if (all->n_dpdata) {
size = all->n_dpdata * sizeof (struct di_priv_format);
all->dpdata_format = off = di_checkmem(st, off, size);
if (ddi_copyin(priv->driver, di_mem_addr(st, off), size,
mode) != 0) {
kmem_free(priv, sizeof (struct di_priv_data));
return (0);
}
off += size;
}
kmem_free(priv, sizeof (struct di_priv_data));
return (off);
}
static void *
di_mem_addr(struct di_state *st, di_off_t off)
{
struct di_mem *dcp = st->memlist;
dcmn_err3((CE_CONT, "di_mem_addr: dcp=%p off=%x\n",
(void *)dcp, off));
ASSERT(off < st->mem_size);
while (off >= dcp->buf_size) {
off -= dcp->buf_size;
dcp = dcp->next;
}
dcmn_err3((CE_CONT, "di_mem_addr: new off=%x, return = %p\n",
off, (void *)(dcp->buf + off)));
return (dcp->buf + off);
}
static uint_t
di_hash_byptr(void *arg, mod_hash_key_t key)
{
struct di_key *dik = key;
size_t rshift;
void *ptr;
ASSERT(arg == NULL);
switch (dik->k_type) {
case DI_DKEY:
ptr = dik->k_u.dkey.dk_dip;
rshift = highbit(sizeof (struct dev_info));
break;
case DI_PKEY:
ptr = dik->k_u.pkey.pk_pip;
rshift = highbit(sizeof (struct mdi_pathinfo));
break;
default:
panic("devinfo: unknown key type");
}
return (mod_hash_byptr((void *)rshift, ptr));
}
static void
di_key_dtor(mod_hash_key_t key)
{
char *path_addr;
struct di_key *dik = key;
switch (dik->k_type) {
case DI_DKEY:
break;
case DI_PKEY:
path_addr = dik->k_u.pkey.pk_path_addr;
if (path_addr)
kmem_free(path_addr, strlen(path_addr) + 1);
break;
default:
panic("devinfo: unknown key type");
}
kmem_free(dik, sizeof (struct di_key));
}
static int
di_dkey_cmp(struct di_dkey *dk1, struct di_dkey *dk2)
{
if (dk1->dk_dip != dk2->dk_dip)
return (dk1->dk_dip > dk2->dk_dip ? 1 : -1);
if (dk1->dk_major != DDI_MAJOR_T_NONE &&
dk2->dk_major != DDI_MAJOR_T_NONE) {
if (dk1->dk_major != dk2->dk_major)
return (dk1->dk_major > dk2->dk_major ? 1 : -1);
if (dk1->dk_inst != dk2->dk_inst)
return (dk1->dk_inst > dk2->dk_inst ? 1 : -1);
}
if (dk1->dk_nodeid != dk2->dk_nodeid)
return (dk1->dk_nodeid > dk2->dk_nodeid ? 1 : -1);
return (0);
}
static int
di_pkey_cmp(struct di_pkey *pk1, struct di_pkey *pk2)
{
char *p1, *p2;
int rv;
if (pk1->pk_pip != pk2->pk_pip)
return (pk1->pk_pip > pk2->pk_pip ? 1 : -1);
p1 = pk1->pk_path_addr;
p2 = pk2->pk_path_addr;
p1 = p1 ? p1 : "";
p2 = p2 ? p2 : "";
rv = strcmp(p1, p2);
if (rv)
return (rv > 0 ? 1 : -1);
if (pk1->pk_client != pk2->pk_client)
return (pk1->pk_client > pk2->pk_client ? 1 : -1);
if (pk1->pk_phci != pk2->pk_phci)
return (pk1->pk_phci > pk2->pk_phci ? 1 : -1);
return (0);
}
static int
di_key_cmp(mod_hash_key_t key1, mod_hash_key_t key2)
{
struct di_key *dik1, *dik2;
dik1 = key1;
dik2 = key2;
if (dik1->k_type != dik2->k_type) {
panic("devinfo: mismatched keys");
}
switch (dik1->k_type) {
case DI_DKEY:
return (di_dkey_cmp(&(dik1->k_u.dkey), &(dik2->k_u.dkey)));
case DI_PKEY:
return (di_pkey_cmp(&(dik1->k_u.pkey), &(dik2->k_u.pkey)));
default:
panic("devinfo: unknown key type");
}
}
static void
di_copy_aliases(struct di_state *st, alias_pair_t *apair, di_off_t *offp)
{
di_off_t off;
struct di_all *all = DI_ALL_PTR(st);
struct di_alias *di_alias;
di_off_t curroff;
dev_info_t *currdip;
size_t size;
currdip = NULL;
if (resolve_pathname(apair->pair_alias, &currdip, NULL, NULL) != 0) {
return;
}
if (di_dip_find(st, currdip, &curroff) != 0) {
ndi_rele_devi(currdip);
return;
}
ndi_rele_devi(currdip);
off = *offp;
size = sizeof (struct di_alias);
size += strlen(apair->pair_alias) + 1;
off = di_checkmem(st, off, size);
di_alias = DI_ALIAS(di_mem_addr(st, off));
di_alias->self = off;
di_alias->next = all->aliases;
all->aliases = off;
(void) strcpy(di_alias->alias, apair->pair_alias);
di_alias->curroff = curroff;
off += size;
*offp = off;
}
static di_off_t
di_snapshot(struct di_state *st)
{
di_off_t off;
struct di_all *all;
dev_info_t *rootnode;
char buf[80];
int plen;
char *path;
vnode_t *vp;
int i;
all = DI_ALL_PTR(st);
dcmn_err((CE_CONT, "Taking a snapshot of devinfo tree...\n"));
if (ddi_aliases_present == B_TRUE && strcmp(all->root_path, "/") != 0) {
rootnode = ddi_alias_redirect(all->root_path);
if (rootnode) {
(void) ddi_pathname(rootnode, all->root_path);
goto got_root;
}
}
plen = strlen("devices/") + strlen(all->root_path) + 1;
path = kmem_alloc(plen, KM_SLEEP);
(void) snprintf(path, plen, "devices/%s", all->root_path);
if (lookupnameat(path, UIO_SYSSPACE, FOLLOW, NULLVPP, &vp, rootdir)) {
dcmn_err((CE_CONT, "Devinfo node %s not found\n",
all->root_path));
kmem_free(path, plen);
return (0);
}
kmem_free(path, plen);
VN_RELE(vp);
rootnode = e_ddi_hold_devi_by_path(all->root_path, 0);
if (rootnode == NULL) {
dcmn_err((CE_CONT, "Devinfo node %s not found\n",
all->root_path));
return (0);
}
got_root:
(void) snprintf(buf, sizeof (buf),
"devinfo registered dips (statep=%p)", (void *)st);
st->reg_dip_hash = mod_hash_create_extended(buf, 64,
di_key_dtor, mod_hash_null_valdtor, di_hash_byptr,
NULL, di_key_cmp, KM_SLEEP);
(void) snprintf(buf, sizeof (buf),
"devinfo registered pips (statep=%p)", (void *)st);
st->reg_pip_hash = mod_hash_create_extended(buf, 64,
di_key_dtor, mod_hash_null_valdtor, di_hash_byptr,
NULL, di_key_cmp, KM_SLEEP);
if (DINFOHP & st->command) {
list_create(&st->hp_list, sizeof (i_hp_t),
offsetof(i_hp_t, hp_link));
}
off = di_copytree(DEVI(rootnode), &all->top_devinfo, st);
if (DINFOPATH & st->command) {
mdi_walk_vhcis(build_vhci_list, st);
}
if (DINFOHP & st->command) {
di_hotplug_children(st);
}
ddi_release_devi(rootnode);
all->devnames = off;
off = di_copydevnm(&all->devnames, st);
st->lnode_count = 0;
st->link_count = 0;
if (DINFOLYR & st->command) {
off = di_getlink_data(off, st);
}
all->aliases = 0;
if (ddi_aliases_present == B_FALSE)
goto done;
for (i = 0; i < ddi_aliases.dali_num_pairs; i++) {
di_copy_aliases(st, &(ddi_aliases.dali_alias_pairs[i]), &off);
}
done:
mod_hash_destroy_hash(st->reg_dip_hash);
mod_hash_destroy_hash(st->reg_pip_hash);
all->snapshot_time = ddi_get_time();
all->cache_checksum = 0;
ASSERT(all->snapshot_time != 0);
return (off);
}
static di_off_t
di_snapshot_and_clean(struct di_state *st)
{
di_off_t off;
modunload_disable();
off = di_snapshot(st);
if (off != 0 && (st->command & DINFOCLEANUP)) {
ASSERT(DEVICES_FILES_CLEANABLE(st));
i_ddi_clean_devices_files();
dev_devices_cleanup();
}
modunload_enable();
return (off);
}
static int
build_vhci_list(dev_info_t *vh_devinfo, void *arg)
{
struct di_all *all;
struct di_node *me;
struct di_state *st;
di_off_t off;
phci_walk_arg_t pwa;
dcmn_err3((CE_CONT, "build_vhci list\n"));
dcmn_err3((CE_CONT, "vhci node %s%d\n",
ddi_driver_name(vh_devinfo), ddi_get_instance(vh_devinfo)));
st = (struct di_state *)arg;
if (di_dip_find(st, vh_devinfo, &off) != 0) {
dcmn_err((CE_WARN, "di_dip_find error for the given node\n"));
return (DDI_WALK_TERMINATE);
}
dcmn_err3((CE_CONT, "st->mem_size: %d vh_devinfo off: 0x%x\n",
st->mem_size, off));
all = DI_ALL_PTR(st);
if (all->top_vhci_devinfo == 0) {
all->top_vhci_devinfo = off;
} else {
me = DI_NODE(di_mem_addr(st, all->top_vhci_devinfo));
while (me->next_vhci != 0) {
me = DI_NODE(di_mem_addr(st, me->next_vhci));
}
me->next_vhci = off;
}
pwa.off = off;
pwa.st = st;
mdi_vhci_walk_phcis(vh_devinfo, build_phci_list, &pwa);
return (DDI_WALK_CONTINUE);
}
static int
build_phci_list(dev_info_t *ph_devinfo, void *arg)
{
struct di_node *vh_di_node;
struct di_node *me;
phci_walk_arg_t *pwa;
di_off_t off;
pwa = (phci_walk_arg_t *)arg;
dcmn_err3((CE_CONT, "build_phci list for vhci at offset: 0x%x\n",
pwa->off));
vh_di_node = DI_NODE(di_mem_addr(pwa->st, pwa->off));
if (di_dip_find(pwa->st, ph_devinfo, &off) != 0) {
dcmn_err((CE_WARN, "di_dip_find error for the given node\n"));
return (DDI_WALK_TERMINATE);
}
dcmn_err3((CE_CONT, "phci node %s%d, at offset 0x%x\n",
ddi_driver_name(ph_devinfo), ddi_get_instance(ph_devinfo), off));
if (vh_di_node->top_phci == 0) {
vh_di_node->top_phci = off;
return (DDI_WALK_CONTINUE);
}
me = DI_NODE(di_mem_addr(pwa->st, vh_di_node->top_phci));
while (me->next_phci != 0) {
me = DI_NODE(di_mem_addr(pwa->st, me->next_phci));
}
me->next_phci = off;
return (DDI_WALK_CONTINUE);
}
static void
snap_driver_list(struct di_state *st, struct devnames *dnp, di_off_t *off_p)
{
struct dev_info *node;
struct di_node *me;
di_off_t off;
ASSERT(mutex_owned(&dnp->dn_lock));
node = DEVI(dnp->dn_head);
for (; node; node = node->devi_next) {
if (di_dip_find(st, (dev_info_t *)node, &off) != 0)
continue;
ASSERT(off > 0);
me = DI_NODE(di_mem_addr(st, off));
ASSERT(me->next == 0 || me->next == -1);
if (me->next != -1)
continue;
*off_p = off;
off_p = &me->next;
}
*off_p = 0;
}
static di_off_t
di_copydevnm(di_off_t *off_p, struct di_state *st)
{
int i;
di_off_t off;
size_t size;
struct di_devnm *dnp;
dcmn_err2((CE_CONT, "di_copydevnm: *off_p = %p\n", (void *)off_p));
size = devcnt * sizeof (struct di_devnm);
*off_p = off = di_checkmem(st, *off_p, size);
dnp = DI_DEVNM(di_mem_addr(st, off));
off += size;
dcmn_err((CE_CONT, "Start copying devnamesp[%d] at offset 0x%x\n",
devcnt, off));
for (i = 0; i < devcnt; i++) {
if (devnamesp[i].dn_name == NULL) {
continue;
}
dcmn_err2((CE_CONT, "di_copydevnm: %s%d, off=%x\n",
devnamesp[i].dn_name, devnamesp[i].dn_instance, off));
size = strlen(devnamesp[i].dn_name) + 1;
dnp[i].name = off = di_checkmem(st, off, size);
(void) strcpy((char *)di_mem_addr(st, off),
devnamesp[i].dn_name);
off += size;
mutex_enter(&devnamesp[i].dn_lock);
snap_driver_list(st, &devnamesp[i], &dnp[i].head);
dnp[i].flags = devnamesp[i].dn_flags;
dnp[i].instance = devnamesp[i].dn_instance;
if ((DINFOPROP & st->command) &&
devnamesp[i].dn_global_prop_ptr) {
dnp[i].global_prop = off;
off = di_getprop(DI_PROP_GLB_LIST,
&devnamesp[i].dn_global_prop_ptr->prop_list,
&dnp[i].global_prop, st, NULL);
}
if (CB_DRV_INSTALLED(devopsp[i])) {
if (devopsp[i]->devo_cb_ops) {
dnp[i].ops |= DI_CB_OPS;
if (devopsp[i]->devo_cb_ops->cb_str)
dnp[i].ops |= DI_STREAM_OPS;
}
if (NEXUS_DRV(devopsp[i])) {
dnp[i].ops |= DI_BUS_OPS;
}
}
mutex_exit(&devnamesp[i].dn_lock);
}
dcmn_err((CE_CONT, "End copying devnamesp at offset 0x%x\n", off));
return (off);
}
static di_off_t
di_copytree(struct dev_info *root, di_off_t *off_p, struct di_state *st)
{
di_off_t off;
struct dev_info *node;
struct di_stack *dsp = kmem_zalloc(sizeof (struct di_stack), KM_SLEEP);
dcmn_err((CE_CONT, "di_copytree: root = %p, *off_p = %x\n",
(void *)root, *off_p));
if (i_ddi_devi_attached((dev_info_t *)root) &&
(st->command & DINFOSUBTREE) && (st->command & DINFOFORCE)) {
(void) ndi_devi_config((dev_info_t *)root,
NDI_CONFIG | NDI_DEVI_PERSIST | NDI_NO_EVENT |
NDI_DRV_CONF_REPROBE);
}
PUSH_STACK(dsp, root, off_p);
while (!EMPTY_STACK(dsp)) {
node = TOP_NODE(dsp);
off = di_copynode(node, dsp, st);
}
kmem_free(dsp, sizeof (struct di_stack));
return (off);
}
static di_off_t
di_copynode(struct dev_info *node, struct di_stack *dsp, struct di_state *st)
{
di_off_t off;
struct di_node *me;
size_t size;
struct dev_info *n;
dcmn_err2((CE_CONT, "di_copynode: depth = %x\n", dsp->depth));
ASSERT((node != NULL) && (node == TOP_NODE(dsp)));
size = sizeof (struct di_node);
*(TOP_OFFSET(dsp)) = off = di_checkmem(st, *(TOP_OFFSET(dsp)), size);
me = DI_NODE(di_mem_addr(st, off));
me->self = off;
off += size;
dcmn_err((CE_CONT, "copy node %s, instance #%d, at offset 0x%x\n",
node->devi_node_name, node->devi_instance, off));
me->instance = node->devi_instance;
me->nodeid = node->devi_nodeid;
me->node_class = node->devi_node_class;
me->attributes = node->devi_node_attributes;
me->state = node->devi_state;
me->flags = node->devi_flags;
me->node_state = node->devi_node_state;
me->next_vhci = 0;
me->top_phci = 0;
me->next_phci = 0;
me->multipath_component = MULTIPATH_COMPONENT_NONE;
me->user_private_data = 0;
if (dsp->depth > 1) {
me->parent = *(PARENT_OFFSET(dsp));
}
di_register_dip(st, (dev_info_t *)node, me->self);
#ifdef DEVID_COMPATIBILITY
if (node->devi_devid_str) {
ddi_devid_t devid;
if (ddi_devid_str_decode(node->devi_devid_str, &devid, NULL) ==
DDI_SUCCESS) {
size = ddi_devid_sizeof(devid);
off = di_checkmem(st, off, size);
me->devid = off;
bcopy(devid, di_mem_addr(st, off), size);
off += size;
ddi_devid_free(devid);
}
}
#endif
if (node->devi_node_name) {
size = strlen(node->devi_node_name) + 1;
me->node_name = off = di_checkmem(st, off, size);
(void) strcpy(di_mem_addr(st, off), node->devi_node_name);
off += size;
}
if (node->devi_compat_names && (node->devi_compat_length > 1)) {
size = node->devi_compat_length;
me->compat_names = off = di_checkmem(st, off, size);
me->compat_length = (int)size;
bcopy(node->devi_compat_names, di_mem_addr(st, off), size);
off += size;
}
if (node->devi_addr) {
size = strlen(node->devi_addr) + 1;
me->address = off = di_checkmem(st, off, size);
(void) strcpy(di_mem_addr(st, off), node->devi_addr);
off += size;
}
if (node->devi_binding_name) {
size = strlen(node->devi_binding_name) + 1;
me->bind_name = off = di_checkmem(st, off, size);
(void) strcpy(di_mem_addr(st, off), node->devi_binding_name);
off += size;
}
me->drv_major = node->devi_major;
if (me->drv_major != -1) {
me->next = -1;
} else {
me->next = 0;
}
if (!((DINFOMINOR | DINFOPROP | DINFOPATH | DINFOHP) & st->command)) {
goto priv_data;
}
if (!(DINFOMINOR & st->command)) {
goto path;
}
ASSERT(DEVI_BUSY_OWNED(node));
if (node->devi_minor) {
me->minor_data = off;
off = di_getmdata(node->devi_minor, &me->minor_data,
me->self, st);
}
path:
if (!(DINFOPATH & st->command)) {
goto property;
}
if (MDI_VHCI(node)) {
me->multipath_component = MULTIPATH_COMPONENT_VHCI;
}
if (MDI_CLIENT(node)) {
me->multipath_component = MULTIPATH_COMPONENT_CLIENT;
me->multipath_client = off;
off = di_getpath_data((dev_info_t *)node, &me->multipath_client,
me->self, st, 1);
dcmn_err((CE_WARN, "me->multipath_client = %x for node %p "
"component type = %d. off=%d",
me->multipath_client,
(void *)node, node->devi_mdi_component, off));
}
if (MDI_PHCI(node)) {
me->multipath_component = MULTIPATH_COMPONENT_PHCI;
me->multipath_phci = off;
off = di_getpath_data((dev_info_t *)node, &me->multipath_phci,
me->self, st, 0);
dcmn_err((CE_WARN, "me->multipath_phci = %x for node %p "
"component type = %d. off=%d",
me->multipath_phci,
(void *)node, node->devi_mdi_component, off));
}
property:
if (!(DINFOPROP & st->command)) {
goto hotplug_data;
}
if (node->devi_drv_prop_ptr) {
me->drv_prop = off;
off = di_getprop(DI_PROP_DRV_LIST, &node->devi_drv_prop_ptr,
&me->drv_prop, st, node);
}
if (node->devi_sys_prop_ptr) {
me->sys_prop = off;
off = di_getprop(DI_PROP_SYS_LIST, &node->devi_sys_prop_ptr,
&me->sys_prop, st, node);
}
if (node->devi_hw_prop_ptr) {
me->hw_prop = off;
off = di_getprop(DI_PROP_HW_LIST, &node->devi_hw_prop_ptr,
&me->hw_prop, st, node);
}
if (node->devi_global_prop_list == NULL) {
me->glob_prop = (di_off_t)-1;
} else {
ASSERT(me->drv_major != -1);
if (node->devi_global_prop_list !=
devnamesp[me->drv_major].dn_global_prop_ptr) {
me->glob_prop = off;
off = di_getprop(DI_PROP_GLB_LIST,
&node->devi_global_prop_list->prop_list,
&me->glob_prop, st, node);
}
}
hotplug_data:
if (!(DINFOHP & st->command)) {
goto priv_data;
}
if (node->devi_hp_hdlp) {
me->hp_data = off;
off = di_gethpdata(node->devi_hp_hdlp, &me->hp_data, st);
}
priv_data:
if (!(DINFOPRIVDATA & st->command)) {
goto pm_info;
}
if (ddi_get_parent_data((dev_info_t *)node) != NULL) {
me->parent_data = off;
off = di_getppdata(node, &me->parent_data, st);
}
if (ddi_get_driver_private((dev_info_t *)node) != NULL) {
me->driver_data = off;
off = di_getdpdata(node, &me->driver_data, st);
}
pm_info:
off = DI_ALIGN(off);
if (!(DINFOSUBTREE & st->command)) {
POP_STACK(dsp);
return (off);
}
if ((n = node->devi_child) != NULL) {
while (n && ndi_dev_is_hidden_node((dev_info_t *)n))
n = n->devi_sibling;
if (n) {
me->child = off;
PUSH_STACK(dsp, n, &me->child);
return (me->child);
}
}
POP_STACK(dsp);
while (!EMPTY_STACK(dsp)) {
if ((n = node->devi_sibling) != NULL) {
while (n && ndi_dev_is_hidden_node((dev_info_t *)n))
n = n->devi_sibling;
if (n) {
me->sibling = DI_ALIGN(off);
PUSH_STACK(dsp, n, &me->sibling);
return (me->sibling);
}
}
node = TOP_NODE(dsp);
me = DI_NODE(di_mem_addr(st, *(TOP_OFFSET(dsp))));
POP_STACK(dsp);
}
return (off);
}
static i_lnode_t *
i_lnode_alloc(int modid)
{
i_lnode_t *i_lnode;
i_lnode = kmem_zalloc(sizeof (i_lnode_t), KM_SLEEP);
ASSERT(modid != -1);
i_lnode->modid = modid;
return (i_lnode);
}
static void
i_lnode_free(i_lnode_t *i_lnode)
{
kmem_free(i_lnode, sizeof (i_lnode_t));
}
static void
i_lnode_check_free(i_lnode_t *i_lnode)
{
ASSERT(i_lnode->self > 0);
ASSERT(i_lnode->di_node->self > 0);
ASSERT(i_lnode->link_in || i_lnode->link_out);
i_lnode_free(i_lnode);
}
static i_link_t *
i_link_alloc(int spec_type)
{
i_link_t *i_link;
i_link = kmem_zalloc(sizeof (i_link_t), KM_SLEEP);
i_link->spec_type = spec_type;
return (i_link);
}
static void
i_link_check_free(i_link_t *i_link)
{
ASSERT(i_link->self > 0);
ASSERT(i_link->src_lnode);
ASSERT(i_link->tgt_lnode);
kmem_free(i_link, sizeof (i_link_t));
}
static uint_t
i_lnode_hashfunc(void *arg, mod_hash_key_t key)
{
i_lnode_t *i_lnode = (i_lnode_t *)key;
struct di_node *ptr;
dev_t dev;
dev = i_lnode->devt;
if (dev != DDI_DEV_T_NONE)
return (i_lnode->modid + getminor(dev) + getmajor(dev));
ptr = i_lnode->di_node;
ASSERT(ptr->self > 0);
if (ptr) {
uintptr_t k = (uintptr_t)ptr;
k >>= (int)highbit(sizeof (struct di_node));
return ((uint_t)k);
}
return (i_lnode->modid);
}
static int
i_lnode_cmp(void *arg1, void *arg2)
{
i_lnode_t *i_lnode1 = (i_lnode_t *)arg1;
i_lnode_t *i_lnode2 = (i_lnode_t *)arg2;
if (i_lnode1->modid != i_lnode2->modid) {
return ((i_lnode1->modid < i_lnode2->modid) ? -1 : 1);
}
if (i_lnode1->di_node != i_lnode2->di_node)
return ((i_lnode1->di_node < i_lnode2->di_node) ? -1 : 1);
if (i_lnode1->devt != i_lnode2->devt)
return ((i_lnode1->devt < i_lnode2->devt) ? -1 : 1);
return (0);
}
static int
di_ldi_callback(const ldi_usage_t *ldi_usage, void *arg)
{
struct di_state *st = (struct di_state *)arg;
i_lnode_t *src_lnode, *tgt_lnode, *i_lnode;
i_link_t **i_link_next, *i_link;
di_off_t soff, toff;
mod_hash_val_t nodep = NULL;
int res;
if ((ldi_usage->src_dip == NULL) || (ldi_usage->tgt_dip == NULL))
return (LDI_USAGE_CONTINUE);
ASSERT(e_ddi_devi_holdcnt(ldi_usage->src_dip));
ASSERT(e_ddi_devi_holdcnt(ldi_usage->tgt_dip));
if (di_dip_find(st, ldi_usage->src_dip, &soff) != 0)
return (LDI_USAGE_CONTINUE);
if (di_dip_find(st, ldi_usage->tgt_dip, &toff) != 0)
return (LDI_USAGE_CONTINUE);
ASSERT(soff > 0);
ASSERT(toff > 0);
i_lnode = i_lnode_alloc(ldi_usage->src_modid);
i_lnode->di_node = DI_NODE(di_mem_addr(st, soff));
i_lnode->devt = ldi_usage->src_devt;
res = mod_hash_find(st->lnode_hash, i_lnode, &nodep);
if (res == MH_ERR_NOTFOUND) {
res = mod_hash_insert(st->lnode_hash, i_lnode, i_lnode);
ASSERT(res == 0);
st->lnode_count++;
src_lnode = i_lnode;
} else {
i_lnode_free(i_lnode);
src_lnode = (i_lnode_t *)nodep;
}
i_lnode = i_lnode_alloc(ldi_usage->tgt_modid);
i_lnode->di_node = DI_NODE(di_mem_addr(st, toff));
i_lnode->devt = ldi_usage->tgt_devt;
res = mod_hash_find(st->lnode_hash, i_lnode, &nodep);
if (res == MH_ERR_NOTFOUND) {
res = mod_hash_insert(st->lnode_hash, i_lnode, i_lnode);
ASSERT(res == 0);
st->lnode_count++;
tgt_lnode = i_lnode;
} else {
i_lnode_free(i_lnode);
tgt_lnode = (i_lnode_t *)nodep;
}
i_link = i_link_alloc(ldi_usage->tgt_spec_type);
i_link->src_lnode = src_lnode;
i_link->tgt_lnode = tgt_lnode;
i_link_next = &(src_lnode->link_out);
while (*i_link_next != NULL) {
if ((i_lnode_cmp(tgt_lnode, (*i_link_next)->tgt_lnode) == 0) &&
(i_link->spec_type == (*i_link_next)->spec_type)) {
kmem_free(i_link, sizeof (i_link_t));
return (LDI_USAGE_CONTINUE);
}
i_link_next = &((*i_link_next)->src_link_next);
}
*i_link_next = i_link;
i_link_next = &(tgt_lnode->link_in);
while (*i_link_next != NULL) {
ASSERT(i_lnode_cmp(src_lnode, (*i_link_next)->src_lnode) != 0);
i_link_next = &((*i_link_next)->tgt_link_next);
}
*i_link_next = i_link;
res = mod_hash_insert(st->link_hash, i_link, i_link);
ASSERT(res == 0);
st->link_count++;
return (LDI_USAGE_CONTINUE);
}
struct i_layer_data {
struct di_state *st;
int lnode_count;
int link_count;
di_off_t lnode_off;
di_off_t link_off;
};
static uint_t
i_link_walker(mod_hash_key_t key, mod_hash_val_t *val, void *arg)
{
i_link_t *i_link = (i_link_t *)key;
struct i_layer_data *data = arg;
struct di_link *me;
struct di_lnode *melnode;
struct di_node *medinode;
ASSERT(i_link->self == 0);
i_link->self = data->link_off +
(data->link_count * sizeof (struct di_link));
data->link_count++;
ASSERT(data->link_off > 0 && data->link_count > 0);
ASSERT(data->lnode_count == data->st->lnode_count);
ASSERT(data->link_count <= data->st->link_count);
me = DI_LINK(di_mem_addr(data->st, i_link->self));
me->self = i_link->self;
me->spec_type = i_link->spec_type;
me->src_lnode = i_link->src_lnode->self;
me->tgt_lnode = i_link->tgt_lnode->self;
melnode = DI_LNODE(di_mem_addr(data->st, me->src_lnode));
me->src_link_next = melnode->link_out;
melnode->link_out = me->self;
melnode = DI_LNODE(di_mem_addr(data->st, me->tgt_lnode));
me->tgt_link_next = melnode->link_in;
melnode->link_in = me->self;
medinode = i_link->src_lnode->di_node;
me->src_node_next = medinode->src_links;
medinode->src_links = me->self;
medinode = i_link->tgt_lnode->di_node;
me->tgt_node_next = medinode->tgt_links;
medinode->tgt_links = me->self;
return (MH_WALK_CONTINUE);
}
static uint_t
i_lnode_walker(mod_hash_key_t key, mod_hash_val_t *val, void *arg)
{
i_lnode_t *i_lnode = (i_lnode_t *)key;
struct i_layer_data *data = arg;
struct di_lnode *me;
struct di_node *medinode;
ASSERT(i_lnode->self == 0);
i_lnode->self = data->lnode_off +
(data->lnode_count * sizeof (struct di_lnode));
data->lnode_count++;
ASSERT(data->lnode_off > 0 && data->lnode_count > 0);
ASSERT(data->link_count == 0);
ASSERT(data->lnode_count <= data->st->lnode_count);
me = DI_LNODE(di_mem_addr(data->st, i_lnode->self));
me->self = i_lnode->self;
if (i_lnode->devt == DDI_DEV_T_NONE) {
me->dev_major = DDI_MAJOR_T_NONE;
me->dev_minor = DDI_MAJOR_T_NONE;
} else {
me->dev_major = getmajor(i_lnode->devt);
me->dev_minor = getminor(i_lnode->devt);
}
ASSERT(i_lnode->di_node && i_lnode->di_node->self > 0);
me->node = i_lnode->di_node->self;
ASSERT((i_lnode->link_in != NULL) || (i_lnode->link_out != NULL));
medinode = i_lnode->di_node;
me->node_next = medinode->lnodes;
medinode->lnodes = me->self;
return (MH_WALK_CONTINUE);
}
static di_off_t
di_getlink_data(di_off_t off, struct di_state *st)
{
struct i_layer_data data = {0};
size_t size;
dcmn_err2((CE_CONT, "di_copylyr: off = %x\n", off));
st->lnode_hash = mod_hash_create_extended("di_lnode_hash", 32,
mod_hash_null_keydtor, (void (*)(mod_hash_val_t))i_lnode_check_free,
i_lnode_hashfunc, NULL, i_lnode_cmp, KM_SLEEP);
st->link_hash = mod_hash_create_ptrhash("di_link_hash", 32,
(void (*)(mod_hash_val_t))i_link_check_free, sizeof (i_link_t));
(void) ldi_usage_walker(st, di_ldi_callback);
if (st->lnode_count == 0) {
ASSERT(st->link_count == 0);
goto out;
}
ASSERT(st->link_count != 0);
size = sizeof (struct di_lnode) * st->lnode_count;
data.lnode_off = off = di_checkmem(st, off, size);
off += size;
size = sizeof (struct di_link) * st->link_count;
data.link_off = off = di_checkmem(st, off, size);
off += size;
data.lnode_count = data.link_count = 0;
data.st = st;
mod_hash_walk(st->lnode_hash, i_lnode_walker, (void *)&data);
ASSERT(data.lnode_count == st->lnode_count);
mod_hash_walk(st->link_hash, i_link_walker, (void *)&data);
ASSERT(data.link_count == st->link_count);
out:
mod_hash_destroy_hash(st->lnode_hash);
mod_hash_destroy_hash(st->link_hash);
st->lnode_count = 0;
st->link_count = 0;
return (off);
}
static di_off_t
di_getmdata(struct ddi_minor_data *mnode, di_off_t *off_p, di_off_t node,
struct di_state *st)
{
di_off_t off;
struct di_minor *me;
size_t size;
dcmn_err2((CE_CONT, "di_getmdata:\n"));
off = di_checkmem(st, *off_p, sizeof (struct di_minor));
*off_p = off;
do {
me = DI_MINOR(di_mem_addr(st, off));
me->self = off;
me->type = mnode->type;
me->node = node;
me->user_private_data = 0;
off += sizeof (struct di_minor);
me->dev_major = getmajor(mnode->ddm_dev);
me->dev_minor = getminor(mnode->ddm_dev);
me->spec_type = mnode->ddm_spec_type;
if (mnode->ddm_name) {
size = strlen(mnode->ddm_name) + 1;
me->name = off = di_checkmem(st, off, size);
(void) strcpy(di_mem_addr(st, off), mnode->ddm_name);
off += size;
}
if (mnode->ddm_node_type) {
size = strlen(mnode->ddm_node_type) + 1;
me->node_type = off = di_checkmem(st, off, size);
(void) strcpy(di_mem_addr(st, off),
mnode->ddm_node_type);
off += size;
}
off = di_checkmem(st, off, sizeof (struct di_minor));
me->next = off;
mnode = mnode->next;
} while (mnode);
me->next = 0;
return (off);
}
static void
di_register_dip(struct di_state *st, dev_info_t *dip, di_off_t off)
{
struct dev_info *node = DEVI(dip);
struct di_key *key = kmem_zalloc(sizeof (*key), KM_SLEEP);
struct di_dkey *dk;
ASSERT(dip);
ASSERT(off > 0);
key->k_type = DI_DKEY;
dk = &(key->k_u.dkey);
dk->dk_dip = dip;
dk->dk_major = node->devi_major;
dk->dk_inst = node->devi_instance;
dk->dk_nodeid = node->devi_nodeid;
if (mod_hash_insert(st->reg_dip_hash, (mod_hash_key_t)key,
(mod_hash_val_t)(uintptr_t)off) != 0) {
panic(
"duplicate devinfo (%p) registered during device "
"tree walk", (void *)dip);
}
}
static int
di_dip_find(struct di_state *st, dev_info_t *dip, di_off_t *off_p)
{
uintptr_t offset;
struct di_key key = {0};
struct di_dkey *dk;
ASSERT(st->reg_dip_hash);
ASSERT(dip);
ASSERT(off_p);
key.k_type = DI_DKEY;
dk = &(key.k_u.dkey);
dk->dk_dip = dip;
dk->dk_major = DEVI(dip)->devi_major;
dk->dk_inst = DEVI(dip)->devi_instance;
dk->dk_nodeid = DEVI(dip)->devi_nodeid;
if (mod_hash_find(st->reg_dip_hash, (mod_hash_key_t)&key,
(mod_hash_val_t *)&offset) == 0) {
*off_p = (di_off_t)offset;
return (0);
} else {
return (-1);
}
}
static void
di_register_pip(struct di_state *st, mdi_pathinfo_t *pip, di_off_t off)
{
struct di_key *key = kmem_zalloc(sizeof (*key), KM_SLEEP);
char *path_addr;
struct di_pkey *pk;
ASSERT(pip);
ASSERT(off > 0);
key->k_type = DI_PKEY;
pk = &(key->k_u.pkey);
pk->pk_pip = pip;
path_addr = mdi_pi_get_addr(pip);
if (path_addr)
pk->pk_path_addr = i_ddi_strdup(path_addr, KM_SLEEP);
pk->pk_client = mdi_pi_get_client(pip);
pk->pk_phci = mdi_pi_get_phci(pip);
if (mod_hash_insert(st->reg_pip_hash, (mod_hash_key_t)key,
(mod_hash_val_t)(uintptr_t)off) != 0) {
panic(
"duplicate pathinfo (%p) registered during device "
"tree walk", (void *)pip);
}
}
static int
di_pip_find(struct di_state *st, mdi_pathinfo_t *pip, di_off_t *off_p)
{
uintptr_t offset;
struct di_key key = {0};
struct di_pkey *pk;
ASSERT(st->reg_pip_hash);
ASSERT(off_p);
if (pip == NULL) {
*off_p = 0;
return (0);
}
key.k_type = DI_PKEY;
pk = &(key.k_u.pkey);
pk->pk_pip = pip;
pk->pk_path_addr = mdi_pi_get_addr(pip);
pk->pk_client = mdi_pi_get_client(pip);
pk->pk_phci = mdi_pi_get_phci(pip);
if (mod_hash_find(st->reg_pip_hash, (mod_hash_key_t)&key,
(mod_hash_val_t *)&offset) == 0) {
*off_p = (di_off_t)offset;
return (0);
} else {
return (-1);
}
}
static di_path_state_t
path_state_convert(mdi_pathinfo_state_t st)
{
switch (st) {
case MDI_PATHINFO_STATE_ONLINE:
return (DI_PATH_STATE_ONLINE);
case MDI_PATHINFO_STATE_STANDBY:
return (DI_PATH_STATE_STANDBY);
case MDI_PATHINFO_STATE_OFFLINE:
return (DI_PATH_STATE_OFFLINE);
case MDI_PATHINFO_STATE_FAULT:
return (DI_PATH_STATE_FAULT);
default:
return (DI_PATH_STATE_UNKNOWN);
}
}
static uint_t
path_flags_convert(uint_t pi_path_flags)
{
uint_t di_path_flags = 0;
if (pi_path_flags & MDI_PATHINFO_FLAGS_DEVICE_REMOVED)
di_path_flags |= DI_PATH_FLAGS_DEVICE_REMOVED;
return (di_path_flags);
}
static di_off_t
di_path_getprop(mdi_pathinfo_t *pip, di_off_t *off_p,
struct di_state *st)
{
nvpair_t *prop = NULL;
struct di_path_prop *me;
int off;
size_t size;
char *str;
uchar_t *buf;
uint_t nelems;
off = *off_p;
if (mdi_pi_get_next_prop(pip, NULL) == NULL) {
*off_p = 0;
return (off);
}
off = di_checkmem(st, off, sizeof (struct di_path_prop));
*off_p = off;
while (prop = mdi_pi_get_next_prop(pip, prop)) {
me = DI_PATHPROP(di_mem_addr(st, off));
me->self = off;
off += sizeof (struct di_path_prop);
size = strlen(nvpair_name(prop)) + 1;
me->prop_name = off = di_checkmem(st, off, size);
(void) strcpy(di_mem_addr(st, off), nvpair_name(prop));
off += size;
switch (nvpair_type(prop)) {
case DATA_TYPE_BYTE:
case DATA_TYPE_INT16:
case DATA_TYPE_UINT16:
case DATA_TYPE_INT32:
case DATA_TYPE_UINT32:
me->prop_type = DDI_PROP_TYPE_INT;
size = sizeof (int32_t);
off = di_checkmem(st, off, size);
(void) nvpair_value_int32(prop,
(int32_t *)di_mem_addr(st, off));
break;
case DATA_TYPE_INT64:
case DATA_TYPE_UINT64:
me->prop_type = DDI_PROP_TYPE_INT64;
size = sizeof (int64_t);
off = di_checkmem(st, off, size);
(void) nvpair_value_int64(prop,
(int64_t *)di_mem_addr(st, off));
break;
case DATA_TYPE_STRING:
me->prop_type = DDI_PROP_TYPE_STRING;
(void) nvpair_value_string(prop, &str);
size = strlen(str) + 1;
off = di_checkmem(st, off, size);
(void) strcpy(di_mem_addr(st, off), str);
break;
case DATA_TYPE_BYTE_ARRAY:
case DATA_TYPE_INT16_ARRAY:
case DATA_TYPE_UINT16_ARRAY:
case DATA_TYPE_INT32_ARRAY:
case DATA_TYPE_UINT32_ARRAY:
case DATA_TYPE_INT64_ARRAY:
case DATA_TYPE_UINT64_ARRAY:
me->prop_type = DDI_PROP_TYPE_BYTE;
(void) nvpair_value_byte_array(prop, &buf, &nelems);
size = nelems;
if (nelems != 0) {
off = di_checkmem(st, off, size);
bcopy(buf, di_mem_addr(st, off), size);
}
break;
default:
size = 0;
break;
}
if (size > 0) {
me->prop_data = off;
}
me->prop_len = (int)size;
off += size;
off = di_checkmem(st, off, sizeof (struct di_path_prop));
me->prop_next = off;
}
me->prop_next = 0;
return (off);
}
static void
di_path_one_endpoint(struct di_path *me, di_off_t noff, di_off_t **off_pp,
int get_client)
{
if (get_client) {
ASSERT(me->path_client == 0);
me->path_client = noff;
ASSERT(me->path_c_link == 0);
*off_pp = &me->path_c_link;
me->path_snap_state &=
~(DI_PATH_SNAP_NOCLIENT | DI_PATH_SNAP_NOCLINK);
} else {
ASSERT(me->path_phci == 0);
me->path_phci = noff;
ASSERT(me->path_p_link == 0);
*off_pp = &me->path_p_link;
me->path_snap_state &=
~(DI_PATH_SNAP_NOPHCI | DI_PATH_SNAP_NOPLINK);
}
}
static di_off_t
di_getpath_data(dev_info_t *dip, di_off_t *off_p, di_off_t noff,
struct di_state *st, int get_client)
{
di_off_t off;
mdi_pathinfo_t *pip;
struct di_path *me;
mdi_pathinfo_t *(*next_pip)(dev_info_t *, mdi_pathinfo_t *);
size_t size;
dcmn_err2((CE_WARN, "di_getpath_data: client = %d", get_client));
if (get_client)
next_pip = &mdi_get_next_phci_path;
else
next_pip = &mdi_get_next_client_path;
off = *off_p;
pip = NULL;
while (pip = (*next_pip)(dip, pip)) {
di_off_t stored_offset;
dcmn_err((CE_WARN, "marshalling pip = %p", (void *)pip));
mdi_pi_lock(pip);
if (mdi_pi_ishidden(pip)) {
dcmn_err((CE_WARN, "hidden, skip"));
mdi_pi_unlock(pip);
continue;
}
if (di_pip_find(st, pip, &stored_offset) != -1) {
me = DI_PATH(di_mem_addr(st, stored_offset));
*off_p = stored_offset;
di_path_one_endpoint(me, noff, &off_p, get_client);
ASSERT(!(me->path_snap_state &
(DI_PATH_SNAP_NOCLIENT|DI_PATH_SNAP_NOPHCI)));
ASSERT(!(me->path_snap_state &
(DI_PATH_SNAP_NOCLINK|DI_PATH_SNAP_NOPLINK)));
mdi_pi_unlock(pip);
continue;
}
size = sizeof (struct di_path);
*off_p = off = di_checkmem(st, off, size);
me = DI_PATH(di_mem_addr(st, off));
me->self = off;
off += size;
me->path_snap_state =
DI_PATH_SNAP_NOCLINK | DI_PATH_SNAP_NOPLINK;
me->path_snap_state |=
DI_PATH_SNAP_NOCLIENT | DI_PATH_SNAP_NOPHCI;
me->path_client = me->path_phci = 0;
me->path_c_link = me->path_p_link = 0;
di_path_one_endpoint(me, noff, &off_p, get_client);
di_register_pip(st, pip, me->self);
me->path_state = path_state_convert(mdi_pi_get_state(pip));
me->path_flags = path_flags_convert(mdi_pi_get_flags(pip));
me->path_instance = mdi_pi_get_path_instance(pip);
size = strlen(mdi_pi_get_addr(pip)) + 1;
me->path_addr = off = di_checkmem(st, off, size);
(void) strcpy(di_mem_addr(st, off), mdi_pi_get_addr(pip));
off += size;
if (DINFOPROP & st->command) {
me->path_prop = off;
off = di_path_getprop(pip, &me->path_prop, st);
} else {
me->path_prop = 0;
}
mdi_pi_unlock(pip);
}
*off_p = 0;
return (off);
}
static int
(*di_getprop_prop_op(struct dev_info *dip))
(dev_t, dev_info_t *, ddi_prop_op_t, int, char *, caddr_t, int *)
{
struct dev_ops *ops;
if ((dip == NULL) || !i_ddi_devi_attached((dev_info_t *)dip))
return (NULL);
ops = dip->devi_ops;
if (ops && ops->devo_cb_ops &&
(ops->devo_cb_ops->cb_prop_op != ddi_prop_op) &&
(ops->devo_cb_ops->cb_prop_op != nodev) &&
(ops->devo_cb_ops->cb_prop_op != nulldev) &&
(ops->devo_cb_ops->cb_prop_op != NULL))
return (ops->devo_cb_ops->cb_prop_op);
return (NULL);
}
static di_off_t
di_getprop_add(int list, int dyn, struct di_state *st, struct dev_info *dip,
int (*prop_op)(),
char *name, dev_t devt, int aflags, int alen, caddr_t aval,
di_off_t off, di_off_t **off_pp)
{
int need_free = 0;
dev_t pdevt;
int pflags;
int rv;
caddr_t val;
int len;
size_t size;
struct di_prop *pp;
if (prop_op) {
ASSERT(dip);
pdevt = (devt == DDI_DEV_T_NONE) ? DDI_DEV_T_ANY : devt;
pflags = aflags & ~DDI_PROP_TYPE_MASK;
pflags |= DDI_PROP_DONTPASS | DDI_PROP_NOTPROM |
DDI_PROP_CONSUMER_TYPED;
ndi_hold_devi((dev_info_t *)dip);
ndi_devi_exit((dev_info_t *)dip);
rv = (*prop_op)(pdevt, (dev_info_t *)dip,
PROP_LEN_AND_VAL_ALLOC, pflags, name, &val, &len);
ndi_devi_enter((dev_info_t *)dip);
ndi_rele_devi((dev_info_t *)dip);
if (rv == DDI_PROP_SUCCESS) {
need_free = 1;
} else if (dyn) {
return (off);
} else {
val = aval;
len = alen;
}
} else {
val = aval;
len = alen;
}
dcmn_err((CE_CONT, "di_getprop_add: list %d %s len %d val %p\n",
list, name ? name : "NULL", len, (void *)val));
size = sizeof (struct di_prop);
**off_pp = off = di_checkmem(st, off, size);
pp = DI_PROP(di_mem_addr(st, off));
pp->self = off;
off += size;
pp->dev_major = getmajor(devt);
pp->dev_minor = getminor(devt);
pp->prop_flags = aflags;
pp->prop_list = list;
if (name) {
size = strlen(name) + 1;
pp->prop_name = off = di_checkmem(st, off, size);
(void) strcpy(di_mem_addr(st, off), name);
off += size;
} else {
pp->prop_name = -1;
}
pp->prop_len = len;
if (val == NULL) {
pp->prop_data = -1;
} else if (len != 0) {
size = len;
pp->prop_data = off = di_checkmem(st, off, size);
bcopy(val, di_mem_addr(st, off), size);
off += size;
}
pp->next = 0;
*off_pp = &pp->next;
if (need_free)
kmem_free(val, len);
return (off);
}
static di_off_t
di_getprop(int list, struct ddi_prop **pprop, di_off_t *off_p,
struct di_state *st, struct dev_info *dip)
{
struct ddi_prop *prop;
int (*prop_op)();
int off;
struct ddi_minor_data *mn;
i_ddi_prop_dyn_t *dp;
struct plist {
struct plist *pl_next;
char *pl_name;
int pl_flags;
dev_t pl_dev;
int pl_len;
caddr_t pl_val;
} *pl, *pl0, **plp;
ASSERT(st != NULL);
off = *off_p;
*off_p = 0;
dcmn_err((CE_CONT, "di_getprop: copy property list %d at addr %p\n",
list, (void *)*pprop));
prop_op = (list == DI_PROP_DRV_LIST) ? di_getprop_prop_op(dip) : NULL;
if (dip)
mutex_enter(&(dip->devi_lock));
for (pl0 = NULL, plp = &pl0, prop = *pprop;
prop; plp = &pl->pl_next, prop = prop->prop_next) {
pl = kmem_alloc(sizeof (*pl), KM_SLEEP);
*plp = pl;
pl->pl_next = NULL;
if (prop->prop_name)
pl->pl_name = i_ddi_strdup(prop->prop_name, KM_SLEEP);
else
pl->pl_name = NULL;
pl->pl_flags = prop->prop_flags;
pl->pl_dev = prop->prop_dev;
if (prop->prop_len) {
pl->pl_len = prop->prop_len;
pl->pl_val = kmem_alloc(pl->pl_len, KM_SLEEP);
bcopy(prop->prop_val, pl->pl_val, pl->pl_len);
} else {
pl->pl_len = 0;
pl->pl_val = NULL;
}
}
if (dip)
mutex_exit(&(dip->devi_lock));
for (pl = pl0; pl; pl = pl0) {
pl0 = pl->pl_next;
off = di_getprop_add(list, 0, st, dip, prop_op, pl->pl_name,
pl->pl_dev, pl->pl_flags, pl->pl_len, pl->pl_val,
off, &off_p);
if (pl->pl_val)
kmem_free(pl->pl_val, pl->pl_len);
if (pl->pl_name)
kmem_free(pl->pl_name, strlen(pl->pl_name) + 1);
kmem_free(pl, sizeof (*pl));
}
if ((prop_op == NULL) || (di_prop_dyn == 0)) {
*off_p = 0;
return (off);
}
for (dp = i_ddi_prop_dyn_driver_get((dev_info_t *)dip);
dp && dp->dp_name; dp++) {
if (dp->dp_spec_type) {
ASSERT(DEVI_BUSY_OWNED(dip));
for (mn = dip->devi_minor; mn; mn = mn->next) {
if (mn->ddm_spec_type != dp->dp_spec_type)
continue;
off = di_getprop_add(list, 1, st, dip, prop_op,
dp->dp_name, mn->ddm_dev, dp->dp_type,
0, NULL, off, &off_p);
}
} else {
off = di_getprop_add(list, 1, st, dip, prop_op,
dp->dp_name, DDI_DEV_T_NONE, dp->dp_type,
0, NULL, off, &off_p);
}
}
for (dp = i_ddi_prop_dyn_parent_get((dev_info_t *)dip);
dp && dp->dp_name; dp++) {
if (dp->dp_spec_type) {
ASSERT(DEVI_BUSY_OWNED(dip));
for (mn = dip->devi_minor; mn; mn = mn->next) {
if (mn->ddm_spec_type != dp->dp_spec_type)
continue;
off = di_getprop_add(list, 1, st, dip, prop_op,
dp->dp_name, mn->ddm_dev, dp->dp_type,
0, NULL, off, &off_p);
}
} else {
off = di_getprop_add(list, 1, st, dip, prop_op,
dp->dp_name, DDI_DEV_T_NONE, dp->dp_type,
0, NULL, off, &off_p);
}
}
*off_p = 0;
return (off);
}
#define DI_MATCH_DRIVER 0
#define DI_MATCH_PARENT 1
struct di_priv_format *
di_match_drv_name(struct dev_info *node, struct di_state *st, int match)
{
int i, count, len;
char *drv_name;
major_t major;
struct di_all *all;
struct di_priv_format *form;
dcmn_err2((CE_CONT, "di_match_drv_name: node = %s, match = %x\n",
node->devi_node_name, match));
if (match == DI_MATCH_PARENT) {
node = DEVI(node->devi_parent);
}
if (node == NULL) {
return (NULL);
}
major = node->devi_major;
if (major == (major_t)(-1)) {
return (NULL);
}
drv_name = ddi_major_to_name(major);
if ((drv_name == NULL) || *drv_name == '\0') {
return (NULL);
}
all = DI_ALL_PTR(st);
if (match == DI_MATCH_PARENT) {
count = all->n_ppdata;
form = DI_PRIV_FORMAT(di_mem_addr(st, all->ppdata_format));
} else {
count = all->n_dpdata;
form = DI_PRIV_FORMAT(di_mem_addr(st, all->dpdata_format));
}
len = strlen(drv_name);
for (i = 0; i < count; i++) {
char *tmp;
tmp = form[i].drv_name;
while (tmp && (*tmp != '\0')) {
if (strncmp(drv_name, tmp, len) == 0) {
return (&form[i]);
}
if (tmp = strchr(tmp, ' ')) {
tmp++;
}
}
}
return (NULL);
}
#define DI_MAX_PRIVDATA (PAGESIZE >> 1)
static di_off_t
di_getprvdata(struct di_priv_format *pdp, struct dev_info *node,
void *data, di_off_t *off_p, struct di_state *st)
{
caddr_t pa;
void *ptr;
int i, size, repeat;
di_off_t off, off0, *tmp;
char *path;
label_t ljb;
dcmn_err2((CE_CONT, "di_getprvdata:\n"));
off = di_checkmem(st, *off_p, DI_MAX_PRIVDATA);
*off_p = off;
if ((pdp->bytes == 0) || pdp->bytes > DI_MAX_PRIVDATA) {
goto failure;
}
if (!on_fault(&ljb)) {
bcopy(data, di_mem_addr(st, off), pdp->bytes);
off0 = DI_ALIGN(pdp->bytes);
for (i = 0; i < MAX_PTR_IN_PRV; i++) {
if (pdp->ptr[i].size == 0) {
goto success;
}
if ((pdp->ptr[i].offset < 0) ||
(pdp->ptr[i].offset > pdp->bytes - sizeof (char *)))
goto failure;
pa = di_mem_addr(st, off + pdp->ptr[i].offset);
tmp = (di_off_t *)(intptr_t)pa;
ptr = *((void **) (intptr_t)pa);
if (ptr == NULL) {
continue;
}
repeat = pdp->ptr[i].len_offset;
if (repeat > pdp->bytes - sizeof (int)) {
goto failure;
}
if (repeat >= 0) {
repeat = *((int *)
(intptr_t)((caddr_t)data + repeat));
} else {
repeat = -repeat;
}
size = pdp->ptr[i].size * repeat;
if ((size <= 0) || (size > (DI_MAX_PRIVDATA - off0))) {
goto failure;
}
*tmp = off0;
bcopy(ptr, di_mem_addr(st, off + off0), size);
off0 += DI_ALIGN(size);
}
} else {
goto failure;
}
success:
no_fault();
return (off + off0);
failure:
no_fault();
path = kmem_alloc(MAXPATHLEN, KM_SLEEP);
cmn_err(CE_WARN, "devinfo: fault on private data for '%s' at %p",
ddi_pathname((dev_info_t *)node, path), data);
kmem_free(path, MAXPATHLEN);
*off_p = -1;
return (off);
}
static di_off_t
di_getppdata(struct dev_info *node, di_off_t *off_p, struct di_state *st)
{
int off;
struct di_priv_format *ppdp;
dcmn_err2((CE_CONT, "di_getppdata:\n"));
if ((ppdp = di_match_drv_name(node, st, DI_MATCH_PARENT)) == NULL) {
off = *off_p;
*off_p = 0;
return (off);
}
return (di_getprvdata(ppdp, node,
ddi_get_parent_data((dev_info_t *)node), off_p, st));
}
static di_off_t
di_getdpdata(struct dev_info *node, di_off_t *off_p, struct di_state *st)
{
int off;
struct di_priv_format *dpdp;
dcmn_err2((CE_CONT, "di_getdpdata:"));
if ((dpdp = di_match_drv_name(node, st, DI_MATCH_DRIVER)) == NULL) {
off = *off_p;
*off_p = 0;
return (off);
}
return (di_getprvdata(dpdp, node,
ddi_get_driver_private((dev_info_t *)node), off_p, st));
}
static di_off_t
di_gethpdata(ddi_hp_cn_handle_t *hp_hdl, di_off_t *off_p,
struct di_state *st)
{
struct i_hp *hp;
struct di_hp *me;
size_t size;
di_off_t off;
dcmn_err2((CE_CONT, "di_gethpdata:\n"));
off = di_checkmem(st, *off_p, sizeof (struct di_hp));
*off_p = off;
do {
me = DI_HP(di_mem_addr(st, off));
me->self = off;
me->hp_name = 0;
me->hp_connection = (int)hp_hdl->cn_info.cn_num;
me->hp_depends_on = (int)hp_hdl->cn_info.cn_num_dpd_on;
(void) ddihp_cn_getstate(hp_hdl);
me->hp_state = (int)hp_hdl->cn_info.cn_state;
me->hp_type = (int)hp_hdl->cn_info.cn_type;
me->hp_type_str = 0;
me->hp_last_change = (uint32_t)hp_hdl->cn_info.cn_last_change;
me->hp_child = 0;
hp = kmem_zalloc(sizeof (i_hp_t), KM_SLEEP);
hp->hp_off = off;
hp->hp_child = hp_hdl->cn_info.cn_child;
list_insert_tail(&st->hp_list, hp);
off += sizeof (struct di_hp);
if (hp_hdl->cn_info.cn_name) {
size = strlen(hp_hdl->cn_info.cn_name) + 1;
me->hp_name = off = di_checkmem(st, off, size);
(void) strcpy(di_mem_addr(st, off),
hp_hdl->cn_info.cn_name);
off += size;
}
if (hp_hdl->cn_info.cn_type_str) {
size = strlen(hp_hdl->cn_info.cn_type_str) + 1;
me->hp_type_str = off = di_checkmem(st, off, size);
(void) strcpy(di_mem_addr(st, off),
hp_hdl->cn_info.cn_type_str);
off += size;
}
if (hp_hdl->next != NULL) {
off = di_checkmem(st, off, sizeof (struct di_hp));
me->next = off;
} else {
me->next = 0;
}
hp_hdl = hp_hdl->next;
} while (hp_hdl);
return (off);
}
static int
di_setstate(struct di_state *st, int new_state)
{
int ret = 0;
mutex_enter(&di_lock);
switch (new_state) {
case IOC_IDLE:
case IOC_DONE:
break;
case IOC_SNAP:
if (st->di_iocstate != IOC_IDLE)
ret = -1;
break;
case IOC_COPY:
if (st->di_iocstate != IOC_DONE)
ret = -1;
break;
default:
ret = -1;
}
if (ret == 0)
st->di_iocstate = new_state;
else
cmn_err(CE_NOTE, "incorrect state transition from %d to %d",
st->di_iocstate, new_state);
mutex_exit(&di_lock);
return (ret);
}
static int
header_plus_one_ok(struct di_all *all)
{
if (all->version != DI_SNAPSHOT_VERSION) {
CACHE_DEBUG((DI_ERR, "bad version: 0x%x", all->version));
return (0);
}
if (all->cache_magic != DI_CACHE_MAGIC) {
CACHE_DEBUG((DI_ERR, "bad magic #: 0x%x", all->cache_magic));
return (0);
}
if (all->snapshot_time == 0) {
CACHE_DEBUG((DI_ERR, "bad timestamp: %ld", all->snapshot_time));
return (0);
}
if (all->top_devinfo == 0) {
CACHE_DEBUG((DI_ERR, "NULL top devinfo"));
return (0);
}
if (all->map_size < sizeof (*all) + 1) {
CACHE_DEBUG((DI_ERR, "bad map size: %u", all->map_size));
return (0);
}
if (all->root_path[0] != '/' || all->root_path[1] != '\0') {
CACHE_DEBUG((DI_ERR, "bad rootpath: %c%c",
all->root_path[0], all->root_path[1]));
return (0);
}
return (1);
}
static int
chunk_write(struct vnode *vp, offset_t off, caddr_t buf, size_t len)
{
rlim64_t rlimit;
ssize_t resid;
int error = 0;
rlimit = RLIM64_INFINITY;
while (len) {
resid = 0;
error = vn_rdwr(UIO_WRITE, vp, buf, len, off,
UIO_SYSSPACE, FSYNC, rlimit, kcred, &resid);
if (error || resid < 0) {
error = error ? error : EIO;
CACHE_DEBUG((DI_ERR, "write error: %d", error));
break;
}
if (resid >= len) {
error = ENOSPC;
break;
}
buf += len - resid;
off += len - resid;
len = resid;
}
return (error);
}
static void
di_cache_write(struct di_cache *cache)
{
struct di_all *all;
struct vnode *vp;
int oflags;
size_t map_size;
size_t chunk;
offset_t off;
int error;
char *buf;
ASSERT(DI_CACHE_LOCKED(*cache));
ASSERT(!servicing_interrupt());
if (cache->cache_size == 0) {
ASSERT(cache->cache_data == NULL);
CACHE_DEBUG((DI_ERR, "Empty cache. Skipping write"));
return;
}
ASSERT(cache->cache_size > 0);
ASSERT(cache->cache_data);
if (!modrootloaded || rootvp == NULL || vn_is_readonly(rootvp)) {
CACHE_DEBUG((DI_ERR, "Can't write to rootFS. Skipping write"));
return;
}
all = (struct di_all *)cache->cache_data;
if (!header_plus_one_ok(all)) {
CACHE_DEBUG((DI_ERR, "Invalid header. Skipping write"));
return;
}
ASSERT(strcmp(all->root_path, "/") == 0);
if (all->map_size > cache->cache_size) {
CACHE_DEBUG((DI_ERR, "map_size (0x%x) > cache_size (0x%x)."
" Skipping write", all->map_size, cache->cache_size));
return;
}
error = vn_remove(DI_CACHE_TEMP, UIO_SYSSPACE, RMFILE);
if (error && error != ENOENT) {
CACHE_DEBUG((DI_ERR, "%s: unlink failed: %d",
DI_CACHE_TEMP, error));
}
if (error == EROFS) {
CACHE_DEBUG((DI_ERR, "RDONLY FS. Skipping write"));
return;
}
vp = NULL;
oflags = (FCREAT|FWRITE);
if (error = vn_open(DI_CACHE_TEMP, UIO_SYSSPACE, oflags,
DI_CACHE_PERMS, &vp, CRCREAT, 0)) {
CACHE_DEBUG((DI_ERR, "%s: create failed: %d",
DI_CACHE_TEMP, error));
return;
}
ASSERT(vp);
if (vn_is_readonly(vp)) {
CACHE_DEBUG((DI_ERR, "cannot write: readonly FS"));
goto fail;
}
map_size = all->map_size;
off = 0;
buf = cache->cache_data;
while (map_size) {
ASSERT(map_size > 0);
if (map_size > di_chunk * PAGESIZE)
chunk = di_chunk * PAGESIZE;
else
chunk = map_size;
error = chunk_write(vp, off, buf, chunk);
if (error) {
CACHE_DEBUG((DI_ERR, "write failed: off=0x%x: %d",
off, error));
goto fail;
}
off += chunk;
buf += chunk;
map_size -= chunk;
if (freemem < desfree)
delay(1);
}
if (error = VOP_FSYNC(vp, FSYNC, kcred, NULL)) {
CACHE_DEBUG((DI_ERR, "FSYNC failed: %d", error));
}
if (error = VOP_CLOSE(vp, oflags, 1, (offset_t)0, kcred, NULL)) {
CACHE_DEBUG((DI_ERR, "close() failed: %d", error));
VN_RELE(vp);
return;
}
VN_RELE(vp);
if (error = vn_rename(DI_CACHE_TEMP, DI_CACHE_FILE, UIO_SYSSPACE)) {
CACHE_DEBUG((DI_ERR, "rename failed: %d", error));
return;
}
CACHE_DEBUG((DI_INFO, "Cache write successful."));
return;
fail:
(void) VOP_CLOSE(vp, oflags, 1, (offset_t)0, kcred, NULL);
VN_RELE(vp);
}
static void
di_cache_read(struct di_cache *cache)
{
struct _buf *file;
struct di_all *all;
int n;
size_t map_size, sz, chunk;
offset_t off;
caddr_t buf;
uint32_t saved_crc, crc;
ASSERT(modrootloaded);
ASSERT(DI_CACHE_LOCKED(*cache));
ASSERT(cache->cache_data == NULL);
ASSERT(cache->cache_size == 0);
ASSERT(!servicing_interrupt());
file = kobj_open_file(DI_CACHE_FILE);
if (file == (struct _buf *)-1) {
CACHE_DEBUG((DI_ERR, "%s: open failed: %d",
DI_CACHE_FILE, ENOENT));
return;
}
all = kmem_zalloc(sizeof (*all) + 1, KM_SLEEP);
n = kobj_read_file(file, (caddr_t)all, sizeof (*all) + 1, 0);
if ((n != sizeof (*all) + 1) || !header_plus_one_ok(all)) {
kmem_free(all, sizeof (*all) + 1);
kobj_close_file(file);
CACHE_DEBUG((DI_ERR, "cache header: read error or invalid"));
return;
}
map_size = all->map_size;
kmem_free(all, sizeof (*all) + 1);
ASSERT(map_size >= sizeof (*all) + 1);
buf = di_cache.cache_data = kmem_alloc(map_size, KM_SLEEP);
sz = map_size;
off = 0;
while (sz) {
chunk = (sz > di_chunk * PAGESIZE) ? di_chunk * PAGESIZE : sz;
n = kobj_read_file(file, buf, chunk, off);
if (n != chunk) {
CACHE_DEBUG((DI_ERR, "%s: read error at offset: %lld",
DI_CACHE_FILE, off));
goto fail;
}
off += chunk;
buf += chunk;
sz -= chunk;
}
ASSERT(off == map_size);
if (kobj_read_file(file, (caddr_t)&sz, 1, off) > 0) {
CACHE_DEBUG((DI_ERR, "%s: file size changed", DI_CACHE_FILE));
goto fail;
}
all = (struct di_all *)di_cache.cache_data;
if (!header_plus_one_ok(all)) {
CACHE_DEBUG((DI_ERR, "%s: file header changed", DI_CACHE_FILE));
goto fail;
}
saved_crc = all->cache_checksum;
all->cache_checksum = 0;
CRC32(crc, di_cache.cache_data, map_size, -1U, crc32_table);
all->cache_checksum = saved_crc;
if (crc != all->cache_checksum) {
CACHE_DEBUG((DI_ERR,
"%s: checksum error: expected=0x%x actual=0x%x",
DI_CACHE_FILE, all->cache_checksum, crc));
goto fail;
}
if (all->map_size != map_size) {
CACHE_DEBUG((DI_ERR, "%s: map size changed", DI_CACHE_FILE));
goto fail;
}
kobj_close_file(file);
di_cache.cache_size = map_size;
return;
fail:
kmem_free(di_cache.cache_data, map_size);
kobj_close_file(file);
di_cache.cache_data = NULL;
di_cache.cache_size = 0;
}
static int
cache_args_valid(struct di_state *st, int *error)
{
ASSERT(error);
ASSERT(st->mem_size > 0);
ASSERT(st->memlist != NULL);
if (!modrootloaded || !i_ddi_io_initialized()) {
CACHE_DEBUG((DI_ERR,
"cache lookup failure: I/O subsystem not inited"));
*error = ENOTACTIVE;
return (0);
}
if (st->command != (DINFOCACHE & DIIOC_MASK)) {
CACHE_DEBUG((DI_ERR,
"cache lookup failure: bad flags: 0x%x",
st->command));
*error = EINVAL;
return (0);
}
if (strcmp(DI_ALL_PTR(st)->root_path, "/") != 0) {
CACHE_DEBUG((DI_ERR,
"cache lookup failure: bad root: %s",
DI_ALL_PTR(st)->root_path));
*error = EINVAL;
return (0);
}
CACHE_DEBUG((DI_INFO, "cache lookup args ok: 0x%x", st->command));
*error = 0;
return (1);
}
static int
snapshot_is_cacheable(struct di_state *st)
{
ASSERT(st->mem_size > 0);
ASSERT(st->memlist != NULL);
if ((st->command & DI_CACHE_SNAPSHOT_FLAGS) !=
(DI_CACHE_SNAPSHOT_FLAGS & DIIOC_MASK)) {
CACHE_DEBUG((DI_INFO,
"not cacheable: incompatible flags: 0x%x",
st->command));
return (0);
}
if (strcmp(DI_ALL_PTR(st)->root_path, "/") != 0) {
CACHE_DEBUG((DI_INFO,
"not cacheable: incompatible root path: %s",
DI_ALL_PTR(st)->root_path));
return (0);
}
CACHE_DEBUG((DI_INFO, "cacheable snapshot request: 0x%x", st->command));
return (1);
}
static int
di_cache_lookup(struct di_state *st)
{
size_t rval;
int cache_valid;
ASSERT(cache_args_valid(st, &cache_valid));
ASSERT(modrootloaded);
DI_CACHE_LOCK(di_cache);
cache_valid = di_cache.cache_valid;
if (cache_valid && di_cache.cache_data == NULL) {
di_cache_read(&di_cache);
if (di_cache.cache_data == NULL)
cache_valid = 0;
}
if (cache_valid) {
ASSERT(di_cache.cache_data != NULL);
ASSERT(di_cache.cache_size > 0);
di_freemem(st);
rval = 0;
if (di_cache2mem(&di_cache, st) > 0) {
ASSERT(DI_ALL_PTR(st));
rval = DI_ALL_PTR(st)->map_size;
ASSERT(rval >= sizeof (struct di_all));
ASSERT(rval <= di_cache.cache_size);
}
} else {
ASSERT(st->command == (DINFOCACHE & DIIOC_MASK));
st->command = (DI_CACHE_SNAPSHOT_FLAGS & DIIOC_MASK);
rval = di_cache_update(st);
st->command = (DINFOCACHE & DIIOC_MASK);
}
DI_CACHE_UNLOCK(di_cache);
ASSERT(rval == 0 || strcmp(DI_ALL_PTR(st)->root_path, "/") == 0);
return ((int)rval);
}
static int
di_cache_update(struct di_state *st)
{
int rval;
uint32_t crc;
struct di_all *all;
ASSERT(DI_CACHE_LOCKED(di_cache));
ASSERT(snapshot_is_cacheable(st));
i_ddi_di_cache_free(&di_cache);
atomic_or_32(&di_cache.cache_valid, 1);
rval = di_snapshot_and_clean(st);
if (rval == 0) {
CACHE_DEBUG((DI_ERR, "can't update cache: bad snapshot"));
return (0);
}
DI_ALL_PTR(st)->map_size = rval;
if (di_mem2cache(st, &di_cache) == 0) {
CACHE_DEBUG((DI_ERR, "can't update cache: copy failed"));
return (0);
}
ASSERT(di_cache.cache_data);
ASSERT(di_cache.cache_size > 0);
all = (struct di_all *)di_cache.cache_data;
all->cache_magic = DI_CACHE_MAGIC;
all->map_size = rval;
ASSERT(all->cache_checksum == 0);
CRC32(crc, di_cache.cache_data, all->map_size, -1U, crc32_table);
all->cache_checksum = crc;
di_cache_write(&di_cache);
return (rval);
}
static void
di_cache_print(di_cache_debug_t msglevel, char *fmt, ...)
{
va_list ap;
if (di_cache_debug <= DI_QUIET)
return;
if (di_cache_debug < msglevel)
return;
switch (msglevel) {
case DI_ERR:
msglevel = CE_WARN;
break;
case DI_INFO:
case DI_TRACE:
default:
msglevel = CE_NOTE;
break;
}
va_start(ap, fmt);
vcmn_err(msglevel, fmt, ap);
va_end(ap);
}
static void
di_hotplug_children(struct di_state *st)
{
di_off_t off;
struct di_hp *hp;
struct i_hp *hp_list_node;
while (hp_list_node = (struct i_hp *)list_remove_head(&st->hp_list)) {
if ((hp_list_node->hp_child != NULL) &&
(di_dip_find(st, hp_list_node->hp_child, &off) == 0)) {
hp = DI_HP(di_mem_addr(st, hp_list_node->hp_off));
hp->hp_child = off;
}
kmem_free(hp_list_node, sizeof (i_hp_t));
}
list_destroy(&st->hp_list);
}