Permalink
Cannot retrieve contributors at this time
Join GitHub today
GitHub is home to over 40 million developers working together to host and review code, manage projects, and build software together.
Sign up
Fetching contributors…
| /* | |
| * CDDL HEADER START | |
| * | |
| * The contents of this file are subject to the terms of the | |
| * Common Development and Distribution License (the "License"). | |
| * You may not use this file except in compliance with the License. | |
| * | |
| * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE | |
| * or http://www.opensolaris.org/os/licensing. | |
| * See the License for the specific language governing permissions | |
| * and limitations under the License. | |
| * | |
| * When distributing Covered Code, include this CDDL HEADER in each | |
| * file and include the License file at usr/src/OPENSOLARIS.LICENSE. | |
| * If applicable, add the following below this CDDL HEADER, with the | |
| * fields enclosed by brackets "[]" replaced with your own identifying | |
| * information: Portions Copyright [yyyy] [name of copyright owner] | |
| * | |
| * CDDL HEADER END | |
| */ | |
| /* | |
| * Copyright (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved. | |
| */ | |
| /* | |
| * driver for accessing kernel devinfo tree. | |
| */ | |
| #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) /* nothing */ | |
| #define dcmn_err2(args) /* nothing */ | |
| #define dcmn_err3(args) /* nothing */ | |
| #endif | |
| /* | |
| * We partition the space of devinfo minor nodes equally between the full and | |
| * unprivileged versions of the driver. The even-numbered minor nodes are the | |
| * full version, while the odd-numbered ones are the read-only version. | |
| */ | |
| static int di_max_opens = 32; | |
| static int di_prop_dyn = 1; /* enable dynamic property support */ | |
| #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 /* snapshot ioctl states */ | |
| #define IOC_SNAP 1 /* snapshot in progress */ | |
| #define IOC_DONE 2 /* snapshot done, but not copied out */ | |
| #define IOC_COPY 3 /* copyout in progress */ | |
| /* | |
| * Keep max alignment so we can move snapshot to different platforms. | |
| * | |
| * NOTE: Most callers should rely on the di_checkmem return value | |
| * being aligned, and reestablish *off_p with aligned value, instead | |
| * of trying to align size of their allocations: this approach will | |
| * minimize memory use. | |
| */ | |
| #define DI_ALIGN(addr) ((addr + 7l) & ~7l) | |
| /* | |
| * To avoid wasting memory, make a linked list of memory chunks. | |
| * Size of each chunk is buf_size. | |
| */ | |
| struct di_mem { | |
| struct di_mem *next; /* link to next chunk */ | |
| char *buf; /* contiguous kernel memory */ | |
| size_t buf_size; /* size of buf in bytes */ | |
| devmap_cookie_t cook; /* cookie from ddi_umem_alloc */ | |
| }; | |
| /* | |
| * This is a stack for walking the tree without using recursion. | |
| * When the devinfo tree height is above some small size, one | |
| * gets watchdog resets on sun4m. | |
| */ | |
| struct di_stack { | |
| void *offset[MAX_TREE_DEPTH]; | |
| struct dev_info *dip[MAX_TREE_DEPTH]; | |
| int circ[MAX_TREE_DEPTH]; | |
| int depth; /* depth of current node to be copied */ | |
| }; | |
| #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)->circ[(stack)->depth - 1]); \ | |
| ((stack)->depth--); \ | |
| } | |
| #define PUSH_STACK(stack, node, off_p) { \ | |
| ASSERT(node != NULL); \ | |
| ndi_devi_enter((dev_info_t *)node, &(stack)->circ[(stack)->depth]); \ | |
| (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)) | |
| /* | |
| * With devfs, the device tree has no global locks. The device tree is | |
| * dynamic and dips may come and go if they are not locked locally. Under | |
| * these conditions, pointers are no longer reliable as unique IDs. | |
| * Specifically, these pointers cannot be used as keys for hash tables | |
| * as the same devinfo structure may be freed in one part of the tree only | |
| * to be allocated as the structure for a different device in another | |
| * part of the tree. This can happen if DR and the snapshot are | |
| * happening concurrently. | |
| * The following data structures act as keys for devinfo nodes and | |
| * pathinfo nodes. | |
| */ | |
| 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 { | |
| /* | |
| * If a di_link struct representing this i_link struct makes it | |
| * into the snapshot, then self will point to the offset of | |
| * the di_link struct in the snapshot | |
| */ | |
| di_off_t self; | |
| int spec_type; /* block or char access type */ | |
| struct i_lnode *src_lnode; /* src i_lnode */ | |
| struct i_lnode *tgt_lnode; /* tgt i_lnode */ | |
| struct i_link *src_link_next; /* next src i_link /w same i_lnode */ | |
| struct i_link *tgt_link_next; /* next tgt i_link /w same i_lnode */ | |
| } i_link_t; | |
| typedef struct i_lnode { | |
| /* | |
| * If a di_lnode struct representing this i_lnode struct makes it | |
| * into the snapshot, then self will point to the offset of | |
| * the di_lnode struct in the snapshot | |
| */ | |
| di_off_t self; | |
| /* | |
| * used for hashing and comparing i_lnodes | |
| */ | |
| int modid; | |
| /* | |
| * public information describing a link endpoint | |
| */ | |
| struct di_node *di_node; /* di_node in snapshot */ | |
| dev_t devt; /* devt */ | |
| /* | |
| * i_link ptr to links coming into this i_lnode node | |
| * (this i_lnode is the target of these i_links) | |
| */ | |
| i_link_t *link_in; | |
| /* | |
| * i_link ptr to links going out of this i_lnode node | |
| * (this i_lnode is the source of these i_links) | |
| */ | |
| i_link_t *link_out; | |
| } i_lnode_t; | |
| typedef struct i_hp { | |
| di_off_t hp_off; /* Offset of di_hp_t in snapshot */ | |
| dev_info_t *hp_child; /* Child devinfo node of the di_hp_t */ | |
| list_node_t hp_link; /* List linkage */ | |
| } i_hp_t; | |
| /* | |
| * Soft state associated with each instance of driver open. | |
| */ | |
| static struct di_state { | |
| di_off_t mem_size; /* total # bytes in memlist */ | |
| struct di_mem *memlist; /* head of memlist */ | |
| uint_t command; /* command from ioctl */ | |
| int di_iocstate; /* snapshot ioctl state */ | |
| 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; /* serialize instance assignment */ | |
| typedef enum { | |
| DI_QUIET = 0, /* DI_QUIET must always be 0 */ | |
| DI_ERR, | |
| DI_INFO, | |
| DI_TRACE, | |
| DI_TRACE1, | |
| DI_TRACE2 | |
| } di_cache_debug_t; | |
| static uint_t di_chunk = 32; /* I/O chunk size in pages */ | |
| #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)) | |
| /* | |
| * Check that whole device tree is being configured as a pre-condition for | |
| * cleaning up /etc/devices files. | |
| */ | |
| #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, /* open */ | |
| di_close, /* close */ | |
| nodev, /* strategy */ | |
| nodev, /* print */ | |
| nodev, /* dump */ | |
| nodev, /* read */ | |
| nodev, /* write */ | |
| di_ioctl, /* ioctl */ | |
| nodev, /* devmap */ | |
| nodev, /* mmap */ | |
| nodev, /* segmap */ | |
| nochpoll, /* poll */ | |
| ddi_prop_op, /* prop_op */ | |
| NULL, /* streamtab */ | |
| D_NEW | D_MP /* Driver compatibility flag */ | |
| }; | |
| static struct dev_ops di_ops = { | |
| DEVO_REV, /* devo_rev, */ | |
| 0, /* refcnt */ | |
| di_info, /* info */ | |
| nulldev, /* identify */ | |
| nulldev, /* probe */ | |
| di_attach, /* attach */ | |
| di_detach, /* detach */ | |
| nodev, /* reset */ | |
| &di_cb_ops, /* driver operations */ | |
| NULL /* bus operations */ | |
| }; | |
| /* | |
| * Module linkage information for the kernel. | |
| */ | |
| 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; | |
| /*ARGSUSED*/ | |
| 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: | |
| /* | |
| * All dev_t's map to the same, single instance. | |
| */ | |
| *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, NULL) == DDI_FAILURE || | |
| ddi_create_minor_node(dip, "devinfo,ro", S_IFCHR, | |
| DI_READONLY_PARENT, DDI_PSEUDO, NULL) == 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); | |
| } | |
| /* | |
| * Allow multiple opens by tweaking the dev_t such that it looks like each | |
| * open is getting a different minor device. Each minor gets a separate | |
| * entry in the di_states[] table. Based on the original minor number, we | |
| * discriminate opens of the full and read-only nodes. If all of the instances | |
| * of the selected minor node are currently open, we return EAGAIN. | |
| */ | |
| /*ARGSUSED*/ | |
| 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; /* It's ours. */ | |
| } | |
| if (m >= di_max_opens) { | |
| /* | |
| * maximum open instance for device reached | |
| */ | |
| 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); | |
| } | |
| /*ARGSUSED*/ | |
| 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)); | |
| /* | |
| * empty slot in state table | |
| */ | |
| 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); | |
| } | |
| /*ARGSUSED*/ | |
| 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: | |
| /* | |
| * This is called from di_init to verify that the driver | |
| * opened is indeed devinfo. The purpose is to guard against | |
| * sending ioctl to an unknown driver in case of an | |
| * unresolved major number conflict during bfu. | |
| */ | |
| *rvalp = DI_MAGIC; | |
| return (0); | |
| case DINFOLODRV: | |
| /* | |
| * Hold an installed driver and return the result | |
| */ | |
| if (DI_UNPRIVILEGED_NODE(m)) { | |
| /* | |
| * Only the fully enabled instances may issue | |
| * DINFOLDDRV. | |
| */ | |
| 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); | |
| } | |
| /* | |
| * Some 3rd party driver's _init() walks the device tree, | |
| * so we load the driver module before configuring driver. | |
| */ | |
| 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; | |
| /* | |
| * i_ddi_load_drvconf() below will trigger a reprobe | |
| * via reset_nexus_flags(). NDI_DRV_CONF_REPROBE isn't | |
| * needed here. | |
| */ | |
| 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: | |
| /* | |
| * The case for copying snapshot to userland | |
| */ | |
| 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); | |
| } | |
| /* | |
| * copyout the snapshot | |
| */ | |
| map_size = (map_size + PAGEOFFSET) & PAGEMASK; | |
| /* | |
| * Return the map size, so caller may do a sanity | |
| * check against the return value of snapshot ioctl() | |
| */ | |
| *rvalp = (int)map_size; | |
| /* | |
| * Copy one chunk at a time | |
| */ | |
| 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) { | |
| /* | |
| * Invalid ioctl command | |
| */ | |
| return (ENOTTY); | |
| } | |
| /* | |
| * take a snapshot | |
| */ | |
| st->command = cmd & DIIOC_MASK; | |
| /*FALLTHROUGH*/ | |
| } | |
| /* | |
| * Obtain enough memory to hold header + rootpath. We prevent kernel | |
| * memory exhaustion by freeing any previously allocated snapshot and | |
| * refusing the operation; otherwise we would be allowing ioctl(), | |
| * ioctl(), ioctl(), ..., panic. | |
| */ | |
| if (di_setstate(st, IOC_SNAP) == -1) | |
| return (EBUSY); | |
| /* | |
| * Initial memlist always holds di_all and the root_path - and | |
| * is at least a page and size. | |
| */ | |
| 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); /* real length of di_all */ | |
| all->devcnt = devcnt; | |
| all->command = st->command; | |
| all->version = DI_SNAPSHOT_VERSION; | |
| all->top_vhci_devinfo = 0; /* filled by build_vhci_list. */ | |
| /* | |
| * Note the endianness in case we need to transport snapshot | |
| * over the network. | |
| */ | |
| #if defined(_LITTLE_ENDIAN) | |
| all->endianness = DI_LITTLE_ENDIAN; | |
| #else | |
| all->endianness = DI_BIG_ENDIAN; | |
| #endif | |
| /* Copyin ioctl args, store in the snapshot. */ | |
| 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; /* real length of root_path */ | |
| 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); | |
| } | |
| /* | |
| * Only the fully enabled version may force load drivers or read | |
| * the parent private data from a driver. | |
| */ | |
| if ((st->command & (DINFOPRIVDATA | DINFOFORCE)) != 0 && | |
| DI_UNPRIVILEGED_NODE(m)) { | |
| di_freemem(st); | |
| (void) di_setstate(st, IOC_IDLE); | |
| return (EACCES); | |
| } | |
| /* Do we need private data? */ | |
| 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: { | |
| /* | |
| * Cannot copy private data from 64-bit kernel | |
| * to 32-bit app | |
| */ | |
| 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 /* !_MULTI_DATAMODEL */ | |
| if ((off = di_copyformat(off, st, arg, mode)) == 0) { | |
| di_freemem(st); | |
| (void) di_setstate(st, IOC_IDLE); | |
| return (EFAULT); | |
| } | |
| #endif /* _MULTI_DATAMODEL */ | |
| } | |
| all->top_devinfo = DI_ALIGN(off); | |
| /* | |
| * For cache lookups we reallocate memory from scratch, | |
| * so the value of "all" is no longer valid. | |
| */ | |
| 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); | |
| } | |
| /* | |
| * Get a chunk of memory >= size, for the snapshot | |
| */ | |
| static void | |
| di_allocmem(struct di_state *st, size_t size) | |
| { | |
| struct di_mem *mem = kmem_zalloc(sizeof (struct di_mem), KM_SLEEP); | |
| /* | |
| * Round up size to nearest power of 2. If it is less | |
| * than st->mem_size, set it to st->mem_size (i.e., | |
| * the mem_size is doubled every time) to reduce the | |
| * number of memory allocations. | |
| */ | |
| 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) { /* first chunk */ | |
| st->memlist = mem; | |
| } else { | |
| /* | |
| * locate end of linked list and add a chunk at the end | |
| */ | |
| struct di_mem *dcp = st->memlist; | |
| while (dcp->next != NULL) { | |
| dcp = dcp->next; | |
| } | |
| dcp->next = mem; | |
| } | |
| st->mem_size += size; | |
| } | |
| /* | |
| * Copy upto bufsiz bytes of the memlist to buf | |
| */ | |
| 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; | |
| } | |
| } | |
| /* | |
| * Free all memory for the snapshot | |
| */ | |
| 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) { /* traverse the linked list */ | |
| 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); | |
| } | |
| /* | |
| * Copies cached data to the di_state structure. | |
| * Returns: | |
| * - size of data copied, on SUCCESS | |
| * - 0 on failure | |
| */ | |
| 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); | |
| /* | |
| * Verify that di_allocmem() allocates contiguous memory, | |
| * so that it is safe to do straight bcopy() | |
| */ | |
| ASSERT(st->memlist != NULL); | |
| ASSERT(st->memlist->next == NULL); | |
| bcopy(cache->cache_data, pa, cache->cache_size); | |
| return (cache->cache_size); | |
| } | |
| /* | |
| * Copies a snapshot from di_state to the cache | |
| * Returns: | |
| * - 0 on failure | |
| * - size of copied data on success | |
| */ | |
| 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); | |
| /* | |
| * The size of the memory list may be much larger than the | |
| * size of valid data (map_size). Cache only the valid data | |
| */ | |
| 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); | |
| } | |
| /* | |
| * Make sure there is at least "size" bytes memory left before | |
| * going on. Otherwise, start on a new chunk. | |
| */ | |
| 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)); | |
| /* | |
| * di_checkmem() shouldn't be called with a size of zero. | |
| * But in case it is, we want to make sure we return a valid | |
| * offset within the memlist and not an offset that points us | |
| * at the end of the memlist. | |
| */ | |
| 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); | |
| } | |
| /* verify that return value is aligned */ | |
| ASSERT(off == DI_ALIGN(off)); | |
| return (off); | |
| } | |
| /* | |
| * Copy the private data format from ioctl arg. | |
| * On success, the ending offset is returned. On error 0 is returned. | |
| */ | |
| 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)); | |
| /* | |
| * Copyin data and check version. | |
| * We only handle private data version 0. | |
| */ | |
| 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); | |
| } | |
| /* | |
| * Save di_priv_data copied from userland in snapshot. | |
| */ | |
| all->pd_version = priv->version; | |
| all->n_ppdata = priv->n_parent; | |
| all->n_dpdata = priv->n_driver; | |
| /* | |
| * copyin private data format, modify offset accordingly | |
| */ | |
| if (all->n_ppdata) { /* parent private data format */ | |
| /* | |
| * check memory | |
| */ | |
| 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) { /* driver private data format */ | |
| /* | |
| * check memory | |
| */ | |
| 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); | |
| } | |
| /* | |
| * Return the real address based on the offset (off) within snapshot | |
| */ | |
| 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); | |
| } | |
| /* | |
| * Ideally we would use the whole key to derive the hash | |
| * value. However, the probability that two keys will | |
| * have the same dip (or pip) is very low, so | |
| * hashing by dip (or pip) pointer should suffice. | |
| */ | |
| 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"); | |
| /*NOTREACHED*/ | |
| } | |
| 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"); | |
| /*NOTREACHED*/ | |
| } | |
| 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"); | |
| /*NOTREACHED*/ | |
| } | |
| 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"); | |
| /*NOTREACHED*/ | |
| } | |
| } | |
| 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; | |
| } | |
| /* | |
| * This is the main function that takes a snapshot | |
| */ | |
| 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")); | |
| /* | |
| * Translate requested root path if an alias and snap-root != "/" | |
| */ | |
| if (ddi_aliases_present == B_TRUE && strcmp(all->root_path, "/") != 0) { | |
| /* If there is no redirected alias, use root_path as is */ | |
| rootnode = ddi_alias_redirect(all->root_path); | |
| if (rootnode) { | |
| (void) ddi_pathname(rootnode, all->root_path); | |
| goto got_root; | |
| } | |
| } | |
| /* | |
| * Verify path before entrusting it to e_ddi_hold_devi_by_path because | |
| * some platforms have OBP bugs where executing the NDI_PROMNAME code | |
| * path against an invalid path results in panic. The lookupnameat | |
| * is done relative to rootdir without a leading '/' on "devices/" | |
| * to force the lookup to occur in the global zone. | |
| */ | |
| 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); | |
| /* | |
| * Hold the devinfo node referred by the path. | |
| */ | |
| 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)); | |
| } | |
| /* | |
| * copy the device tree | |
| */ | |
| 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); | |
| /* | |
| * copy the devnames array | |
| */ | |
| all->devnames = off; | |
| off = di_copydevnm(&all->devnames, st); | |
| /* initialize the hash tables */ | |
| 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: | |
| /* | |
| * Free up hash tables | |
| */ | |
| mod_hash_destroy_hash(st->reg_dip_hash); | |
| mod_hash_destroy_hash(st->reg_pip_hash); | |
| /* | |
| * Record the timestamp now that we are done with snapshot. | |
| * | |
| * We compute the checksum later and then only if we cache | |
| * the snapshot, since checksumming adds some overhead. | |
| * The checksum is checked later if we read the cache file. | |
| * from disk. | |
| * | |
| * Set checksum field to 0 as CRC is calculated with that | |
| * field set to 0. | |
| */ | |
| all->snapshot_time = ddi_get_time(); | |
| all->cache_checksum = 0; | |
| ASSERT(all->snapshot_time != 0); | |
| return (off); | |
| } | |
| /* | |
| * Take a snapshot and clean /etc/devices files if DINFOCLEANUP is set | |
| */ | |
| 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)); | |
| /* | |
| * Cleanup /etc/devices files: | |
| * In order to accurately account for the system configuration | |
| * in /etc/devices files, the appropriate drivers must be | |
| * fully configured before the cleanup starts. | |
| * So enable modunload only after the cleanup. | |
| */ | |
| i_ddi_clean_devices_files(); | |
| /* | |
| * Remove backing store nodes for unused devices, | |
| * which retain past permissions customizations | |
| * and may be undesired for newly configured devices. | |
| */ | |
| dev_devices_cleanup(); | |
| } | |
| modunload_enable(); | |
| return (off); | |
| } | |
| /* | |
| * construct vhci linkage in the snapshot. | |
| */ | |
| 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); | |
| } | |
| /* | |
| * construct phci linkage for the given vhci in the snapshot. | |
| */ | |
| 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); | |
| } | |
| /* | |
| * Assumes all devinfo nodes in device tree have been snapshotted | |
| */ | |
| 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); | |
| /* | |
| * Only nodes which were BOUND when they were | |
| * snapshotted will be added to per-driver list. | |
| */ | |
| if (me->next != -1) | |
| continue; | |
| *off_p = off; | |
| off_p = &me->next; | |
| } | |
| *off_p = 0; | |
| } | |
| /* | |
| * Copy the devnames array, so we have a list of drivers in the snapshot. | |
| * Also makes it possible to locate the per-driver devinfo nodes. | |
| */ | |
| 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)); | |
| /* | |
| * make sure there is some allocated memory | |
| */ | |
| 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; | |
| } | |
| /* | |
| * dn_name is not freed during driver unload or removal. | |
| * | |
| * There is a race condition when make_devname() changes | |
| * dn_name during our strcpy. This should be rare since | |
| * only add_drv does this. At any rate, we never had a | |
| * problem with ddi_name_to_major(), which should have | |
| * the same problem. | |
| */ | |
| 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); | |
| /* | |
| * Snapshot per-driver node list | |
| */ | |
| snap_driver_list(st, &devnamesp[i], &dnp[i].head); | |
| /* | |
| * This is not used by libdevinfo, leave it for now | |
| */ | |
| dnp[i].flags = devnamesp[i].dn_flags; | |
| dnp[i].instance = devnamesp[i].dn_instance; | |
| /* | |
| * get global properties | |
| */ | |
| 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); | |
| } | |
| /* | |
| * Bit encode driver ops: & bus_ops, cb_ops, & cb_ops->cb_str | |
| */ | |
| 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); | |
| } | |
| /* | |
| * Copy the kernel devinfo tree. The tree and the devnames array forms | |
| * the entire snapshot (see also di_copydevnm). | |
| */ | |
| 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)); | |
| /* force attach drivers */ | |
| 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 top_devinfo onto a stack | |
| * | |
| * The stack is necessary to avoid recursion, which can overrun | |
| * the kernel stack. | |
| */ | |
| PUSH_STACK(dsp, root, off_p); | |
| /* | |
| * As long as there is a node on the stack, copy the node. | |
| * di_copynode() is responsible for pushing and popping | |
| * child and sibling nodes on the stack. | |
| */ | |
| while (!EMPTY_STACK(dsp)) { | |
| node = TOP_NODE(dsp); | |
| off = di_copynode(node, dsp, st); | |
| } | |
| /* | |
| * Free the stack structure | |
| */ | |
| kmem_free(dsp, sizeof (struct di_stack)); | |
| return (off); | |
| } | |
| /* | |
| * This is the core function, which copies all data associated with a single | |
| * node into the snapshot. The amount of information is determined by the | |
| * ioctl command. | |
| */ | |
| 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))); | |
| /* | |
| * check memory usage, and fix offsets accordingly. | |
| */ | |
| 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)); | |
| /* | |
| * Node parameters: | |
| * self -- offset of current node within snapshot | |
| * nodeid -- pointer to PROM node (tri-valued) | |
| * state -- hot plugging device state | |
| * node_state -- devinfo node state | |
| */ | |
| 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; /* Filled up by build_vhci_list. */ | |
| me->top_phci = 0; /* Filled up by build_phci_list. */ | |
| me->next_phci = 0; /* Filled up by build_phci_list. */ | |
| me->multipath_component = MULTIPATH_COMPONENT_NONE; /* set default. */ | |
| me->user_private_data = NULL; | |
| /* | |
| * Get parent's offset in snapshot from the stack | |
| * and store it in the current node | |
| */ | |
| if (dsp->depth > 1) { | |
| me->parent = *(PARENT_OFFSET(dsp)); | |
| } | |
| /* | |
| * Save the offset of this di_node in a hash table. | |
| * This is used later to resolve references to this | |
| * dip from other parts of the tree (per-driver list, | |
| * multipathing linkages, layered usage linkages). | |
| * The key used for the hash table is derived from | |
| * information in the dip. | |
| */ | |
| di_register_dip(st, (dev_info_t *)node, me->self); | |
| #ifdef DEVID_COMPATIBILITY | |
| /* check for devid as property marker */ | |
| if (node->devi_devid_str) { | |
| ddi_devid_t devid; | |
| /* | |
| * The devid is now represented as a property. For | |
| * compatibility with di_devid() interface in libdevinfo we | |
| * must return it as a binary structure in the snapshot. When | |
| * (if) di_devid() is removed from libdevinfo then the code | |
| * related to DEVID_COMPATIBILITY can be removed. | |
| */ | |
| 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 /* DEVID_COMPATIBILITY */ | |
| 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 the dip is BOUND, set the next pointer of the | |
| * per-instance list to -1, indicating that it is yet to be resolved. | |
| * This will be resolved later in snap_driver_list(). | |
| */ | |
| if (me->drv_major != -1) { | |
| me->next = -1; | |
| } else { | |
| me->next = 0; | |
| } | |
| /* | |
| * An optimization to skip mutex_enter when not needed. | |
| */ | |
| if (!((DINFOMINOR | DINFOPROP | DINFOPATH | DINFOHP) & st->command)) { | |
| goto priv_data; | |
| } | |
| /* | |
| * LOCKING: We already have an active ndi_devi_enter to gather the | |
| * minor data, and we will take devi_lock to gather properties as | |
| * needed off di_getprop. | |
| */ | |
| if (!(DINFOMINOR & st->command)) { | |
| goto path; | |
| } | |
| ASSERT(DEVI_BUSY_OWNED(node)); | |
| if (node->devi_minor) { /* minor data */ | |
| 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) { /* driver property list */ | |
| 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) { /* system property list */ | |
| 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) { /* hardware property list */ | |
| 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; /* not global property */ | |
| } else { | |
| /* | |
| * Make copy of global property list if this devinfo refers | |
| * global properties different from what's on the devnames | |
| * array. It can happen if there has been a forced | |
| * driver.conf update. See mod_drv(1M). | |
| */ | |
| 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) { /* hotplug data */ | |
| 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: /* NOT implemented */ | |
| subtree: | |
| /* keep the stack aligned */ | |
| off = DI_ALIGN(off); | |
| if (!(DINFOSUBTREE & st->command)) { | |
| POP_STACK(dsp); | |
| return (off); | |
| } | |
| child: | |
| /* | |
| * If there is a visible child--push child onto stack. | |
| * Hold the parent (me) busy while doing so. | |
| */ | |
| if ((n = node->devi_child) != NULL) { | |
| /* skip hidden nodes */ | |
| 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); | |
| } | |
| } | |
| sibling: | |
| /* | |
| * Done with any child nodes, unroll the stack till a visible | |
| * sibling of a parent node is found or root node is reached. | |
| */ | |
| POP_STACK(dsp); | |
| while (!EMPTY_STACK(dsp)) { | |
| if ((n = node->devi_sibling) != NULL) { | |
| /* skip hidden nodes */ | |
| 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); | |
| } | |
| /* | |
| * DONE with all nodes | |
| */ | |
| 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) | |
| { | |
| /* This lnode and its dip must have been snapshotted */ | |
| ASSERT(i_lnode->self > 0); | |
| ASSERT(i_lnode->di_node->self > 0); | |
| /* at least 1 link (in or out) must exist for this lnode */ | |
| 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) | |
| { | |
| /* This link must have been snapshotted */ | |
| ASSERT(i_link->self > 0); | |
| /* Both endpoint lnodes must exist for this link */ | |
| ASSERT(i_link->src_lnode); | |
| ASSERT(i_link->tgt_lnode); | |
| kmem_free(i_link, sizeof (i_link_t)); | |
| } | |
| /*ARGSUSED*/ | |
| 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); | |
| } | |
| /* | |
| * An lnode represents a {dip, dev_t} tuple. A link represents a | |
| * {src_lnode, tgt_lnode, spec_type} tuple. | |
| * The following callback assumes that LDI framework ref-counts the | |
| * src_dip and tgt_dip while invoking this callback. | |
| */ | |
| 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 the source or target of this device usage information doesn't | |
| * correspond to a device node then we don't report it via | |
| * libdevinfo so return. | |
| */ | |
| 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)); | |
| /* | |
| * Skip the ldi_usage if either src or tgt dip is not in the | |
| * snapshot. This saves us from pruning bad lnodes/links later. | |
| */ | |
| 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); | |
| /* | |
| * allocate an i_lnode and add it to the lnode hash | |
| * if it is not already present. For this particular | |
| * link the lnode is a source, but it may | |
| * participate as tgt or src in any number of layered | |
| * operations - so it may already be in the hash. | |
| */ | |
| 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) { | |
| /* | |
| * new i_lnode | |
| * add it to the hash and increment the lnode count | |
| */ | |
| res = mod_hash_insert(st->lnode_hash, i_lnode, i_lnode); | |
| ASSERT(res == 0); | |
| st->lnode_count++; | |
| src_lnode = i_lnode; | |
| } else { | |
| /* this i_lnode already exists in the lnode_hash */ | |
| i_lnode_free(i_lnode); | |
| src_lnode = (i_lnode_t *)nodep; | |
| } | |
| /* | |
| * allocate a tgt i_lnode and add it to the lnode hash | |
| */ | |
| 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) { | |
| /* | |
| * new i_lnode | |
| * add it to the hash and increment the lnode count | |
| */ | |
| res = mod_hash_insert(st->lnode_hash, i_lnode, i_lnode); | |
| ASSERT(res == 0); | |
| st->lnode_count++; | |
| tgt_lnode = i_lnode; | |
| } else { | |
| /* this i_lnode already exists in the lnode_hash */ | |
| i_lnode_free(i_lnode); | |
| tgt_lnode = (i_lnode_t *)nodep; | |
| } | |
| /* | |
| * allocate a i_link | |
| */ | |
| i_link = i_link_alloc(ldi_usage->tgt_spec_type); | |
| i_link->src_lnode = src_lnode; | |
| i_link->tgt_lnode = tgt_lnode; | |
| /* | |
| * add this link onto the src i_lnodes outbound i_link list | |
| */ | |
| 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)) { | |
| /* this link already exists */ | |
| 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; | |
| /* | |
| * add this link onto the tgt i_lnodes inbound i_link list | |
| */ | |
| 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; | |
| /* | |
| * add this i_link to the link hash | |
| */ | |
| 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; | |
| }; | |
| /*ARGSUSED*/ | |
| 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); /* lnodes done */ | |
| ASSERT(data->link_count <= data->st->link_count); | |
| /* fill in fields for the di_link snapshot */ | |
| me = DI_LINK(di_mem_addr(data->st, i_link->self)); | |
| me->self = i_link->self; | |
| me->spec_type = i_link->spec_type; | |
| /* | |
| * The src_lnode and tgt_lnode i_lnode_t for this i_link_t | |
| * are created during the LDI table walk. Since we are | |
| * walking the link hash, the lnode hash has already been | |
| * walked and the lnodes have been snapshotted. Save lnode | |
| * offsets. | |
| */ | |
| me->src_lnode = i_link->src_lnode->self; | |
| me->tgt_lnode = i_link->tgt_lnode->self; | |
| /* | |
| * Save this link's offset in the src_lnode snapshot's link_out | |
| * field | |
| */ | |
| melnode = DI_LNODE(di_mem_addr(data->st, me->src_lnode)); | |
| me->src_link_next = melnode->link_out; | |
| melnode->link_out = me->self; | |
| /* | |
| * Put this link on the tgt_lnode's link_in field | |
| */ | |
| melnode = DI_LNODE(di_mem_addr(data->st, me->tgt_lnode)); | |
| me->tgt_link_next = melnode->link_in; | |
| melnode->link_in = me->self; | |
| /* | |
| * An i_lnode_t is only created if the corresponding dip exists | |
| * in the snapshot. A pointer to the di_node is saved in the | |
| * i_lnode_t when it is allocated. For this link, get the di_node | |
| * for the source lnode. Then put the link on the di_node's list | |
| * of src links | |
| */ | |
| medinode = i_link->src_lnode->di_node; | |
| me->src_node_next = medinode->src_links; | |
| medinode->src_links = me->self; | |
| /* | |
| * Put this link on the tgt_links list of the target | |
| * dip. | |
| */ | |
| medinode = i_link->tgt_lnode->di_node; | |
| me->tgt_node_next = medinode->tgt_links; | |
| medinode->tgt_links = me->self; | |
| return (MH_WALK_CONTINUE); | |
| } | |
| /*ARGSUSED*/ | |
| 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); /* links not done yet */ | |
| ASSERT(data->lnode_count <= data->st->lnode_count); | |
| /* fill in fields for the di_lnode snapshot */ | |
| 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); | |
| } | |
| /* | |
| * The dip corresponding to this lnode must exist in | |
| * the snapshot or we wouldn't have created the i_lnode_t | |
| * during LDI walk. Save the offset of the dip. | |
| */ | |
| ASSERT(i_lnode->di_node && i_lnode->di_node->self > 0); | |
| me->node = i_lnode->di_node->self; | |
| /* | |
| * There must be at least one link in or out of this lnode | |
| * or we wouldn't have created it. These fields will be set | |
| * during the link hash walk. | |
| */ | |
| ASSERT((i_lnode->link_in != NULL) || (i_lnode->link_out != NULL)); | |
| /* | |
| * set the offset of the devinfo node associated with this | |
| * lnode. Also update the node_next next pointer. this pointer | |
| * is set if there are multiple lnodes associated with the same | |
| * devinfo node. (could occure when multiple minor nodes | |
| * are open for one device, etc.) | |
| */ | |
| 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)); | |
| /* get driver layering information */ | |
| (void) ldi_usage_walker(st, di_ldi_callback); | |
| /* check if there is any link data to include in the snapshot */ | |
| if (st->lnode_count == 0) { | |
| ASSERT(st->link_count == 0); | |
| goto out; | |
| } | |
| ASSERT(st->link_count != 0); | |
| /* get a pointer to snapshot memory for all the di_lnodes */ | |
| size = sizeof (struct di_lnode) * st->lnode_count; | |
| data.lnode_off = off = di_checkmem(st, off, size); | |
| off += size; | |
| /* get a pointer to snapshot memory for all the di_links */ | |
| 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; | |
| /* | |
| * We have lnodes and links that will go into the | |
| * snapshot, so let's walk the respective hashes | |
| * and snapshot them. The various linkages are | |
| * also set up during the walk. | |
| */ | |
| 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: | |
| /* free up the i_lnodes and i_links used to create the snapshot */ | |
| mod_hash_destroy_hash(st->lnode_hash); | |
| mod_hash_destroy_hash(st->link_hash); | |
| st->lnode_count = 0; | |
| st->link_count = 0; | |
| return (off); | |
| } | |
| /* | |
| * Copy all minor data nodes attached to a devinfo node into the snapshot. | |
| * It is called from di_copynode with active ndi_devi_enter to protect | |
| * the list of minor nodes. | |
| */ | |
| 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")); | |
| /* | |
| * check memory first | |
| */ | |
| 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 = NULL; | |
| off += sizeof (struct di_minor); | |
| /* | |
| * Split dev_t to major/minor, so it works for | |
| * both ILP32 and LP64 model | |
| */ | |
| 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); | |
| } | |
| /* | |
| * di_register_dip(), di_find_dip(): The dip must be protected | |
| * from deallocation when using these routines - this can either | |
| * be a reference count, a busy hold or a per-driver lock. | |
| */ | |
| 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 must be used because it matches the size of void *; | |
| * mod_hash expects clients to place results into pointer-size | |
| * containers; since di_off_t is always a 32-bit offset, alignment | |
| * would otherwise be broken on 64-bit kernels. | |
| */ | |
| 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); | |
| } | |
| } | |
| /* | |
| * di_register_pip(), di_find_pip(): The pip must be protected from deallocation | |
| * when using these routines. The caller must do this by protecting the | |
| * client(or phci)<->pip linkage while traversing the list and then holding the | |
| * pip when it is found in the list. | |
| */ | |
| 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); | |
| } | |
| } | |
| /* | |
| * As with di_register_pip, the caller must hold or lock the pip | |
| */ | |
| static int | |
| di_pip_find(struct di_state *st, mdi_pathinfo_t *pip, di_off_t *off_p) | |
| { | |
| /* | |
| * uintptr_t must be used because it matches the size of void *; | |
| * mod_hash expects clients to place results into pointer-size | |
| * containers; since di_off_t is always a 32-bit offset, alignment | |
| * would otherwise be broken on 64-bit kernels. | |
| */ | |
| 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; | |
| /* MDI_PATHINFO_FLAGS_HIDDEN nodes not in snapshot */ | |
| 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); | |
| /* | |
| * property name | |
| */ | |
| 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: /* Unknown or unhandled type; skip it */ | |
| 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); | |
| } | |
| } | |
| /* | |
| * off_p: pointer to the linkage field. This links pips along the client|phci | |
| * linkage list. | |
| * noff : Offset for the endpoint dip snapshot. | |
| */ | |
| 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)); | |
| /* | |
| * The naming of the following mdi_xyz() is unfortunately | |
| * non-intuitive. mdi_get_next_phci_path() follows the | |
| * client_link i.e. the list of pip's belonging to the | |
| * given client dip. | |
| */ | |
| 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); | |
| /* We don't represent hidden paths in the snapshot */ | |
| 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) { | |
| /* | |
| * We've already seen this pathinfo node so we need to | |
| * take care not to snap it again; However, one endpoint | |
| * and linkage will be set here. The other endpoint | |
| * and linkage has already been set when the pip was | |
| * first snapshotted i.e. when the other endpoint dip | |
| * was snapshotted. | |
| */ | |
| me = DI_PATH(di_mem_addr(st, stored_offset)); | |
| *off_p = stored_offset; | |
| di_path_one_endpoint(me, noff, &off_p, get_client); | |
| /* | |
| * The other endpoint and linkage were set when this | |
| * pip was snapshotted. So we are done with both | |
| * endpoints and linkages. | |
| */ | |
| 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; | |
| } | |
| /* | |
| * Now that we need to snapshot this pip, check memory | |
| */ | |
| 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; | |
| /* | |
| * Zero out fields as di_checkmem() doesn't guarantee | |
| * zero-filled memory | |
| */ | |
| 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); | |
| /* | |
| * Note the existence of this pathinfo | |
| */ | |
| 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); | |
| /* | |
| * Get intermediate addressing info. | |
| */ | |
| 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; | |
| /* | |
| * Get path properties if props are to be included in the | |
| * snapshot | |
| */ | |
| 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); | |
| } | |
| /* | |
| * Return driver prop_op entry point for the specified devinfo node. | |
| * | |
| * To return a non-NULL value: | |
| * - driver must be attached and held: | |
| * If driver is not attached we ignore the driver property list. | |
| * No one should rely on such properties. | |
| * - driver "cb_prop_op != ddi_prop_op": | |
| * If "cb_prop_op == ddi_prop_op", framework does not need to call driver. | |
| * XXX or parent's bus_prop_op != ddi_bus_prop_op | |
| */ | |
| 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 driver is not attached we ignore the driver property list. */ | |
| if ((dip == NULL) || !i_ddi_devi_attached((dev_info_t *)dip)) | |
| return (NULL); | |
| /* | |
| * Some nexus drivers incorrectly set cb_prop_op to nodev, nulldev, | |
| * or even 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 we have prop_op function, ask driver for latest value */ | |
| if (prop_op) { | |
| ASSERT(dip); | |
| /* Must search DDI_DEV_T_NONE with DDI_DEV_T_ANY */ | |
| pdevt = (devt == DDI_DEV_T_NONE) ? DDI_DEV_T_ANY : devt; | |
| /* | |
| * We have type information in flags, but are invoking an | |
| * old non-typed prop_op(9E) interface. Since not all types are | |
| * part of DDI_PROP_TYPE_ANY (example is DDI_PROP_TYPE_INT64), | |
| * we set DDI_PROP_CONSUMER_TYPED - causing the framework to | |
| * expand type bits beyond DDI_PROP_TYPE_ANY. This allows us | |
| * to use the legacy prop_op(9E) interface to obtain updates | |
| * non-DDI_PROP_TYPE_ANY dynamic properties. | |
| */ | |
| pflags = aflags & ~DDI_PROP_TYPE_MASK; | |
| pflags |= DDI_PROP_DONTPASS | DDI_PROP_NOTPROM | | |
| DDI_PROP_CONSUMER_TYPED; | |
| /* | |
| * Hold and exit across prop_op(9E) to avoid lock order | |
| * issues between | |
| * [ndi_devi_enter() ..prop_op(9E).. driver-lock] | |
| * .vs. | |
| * [..ioctl(9E).. driver-lock ..ddi_remove_minor_node(9F).. | |
| * ndi_devi_enter()] | |
| * ordering. | |
| */ | |
| ndi_hold_devi((dev_info_t *)dip); | |
| ndi_devi_exit((dev_info_t *)dip, dip->devi_circular); | |
| rv = (*prop_op)(pdevt, (dev_info_t *)dip, | |
| PROP_LEN_AND_VAL_ALLOC, pflags, name, &val, &len); | |
| ndi_devi_enter((dev_info_t *)dip, &dip->devi_circular); | |
| ndi_rele_devi((dev_info_t *)dip); | |
| if (rv == DDI_PROP_SUCCESS) { | |
| need_free = 1; /* dynamic prop obtained */ | |
| } else if (dyn) { | |
| /* | |
| * A dynamic property must succeed prop_op(9E) to show | |
| * up in the snapshot - that is the only source of its | |
| * value. | |
| */ | |
| return (off); /* dynamic prop not supported */ | |
| } else { | |
| /* | |
| * In case calling the driver caused an update off | |
| * prop_op(9E) of a non-dynamic property (code leading | |
| * to ddi_prop_change), we defer picking up val and | |
| * len informatiojn until after prop_op(9E) to ensure | |
| * that we snapshot the latest value. | |
| */ | |
| 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; | |
| /* property name */ | |
| 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; /* assume tail for now */ | |
| *off_pp = &pp->next; /* return pointer to our next */ | |
| if (need_free) /* free PROP_LEN_AND_VAL_ALLOC alloc */ | |
| kmem_free(val, len); | |
| return (off); | |
| } | |
| /* | |
| * Copy a list of properties attached to a devinfo node. Called from | |
| * di_copynode with active ndi_devi_enter. The major number is passed in case | |
| * we need to call driver's prop_op entry. The value of list indicates | |
| * which list we are copying. Possible values are: | |
| * DI_PROP_DRV_LIST, DI_PROP_SYS_LIST, DI_PROP_GLB_LIST, DI_PROP_HW_LIST | |
| */ | |
| 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)); | |
| /* get pointer to driver's prop_op(9E) implementation if DRV_LIST */ | |
| prop_op = (list == DI_PROP_DRV_LIST) ? di_getprop_prop_op(dip) : NULL; | |
| /* | |
| * Form private list of properties, holding devi_lock for properties | |
| * that hang off the dip. | |
| */ | |
| 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)); | |
| /* | |
| * Now that we have dropped devi_lock, perform a second-pass to | |
| * add properties to the snapshot. We do this as a second pass | |
| * because we may need to call prop_op(9E) and we can't hold | |
| * devi_lock across that call. | |
| */ | |
| 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 there is no prop_op or dynamic property support has been | |
| * disabled, we are done. | |
| */ | |
| if ((prop_op == NULL) || (di_prop_dyn == 0)) { | |
| *off_p = 0; | |
| return (off); | |
| } | |
| /* Add dynamic driver properties to snapshot */ | |
| for (dp = i_ddi_prop_dyn_driver_get((dev_info_t *)dip); | |
| dp && dp->dp_name; dp++) { | |
| if (dp->dp_spec_type) { | |
| /* if spec_type, property of matching minor */ | |
| 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 { | |
| /* property of devinfo node */ | |
| 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); | |
| } | |
| } | |
| /* Add dynamic parent properties to snapshot */ | |
| for (dp = i_ddi_prop_dyn_parent_get((dev_info_t *)dip); | |
| dp && dp->dp_name; dp++) { | |
| if (dp->dp_spec_type) { | |
| /* if spec_type, property of matching minor */ | |
| 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 { | |
| /* property of devinfo node */ | |
| 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); | |
| } | |
| /* | |
| * find private data format attached to a dip | |
| * parent = 1 to match driver name of parent dip (for parent private data) | |
| * 0 to match driver name of current dip (for driver private data) | |
| */ | |
| #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); | |
| } | |
| /* | |
| * Match the driver name. | |
| */ | |
| drv_name = ddi_major_to_name(major); | |
| if ((drv_name == NULL) || *drv_name == '\0') { | |
| return (NULL); | |
| } | |
| /* Now get the di_priv_format array */ | |
| 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]); | |
| } | |
| /* | |
| * Move to next driver name, skipping a white space | |
| */ | |
| if (tmp = strchr(tmp, ' ')) { | |
| tmp++; | |
| } | |
| } | |
| } | |
| return (NULL); | |
| } | |
| /* | |
| * The following functions copy data as specified by the format passed in. | |
| * To prevent invalid format from panicing the system, we call on_fault(). | |
| * A return value of 0 indicates an error. Otherwise, the total offset | |
| * is returned. | |
| */ | |
| #define DI_MAX_PRIVDATA (PAGESIZE >> 1) /* max private data size */ | |
| 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")); | |
| /* | |
| * check memory availability. Private data size is | |
| * limited to DI_MAX_PRIVDATA. | |
| */ | |
| 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)) { | |
| /* copy the struct */ | |
| bcopy(data, di_mem_addr(st, off), pdp->bytes); | |
| off0 = DI_ALIGN(pdp->bytes); /* XXX remove DI_ALIGN */ | |
| /* dereferencing pointers */ | |
| for (i = 0; i < MAX_PTR_IN_PRV; i++) { | |
| if (pdp->ptr[i].size == 0) { | |
| goto success; /* no more ptrs */ | |
| } | |
| /* | |
| * first, get the pointer content | |
| */ | |
| if ((pdp->ptr[i].offset < 0) || | |
| (pdp->ptr[i].offset > pdp->bytes - sizeof (char *))) | |
| goto failure; /* wrong offset */ | |
| pa = di_mem_addr(st, off + pdp->ptr[i].offset); | |
| /* save a tmp ptr to store off_t later */ | |
| tmp = (di_off_t *)(intptr_t)pa; | |
| /* get pointer value, if NULL continue */ | |
| ptr = *((void **) (intptr_t)pa); | |
| if (ptr == NULL) { | |
| continue; | |
| } | |
| /* | |
| * next, find the repeat count (array dimension) | |
| */ | |
| repeat = pdp->ptr[i].len_offset; | |
| /* | |
| * Positive value indicates a fixed sized array. | |
| * 0 or negative value indicates variable sized array. | |
| * | |
| * For variable sized array, the variable must be | |
| * an int member of the structure, with an offset | |
| * equal to the absolution value of struct member. | |
| */ | |
| if (repeat > pdp->bytes - sizeof (int)) { | |
| goto failure; /* wrong offset */ | |
| } | |
| if (repeat >= 0) { | |
| repeat = *((int *) | |
| (intptr_t)((caddr_t)data + repeat)); | |
| } else { | |
| repeat = -repeat; | |
| } | |
| /* | |
| * next, get the size of the object to be copied | |
| */ | |
| size = pdp->ptr[i].size * repeat; | |
| /* | |
| * Arbitrarily limit the total size of object to be | |
| * copied (1 byte to 1/4 page). | |
| */ | |
| if ((size <= 0) || (size > (DI_MAX_PRIVDATA - off0))) { | |
| goto failure; /* wrong size or too big */ | |
| } | |
| /* | |
| * Now copy the data | |
| */ | |
| *tmp = off0; | |
| bcopy(ptr, di_mem_addr(st, off + off0), size); | |
| off0 += DI_ALIGN(size); /* XXX remove DI_ALIGN */ | |
| } | |
| } else { | |
| goto failure; | |
| } | |
| success: | |
| /* | |
| * success if reached here | |
| */ | |
| no_fault(); | |
| return (off + off0); | |
| /*NOTREACHED*/ | |
| failure: | |
| /* | |
| * fault occurred | |
| */ | |
| 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; /* set private data to indicate error */ | |
| return (off); | |
| } | |
| /* | |
| * get parent private data; on error, returns original offset | |
| */ | |
| 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")); | |
| /* find the parent data format */ | |
| if ((ppdp = di_match_drv_name(node, st, DI_MATCH_PARENT)) == NULL) { | |
| off = *off_p; | |
| *off_p = 0; /* set parent data to none */ | |
| return (off); | |
| } | |
| return (di_getprvdata(ppdp, node, | |
| ddi_get_parent_data((dev_info_t *)node), off_p, st)); | |
| } | |
| /* | |
| * get parent private data; returns original offset | |
| */ | |
| 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:")); | |
| /* find the parent data format */ | |
| if ((dpdp = di_match_drv_name(node, st, DI_MATCH_DRIVER)) == NULL) { | |
| off = *off_p; | |
| *off_p = 0; /* set driver data to none */ | |
| return (off); | |
| } | |
| return (di_getprvdata(dpdp, node, | |
| ddi_get_driver_private((dev_info_t *)node), off_p, st)); | |
| } | |
| /* | |
| * Copy hotplug data associated with a devinfo node into the snapshot. | |
| */ | |
| 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")); | |
| /* | |
| * check memory first | |
| */ | |
| 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; | |
| /* | |
| * Child links are resolved later by di_hotplug_children(). | |
| * Store a reference to this di_hp_t in the list used later | |
| * by di_hotplug_children(). | |
| */ | |
| 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); | |
| /* Add name of this di_hp_t to the snapshot */ | |
| 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; | |
| } | |
| /* Add type description of this di_hp_t to the snapshot */ | |
| 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; | |
| } | |
| /* | |
| * Set link to next in the chain of di_hp_t nodes, | |
| * or terminate the chain when processing the last node. | |
| */ | |
| if (hp_hdl->next != NULL) { | |
| off = di_checkmem(st, off, sizeof (struct di_hp)); | |
| me->next = off; | |
| } else { | |
| me->next = 0; | |
| } | |
| /* Update pointer to next in the chain */ | |
| hp_hdl = hp_hdl->next; | |
| } while (hp_hdl); | |
| return (off); | |
| } | |
| /* | |
| * The driver is stateful across DINFOCPYALL and DINFOUSRLD. | |
| * This function encapsulates the state machine: | |
| * | |
| * -> IOC_IDLE -> IOC_SNAP -> IOC_DONE -> IOC_COPY -> | |
| * | SNAPSHOT USRLD | | |
| * -------------------------------------------------- | |
| * | |
| * Returns 0 on success and -1 on failure | |
| */ | |
| 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); | |
| } | |
| /* | |
| * We cannot assume the presence of the entire | |
| * snapshot in this routine. All we are guaranteed | |
| * is the di_all struct + 1 byte (for root_path) | |
| */ | |
| static int | |
| header_plus_one_ok(struct di_all *all) | |
| { | |
| /* | |
| * Refuse to read old versions | |
| */ | |
| 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); | |
| } | |
| /* | |
| * We can't check checksum here as we just have the header | |
| */ | |
| 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; | |
| } | |
| /* | |
| * Check if we are making progress | |
| */ | |
| 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); | |
| /* | |
| * The cache_size is the total allocated memory for the cache. | |
| * The map_size is the actual size of valid data in the cache. | |
| * map_size may be smaller than cache_size but cannot exceed | |
| * cache_size. | |
| */ | |
| 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; | |
| } | |
| /* | |
| * First unlink the temp file | |
| */ | |
| 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); | |
| /* | |
| * Paranoid: Check if the file is on a read-only FS | |
| */ | |
| if (vn_is_readonly(vp)) { | |
| CACHE_DEBUG((DI_ERR, "cannot write: readonly FS")); | |
| goto fail; | |
| } | |
| /* | |
| * Note that we only write map_size bytes to disk - this saves | |
| * space as the actual cache size may be larger than size of | |
| * valid data in the cache. | |
| * Another advantage is that it makes verification of size | |
| * easier when the file is read later. | |
| */ | |
| map_size = all->map_size; | |
| off = 0; | |
| buf = cache->cache_data; | |
| while (map_size) { | |
| ASSERT(map_size > 0); | |
| /* | |
| * Write in chunks so that VM system | |
| * is not overwhelmed | |
| */ | |
| 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 low on memory, give pageout a chance to run */ | |
| if (freemem < desfree) | |
| delay(1); | |
| } | |
| /* | |
| * Now sync the file and close it | |
| */ | |
| 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); | |
| /* | |
| * Now do the rename | |
| */ | |
| 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); | |
| } | |
| /* | |
| * Since we could be called early in boot, | |
| * use kobj_read_file() | |
| */ | |
| 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; | |
| } | |
| /* | |
| * Read in the header+root_path first. The root_path must be "/" | |
| */ | |
| 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) { | |
| /* Don't overload VM with large reads */ | |
| 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); | |
| /* | |
| * Read past expected EOF to verify 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; | |
| } | |
| /* | |
| * Compute CRC with checksum field in the cache data set to 0 | |
| */ | |
| 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; | |
| } | |
| /* | |
| * Checks if arguments are valid for using the cache. | |
| */ | |
| 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); | |
| } | |
| /* | |
| * No other flags allowed with DINFOCACHE | |
| */ | |
| 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); | |
| /* | |
| * The following assignment determines the validity | |
| * of the cache as far as this snapshot is concerned. | |
| */ | |
| cache_valid = di_cache.cache_valid; | |
| if (cache_valid && di_cache.cache_data == NULL) { | |
| di_cache_read(&di_cache); | |
| /* check for read or file error */ | |
| if (di_cache.cache_data == NULL) | |
| cache_valid = 0; | |
| } | |
| if (cache_valid) { | |
| /* | |
| * Ok, the cache was valid as of this particular | |
| * snapshot. Copy the cached snapshot. This is safe | |
| * to do as the cache cannot be freed (we hold the | |
| * cache lock). Free the memory allocated in di_state | |
| * up until this point - we will simply copy everything | |
| * in the cache. | |
| */ | |
| ASSERT(di_cache.cache_data != NULL); | |
| ASSERT(di_cache.cache_size > 0); | |
| di_freemem(st); | |
| rval = 0; | |
| if (di_cache2mem(&di_cache, st) > 0) { | |
| /* | |
| * map_size is size of valid data in the | |
| * cached snapshot and may be less than | |
| * size of the cache. | |
| */ | |
| 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 { | |
| /* | |
| * The cache isn't valid, we need to take a snapshot. | |
| * Set the command flags appropriately | |
| */ | |
| 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); | |
| /* | |
| * For cached snapshots, the devinfo driver always returns | |
| * a snapshot rooted at "/". | |
| */ | |
| ASSERT(rval == 0 || strcmp(DI_ALL_PTR(st)->root_path, "/") == 0); | |
| return ((int)rval); | |
| } | |
| /* | |
| * This is a forced update of the cache - the previous state of the cache | |
| * may be: | |
| * - unpopulated | |
| * - populated and invalid | |
| * - populated and valid | |
| */ | |
| 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)); | |
| /* | |
| * Free the in-core cache and the on-disk file (if they exist) | |
| */ | |
| i_ddi_di_cache_free(&di_cache); | |
| /* | |
| * Set valid flag before taking the snapshot, | |
| * so that any invalidations that arrive | |
| * during or after the snapshot are not | |
| * removed by us. | |
| */ | |
| 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); | |
| /* | |
| * Now that we have cached the snapshot, compute its checksum. | |
| * The checksum is only computed over the valid data in the | |
| * cache, not the entire cache. | |
| * Also, set all the fields (except checksum) before computing | |
| * checksum. | |
| */ | |
| 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); | |
| } |