/* * 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) 1994, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * Copyright 2019 Joyent, Inc. */ /* * Layered driver support. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Device contract related */ #include #include /* * Define macros to manipulate snode, vnode, and open device flags */ #define VTYP_VALID(i) (((i) == VCHR) || ((i) == VBLK)) #define VTYP_TO_OTYP(i) (((i) == VCHR) ? OTYP_CHR : OTYP_BLK) #define VTYP_TO_STYP(i) (((i) == VCHR) ? S_IFCHR : S_IFBLK) #define OTYP_VALID(i) (((i) == OTYP_CHR) || ((i) == OTYP_BLK)) #define OTYP_TO_VTYP(i) (((i) == OTYP_CHR) ? VCHR : VBLK) #define OTYP_TO_STYP(i) (((i) == OTYP_CHR) ? S_IFCHR : S_IFBLK) #define STYP_VALID(i) (((i) == S_IFCHR) || ((i) == S_IFBLK)) #define STYP_TO_VTYP(i) (((i) == S_IFCHR) ? VCHR : VBLK) /* * Define macros for accessing layered driver hash structures */ #define LH_HASH(vp) (handle_hash_func(vp) % LH_HASH_SZ) #define LI_HASH(mid, dip, dev) (ident_hash_func(mid, dip, dev) % LI_HASH_SZ) /* * Define layered handle flags used in the lh_type field */ #define LH_STREAM (0x1) /* handle to a streams device */ #define LH_CBDEV (0x2) /* handle to a char/block device */ /* * Define macro for devid property lookups */ #define DEVID_PROP_FLAGS (DDI_PROP_DONTPASS | \ DDI_PROP_TYPE_STRING|DDI_PROP_CANSLEEP) /* * Dummy string for NDI events */ #define NDI_EVENT_SERVICE "NDI_EVENT_SERVICE" static void ldi_ev_lock(void); static void ldi_ev_unlock(void); #ifdef LDI_OBSOLETE_EVENT int ldi_remove_event_handler(ldi_handle_t lh, ldi_callback_id_t id); #endif /* * globals */ static kmutex_t ldi_ident_hash_lock[LI_HASH_SZ]; static struct ldi_ident *ldi_ident_hash[LI_HASH_SZ]; static kmutex_t ldi_handle_hash_lock[LH_HASH_SZ]; static struct ldi_handle *ldi_handle_hash[LH_HASH_SZ]; static size_t ldi_handle_hash_count; /* * Use of "ldi_ev_callback_list" must be protected by ldi_ev_lock() * and ldi_ev_unlock(). */ static struct ldi_ev_callback_list ldi_ev_callback_list; static uint32_t ldi_ev_id_pool = 0; struct ldi_ev_cookie { char *ck_evname; uint_t ck_sync; uint_t ck_ctype; }; static struct ldi_ev_cookie ldi_ev_cookies[] = { { LDI_EV_OFFLINE, 1, CT_DEV_EV_OFFLINE}, { LDI_EV_DEGRADE, 0, CT_DEV_EV_DEGRADED}, { LDI_EV_DEVICE_REMOVE, 0, 0}, { NULL} /* must terminate list */ }; void ldi_init(void) { int i; ldi_handle_hash_count = 0; for (i = 0; i < LH_HASH_SZ; i++) { mutex_init(&ldi_handle_hash_lock[i], NULL, MUTEX_DEFAULT, NULL); ldi_handle_hash[i] = NULL; } for (i = 0; i < LI_HASH_SZ; i++) { mutex_init(&ldi_ident_hash_lock[i], NULL, MUTEX_DEFAULT, NULL); ldi_ident_hash[i] = NULL; } /* * Initialize the LDI event subsystem */ mutex_init(&ldi_ev_callback_list.le_lock, NULL, MUTEX_DEFAULT, NULL); cv_init(&ldi_ev_callback_list.le_cv, NULL, CV_DEFAULT, NULL); ldi_ev_callback_list.le_busy = 0; ldi_ev_callback_list.le_thread = NULL; ldi_ev_callback_list.le_walker_next = NULL; ldi_ev_callback_list.le_walker_prev = NULL; list_create(&ldi_ev_callback_list.le_head, sizeof (ldi_ev_callback_impl_t), offsetof(ldi_ev_callback_impl_t, lec_list)); } /* * LDI ident manipulation functions */ static uint_t ident_hash_func(modid_t modid, dev_info_t *dip, dev_t dev) { if (dip != NULL) { uintptr_t k = (uintptr_t)dip; k >>= (int)highbit(sizeof (struct dev_info)); return ((uint_t)k); } else if (dev != DDI_DEV_T_NONE) { return (modid + getminor(dev) + getmajor(dev)); } else { return (modid); } } static struct ldi_ident ** ident_find_ref_nolock(modid_t modid, dev_info_t *dip, dev_t dev, major_t major) { struct ldi_ident **lipp = NULL; uint_t index = LI_HASH(modid, dip, dev); ASSERT(MUTEX_HELD(&ldi_ident_hash_lock[index])); for (lipp = &(ldi_ident_hash[index]); (*lipp != NULL); lipp = &((*lipp)->li_next)) { if (((*lipp)->li_modid == modid) && ((*lipp)->li_major == major) && ((*lipp)->li_dip == dip) && ((*lipp)->li_dev == dev)) break; } ASSERT(lipp != NULL); return (lipp); } static struct ldi_ident * ident_alloc(char *mod_name, dev_info_t *dip, dev_t dev, major_t major) { struct ldi_ident *lip, **lipp, *retlip; modid_t modid; uint_t index; ASSERT(mod_name != NULL); /* get the module id */ modid = mod_name_to_modid(mod_name); ASSERT(modid != -1); /* allocate a new ident in case we need it */ lip = kmem_zalloc(sizeof (*lip), KM_SLEEP); /* search the hash for a matching ident */ index = LI_HASH(modid, dip, dev); mutex_enter(&ldi_ident_hash_lock[index]); lipp = ident_find_ref_nolock(modid, dip, dev, major); if (*lipp != NULL) { /* we found an ident in the hash */ ASSERT(strcmp((*lipp)->li_modname, mod_name) == 0); (*lipp)->li_ref++; retlip = *lipp; mutex_exit(&ldi_ident_hash_lock[index]); kmem_free(lip, sizeof (struct ldi_ident)); return (retlip); } /* initialize the new ident */ lip->li_next = NULL; lip->li_ref = 1; lip->li_modid = modid; lip->li_major = major; lip->li_dip = dip; lip->li_dev = dev; (void) strncpy(lip->li_modname, mod_name, sizeof (lip->li_modname) - 1); /* add it to the ident hash */ lip->li_next = ldi_ident_hash[index]; ldi_ident_hash[index] = lip; mutex_exit(&ldi_ident_hash_lock[index]); return (lip); } static void ident_hold(struct ldi_ident *lip) { uint_t index; ASSERT(lip != NULL); index = LI_HASH(lip->li_modid, lip->li_dip, lip->li_dev); mutex_enter(&ldi_ident_hash_lock[index]); ASSERT(lip->li_ref > 0); lip->li_ref++; mutex_exit(&ldi_ident_hash_lock[index]); } static void ident_release(struct ldi_ident *lip) { struct ldi_ident **lipp; uint_t index; ASSERT(lip != NULL); index = LI_HASH(lip->li_modid, lip->li_dip, lip->li_dev); mutex_enter(&ldi_ident_hash_lock[index]); ASSERT(lip->li_ref > 0); if (--lip->li_ref > 0) { /* there are more references to this ident */ mutex_exit(&ldi_ident_hash_lock[index]); return; } /* this was the last reference/open for this ident. free it. */ lipp = ident_find_ref_nolock( lip->li_modid, lip->li_dip, lip->li_dev, lip->li_major); ASSERT((lipp != NULL) && (*lipp != NULL)); *lipp = lip->li_next; mutex_exit(&ldi_ident_hash_lock[index]); kmem_free(lip, sizeof (struct ldi_ident)); } /* * LDI handle manipulation functions */ static uint_t handle_hash_func(void *vp) { uintptr_t k = (uintptr_t)vp; k >>= (int)highbit(sizeof (vnode_t)); return ((uint_t)k); } static struct ldi_handle ** handle_find_ref_nolock(vnode_t *vp, struct ldi_ident *ident) { struct ldi_handle **lhpp = NULL; uint_t index = LH_HASH(vp); ASSERT(MUTEX_HELD(&ldi_handle_hash_lock[index])); for (lhpp = &(ldi_handle_hash[index]); (*lhpp != NULL); lhpp = &((*lhpp)->lh_next)) { if (((*lhpp)->lh_ident == ident) && ((*lhpp)->lh_vp == vp)) break; } ASSERT(lhpp != NULL); return (lhpp); } static struct ldi_handle * handle_find(vnode_t *vp, struct ldi_ident *ident) { struct ldi_handle **lhpp, *retlhp; int index = LH_HASH(vp); mutex_enter(&ldi_handle_hash_lock[index]); lhpp = handle_find_ref_nolock(vp, ident); retlhp = *lhpp; mutex_exit(&ldi_handle_hash_lock[index]); return (retlhp); } static struct ldi_handle * handle_alloc(vnode_t *vp, struct ldi_ident *ident) { struct ldi_handle *lhp, **lhpp, *retlhp; uint_t index; ASSERT((vp != NULL) && (ident != NULL)); /* allocate a new handle in case we need it */ lhp = kmem_zalloc(sizeof (*lhp), KM_SLEEP); /* search the hash for a matching handle */ index = LH_HASH(vp); mutex_enter(&ldi_handle_hash_lock[index]); lhpp = handle_find_ref_nolock(vp, ident); if (*lhpp != NULL) { /* we found a handle in the hash */ (*lhpp)->lh_ref++; retlhp = *lhpp; mutex_exit(&ldi_handle_hash_lock[index]); LDI_ALLOCFREE((CE_WARN, "ldi handle alloc: dup " "lh=0x%p, ident=0x%p, vp=0x%p, drv=%s, minor=0x%x", (void *)retlhp, (void *)ident, (void *)vp, mod_major_to_name(getmajor(vp->v_rdev)), getminor(vp->v_rdev))); kmem_free(lhp, sizeof (struct ldi_handle)); return (retlhp); } /* initialize the new handle */ lhp->lh_ref = 1; lhp->lh_vp = vp; lhp->lh_ident = ident; #ifdef LDI_OBSOLETE_EVENT mutex_init(lhp->lh_lock, NULL, MUTEX_DEFAULT, NULL); #endif /* set the device type for this handle */ lhp->lh_type = 0; if (vp->v_stream) { ASSERT(vp->v_type == VCHR); lhp->lh_type |= LH_STREAM; } else { lhp->lh_type |= LH_CBDEV; } /* get holds on other objects */ ident_hold(ident); ASSERT(vp->v_count >= 1); VN_HOLD(vp); /* add it to the handle hash */ lhp->lh_next = ldi_handle_hash[index]; ldi_handle_hash[index] = lhp; atomic_inc_ulong(&ldi_handle_hash_count); LDI_ALLOCFREE((CE_WARN, "ldi handle alloc: new " "lh=0x%p, ident=0x%p, vp=0x%p, drv=%s, minor=0x%x", (void *)lhp, (void *)ident, (void *)vp, mod_major_to_name(getmajor(vp->v_rdev)), getminor(vp->v_rdev))); mutex_exit(&ldi_handle_hash_lock[index]); return (lhp); } static void handle_release(struct ldi_handle *lhp) { struct ldi_handle **lhpp; uint_t index; ASSERT(lhp != NULL); index = LH_HASH(lhp->lh_vp); mutex_enter(&ldi_handle_hash_lock[index]); LDI_ALLOCFREE((CE_WARN, "ldi handle release: " "lh=0x%p, ident=0x%p, vp=0x%p, drv=%s, minor=0x%x", (void *)lhp, (void *)lhp->lh_ident, (void *)lhp->lh_vp, mod_major_to_name(getmajor(lhp->lh_vp->v_rdev)), getminor(lhp->lh_vp->v_rdev))); ASSERT(lhp->lh_ref > 0); if (--lhp->lh_ref > 0) { /* there are more references to this handle */ mutex_exit(&ldi_handle_hash_lock[index]); return; } /* this was the last reference/open for this handle. free it. */ lhpp = handle_find_ref_nolock(lhp->lh_vp, lhp->lh_ident); ASSERT((lhpp != NULL) && (*lhpp != NULL)); *lhpp = lhp->lh_next; atomic_dec_ulong(&ldi_handle_hash_count); mutex_exit(&ldi_handle_hash_lock[index]); VN_RELE(lhp->lh_vp); ident_release(lhp->lh_ident); #ifdef LDI_OBSOLETE_EVENT mutex_destroy(lhp->lh_lock); #endif kmem_free(lhp, sizeof (struct ldi_handle)); } #ifdef LDI_OBSOLETE_EVENT /* * LDI event manipulation functions */ static void handle_event_add(ldi_event_t *lep) { struct ldi_handle *lhp = lep->le_lhp; ASSERT(lhp != NULL); mutex_enter(lhp->lh_lock); if (lhp->lh_events == NULL) { lhp->lh_events = lep; mutex_exit(lhp->lh_lock); return; } lep->le_next = lhp->lh_events; lhp->lh_events->le_prev = lep; lhp->lh_events = lep; mutex_exit(lhp->lh_lock); } static void handle_event_remove(ldi_event_t *lep) { struct ldi_handle *lhp = lep->le_lhp; ASSERT(lhp != NULL); mutex_enter(lhp->lh_lock); if (lep->le_prev) lep->le_prev->le_next = lep->le_next; if (lep->le_next) lep->le_next->le_prev = lep->le_prev; if (lhp->lh_events == lep) lhp->lh_events = lep->le_next; mutex_exit(lhp->lh_lock); } static void i_ldi_callback(dev_info_t *dip, ddi_eventcookie_t event_cookie, void *arg, void *bus_impldata) { ldi_event_t *lep = (ldi_event_t *)arg; ASSERT(lep != NULL); LDI_EVENTCB((CE_NOTE, "%s: dip=0x%p, " "event_cookie=0x%p, ldi_eventp=0x%p", "i_ldi_callback", (void *)dip, (void *)event_cookie, (void *)lep)); lep->le_handler(lep->le_lhp, event_cookie, lep->le_arg, bus_impldata); } #endif /* * LDI open helper functions */ /* get a vnode to a device by dev_t and otyp */ static int ldi_vp_from_dev(dev_t dev, int otyp, vnode_t **vpp) { dev_info_t *dip; vnode_t *vp; /* sanity check required input parameters */ if ((dev == DDI_DEV_T_NONE) || (!OTYP_VALID(otyp)) || (vpp == NULL)) return (EINVAL); if ((dip = e_ddi_hold_devi_by_dev(dev, 0)) == NULL) return (ENODEV); vp = makespecvp(dev, OTYP_TO_VTYP(otyp)); spec_assoc_vp_with_devi(vp, dip); ddi_release_devi(dip); /* from e_ddi_hold_devi_by_dev */ *vpp = vp; return (0); } /* get a vnode to a device by pathname */ int ldi_vp_from_name(char *path, vnode_t **vpp) { vnode_t *vp = NULL; int ret; /* sanity check required input parameters */ if ((path == NULL) || (vpp == NULL)) return (EINVAL); if (modrootloaded) { cred_t *saved_cred = curthread->t_cred; /* we don't want lookupname to fail because of credentials */ curthread->t_cred = kcred; /* * all lookups should be done in the global zone. but * lookupnameat() won't actually do this if an absolute * path is passed in. since the ldi interfaces require an * absolute path we pass lookupnameat() a pointer to * the character after the leading '/' and tell it to * start searching at the current system root directory. */ ASSERT(*path == '/'); ret = lookupnameat(path + 1, UIO_SYSSPACE, FOLLOW, NULLVPP, &vp, rootdir); /* restore this threads credentials */ curthread->t_cred = saved_cred; if (ret == 0) { if (!vn_matchops(vp, spec_getvnodeops()) || !VTYP_VALID(vp->v_type)) { VN_RELE(vp); return (ENXIO); } } } if (vp == NULL) { dev_info_t *dip; dev_t dev; int spec_type; /* * Root is not mounted, the minor node is not specified, * or an OBP path has been specified. */ /* * Determine if path can be pruned to produce an * OBP or devfs path for resolve_pathname. */ if (strncmp(path, "/devices/", 9) == 0) path += strlen("/devices"); /* * if no minor node was specified the DEFAULT minor node * will be returned. if there is no DEFAULT minor node * one will be fabricated of type S_IFCHR with the minor * number equal to the instance number. */ ret = resolve_pathname(path, &dip, &dev, &spec_type); if (ret != 0) return (ENODEV); ASSERT(STYP_VALID(spec_type)); vp = makespecvp(dev, STYP_TO_VTYP(spec_type)); spec_assoc_vp_with_devi(vp, dip); ddi_release_devi(dip); } *vpp = vp; return (0); } static int ldi_devid_match(ddi_devid_t devid, dev_info_t *dip, dev_t dev) { char *devidstr; ddi_prop_t *propp; /* convert devid as a string property */ if ((devidstr = ddi_devid_str_encode(devid, NULL)) == NULL) return (0); /* * Search for the devid. For speed and ease in locking this * code directly uses the property implementation. See * ddi_common_devid_to_devlist() for a comment as to why. */ mutex_enter(&(DEVI(dip)->devi_lock)); /* check if there is a DDI_DEV_T_NONE devid property */ propp = i_ddi_prop_search(DDI_DEV_T_NONE, DEVID_PROP_NAME, DEVID_PROP_FLAGS, &DEVI(dip)->devi_hw_prop_ptr); if (propp != NULL) { if (ddi_devid_str_compare(propp->prop_val, devidstr) == 0) { /* a DDI_DEV_T_NONE devid exists and matchs */ mutex_exit(&(DEVI(dip)->devi_lock)); ddi_devid_str_free(devidstr); return (1); } else { /* a DDI_DEV_T_NONE devid exists and doesn't match */ mutex_exit(&(DEVI(dip)->devi_lock)); ddi_devid_str_free(devidstr); return (0); } } /* check if there is a devt specific devid property */ propp = i_ddi_prop_search(dev, DEVID_PROP_NAME, DEVID_PROP_FLAGS, &(DEVI(dip)->devi_hw_prop_ptr)); if (propp != NULL) { if (ddi_devid_str_compare(propp->prop_val, devidstr) == 0) { /* a devt specific devid exists and matchs */ mutex_exit(&(DEVI(dip)->devi_lock)); ddi_devid_str_free(devidstr); return (1); } else { /* a devt specific devid exists and doesn't match */ mutex_exit(&(DEVI(dip)->devi_lock)); ddi_devid_str_free(devidstr); return (0); } } /* we didn't find any devids associated with the device */ mutex_exit(&(DEVI(dip)->devi_lock)); ddi_devid_str_free(devidstr); return (0); } /* get a handle to a device by devid and minor name */ int ldi_vp_from_devid(ddi_devid_t devid, char *minor_name, vnode_t **vpp) { dev_info_t *dip; vnode_t *vp; int ret, i, ndevs, styp; dev_t dev, *devs; /* sanity check required input parameters */ if ((devid == NULL) || (minor_name == NULL) || (vpp == NULL)) return (EINVAL); ret = ddi_lyr_devid_to_devlist(devid, minor_name, &ndevs, &devs); if ((ret != DDI_SUCCESS) || (ndevs <= 0)) return (ENODEV); for (i = 0; i < ndevs; i++) { dev = devs[i]; if ((dip = e_ddi_hold_devi_by_dev(dev, 0)) == NULL) continue; /* * now we have to verify that the devid of the disk * still matches what was requested. * * we have to do this because the devid could have * changed between the call to ddi_lyr_devid_to_devlist() * and e_ddi_hold_devi_by_dev(). this is because when * ddi_lyr_devid_to_devlist() returns a list of devts * there is no kind of hold on those devts so a device * could have been replaced out from under us in the * interim. */ if ((i_ddi_minorname_to_devtspectype(dip, minor_name, NULL, &styp) == DDI_SUCCESS) && ldi_devid_match(devid, dip, dev)) break; ddi_release_devi(dip); /* from e_ddi_hold_devi_by_dev() */ } ddi_lyr_free_devlist(devs, ndevs); if (i == ndevs) return (ENODEV); ASSERT(STYP_VALID(styp)); vp = makespecvp(dev, STYP_TO_VTYP(styp)); spec_assoc_vp_with_devi(vp, dip); ddi_release_devi(dip); /* from e_ddi_hold_devi_by_dev */ *vpp = vp; return (0); } /* given a vnode, open a device */ static int ldi_open_by_vp(vnode_t **vpp, int flag, cred_t *cr, ldi_handle_t *lhp, struct ldi_ident *li) { struct ldi_handle *nlhp; vnode_t *vp; int err; ASSERT((vpp != NULL) && (*vpp != NULL)); ASSERT((lhp != NULL) && (li != NULL)); vp = *vpp; /* if the vnode passed in is not a device, then bail */ if (!vn_matchops(vp, spec_getvnodeops()) || !VTYP_VALID(vp->v_type)) return (ENXIO); /* * the caller may have specified a node that * doesn't have cb_ops defined. the ldi doesn't yet * support opening devices without a valid cb_ops. */ if (devopsp[getmajor(vp->v_rdev)]->devo_cb_ops == NULL) return (ENXIO); /* open the device */ if ((err = VOP_OPEN(&vp, flag | FKLYR, cr, NULL)) != 0) return (err); /* possible clone open, make sure that we still have a spec node */ ASSERT(vn_matchops(vp, spec_getvnodeops())); nlhp = handle_alloc(vp, li); if (vp != *vpp) { /* * allocating the layered handle took a new hold on the vnode * so we can release the hold that was returned by the clone * open */ LDI_OPENCLOSE((CE_WARN, "%s: lh=0x%p", "ldi clone open", (void *)nlhp)); } else { LDI_OPENCLOSE((CE_WARN, "%s: lh=0x%p", "ldi open", (void *)nlhp)); } *vpp = vp; *lhp = (ldi_handle_t)nlhp; return (0); } /* Call a drivers prop_op(9E) interface */ static int i_ldi_prop_op(dev_t dev, dev_info_t *dip, ddi_prop_op_t prop_op, int flags, char *name, caddr_t valuep, int *lengthp) { struct dev_ops *ops = NULL; int res; ASSERT((dip != NULL) && (name != NULL)); ASSERT((prop_op == PROP_LEN) || (valuep != NULL)); ASSERT(lengthp != NULL); /* * we can only be invoked after a driver has been opened and * someone has a layered handle to it, so there had better be * a valid ops vector. */ ops = DEVI(dip)->devi_ops; ASSERT(ops && ops->devo_cb_ops); /* * Some nexus drivers incorrectly set cb_prop_op to nodev, * nulldev or even NULL. */ if ((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 (DDI_PROP_NOT_FOUND); } /* check if this is actually DDI_DEV_T_ANY query */ if (flags & LDI_DEV_T_ANY) { flags &= ~LDI_DEV_T_ANY; dev = DDI_DEV_T_ANY; } res = cdev_prop_op(dev, dip, prop_op, flags, name, valuep, lengthp); return (res); } static void i_ldi_prop_op_free(struct prop_driver_data *pdd) { kmem_free(pdd, pdd->pdd_size); } static caddr_t i_ldi_prop_op_alloc(int prop_len) { struct prop_driver_data *pdd; int pdd_size; pdd_size = sizeof (struct prop_driver_data) + prop_len; pdd = kmem_alloc(pdd_size, KM_SLEEP); pdd->pdd_size = pdd_size; pdd->pdd_prop_free = i_ldi_prop_op_free; return ((caddr_t)&pdd[1]); } /* * i_ldi_prop_op_typed() is a wrapper for i_ldi_prop_op that is used * by the typed ldi property lookup interfaces. */ static int i_ldi_prop_op_typed(dev_t dev, dev_info_t *dip, int flags, char *name, caddr_t *datap, int *lengthp, int elem_size) { caddr_t prop_val; int prop_len, res; ASSERT((dip != NULL) && (name != NULL)); ASSERT((datap != NULL) && (lengthp != NULL)); /* * first call the drivers prop_op() interface to allow it * it to override default property values. */ res = i_ldi_prop_op(dev, dip, PROP_LEN, flags | DDI_PROP_DYNAMIC, name, NULL, &prop_len); if (res != DDI_PROP_SUCCESS) return (DDI_PROP_NOT_FOUND); /* sanity check the property length */ if (prop_len == 0) { /* * the ddi typed interfaces don't allow a drivers to * create properties with a length of 0. so we should * prevent drivers from returning 0 length dynamic * properties for typed property lookups. */ return (DDI_PROP_NOT_FOUND); } /* sanity check the property length against the element size */ if (elem_size && ((prop_len % elem_size) != 0)) return (DDI_PROP_NOT_FOUND); /* * got it. now allocate a prop_driver_data struct so that the * user can free the property via ddi_prop_free(). */ prop_val = i_ldi_prop_op_alloc(prop_len); /* lookup the property again, this time get the value */ res = i_ldi_prop_op(dev, dip, PROP_LEN_AND_VAL_BUF, flags | DDI_PROP_DYNAMIC, name, prop_val, &prop_len); if (res != DDI_PROP_SUCCESS) { ddi_prop_free(prop_val); return (DDI_PROP_NOT_FOUND); } /* sanity check the property length */ if (prop_len == 0) { ddi_prop_free(prop_val); return (DDI_PROP_NOT_FOUND); } /* sanity check the property length against the element size */ if (elem_size && ((prop_len % elem_size) != 0)) { ddi_prop_free(prop_val); return (DDI_PROP_NOT_FOUND); } /* * return the prop_driver_data struct and, optionally, the length * of the data. */ *datap = prop_val; *lengthp = prop_len; return (DDI_PROP_SUCCESS); } /* * i_check_string looks at a string property and makes sure its * a valid null terminated string */ static int i_check_string(char *str, int prop_len) { int i; ASSERT(str != NULL); for (i = 0; i < prop_len; i++) { if (str[i] == '\0') return (0); } return (1); } /* * i_pack_string_array takes a a string array property that is represented * as a concatenation of strings (with the NULL character included for * each string) and converts it into a format that can be returned by * ldi_prop_lookup_string_array. */ static int i_pack_string_array(char *str_concat, int prop_len, char ***str_arrayp, int *nelemp) { int i, nelem, pack_size; char **str_array, *strptr; /* * first we need to sanity check the input string array. * in essence this can be done my making sure that the last * character of the array passed in is null. (meaning the last * string in the array is NULL terminated. */ if (str_concat[prop_len - 1] != '\0') return (1); /* now let's count the number of strings in the array */ for (nelem = i = 0; i < prop_len; i++) if (str_concat[i] == '\0') nelem++; ASSERT(nelem >= 1); /* now let's allocate memory for the new packed property */ pack_size = (sizeof (char *) * (nelem + 1)) + prop_len; str_array = (char **)i_ldi_prop_op_alloc(pack_size); /* let's copy the actual string data into the new property */ strptr = (char *)&(str_array[nelem + 1]); bcopy(str_concat, strptr, prop_len); /* now initialize the string array pointers */ for (i = 0; i < nelem; i++) { str_array[i] = strptr; strptr += strlen(strptr) + 1; } str_array[nelem] = NULL; /* set the return values */ *str_arrayp = str_array; *nelemp = nelem; return (0); } /* * LDI Project private device usage interfaces */ /* * Get a count of how many devices are currentl open by different consumers */ int ldi_usage_count() { return (ldi_handle_hash_count); } static void ldi_usage_walker_tgt_helper(ldi_usage_t *ldi_usage, vnode_t *vp) { dev_info_t *dip; dev_t dev; ASSERT(STYP_VALID(VTYP_TO_STYP(vp->v_type))); /* get the target devt */ dev = vp->v_rdev; /* try to get the target dip */ dip = VTOCS(vp)->s_dip; if (dip != NULL) { e_ddi_hold_devi(dip); } else if (dev != DDI_DEV_T_NONE) { dip = e_ddi_hold_devi_by_dev(dev, 0); } /* set the target information */ ldi_usage->tgt_name = mod_major_to_name(getmajor(dev)); ldi_usage->tgt_modid = mod_name_to_modid(ldi_usage->tgt_name); ldi_usage->tgt_devt = dev; ldi_usage->tgt_spec_type = VTYP_TO_STYP(vp->v_type); ldi_usage->tgt_dip = dip; } static int ldi_usage_walker_helper(struct ldi_ident *lip, vnode_t *vp, void *arg, int (*callback)(const ldi_usage_t *, void *)) { ldi_usage_t ldi_usage; struct devnames *dnp; dev_info_t *dip; major_t major; dev_t dev; int ret = LDI_USAGE_CONTINUE; /* set the target device information */ ldi_usage_walker_tgt_helper(&ldi_usage, vp); /* get the source devt */ dev = lip->li_dev; /* try to get the source dip */ dip = lip->li_dip; if (dip != NULL) { e_ddi_hold_devi(dip); } else if (dev != DDI_DEV_T_NONE) { dip = e_ddi_hold_devi_by_dev(dev, 0); } /* set the valid source information */ ldi_usage.src_modid = lip->li_modid; ldi_usage.src_name = lip->li_modname; ldi_usage.src_devt = dev; ldi_usage.src_dip = dip; /* * if the source ident represents either: * * - a kernel module (and not a device or device driver) * - a device node * * then we currently have all the info we need to report the * usage information so invoke the callback function. */ if (((lip->li_major == -1) && (dev == DDI_DEV_T_NONE)) || (dip != NULL)) { ret = callback(&ldi_usage, arg); if (dip != NULL) ddi_release_devi(dip); if (ldi_usage.tgt_dip != NULL) ddi_release_devi(ldi_usage.tgt_dip); return (ret); } /* * now this is kinda gross. * * what we do here is attempt to associate every device instance * of the source driver on the system with the open target driver. * we do this because we don't know which instance of the device * could potentially access the lower device so we assume that all * the instances could access it. * * there are two ways we could have gotten here: * * 1) this layered ident represents one created using only a * major number or a driver module name. this means that when * it was created we could not associate it with a particular * dev_t or device instance. * * when could this possibly happen you ask? * * a perfect example of this is streams persistent links. * when a persistant streams link is formed we can't associate * the lower device stream with any particular upper device * stream or instance. this is because any particular upper * device stream could be closed, then another could be * opened with a different dev_t and device instance, and it * would still have access to the lower linked stream. * * since any instance of the upper streams driver could * potentially access the lower stream whenever it wants, * we represent that here by associating the opened lower * device with every existing device instance of the upper * streams driver. * * 2) This case should really never happen but we'll include it * for completeness. * * it's possible that we could have gotten here because we * have a dev_t for the upper device but we couldn't find a * dip associated with that dev_t. * * the only types of devices that have dev_t without an * associated dip are unbound DLPIv2 network devices. These * types of devices exist to be able to attach a stream to any * instance of a hardware network device. since these types of * devices are usually hardware devices they should never * really have other devices open. */ if (dev != DDI_DEV_T_NONE) major = getmajor(dev); else major = lip->li_major; ASSERT3U(major, <, devcnt); dnp = &devnamesp[major]; LOCK_DEV_OPS(&dnp->dn_lock); dip = dnp->dn_head; while ((dip) && (ret == LDI_USAGE_CONTINUE)) { e_ddi_hold_devi(dip); UNLOCK_DEV_OPS(&dnp->dn_lock); /* set the source dip */ ldi_usage.src_dip = dip; /* invoke the callback function */ ret = callback(&ldi_usage, arg); LOCK_DEV_OPS(&dnp->dn_lock); ddi_release_devi(dip); dip = ddi_get_next(dip); } UNLOCK_DEV_OPS(&dnp->dn_lock); /* if there was a target dip, release it */ if (ldi_usage.tgt_dip != NULL) ddi_release_devi(ldi_usage.tgt_dip); return (ret); } /* * ldi_usage_walker() - this walker reports LDI kernel device usage * information via the callback() callback function. the LDI keeps track * of what devices are being accessed in its own internal data structures. * this function walks those data structures to determine device usage. */ void ldi_usage_walker(void *arg, int (*callback)(const ldi_usage_t *, void *)) { struct ldi_handle *lhp; struct ldi_ident *lip; vnode_t *vp; int i; int ret = LDI_USAGE_CONTINUE; for (i = 0; i < LH_HASH_SZ; i++) { mutex_enter(&ldi_handle_hash_lock[i]); lhp = ldi_handle_hash[i]; while ((lhp != NULL) && (ret == LDI_USAGE_CONTINUE)) { lip = lhp->lh_ident; vp = lhp->lh_vp; /* invoke the devinfo callback function */ ret = ldi_usage_walker_helper(lip, vp, arg, callback); lhp = lhp->lh_next; } mutex_exit(&ldi_handle_hash_lock[i]); if (ret != LDI_USAGE_CONTINUE) break; } } /* * LDI Project private interfaces (streams linking interfaces) * * Streams supports a type of built in device layering via linking. * Certain types of streams drivers can be streams multiplexors. * A streams multiplexor supports the I_LINK/I_PLINK operation. * These operations allows other streams devices to be linked under the * multiplexor. By definition all streams multiplexors are devices * so this linking is a type of device layering where the multiplexor * device is layered on top of the device linked below it. */ /* * ldi_mlink_lh() is invoked when streams are linked using LDI handles. * It is not used for normal I_LINKs and I_PLINKs using file descriptors. * * The streams framework keeps track of links via the file_t of the lower * stream. The LDI keeps track of devices using a vnode. In the case * of a streams link created via an LDI handle, fnk_lh() allocates * a file_t that the streams framework can use to track the linkage. */ int ldi_mlink_lh(vnode_t *vp, int cmd, intptr_t arg, cred_t *crp, int *rvalp) { struct ldi_handle *lhp = (struct ldi_handle *)arg; vnode_t *vpdown; file_t *fpdown; int err; if (lhp == NULL) return (EINVAL); vpdown = lhp->lh_vp; ASSERT(vn_matchops(vpdown, spec_getvnodeops())); ASSERT(cmd == _I_PLINK_LH); /* * create a new lower vnode and a file_t that points to it, * streams linking requires a file_t. falloc() returns with * fpdown locked. */ VN_HOLD(vpdown); (void) falloc(vpdown, FREAD|FWRITE, &fpdown, NULL); mutex_exit(&fpdown->f_tlock); /* try to establish the link */ err = mlink_file(vp, I_PLINK, fpdown, crp, rvalp, 1); if (err != 0) { /* the link failed, free the file_t and release the vnode */ mutex_enter(&fpdown->f_tlock); unfalloc(fpdown); VN_RELE(vpdown); } return (err); } /* * ldi_mlink_fp() is invoked for all successful streams linkages created * via I_LINK and I_PLINK. ldi_mlink_fp() records the linkage information * in its internal state so that the devinfo snapshot code has some * observability into streams device linkage information. */ int ldi_mlink_fp(struct stdata *stp, file_t *fpdown, int lhlink, int type) { vnode_t *vp = fpdown->f_vnode; struct snode *sp, *csp; ldi_ident_t li; major_t major; int ret; /* * If the lower stream is not a device then return but claim to have * succeeded, which matches our historical behaviour of just not * setting up LDI in this case. */ if (!vn_matchops(vp, spec_getvnodeops())) return (0); ASSERT(!servicing_interrupt()); LDI_STREAMS_LNK((CE_NOTE, "%s: linking streams " "stp=0x%p, fpdown=0x%p", "ldi_mlink_fp", (void *)stp, (void *)fpdown)); sp = VTOS(vp); csp = VTOS(sp->s_commonvp); /* get a layered ident for the upper stream */ if (type == LINKNORMAL) { /* * if the link is not persistant then we can associate * the upper stream with a dev_t. this is because the * upper stream is associated with a vnode, which is * associated with a dev_t and this binding can't change * during the life of the stream. since the link isn't * persistant once the stream is destroyed the link is * destroyed. so the dev_t will be valid for the life * of the link. */ ret = ldi_ident_from_stream(getendq(stp->sd_wrq), &li); } else { /* * if the link is persistant we can only associate the * link with a driver (and not a dev_t.) this is * because subsequent opens of the upper device may result * in a different stream (and dev_t) having access to * the lower stream. * * for example, if the upper stream is closed after the * persistant link operation is completed, a subsequent * open of the upper device will create a new stream which * may have a different dev_t and an unlink operation * can be performed using this new upper stream. */ VERIFY3S(type, ==, LINKPERSIST); major = getmajor(stp->sd_vnode->v_rdev); ret = ldi_ident_from_major(major, &li); } if (ret != 0) return (ret); /* check if this was a plink via a layered handle */ if (lhlink) { /* * increment the common snode s_count. * * this is done because after the link operation there * are two ways that s_count can be decremented. * * when the layered handle used to create the link is * closed, spec_close() is called and it will decrement * s_count in the common snode. if we don't increment * s_count here then this could cause spec_close() to * actually close the device while it's still linked * under a multiplexer. * * also, when the lower stream is unlinked, closef() is * called for the file_t associated with this snode. * closef() will call spec_close(), which will decrement * s_count. if we dont't increment s_count here then this * could cause spec_close() to actually close the device * while there may still be valid layered handles * pointing to it. */ VERIFY3S(type, ==, LINKPERSIST); mutex_enter(&csp->s_lock); VERIFY(csp->s_count >= 1); csp->s_count++; mutex_exit(&csp->s_lock); /* * decrement the f_count. * this is done because the layered driver framework does * not actually cache a copy of the file_t allocated to * do the link. this is done here instead of in ldi_mlink_lh() * because there is a window in ldi_mlink_lh() between where * milnk_file() returns and we would decrement the f_count * when the stream could be unlinked. */ mutex_enter(&fpdown->f_tlock); fpdown->f_count--; mutex_exit(&fpdown->f_tlock); } /* * NOTE: here we rely on the streams subsystem not allowing * a stream to be multiplexed more than once. if this * changes, we break. * * mark the snode/stream as multiplexed */ mutex_enter(&sp->s_lock); VERIFY(!(sp->s_flag & SMUXED)); sp->s_flag |= SMUXED; mutex_exit(&sp->s_lock); (void) handle_alloc(vp, (struct ldi_ident *)li); ldi_ident_release(li); return (0); } int ldi_munlink_fp(struct stdata *stp, file_t *fpdown, int type) { struct ldi_handle *lhp; vnode_t *vp = (vnode_t *)fpdown->f_vnode; struct snode *sp; ldi_ident_t li; major_t major; int ret; /* * If the lower stream is not a device then return but claim to have * succeeded, which matches our historical behaviour of just not * setting up LDI in this case. */ if (!vn_matchops(vp, spec_getvnodeops())) return (0); ASSERT(!servicing_interrupt()); LDI_STREAMS_LNK((CE_NOTE, "%s: unlinking streams " "stp=0x%p, fpdown=0x%p", "ldi_munlink_fp", (void *)stp, (void *)fpdown)); /* * clear the owner for this snode * see the comment in ldi_mlink_fp() for information about how * the ident is allocated */ if (type == LINKNORMAL) { ret = ldi_ident_from_stream(getendq(stp->sd_wrq), &li); } else { VERIFY3S(type, ==, LINKPERSIST); major = getmajor(stp->sd_vnode->v_rdev); ret = ldi_ident_from_major(major, &li); } if (ret != 0) return (ret); /* * NOTE: here we rely on the streams subsystem not allowing * a stream to be multiplexed more than once. if this * changes, we break. * * mark the snode/stream as not multiplexed */ sp = VTOS(vp); mutex_enter(&sp->s_lock); VERIFY(sp->s_flag & SMUXED); sp->s_flag &= ~SMUXED; mutex_exit(&sp->s_lock); lhp = handle_find(vp, (struct ldi_ident *)li); handle_release(lhp); ldi_ident_release(li); return (0); } /* * LDI Consolidation private interfaces */ int ldi_ident_from_mod(struct modlinkage *modlp, ldi_ident_t *lip) { struct modctl *modp; major_t major; char *name; if ((modlp == NULL) || (lip == NULL)) return (EINVAL); ASSERT(!servicing_interrupt()); modp = mod_getctl(modlp); if (modp == NULL) return (EINVAL); name = modp->mod_modname; if (name == NULL) return (EINVAL); major = mod_name_to_major(name); *lip = (ldi_ident_t)ident_alloc(name, NULL, DDI_DEV_T_NONE, major); LDI_ALLOCFREE((CE_WARN, "%s: li=0x%p, mod=%s", "ldi_ident_from_mod", (void *)*lip, name)); return (0); } ldi_ident_t ldi_ident_from_anon() { ldi_ident_t lip; ASSERT(!servicing_interrupt()); lip = (ldi_ident_t)ident_alloc("genunix", NULL, DDI_DEV_T_NONE, -1); LDI_ALLOCFREE((CE_WARN, "%s: li=0x%p, mod=%s", "ldi_ident_from_anon", (void *)lip, "genunix")); return (lip); } /* * LDI Public interfaces */ int ldi_ident_from_stream(struct queue *sq, ldi_ident_t *lip) { struct stdata *stp; dev_t dev; char *name; if ((sq == NULL) || (lip == NULL)) return (EINVAL); ASSERT(!servicing_interrupt()); stp = sq->q_stream; if (!vn_matchops(stp->sd_vnode, spec_getvnodeops())) return (EINVAL); dev = stp->sd_vnode->v_rdev; name = mod_major_to_name(getmajor(dev)); if (name == NULL) return (EINVAL); *lip = (ldi_ident_t)ident_alloc(name, NULL, dev, -1); LDI_ALLOCFREE((CE_WARN, "%s: li=0x%p, mod=%s, minor=0x%x, stp=0x%p", "ldi_ident_from_stream", (void *)*lip, name, getminor(dev), (void *)stp)); return (0); } int ldi_ident_from_dev(dev_t dev, ldi_ident_t *lip) { char *name; if (lip == NULL) return (EINVAL); ASSERT(!servicing_interrupt()); name = mod_major_to_name(getmajor(dev)); if (name == NULL) return (EINVAL); *lip = (ldi_ident_t)ident_alloc(name, NULL, dev, -1); LDI_ALLOCFREE((CE_WARN, "%s: li=0x%p, mod=%s, minor=0x%x", "ldi_ident_from_dev", (void *)*lip, name, getminor(dev))); return (0); } int ldi_ident_from_dip(dev_info_t *dip, ldi_ident_t *lip) { struct dev_info *devi = (struct dev_info *)dip; char *name; if ((dip == NULL) || (lip == NULL)) return (EINVAL); ASSERT(!servicing_interrupt()); name = mod_major_to_name(devi->devi_major); if (name == NULL) return (EINVAL); *lip = (ldi_ident_t)ident_alloc(name, dip, DDI_DEV_T_NONE, -1); LDI_ALLOCFREE((CE_WARN, "%s: li=0x%p, mod=%s, dip=0x%p", "ldi_ident_from_dip", (void *)*lip, name, (void *)devi)); return (0); } int ldi_ident_from_major(major_t major, ldi_ident_t *lip) { char *name; if (lip == NULL) return (EINVAL); ASSERT(!servicing_interrupt()); name = mod_major_to_name(major); if (name == NULL) return (EINVAL); *lip = (ldi_ident_t)ident_alloc(name, NULL, DDI_DEV_T_NONE, major); LDI_ALLOCFREE((CE_WARN, "%s: li=0x%p, mod=%s", "ldi_ident_from_major", (void *)*lip, name)); return (0); } void ldi_ident_release(ldi_ident_t li) { struct ldi_ident *ident = (struct ldi_ident *)li; char *name; if (li == NULL) return; ASSERT(!servicing_interrupt()); name = ident->li_modname; LDI_ALLOCFREE((CE_WARN, "%s: li=0x%p, mod=%s", "ldi_ident_release", (void *)li, name)); ident_release((struct ldi_ident *)li); } /* get a handle to a device by dev_t and otyp */ int ldi_open_by_dev(dev_t *devp, int otyp, int flag, cred_t *cr, ldi_handle_t *lhp, ldi_ident_t li) { struct ldi_ident *lip = (struct ldi_ident *)li; int ret; vnode_t *vp; /* sanity check required input parameters */ if ((devp == NULL) || (!OTYP_VALID(otyp)) || (cr == NULL) || (lhp == NULL) || (lip == NULL)) return (EINVAL); ASSERT(!servicing_interrupt()); if ((ret = ldi_vp_from_dev(*devp, otyp, &vp)) != 0) return (ret); if ((ret = ldi_open_by_vp(&vp, flag, cr, lhp, lip)) == 0) { *devp = vp->v_rdev; } VN_RELE(vp); return (ret); } /* get a handle to a device by pathname */ int ldi_open_by_name(char *pathname, int flag, cred_t *cr, ldi_handle_t *lhp, ldi_ident_t li) { struct ldi_ident *lip = (struct ldi_ident *)li; int ret; vnode_t *vp; /* sanity check required input parameters */ if ((pathname == NULL) || (*pathname != '/') || (cr == NULL) || (lhp == NULL) || (lip == NULL)) return (EINVAL); ASSERT(!servicing_interrupt()); if ((ret = ldi_vp_from_name(pathname, &vp)) != 0) return (ret); ret = ldi_open_by_vp(&vp, flag, cr, lhp, lip); VN_RELE(vp); return (ret); } /* get a handle to a device by devid and minor_name */ int ldi_open_by_devid(ddi_devid_t devid, char *minor_name, int flag, cred_t *cr, ldi_handle_t *lhp, ldi_ident_t li) { struct ldi_ident *lip = (struct ldi_ident *)li; int ret; vnode_t *vp; /* sanity check required input parameters */ if ((minor_name == NULL) || (cr == NULL) || (lhp == NULL) || (lip == NULL)) return (EINVAL); ASSERT(!servicing_interrupt()); if ((ret = ldi_vp_from_devid(devid, minor_name, &vp)) != 0) return (ret); ret = ldi_open_by_vp(&vp, flag, cr, lhp, lip); VN_RELE(vp); return (ret); } int ldi_close(ldi_handle_t lh, int flag, cred_t *cr) { struct ldi_handle *handlep = (struct ldi_handle *)lh; struct ldi_event *lep; int err = 0; int notify = 0; list_t *listp; ldi_ev_callback_impl_t *lecp; if (lh == NULL) return (EINVAL); ASSERT(!servicing_interrupt()); #ifdef LDI_OBSOLETE_EVENT /* * Any event handlers should have been unregistered by the * time ldi_close() is called. If they haven't then it's a * bug. * * In a debug kernel we'll panic to make the problem obvious. */ ASSERT(handlep->lh_events == NULL); /* * On a production kernel we'll "do the right thing" (unregister * the event handlers) and then complain about having to do the * work ourselves. */ while ((lep = handlep->lh_events) != NULL) { err = 1; (void) ldi_remove_event_handler(lh, (ldi_callback_id_t)lep); } if (err) { struct ldi_ident *lip = handlep->lh_ident; ASSERT(lip != NULL); cmn_err(CE_NOTE, "ldi err: %s " "failed to unregister layered event handlers before " "closing devices", lip->li_modname); } #endif /* do a layered close on the device */ err = VOP_CLOSE(handlep->lh_vp, flag | FKLYR, 1, (offset_t)0, cr, NULL); LDI_OPENCLOSE((CE_WARN, "%s: lh=0x%p", "ldi close", (void *)lh)); /* * Search the event callback list for callbacks with this * handle. There are 2 cases * 1. Called in the context of a notify. The handle consumer * is releasing its hold on the device to allow a reconfiguration * of the device. Simply NULL out the handle and the notify callback. * The finalize callback is still available so that the consumer * knows of the final disposition of the device. * 2. Not called in the context of notify. NULL out the handle as well * as the notify and finalize callbacks. Since the consumer has * closed the handle, we assume it is not interested in the * notify and finalize callbacks. */ ldi_ev_lock(); if (handlep->lh_flags & LH_FLAGS_NOTIFY) notify = 1; listp = &ldi_ev_callback_list.le_head; for (lecp = list_head(listp); lecp; lecp = list_next(listp, lecp)) { if (lecp->lec_lhp != handlep) continue; lecp->lec_lhp = NULL; lecp->lec_notify = NULL; LDI_EVDBG((CE_NOTE, "ldi_close: NULLed lh and notify")); if (!notify) { LDI_EVDBG((CE_NOTE, "ldi_close: NULLed finalize")); lecp->lec_finalize = NULL; } } if (notify) handlep->lh_flags &= ~LH_FLAGS_NOTIFY; ldi_ev_unlock(); /* * Free the handle even if the device close failed. why? * * If the device close failed we can't really make assumptions * about the devices state so we shouldn't allow access to the * device via this handle any more. If the device consumer wants * to access the device again they should open it again. * * This is the same way file/device close failures are handled * in other places like spec_close() and closeandsetf(). */ handle_release(handlep); return (err); } int ldi_read(ldi_handle_t lh, struct uio *uiop, cred_t *credp) { struct ldi_handle *handlep = (struct ldi_handle *)lh; vnode_t *vp; dev_t dev; int ret; if (lh == NULL) return (EINVAL); vp = handlep->lh_vp; dev = vp->v_rdev; if (handlep->lh_type & LH_CBDEV) { ret = cdev_read(dev, uiop, credp); } else if (handlep->lh_type & LH_STREAM) { ret = strread(vp, uiop, credp); } else { return (ENOTSUP); } return (ret); } int ldi_write(ldi_handle_t lh, struct uio *uiop, cred_t *credp) { struct ldi_handle *handlep = (struct ldi_handle *)lh; vnode_t *vp; dev_t dev; int ret; if (lh == NULL) return (EINVAL); vp = handlep->lh_vp; dev = vp->v_rdev; if (handlep->lh_type & LH_CBDEV) { ret = cdev_write(dev, uiop, credp); } else if (handlep->lh_type & LH_STREAM) { ret = strwrite(vp, uiop, credp); } else { return (ENOTSUP); } return (ret); } int ldi_get_size(ldi_handle_t lh, uint64_t *sizep) { int otyp; uint_t value; int64_t drv_prop64; struct ldi_handle *handlep = (struct ldi_handle *)lh; uint_t blksize; int blkshift; if ((lh == NULL) || (sizep == NULL)) return (DDI_FAILURE); if (handlep->lh_type & LH_STREAM) return (DDI_FAILURE); /* * Determine device type (char or block). * Character devices support Size/size * property value. Block devices may support * Nblocks/nblocks or Size/size property value. */ if ((ldi_get_otyp(lh, &otyp)) != 0) return (DDI_FAILURE); if (otyp == OTYP_BLK) { if (ldi_prop_exists(lh, DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, "Nblocks")) { drv_prop64 = ldi_prop_get_int64(lh, DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, "Nblocks", 0); blksize = ldi_prop_get_int(lh, DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, "blksize", DEV_BSIZE); if (blksize == DEV_BSIZE) blksize = ldi_prop_get_int(lh, LDI_DEV_T_ANY | DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, "device-blksize", DEV_BSIZE); /* blksize must be a power of two */ ASSERT(BIT_ONLYONESET(blksize)); blkshift = highbit(blksize) - 1; /* * We don't support Nblocks values that don't have * an accurate uint64_t byte count representation. */ if ((uint64_t)drv_prop64 >= (UINT64_MAX >> blkshift)) return (DDI_FAILURE); *sizep = (uint64_t) (((u_offset_t)drv_prop64) << blkshift); return (DDI_SUCCESS); } if (ldi_prop_exists(lh, DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, "nblocks")) { value = ldi_prop_get_int(lh, DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, "nblocks", 0); blksize = ldi_prop_get_int(lh, DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, "blksize", DEV_BSIZE); if (blksize == DEV_BSIZE) blksize = ldi_prop_get_int(lh, LDI_DEV_T_ANY | DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, "device-blksize", DEV_BSIZE); /* blksize must be a power of two */ ASSERT(BIT_ONLYONESET(blksize)); blkshift = highbit(blksize) - 1; /* * We don't support nblocks values that don't have an * accurate uint64_t byte count representation. */ if ((uint64_t)value >= (UINT64_MAX >> blkshift)) return (DDI_FAILURE); *sizep = (uint64_t) (((u_offset_t)value) << blkshift); return (DDI_SUCCESS); } } if (ldi_prop_exists(lh, DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, "Size")) { drv_prop64 = ldi_prop_get_int64(lh, DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, "Size", 0); *sizep = (uint64_t)drv_prop64; return (DDI_SUCCESS); } if (ldi_prop_exists(lh, DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, "size")) { value = ldi_prop_get_int(lh, DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, "size", 0); *sizep = (uint64_t)value; return (DDI_SUCCESS); } /* unable to determine device size */ return (DDI_FAILURE); } int ldi_ioctl(ldi_handle_t lh, int cmd, intptr_t arg, int mode, cred_t *cr, int *rvalp) { struct ldi_handle *handlep = (struct ldi_handle *)lh; vnode_t *vp; dev_t dev; int ret, copymode, unused; if (lh == NULL) return (EINVAL); /* * if the data pointed to by arg is located in the kernel then * make sure the FNATIVE flag is set. */ if (mode & FKIOCTL) mode = (mode & ~FMODELS) | FNATIVE | FKIOCTL; /* * Some drivers assume that rvalp will always be non-NULL, so in * an attempt to avoid panics if the caller passed in a NULL * value, update rvalp to point to a temporary variable. */ if (rvalp == NULL) rvalp = &unused; vp = handlep->lh_vp; dev = vp->v_rdev; if (handlep->lh_type & LH_CBDEV) { ret = cdev_ioctl(dev, cmd, arg, mode, cr, rvalp); } else if (handlep->lh_type & LH_STREAM) { copymode = (mode & FKIOCTL) ? K_TO_K : U_TO_K; /* * if we get an I_PLINK from within the kernel the * arg is a layered handle pointer instead of * a file descriptor, so we translate this ioctl * into a private one that can handle this. */ if ((mode & FKIOCTL) && (cmd == I_PLINK)) cmd = _I_PLINK_LH; ret = strioctl(vp, cmd, arg, mode, copymode, cr, rvalp); } else { return (ENOTSUP); } return (ret); } int ldi_poll(ldi_handle_t lh, short events, int anyyet, short *reventsp, struct pollhead **phpp) { struct ldi_handle *handlep = (struct ldi_handle *)lh; vnode_t *vp; dev_t dev; int ret; if (lh == NULL) return (EINVAL); vp = handlep->lh_vp; dev = vp->v_rdev; if (handlep->lh_type & LH_CBDEV) { ret = cdev_poll(dev, events, anyyet, reventsp, phpp); } else if (handlep->lh_type & LH_STREAM) { ret = strpoll(vp->v_stream, events, anyyet, reventsp, phpp); } else { return (ENOTSUP); } return (ret); } int ldi_prop_op(ldi_handle_t lh, ddi_prop_op_t prop_op, int flags, char *name, caddr_t valuep, int *length) { struct ldi_handle *handlep = (struct ldi_handle *)lh; dev_t dev; dev_info_t *dip; int ret; struct snode *csp; if ((lh == NULL) || (name == NULL) || (strlen(name) == 0)) return (DDI_PROP_INVAL_ARG); if ((prop_op != PROP_LEN) && (valuep == NULL)) return (DDI_PROP_INVAL_ARG); if (length == NULL) return (DDI_PROP_INVAL_ARG); /* * try to find the associated dip, * this places a hold on the driver */ dev = handlep->lh_vp->v_rdev; csp = VTOCS(handlep->lh_vp); mutex_enter(&csp->s_lock); if ((dip = csp->s_dip) != NULL) e_ddi_hold_devi(dip); mutex_exit(&csp->s_lock); if (dip == NULL) dip = e_ddi_hold_devi_by_dev(dev, 0); if (dip == NULL) return (DDI_PROP_NOT_FOUND); ret = i_ldi_prop_op(dev, dip, prop_op, flags, name, valuep, length); ddi_release_devi(dip); return (ret); } int ldi_strategy(ldi_handle_t lh, struct buf *bp) { struct ldi_handle *handlep = (struct ldi_handle *)lh; dev_t dev; if ((lh == NULL) || (bp == NULL)) return (EINVAL); /* this entry point is only supported for cb devices */ dev = handlep->lh_vp->v_rdev; if (!(handlep->lh_type & LH_CBDEV)) return (ENOTSUP); bp->b_edev = dev; bp->b_dev = cmpdev(dev); return (bdev_strategy(bp)); } int ldi_dump(ldi_handle_t lh, caddr_t addr, daddr_t blkno, int nblk) { struct ldi_handle *handlep = (struct ldi_handle *)lh; dev_t dev; if (lh == NULL) return (EINVAL); /* this entry point is only supported for cb devices */ dev = handlep->lh_vp->v_rdev; if (!(handlep->lh_type & LH_CBDEV)) return (ENOTSUP); return (bdev_dump(dev, addr, blkno, nblk)); } int ldi_devmap(ldi_handle_t lh, devmap_cookie_t dhp, offset_t off, size_t len, size_t *maplen, uint_t model) { struct ldi_handle *handlep = (struct ldi_handle *)lh; dev_t dev; if (lh == NULL) return (EINVAL); /* this entry point is only supported for cb devices */ dev = handlep->lh_vp->v_rdev; if (!(handlep->lh_type & LH_CBDEV)) return (ENOTSUP); return (cdev_devmap(dev, dhp, off, len, maplen, model)); } int ldi_aread(ldi_handle_t lh, struct aio_req *aio_reqp, cred_t *cr) { struct ldi_handle *handlep = (struct ldi_handle *)lh; dev_t dev; struct cb_ops *cb; if (lh == NULL) return (EINVAL); /* this entry point is only supported for cb devices */ if (!(handlep->lh_type & LH_CBDEV)) return (ENOTSUP); /* * Kaio is only supported on block devices. */ dev = handlep->lh_vp->v_rdev; cb = devopsp[getmajor(dev)]->devo_cb_ops; if (cb->cb_strategy == nodev || cb->cb_strategy == NULL) return (ENOTSUP); if (cb->cb_aread == NULL) return (ENOTSUP); return (cb->cb_aread(dev, aio_reqp, cr)); } int ldi_awrite(ldi_handle_t lh, struct aio_req *aio_reqp, cred_t *cr) { struct ldi_handle *handlep = (struct ldi_handle *)lh; struct cb_ops *cb; dev_t dev; if (lh == NULL) return (EINVAL); /* this entry point is only supported for cb devices */ if (!(handlep->lh_type & LH_CBDEV)) return (ENOTSUP); /* * Kaio is only supported on block devices. */ dev = handlep->lh_vp->v_rdev; cb = devopsp[getmajor(dev)]->devo_cb_ops; if (cb->cb_strategy == nodev || cb->cb_strategy == NULL) return (ENOTSUP); if (cb->cb_awrite == NULL) return (ENOTSUP); return (cb->cb_awrite(dev, aio_reqp, cr)); } int ldi_putmsg(ldi_handle_t lh, mblk_t *smp) { struct ldi_handle *handlep = (struct ldi_handle *)lh; int ret; if ((lh == NULL) || (smp == NULL)) return (EINVAL); if (!(handlep->lh_type & LH_STREAM)) { freemsg(smp); return (ENOTSUP); } /* * If we don't have db_credp, set it. Note that we can not be called * from interrupt context. */ if (msg_getcred(smp, NULL) == NULL) mblk_setcred(smp, CRED(), curproc->p_pid); /* Send message while honoring flow control */ ret = kstrputmsg(handlep->lh_vp, smp, NULL, 0, 0, MSG_BAND | MSG_HOLDSIG | MSG_IGNERROR, 0); return (ret); } int ldi_getmsg(ldi_handle_t lh, mblk_t **rmp, timestruc_t *timeo) { struct ldi_handle *handlep = (struct ldi_handle *)lh; clock_t timout; /* milliseconds */ uchar_t pri; rval_t rval; int ret, pflag; if (lh == NULL) return (EINVAL); if (!(handlep->lh_type & LH_STREAM)) return (ENOTSUP); /* Convert from nanoseconds to milliseconds */ if (timeo != NULL) { timout = timeo->tv_sec * 1000 + timeo->tv_nsec / 1000000; if (timout > INT_MAX) return (EINVAL); } else timout = -1; /* Wait for timeout millseconds for a message */ pflag = MSG_ANY; pri = 0; *rmp = NULL; ret = kstrgetmsg(handlep->lh_vp, rmp, NULL, &pri, &pflag, timout, &rval); return (ret); } int ldi_get_dev(ldi_handle_t lh, dev_t *devp) { struct ldi_handle *handlep = (struct ldi_handle *)lh; if ((lh == NULL) || (devp == NULL)) return (EINVAL); *devp = handlep->lh_vp->v_rdev; return (0); } int ldi_get_otyp(ldi_handle_t lh, int *otyp) { struct ldi_handle *handlep = (struct ldi_handle *)lh; if ((lh == NULL) || (otyp == NULL)) return (EINVAL); *otyp = VTYP_TO_OTYP(handlep->lh_vp->v_type); return (0); } int ldi_get_devid(ldi_handle_t lh, ddi_devid_t *devid) { struct ldi_handle *handlep = (struct ldi_handle *)lh; int ret; dev_t dev; if ((lh == NULL) || (devid == NULL)) return (EINVAL); dev = handlep->lh_vp->v_rdev; ret = ddi_lyr_get_devid(dev, devid); if (ret != DDI_SUCCESS) return (ENOTSUP); return (0); } int ldi_get_minor_name(ldi_handle_t lh, char **minor_name) { struct ldi_handle *handlep = (struct ldi_handle *)lh; int ret, otyp; dev_t dev; if ((lh == NULL) || (minor_name == NULL)) return (EINVAL); dev = handlep->lh_vp->v_rdev; otyp = VTYP_TO_OTYP(handlep->lh_vp->v_type); ret = ddi_lyr_get_minor_name(dev, OTYP_TO_STYP(otyp), minor_name); if (ret != DDI_SUCCESS) return (ENOTSUP); return (0); } int ldi_prop_lookup_int_array(ldi_handle_t lh, uint_t flags, char *name, int **data, uint_t *nelements) { struct ldi_handle *handlep = (struct ldi_handle *)lh; dev_info_t *dip; dev_t dev; int res; struct snode *csp; if ((lh == NULL) || (name == NULL) || (strlen(name) == 0)) return (DDI_PROP_INVAL_ARG); dev = handlep->lh_vp->v_rdev; csp = VTOCS(handlep->lh_vp); mutex_enter(&csp->s_lock); if ((dip = csp->s_dip) != NULL) e_ddi_hold_devi(dip); mutex_exit(&csp->s_lock); if (dip == NULL) dip = e_ddi_hold_devi_by_dev(dev, 0); if (dip == NULL) { flags |= DDI_UNBND_DLPI2; } else if (flags & LDI_DEV_T_ANY) { flags &= ~LDI_DEV_T_ANY; dev = DDI_DEV_T_ANY; } if (dip != NULL) { int *prop_val, prop_len; res = i_ldi_prop_op_typed(dev, dip, flags, name, (caddr_t *)&prop_val, &prop_len, sizeof (int)); /* if we got it then return it */ if (res == DDI_PROP_SUCCESS) { *nelements = prop_len / sizeof (int); *data = prop_val; ddi_release_devi(dip); return (res); } } /* call the normal property interfaces */ res = ddi_prop_lookup_int_array(dev, dip, flags, name, data, nelements); if (dip != NULL) ddi_release_devi(dip); return (res); } int ldi_prop_lookup_int64_array(ldi_handle_t lh, uint_t flags, char *name, int64_t **data, uint_t *nelements) { struct ldi_handle *handlep = (struct ldi_handle *)lh; dev_info_t *dip; dev_t dev; int res; struct snode *csp; if ((lh == NULL) || (name == NULL) || (strlen(name) == 0)) return (DDI_PROP_INVAL_ARG); dev = handlep->lh_vp->v_rdev; csp = VTOCS(handlep->lh_vp); mutex_enter(&csp->s_lock); if ((dip = csp->s_dip) != NULL) e_ddi_hold_devi(dip); mutex_exit(&csp->s_lock); if (dip == NULL) dip = e_ddi_hold_devi_by_dev(dev, 0); if (dip == NULL) { flags |= DDI_UNBND_DLPI2; } else if (flags & LDI_DEV_T_ANY) { flags &= ~LDI_DEV_T_ANY; dev = DDI_DEV_T_ANY; } if (dip != NULL) { int64_t *prop_val; int prop_len; res = i_ldi_prop_op_typed(dev, dip, flags, name, (caddr_t *)&prop_val, &prop_len, sizeof (int64_t)); /* if we got it then return it */ if (res == DDI_PROP_SUCCESS) { *nelements = prop_len / sizeof (int64_t); *data = prop_val; ddi_release_devi(dip); return (res); } } /* call the normal property interfaces */ res = ddi_prop_lookup_int64_array(dev, dip, flags, name, data, nelements); if (dip != NULL) ddi_release_devi(dip); return (res); } int ldi_prop_lookup_string_array(ldi_handle_t lh, uint_t flags, char *name, char ***data, uint_t *nelements) { struct ldi_handle *handlep = (struct ldi_handle *)lh; dev_info_t *dip; dev_t dev; int res; struct snode *csp; if ((lh == NULL) || (name == NULL) || (strlen(name) == 0)) return (DDI_PROP_INVAL_ARG); dev = handlep->lh_vp->v_rdev; csp = VTOCS(handlep->lh_vp); mutex_enter(&csp->s_lock); if ((dip = csp->s_dip) != NULL) e_ddi_hold_devi(dip); mutex_exit(&csp->s_lock); if (dip == NULL) dip = e_ddi_hold_devi_by_dev(dev, 0); if (dip == NULL) { flags |= DDI_UNBND_DLPI2; } else if (flags & LDI_DEV_T_ANY) { flags &= ~LDI_DEV_T_ANY; dev = DDI_DEV_T_ANY; } if (dip != NULL) { char *prop_val; int prop_len; res = i_ldi_prop_op_typed(dev, dip, flags, name, (caddr_t *)&prop_val, &prop_len, 0); /* if we got it then return it */ if (res == DDI_PROP_SUCCESS) { char **str_array; int nelem; /* * pack the returned string array into the format * our callers expect */ if (i_pack_string_array(prop_val, prop_len, &str_array, &nelem) == 0) { *data = str_array; *nelements = nelem; ddi_prop_free(prop_val); ddi_release_devi(dip); return (res); } /* * the format of the returned property must have * been bad so throw it out */ ddi_prop_free(prop_val); } } /* call the normal property interfaces */ res = ddi_prop_lookup_string_array(dev, dip, flags, name, data, nelements); if (dip != NULL) ddi_release_devi(dip); return (res); } int ldi_prop_lookup_string(ldi_handle_t lh, uint_t flags, char *name, char **data) { struct ldi_handle *handlep = (struct ldi_handle *)lh; dev_info_t *dip; dev_t dev; int res; struct snode *csp; if ((lh == NULL) || (name == NULL) || (strlen(name) == 0)) return (DDI_PROP_INVAL_ARG); dev = handlep->lh_vp->v_rdev; csp = VTOCS(handlep->lh_vp); mutex_enter(&csp->s_lock); if ((dip = csp->s_dip) != NULL) e_ddi_hold_devi(dip); mutex_exit(&csp->s_lock); if (dip == NULL) dip = e_ddi_hold_devi_by_dev(dev, 0); if (dip == NULL) { flags |= DDI_UNBND_DLPI2; } else if (flags & LDI_DEV_T_ANY) { flags &= ~LDI_DEV_T_ANY; dev = DDI_DEV_T_ANY; } if (dip != NULL) { char *prop_val; int prop_len; res = i_ldi_prop_op_typed(dev, dip, flags, name, (caddr_t *)&prop_val, &prop_len, 0); /* if we got it then return it */ if (res == DDI_PROP_SUCCESS) { /* * sanity check the vaule returned. */ if (i_check_string(prop_val, prop_len)) { ddi_prop_free(prop_val); } else { *data = prop_val; ddi_release_devi(dip); return (res); } } } /* call the normal property interfaces */ res = ddi_prop_lookup_string(dev, dip, flags, name, data); if (dip != NULL) ddi_release_devi(dip); #ifdef DEBUG if (res == DDI_PROP_SUCCESS) { /* * keep ourselves honest * make sure the framework returns strings in the * same format as we're demanding from drivers. */ struct prop_driver_data *pdd; int pdd_prop_size; pdd = ((struct prop_driver_data *)(*data)) - 1; pdd_prop_size = pdd->pdd_size - sizeof (struct prop_driver_data); ASSERT(i_check_string(*data, pdd_prop_size) == 0); } #endif /* DEBUG */ return (res); } int ldi_prop_lookup_byte_array(ldi_handle_t lh, uint_t flags, char *name, uchar_t **data, uint_t *nelements) { struct ldi_handle *handlep = (struct ldi_handle *)lh; dev_info_t *dip; dev_t dev; int res; struct snode *csp; if ((lh == NULL) || (name == NULL) || (strlen(name) == 0)) return (DDI_PROP_INVAL_ARG); dev = handlep->lh_vp->v_rdev; csp = VTOCS(handlep->lh_vp); mutex_enter(&csp->s_lock); if ((dip = csp->s_dip) != NULL) e_ddi_hold_devi(dip); mutex_exit(&csp->s_lock); if (dip == NULL) dip = e_ddi_hold_devi_by_dev(dev, 0); if (dip == NULL) { flags |= DDI_UNBND_DLPI2; } else if (flags & LDI_DEV_T_ANY) { flags &= ~LDI_DEV_T_ANY; dev = DDI_DEV_T_ANY; } if (dip != NULL) { uchar_t *prop_val; int prop_len; res = i_ldi_prop_op_typed(dev, dip, flags, name, (caddr_t *)&prop_val, &prop_len, sizeof (uchar_t)); /* if we got it then return it */ if (res == DDI_PROP_SUCCESS) { *nelements = prop_len / sizeof (uchar_t); *data = prop_val; ddi_release_devi(dip); return (res); } } /* call the normal property interfaces */ res = ddi_prop_lookup_byte_array(dev, dip, flags, name, data, nelements); if (dip != NULL) ddi_release_devi(dip); return (res); } int ldi_prop_get_int(ldi_handle_t lh, uint_t flags, char *name, int defvalue) { struct ldi_handle *handlep = (struct ldi_handle *)lh; dev_info_t *dip; dev_t dev; int res; struct snode *csp; if ((lh == NULL) || (name == NULL) || (strlen(name) == 0)) return (defvalue); dev = handlep->lh_vp->v_rdev; csp = VTOCS(handlep->lh_vp); mutex_enter(&csp->s_lock); if ((dip = csp->s_dip) != NULL) e_ddi_hold_devi(dip); mutex_exit(&csp->s_lock); if (dip == NULL) dip = e_ddi_hold_devi_by_dev(dev, 0); if (dip == NULL) { flags |= DDI_UNBND_DLPI2; } else if (flags & LDI_DEV_T_ANY) { flags &= ~LDI_DEV_T_ANY; dev = DDI_DEV_T_ANY; } if (dip != NULL) { int prop_val; int prop_len; /* * first call the drivers prop_op interface to allow it * it to override default property values. */ prop_len = sizeof (int); res = i_ldi_prop_op(dev, dip, PROP_LEN_AND_VAL_BUF, flags | DDI_PROP_DYNAMIC, name, (caddr_t)&prop_val, &prop_len); /* if we got it then return it */ if ((res == DDI_PROP_SUCCESS) && (prop_len == sizeof (int))) { res = prop_val; ddi_release_devi(dip); return (res); } } /* call the normal property interfaces */ res = ddi_prop_get_int(dev, dip, flags, name, defvalue); if (dip != NULL) ddi_release_devi(dip); return (res); } int64_t ldi_prop_get_int64(ldi_handle_t lh, uint_t flags, char *name, int64_t defvalue) { struct ldi_handle *handlep = (struct ldi_handle *)lh; dev_info_t *dip; dev_t dev; int64_t res; struct snode *csp; if ((lh == NULL) || (name == NULL) || (strlen(name) == 0)) return (defvalue); dev = handlep->lh_vp->v_rdev; csp = VTOCS(handlep->lh_vp); mutex_enter(&csp->s_lock); if ((dip = csp->s_dip) != NULL) e_ddi_hold_devi(dip); mutex_exit(&csp->s_lock); if (dip == NULL) dip = e_ddi_hold_devi_by_dev(dev, 0); if (dip == NULL) { flags |= DDI_UNBND_DLPI2; } else if (flags & LDI_DEV_T_ANY) { flags &= ~LDI_DEV_T_ANY; dev = DDI_DEV_T_ANY; } if (dip != NULL) { int64_t prop_val; int prop_len; /* * first call the drivers prop_op interface to allow it * it to override default property values. */ prop_len = sizeof (int64_t); res = i_ldi_prop_op(dev, dip, PROP_LEN_AND_VAL_BUF, flags | DDI_PROP_DYNAMIC, name, (caddr_t)&prop_val, &prop_len); /* if we got it then return it */ if ((res == DDI_PROP_SUCCESS) && (prop_len == sizeof (int64_t))) { res = prop_val; ddi_release_devi(dip); return (res); } } /* call the normal property interfaces */ res = ddi_prop_get_int64(dev, dip, flags, name, defvalue); if (dip != NULL) ddi_release_devi(dip); return (res); } int ldi_prop_exists(ldi_handle_t lh, uint_t flags, char *name) { struct ldi_handle *handlep = (struct ldi_handle *)lh; dev_info_t *dip; dev_t dev; int res, prop_len; struct snode *csp; if ((lh == NULL) || (name == NULL) || (strlen(name) == 0)) return (0); dev = handlep->lh_vp->v_rdev; csp = VTOCS(handlep->lh_vp); mutex_enter(&csp->s_lock); if ((dip = csp->s_dip) != NULL) e_ddi_hold_devi(dip); mutex_exit(&csp->s_lock); if (dip == NULL) dip = e_ddi_hold_devi_by_dev(dev, 0); /* if NULL dip, prop does NOT exist */ if (dip == NULL) return (0); if (flags & LDI_DEV_T_ANY) { flags &= ~LDI_DEV_T_ANY; dev = DDI_DEV_T_ANY; } /* * first call the drivers prop_op interface to allow it * it to override default property values. */ res = i_ldi_prop_op(dev, dip, PROP_LEN, flags | DDI_PROP_DYNAMIC, name, NULL, &prop_len); if (res == DDI_PROP_SUCCESS) { ddi_release_devi(dip); return (1); } /* call the normal property interfaces */ res = ddi_prop_exists(dev, dip, flags, name); ddi_release_devi(dip); return (res); } #ifdef LDI_OBSOLETE_EVENT int ldi_get_eventcookie(ldi_handle_t lh, char *name, ddi_eventcookie_t *ecp) { struct ldi_handle *handlep = (struct ldi_handle *)lh; dev_info_t *dip; dev_t dev; int res; struct snode *csp; if ((lh == NULL) || (name == NULL) || (strlen(name) == 0) || (ecp == NULL)) { return (DDI_FAILURE); } ASSERT(!servicing_interrupt()); dev = handlep->lh_vp->v_rdev; csp = VTOCS(handlep->lh_vp); mutex_enter(&csp->s_lock); if ((dip = csp->s_dip) != NULL) e_ddi_hold_devi(dip); mutex_exit(&csp->s_lock); if (dip == NULL) dip = e_ddi_hold_devi_by_dev(dev, 0); if (dip == NULL) return (DDI_FAILURE); LDI_EVENTCB((CE_NOTE, "%s: event_name=%s, " "dip=0x%p, event_cookiep=0x%p", "ldi_get_eventcookie", name, (void *)dip, (void *)ecp)); res = ddi_get_eventcookie(dip, name, ecp); ddi_release_devi(dip); return (res); } int ldi_add_event_handler(ldi_handle_t lh, ddi_eventcookie_t ec, void (*handler)(ldi_handle_t, ddi_eventcookie_t, void *, void *), void *arg, ldi_callback_id_t *id) { struct ldi_handle *handlep = (struct ldi_handle *)lh; struct ldi_event *lep; dev_info_t *dip; dev_t dev; int res; struct snode *csp; if ((lh == NULL) || (ec == NULL) || (handler == NULL) || (id == NULL)) return (DDI_FAILURE); ASSERT(!servicing_interrupt()); dev = handlep->lh_vp->v_rdev; csp = VTOCS(handlep->lh_vp); mutex_enter(&csp->s_lock); if ((dip = csp->s_dip) != NULL) e_ddi_hold_devi(dip); mutex_exit(&csp->s_lock); if (dip == NULL) dip = e_ddi_hold_devi_by_dev(dev, 0); if (dip == NULL) return (DDI_FAILURE); lep = kmem_zalloc(sizeof (struct ldi_event), KM_SLEEP); lep->le_lhp = handlep; lep->le_arg = arg; lep->le_handler = handler; if ((res = ddi_add_event_handler(dip, ec, i_ldi_callback, (void *)lep, &lep->le_id)) != DDI_SUCCESS) { LDI_EVENTCB((CE_WARN, "%s: unable to add" "event callback", "ldi_add_event_handler")); ddi_release_devi(dip); kmem_free(lep, sizeof (struct ldi_event)); return (res); } *id = (ldi_callback_id_t)lep; LDI_EVENTCB((CE_NOTE, "%s: dip=0x%p, event=0x%p, " "ldi_eventp=0x%p, cb_id=0x%p", "ldi_add_event_handler", (void *)dip, (void *)ec, (void *)lep, (void *)id)); handle_event_add(lep); ddi_release_devi(dip); return (res); } int ldi_remove_event_handler(ldi_handle_t lh, ldi_callback_id_t id) { ldi_event_t *lep = (ldi_event_t *)id; int res; if ((lh == NULL) || (id == NULL)) return (DDI_FAILURE); ASSERT(!servicing_interrupt()); if ((res = ddi_remove_event_handler(lep->le_id)) != DDI_SUCCESS) { LDI_EVENTCB((CE_WARN, "%s: unable to remove " "event callback", "ldi_remove_event_handler")); return (res); } handle_event_remove(lep); kmem_free(lep, sizeof (struct ldi_event)); return (res); } #endif /* * Here are some definitions of terms used in the following LDI events * code: * * "LDI events" AKA "native events": These are events defined by the * "new" LDI event framework. These events are serviced by the LDI event * framework itself and thus are native to it. * * "LDI contract events": These are contract events that correspond to the * LDI events. This mapping of LDI events to contract events is defined by * the ldi_ev_cookies[] array above. * * NDI events: These are events which are serviced by the NDI event subsystem. * LDI subsystem just provides a thin wrapper around the NDI event interfaces * These events are therefore *not* native events. */ static int ldi_native_event(const char *evname) { int i; LDI_EVTRC((CE_NOTE, "ldi_native_event: entered: ev=%s", evname)); for (i = 0; ldi_ev_cookies[i].ck_evname != NULL; i++) { if (strcmp(ldi_ev_cookies[i].ck_evname, evname) == 0) return (1); } return (0); } static uint_t ldi_ev_sync_event(const char *evname) { int i; ASSERT(ldi_native_event(evname)); LDI_EVTRC((CE_NOTE, "ldi_ev_sync_event: entered: %s", evname)); for (i = 0; ldi_ev_cookies[i].ck_evname != NULL; i++) { if (strcmp(ldi_ev_cookies[i].ck_evname, evname) == 0) return (ldi_ev_cookies[i].ck_sync); } /* * This should never happen until non-contract based * LDI events are introduced. If that happens, we will * use a "special" token to indicate that there are no * contracts corresponding to this LDI event. */ cmn_err(CE_PANIC, "Unknown LDI event: %s", evname); return (0); } static uint_t ldi_contract_event(const char *evname) { int i; ASSERT(ldi_native_event(evname)); LDI_EVTRC((CE_NOTE, "ldi_contract_event: entered: %s", evname)); for (i = 0; ldi_ev_cookies[i].ck_evname != NULL; i++) { if (strcmp(ldi_ev_cookies[i].ck_evname, evname) == 0) return (ldi_ev_cookies[i].ck_ctype); } /* * This should never happen until non-contract based * LDI events are introduced. If that happens, we will * use a "special" token to indicate that there are no * contracts corresponding to this LDI event. */ cmn_err(CE_PANIC, "Unknown LDI event: %s", evname); return (0); } char * ldi_ev_get_type(ldi_ev_cookie_t cookie) { int i; struct ldi_ev_cookie *cookie_impl = (struct ldi_ev_cookie *)cookie; for (i = 0; ldi_ev_cookies[i].ck_evname != NULL; i++) { if (&ldi_ev_cookies[i] == cookie_impl) { LDI_EVTRC((CE_NOTE, "ldi_ev_get_type: LDI: %s", ldi_ev_cookies[i].ck_evname)); return (ldi_ev_cookies[i].ck_evname); } } /* * Not an LDI native event. Must be NDI event service. * Just return a generic string */ LDI_EVTRC((CE_NOTE, "ldi_ev_get_type: is NDI")); return (NDI_EVENT_SERVICE); } static int ldi_native_cookie(ldi_ev_cookie_t cookie) { int i; struct ldi_ev_cookie *cookie_impl = (struct ldi_ev_cookie *)cookie; for (i = 0; ldi_ev_cookies[i].ck_evname != NULL; i++) { if (&ldi_ev_cookies[i] == cookie_impl) { LDI_EVTRC((CE_NOTE, "ldi_native_cookie: native LDI")); return (1); } } LDI_EVTRC((CE_NOTE, "ldi_native_cookie: is NDI")); return (0); } static ldi_ev_cookie_t ldi_get_native_cookie(const char *evname) { int i; for (i = 0; ldi_ev_cookies[i].ck_evname != NULL; i++) { if (strcmp(ldi_ev_cookies[i].ck_evname, evname) == 0) { LDI_EVTRC((CE_NOTE, "ldi_get_native_cookie: found")); return ((ldi_ev_cookie_t)&ldi_ev_cookies[i]); } } LDI_EVTRC((CE_NOTE, "ldi_get_native_cookie: NOT found")); return (NULL); } /* * ldi_ev_lock() needs to be recursive, since layered drivers may call * other LDI interfaces (such as ldi_close() from within the context of * a notify callback. Since the notify callback is called with the * ldi_ev_lock() held and ldi_close() also grabs ldi_ev_lock, the lock needs * to be recursive. */ static void ldi_ev_lock(void) { LDI_EVTRC((CE_NOTE, "ldi_ev_lock: entered")); mutex_enter(&ldi_ev_callback_list.le_lock); if (ldi_ev_callback_list.le_thread == curthread) { ASSERT(ldi_ev_callback_list.le_busy >= 1); ldi_ev_callback_list.le_busy++; } else { while (ldi_ev_callback_list.le_busy) cv_wait(&ldi_ev_callback_list.le_cv, &ldi_ev_callback_list.le_lock); ASSERT(ldi_ev_callback_list.le_thread == NULL); ldi_ev_callback_list.le_busy = 1; ldi_ev_callback_list.le_thread = curthread; } mutex_exit(&ldi_ev_callback_list.le_lock); LDI_EVTRC((CE_NOTE, "ldi_ev_lock: exit")); } static void ldi_ev_unlock(void) { LDI_EVTRC((CE_NOTE, "ldi_ev_unlock: entered")); mutex_enter(&ldi_ev_callback_list.le_lock); ASSERT(ldi_ev_callback_list.le_thread == curthread); ASSERT(ldi_ev_callback_list.le_busy >= 1); ldi_ev_callback_list.le_busy--; if (ldi_ev_callback_list.le_busy == 0) { ldi_ev_callback_list.le_thread = NULL; cv_signal(&ldi_ev_callback_list.le_cv); } mutex_exit(&ldi_ev_callback_list.le_lock); LDI_EVTRC((CE_NOTE, "ldi_ev_unlock: exit")); } int ldi_ev_get_cookie(ldi_handle_t lh, char *evname, ldi_ev_cookie_t *cookiep) { struct ldi_handle *handlep = (struct ldi_handle *)lh; dev_info_t *dip; dev_t dev; int res; struct snode *csp; ddi_eventcookie_t ddi_cookie; ldi_ev_cookie_t tcookie; LDI_EVDBG((CE_NOTE, "ldi_ev_get_cookie: entered: evname=%s", evname ? evname : "")); if (lh == NULL || evname == NULL || strlen(evname) == 0 || cookiep == NULL) { LDI_EVDBG((CE_NOTE, "ldi_ev_get_cookie: invalid args")); return (LDI_EV_FAILURE); } *cookiep = NULL; /* * First check if it is a LDI native event */ tcookie = ldi_get_native_cookie(evname); if (tcookie) { LDI_EVDBG((CE_NOTE, "ldi_ev_get_cookie: got native cookie")); *cookiep = tcookie; return (LDI_EV_SUCCESS); } /* * Not a LDI native event. Try NDI event services */ dev = handlep->lh_vp->v_rdev; csp = VTOCS(handlep->lh_vp); mutex_enter(&csp->s_lock); if ((dip = csp->s_dip) != NULL) e_ddi_hold_devi(dip); mutex_exit(&csp->s_lock); if (dip == NULL) dip = e_ddi_hold_devi_by_dev(dev, 0); if (dip == NULL) { cmn_err(CE_WARN, "ldi_ev_get_cookie: No devinfo node for LDI " "handle: %p", (void *)handlep); return (LDI_EV_FAILURE); } LDI_EVDBG((CE_NOTE, "Calling ddi_get_eventcookie: dip=%p, ev=%s", (void *)dip, evname)); res = ddi_get_eventcookie(dip, evname, &ddi_cookie); ddi_release_devi(dip); if (res == DDI_SUCCESS) { LDI_EVDBG((CE_NOTE, "ldi_ev_get_cookie: NDI cookie found")); *cookiep = (ldi_ev_cookie_t)ddi_cookie; return (LDI_EV_SUCCESS); } else { LDI_EVDBG((CE_WARN, "ldi_ev_get_cookie: NDI cookie: failed")); return (LDI_EV_FAILURE); } } /*ARGSUSED*/ static void i_ldi_ev_callback(dev_info_t *dip, ddi_eventcookie_t event_cookie, void *arg, void *ev_data) { ldi_ev_callback_impl_t *lecp = (ldi_ev_callback_impl_t *)arg; ASSERT(lecp != NULL); ASSERT(!ldi_native_cookie(lecp->lec_cookie)); ASSERT(lecp->lec_lhp); ASSERT(lecp->lec_notify == NULL); ASSERT(lecp->lec_finalize); LDI_EVDBG((CE_NOTE, "i_ldi_ev_callback: ldh=%p, cookie=%p, arg=%p, " "ev_data=%p", (void *)lecp->lec_lhp, (void *)event_cookie, (void *)lecp->lec_arg, (void *)ev_data)); lecp->lec_finalize(lecp->lec_lhp, (ldi_ev_cookie_t)event_cookie, lecp->lec_arg, ev_data); } int ldi_ev_register_callbacks(ldi_handle_t lh, ldi_ev_cookie_t cookie, ldi_ev_callback_t *callb, void *arg, ldi_callback_id_t *id) { struct ldi_handle *lhp = (struct ldi_handle *)lh; ldi_ev_callback_impl_t *lecp; dev_t dev; struct snode *csp; dev_info_t *dip; int ddi_event; ASSERT(!servicing_interrupt()); if (lh == NULL || cookie == NULL || callb == NULL || id == NULL) { LDI_EVDBG((CE_NOTE, "ldi_ev_register_callbacks: Invalid args")); return (LDI_EV_FAILURE); } if (callb->cb_vers != LDI_EV_CB_VERS) { LDI_EVDBG((CE_NOTE, "ldi_ev_register_callbacks: Invalid vers")); return (LDI_EV_FAILURE); } if (callb->cb_notify == NULL && callb->cb_finalize == NULL) { LDI_EVDBG((CE_NOTE, "ldi_ev_register_callbacks: NULL callb")); return (LDI_EV_FAILURE); } *id = 0; dev = lhp->lh_vp->v_rdev; csp = VTOCS(lhp->lh_vp); mutex_enter(&csp->s_lock); if ((dip = csp->s_dip) != NULL) e_ddi_hold_devi(dip); mutex_exit(&csp->s_lock); if (dip == NULL) dip = e_ddi_hold_devi_by_dev(dev, 0); if (dip == NULL) { cmn_err(CE_WARN, "ldi_ev_register: No devinfo node for " "LDI handle: %p", (void *)lhp); return (LDI_EV_FAILURE); } lecp = kmem_zalloc(sizeof (ldi_ev_callback_impl_t), KM_SLEEP); ddi_event = 0; if (!ldi_native_cookie(cookie)) { if (callb->cb_notify || callb->cb_finalize == NULL) { /* * NDI event services only accept finalize */ cmn_err(CE_WARN, "%s: module: %s: NDI event cookie. " "Only finalize" " callback supported with this cookie", "ldi_ev_register_callbacks", lhp->lh_ident->li_modname); kmem_free(lecp, sizeof (ldi_ev_callback_impl_t)); ddi_release_devi(dip); return (LDI_EV_FAILURE); } if (ddi_add_event_handler(dip, (ddi_eventcookie_t)cookie, i_ldi_ev_callback, (void *)lecp, (ddi_callback_id_t *)&lecp->lec_id) != DDI_SUCCESS) { kmem_free(lecp, sizeof (ldi_ev_callback_impl_t)); ddi_release_devi(dip); LDI_EVDBG((CE_NOTE, "ldi_ev_register_callbacks(): " "ddi_add_event_handler failed")); return (LDI_EV_FAILURE); } ddi_event = 1; LDI_EVDBG((CE_NOTE, "ldi_ev_register_callbacks(): " "ddi_add_event_handler success")); } ldi_ev_lock(); /* * Add the notify/finalize callback to the LDI's list of callbacks. */ lecp->lec_lhp = lhp; lecp->lec_dev = lhp->lh_vp->v_rdev; lecp->lec_spec = VTYP_TO_STYP(lhp->lh_vp->v_type); lecp->lec_notify = callb->cb_notify; lecp->lec_finalize = callb->cb_finalize; lecp->lec_arg = arg; lecp->lec_cookie = cookie; if (!ddi_event) lecp->lec_id = (void *)(uintptr_t)(++ldi_ev_id_pool); else ASSERT(lecp->lec_id); lecp->lec_dip = dip; list_insert_tail(&ldi_ev_callback_list.le_head, lecp); *id = (ldi_callback_id_t)lecp->lec_id; ldi_ev_unlock(); ddi_release_devi(dip); LDI_EVDBG((CE_NOTE, "ldi_ev_register_callbacks: registered " "notify/finalize")); return (LDI_EV_SUCCESS); } static int ldi_ev_device_match(ldi_ev_callback_impl_t *lecp, dev_info_t *dip, dev_t dev, int spec_type) { ASSERT(lecp); ASSERT(dip); ASSERT(dev != DDI_DEV_T_NONE); ASSERT(dev != NODEV); ASSERT((dev == DDI_DEV_T_ANY && spec_type == 0) || (spec_type == S_IFCHR || spec_type == S_IFBLK)); ASSERT(lecp->lec_dip); ASSERT(lecp->lec_spec == S_IFCHR || lecp->lec_spec == S_IFBLK); ASSERT(lecp->lec_dev != DDI_DEV_T_ANY); ASSERT(lecp->lec_dev != DDI_DEV_T_NONE); ASSERT(lecp->lec_dev != NODEV); if (dip != lecp->lec_dip) return (0); if (dev != DDI_DEV_T_ANY) { if (dev != lecp->lec_dev || spec_type != lecp->lec_spec) return (0); } LDI_EVTRC((CE_NOTE, "ldi_ev_device_match: MATCH dip=%p", (void *)dip)); return (1); } /* * LDI framework function to post a "notify" event to all layered drivers * that have registered for that event * * Returns: * LDI_EV_SUCCESS - registered callbacks allow event * LDI_EV_FAILURE - registered callbacks block event * LDI_EV_NONE - No matching LDI callbacks * * This function is *not* to be called by layered drivers. It is for I/O * framework code in Solaris, such as the I/O retire code and DR code * to call while servicing a device event such as offline or degraded. */ int ldi_invoke_notify(dev_info_t *dip, dev_t dev, int spec_type, char *event, void *ev_data) { ldi_ev_callback_impl_t *lecp; list_t *listp; int ret; char *lec_event; ASSERT(dip); ASSERT(dev != DDI_DEV_T_NONE); ASSERT(dev != NODEV); ASSERT((dev == DDI_DEV_T_ANY && spec_type == 0) || (spec_type == S_IFCHR || spec_type == S_IFBLK)); ASSERT(event); ASSERT(ldi_native_event(event)); ASSERT(ldi_ev_sync_event(event)); LDI_EVDBG((CE_NOTE, "ldi_invoke_notify(): entered: dip=%p, ev=%s", (void *)dip, event)); ret = LDI_EV_NONE; ldi_ev_lock(); VERIFY(ldi_ev_callback_list.le_walker_next == NULL); listp = &ldi_ev_callback_list.le_head; for (lecp = list_head(listp); lecp; lecp = ldi_ev_callback_list.le_walker_next) { ldi_ev_callback_list.le_walker_next = list_next(listp, lecp); /* Check if matching device */ if (!ldi_ev_device_match(lecp, dip, dev, spec_type)) continue; if (lecp->lec_lhp == NULL) { /* * Consumer has unregistered the handle and so * is no longer interested in notify events. */ LDI_EVDBG((CE_NOTE, "ldi_invoke_notify(): No LDI " "handle, skipping")); continue; } if (lecp->lec_notify == NULL) { LDI_EVDBG((CE_NOTE, "ldi_invoke_notify(): No notify " "callback. skipping")); continue; /* not interested in notify */ } /* * Check if matching event */ lec_event = ldi_ev_get_type(lecp->lec_cookie); if (strcmp(event, lec_event) != 0) { LDI_EVDBG((CE_NOTE, "ldi_invoke_notify(): Not matching" " event {%s,%s}. skipping", event, lec_event)); continue; } lecp->lec_lhp->lh_flags |= LH_FLAGS_NOTIFY; if (lecp->lec_notify(lecp->lec_lhp, lecp->lec_cookie, lecp->lec_arg, ev_data) != LDI_EV_SUCCESS) { ret = LDI_EV_FAILURE; LDI_EVDBG((CE_NOTE, "ldi_invoke_notify(): notify" " FAILURE")); break; } /* We have a matching callback that allows the event to occur */ ret = LDI_EV_SUCCESS; LDI_EVDBG((CE_NOTE, "ldi_invoke_notify(): 1 consumer success")); } if (ret != LDI_EV_FAILURE) goto out; LDI_EVDBG((CE_NOTE, "ldi_invoke_notify(): undoing notify")); /* * Undo notifies already sent */ lecp = list_prev(listp, lecp); VERIFY(ldi_ev_callback_list.le_walker_prev == NULL); for (; lecp; lecp = ldi_ev_callback_list.le_walker_prev) { ldi_ev_callback_list.le_walker_prev = list_prev(listp, lecp); /* * Check if matching device */ if (!ldi_ev_device_match(lecp, dip, dev, spec_type)) continue; if (lecp->lec_finalize == NULL) { LDI_EVDBG((CE_NOTE, "ldi_invoke_notify(): no finalize, " "skipping")); continue; /* not interested in finalize */ } /* * it is possible that in response to a notify event a * layered driver closed its LDI handle so it is ok * to have a NULL LDI handle for finalize. The layered * driver is expected to maintain state in its "arg" * parameter to keep track of the closed device. */ /* Check if matching event */ lec_event = ldi_ev_get_type(lecp->lec_cookie); if (strcmp(event, lec_event) != 0) { LDI_EVDBG((CE_NOTE, "ldi_invoke_notify(): not matching " "event: %s,%s, skipping", event, lec_event)); continue; } LDI_EVDBG((CE_NOTE, "ldi_invoke_notify(): calling finalize")); lecp->lec_finalize(lecp->lec_lhp, lecp->lec_cookie, LDI_EV_FAILURE, lecp->lec_arg, ev_data); /* * If LDI native event and LDI handle closed in context * of notify, NULL out the finalize callback as we have * already called the 1 finalize above allowed in this situation */ if (lecp->lec_lhp == NULL && ldi_native_cookie(lecp->lec_cookie)) { LDI_EVDBG((CE_NOTE, "ldi_invoke_notify(): NULL-ing finalize after " "calling 1 finalize following ldi_close")); lecp->lec_finalize = NULL; } } out: ldi_ev_callback_list.le_walker_next = NULL; ldi_ev_callback_list.le_walker_prev = NULL; ldi_ev_unlock(); if (ret == LDI_EV_NONE) { LDI_EVDBG((CE_NOTE, "ldi_invoke_notify(): no matching " "LDI callbacks")); } return (ret); } /* * Framework function to be called from a layered driver to propagate * LDI "notify" events to exported minors. * * This function is a public interface exported by the LDI framework * for use by layered drivers to propagate device events up the software * stack. */ int ldi_ev_notify(dev_info_t *dip, minor_t minor, int spec_type, ldi_ev_cookie_t cookie, void *ev_data) { char *evname = ldi_ev_get_type(cookie); uint_t ct_evtype; dev_t dev; major_t major; int retc; int retl; ASSERT(spec_type == S_IFBLK || spec_type == S_IFCHR); ASSERT(dip); ASSERT(ldi_native_cookie(cookie)); LDI_EVDBG((CE_NOTE, "ldi_ev_notify(): entered: event=%s, dip=%p", evname, (void *)dip)); if (!ldi_ev_sync_event(evname)) { cmn_err(CE_PANIC, "ldi_ev_notify(): %s not a " "negotiatable event", evname); } major = ddi_driver_major(dip); if (major == DDI_MAJOR_T_NONE) { char *path = kmem_alloc(MAXPATHLEN, KM_SLEEP); (void) ddi_pathname(dip, path); cmn_err(CE_WARN, "ldi_ev_notify: cannot derive major number " "for device %s", path); kmem_free(path, MAXPATHLEN); return (LDI_EV_FAILURE); } dev = makedevice(major, minor); /* * Generate negotiation contract events on contracts (if any) associated * with this minor. */ LDI_EVDBG((CE_NOTE, "ldi_ev_notify(): calling contract nego.")); ct_evtype = ldi_contract_event(evname); retc = contract_device_negotiate(dip, dev, spec_type, ct_evtype); if (retc == CT_NACK) { LDI_EVDBG((CE_NOTE, "ldi_ev_notify(): contract neg. NACK")); return (LDI_EV_FAILURE); } LDI_EVDBG((CE_NOTE, "ldi_ev_notify(): LDI invoke notify")); retl = ldi_invoke_notify(dip, dev, spec_type, evname, ev_data); if (retl == LDI_EV_FAILURE) { LDI_EVDBG((CE_NOTE, "ldi_ev_notify(): ldi_invoke_notify " "returned FAILURE. Calling contract negend")); contract_device_negend(dip, dev, spec_type, CT_EV_FAILURE); return (LDI_EV_FAILURE); } /* * The very fact that we are here indicates that there is a * LDI callback (and hence a constraint) for the retire of the * HW device. So we just return success even if there are no * contracts or LDI callbacks against the minors layered on top * of the HW minors */ LDI_EVDBG((CE_NOTE, "ldi_ev_notify(): returning SUCCESS")); return (LDI_EV_SUCCESS); } /* * LDI framework function to invoke "finalize" callbacks for all layered * drivers that have registered callbacks for that event. * * This function is *not* to be called by layered drivers. It is for I/O * framework code in Solaris, such as the I/O retire code and DR code * to call while servicing a device event such as offline or degraded. */ void ldi_invoke_finalize(dev_info_t *dip, dev_t dev, int spec_type, char *event, int ldi_result, void *ev_data) { ldi_ev_callback_impl_t *lecp; list_t *listp; char *lec_event; int found = 0; ASSERT(dip); ASSERT(dev != DDI_DEV_T_NONE); ASSERT(dev != NODEV); ASSERT((dev == DDI_DEV_T_ANY && spec_type == 0) || (spec_type == S_IFCHR || spec_type == S_IFBLK)); ASSERT(event); ASSERT(ldi_native_event(event)); ASSERT(ldi_result == LDI_EV_SUCCESS || ldi_result == LDI_EV_FAILURE); LDI_EVDBG((CE_NOTE, "ldi_invoke_finalize(): entered: dip=%p, result=%d" " event=%s", (void *)dip, ldi_result, event)); ldi_ev_lock(); VERIFY(ldi_ev_callback_list.le_walker_next == NULL); listp = &ldi_ev_callback_list.le_head; for (lecp = list_head(listp); lecp; lecp = ldi_ev_callback_list.le_walker_next) { ldi_ev_callback_list.le_walker_next = list_next(listp, lecp); if (lecp->lec_finalize == NULL) { LDI_EVDBG((CE_NOTE, "ldi_invoke_finalize(): No " "finalize. Skipping")); continue; /* Not interested in finalize */ } /* * Check if matching device */ if (!ldi_ev_device_match(lecp, dip, dev, spec_type)) continue; /* * It is valid for the LDI handle to be NULL during finalize. * The layered driver may have done an LDI close in the notify * callback. */ /* * Check if matching event */ lec_event = ldi_ev_get_type(lecp->lec_cookie); if (strcmp(event, lec_event) != 0) { LDI_EVDBG((CE_NOTE, "ldi_invoke_finalize(): Not " "matching event {%s,%s}. Skipping", event, lec_event)); continue; } LDI_EVDBG((CE_NOTE, "ldi_invoke_finalize(): calling finalize")); found = 1; lecp->lec_finalize(lecp->lec_lhp, lecp->lec_cookie, ldi_result, lecp->lec_arg, ev_data); /* * If LDI native event and LDI handle closed in context * of notify, NULL out the finalize callback as we have * already called the 1 finalize above allowed in this situation */ if (lecp->lec_lhp == NULL && ldi_native_cookie(lecp->lec_cookie)) { LDI_EVDBG((CE_NOTE, "ldi_invoke_finalize(): NULLing finalize after " "calling 1 finalize following ldi_close")); lecp->lec_finalize = NULL; } } ldi_ev_callback_list.le_walker_next = NULL; ldi_ev_unlock(); if (found) return; LDI_EVDBG((CE_NOTE, "ldi_invoke_finalize(): no matching callbacks")); } /* * Framework function to be called from a layered driver to propagate * LDI "finalize" events to exported minors. * * This function is a public interface exported by the LDI framework * for use by layered drivers to propagate device events up the software * stack. */ void ldi_ev_finalize(dev_info_t *dip, minor_t minor, int spec_type, int ldi_result, ldi_ev_cookie_t cookie, void *ev_data) { dev_t dev; major_t major; char *evname; int ct_result = (ldi_result == LDI_EV_SUCCESS) ? CT_EV_SUCCESS : CT_EV_FAILURE; uint_t ct_evtype; ASSERT(dip); ASSERT(spec_type == S_IFBLK || spec_type == S_IFCHR); ASSERT(ldi_result == LDI_EV_SUCCESS || ldi_result == LDI_EV_FAILURE); ASSERT(ldi_native_cookie(cookie)); LDI_EVDBG((CE_NOTE, "ldi_ev_finalize: entered: dip=%p", (void *)dip)); major = ddi_driver_major(dip); if (major == DDI_MAJOR_T_NONE) { char *path = kmem_alloc(MAXPATHLEN, KM_SLEEP); (void) ddi_pathname(dip, path); cmn_err(CE_WARN, "ldi_ev_finalize: cannot derive major number " "for device %s", path); kmem_free(path, MAXPATHLEN); return; } dev = makedevice(major, minor); evname = ldi_ev_get_type(cookie); LDI_EVDBG((CE_NOTE, "ldi_ev_finalize: calling contracts")); ct_evtype = ldi_contract_event(evname); contract_device_finalize(dip, dev, spec_type, ct_evtype, ct_result); LDI_EVDBG((CE_NOTE, "ldi_ev_finalize: calling ldi_invoke_finalize")); ldi_invoke_finalize(dip, dev, spec_type, evname, ldi_result, ev_data); } int ldi_ev_remove_callbacks(ldi_callback_id_t id) { ldi_ev_callback_impl_t *lecp; ldi_ev_callback_impl_t *next; ldi_ev_callback_impl_t *found; list_t *listp; ASSERT(!servicing_interrupt()); if (id == 0) { cmn_err(CE_WARN, "ldi_ev_remove_callbacks: Invalid ID 0"); return (LDI_EV_FAILURE); } LDI_EVDBG((CE_NOTE, "ldi_ev_remove_callbacks: entered: id=%p", (void *)id)); ldi_ev_lock(); listp = &ldi_ev_callback_list.le_head; next = found = NULL; for (lecp = list_head(listp); lecp; lecp = next) { next = list_next(listp, lecp); if (lecp->lec_id == id) { VERIFY(found == NULL); /* * If there is a walk in progress, shift that walk * along to the next element so that we can remove * this one. This allows us to unregister an arbitrary * number of callbacks from within a callback. * * See the struct definition (in sunldi_impl.h) for * more information. */ if (ldi_ev_callback_list.le_walker_next == lecp) ldi_ev_callback_list.le_walker_next = next; if (ldi_ev_callback_list.le_walker_prev == lecp) ldi_ev_callback_list.le_walker_prev = list_prev( listp, ldi_ev_callback_list.le_walker_prev); list_remove(listp, lecp); found = lecp; } } ldi_ev_unlock(); if (found == NULL) { cmn_err(CE_WARN, "No LDI event handler for id (%p)", (void *)id); return (LDI_EV_SUCCESS); } if (!ldi_native_cookie(found->lec_cookie)) { ASSERT(found->lec_notify == NULL); if (ddi_remove_event_handler((ddi_callback_id_t)id) != DDI_SUCCESS) { cmn_err(CE_WARN, "failed to remove NDI event handler " "for id (%p)", (void *)id); ldi_ev_lock(); list_insert_tail(listp, found); ldi_ev_unlock(); return (LDI_EV_FAILURE); } LDI_EVDBG((CE_NOTE, "ldi_ev_remove_callbacks: NDI event " "service removal succeeded")); } else { LDI_EVDBG((CE_NOTE, "ldi_ev_remove_callbacks: removed " "LDI native callbacks")); } kmem_free(found, sizeof (ldi_ev_callback_impl_t)); return (LDI_EV_SUCCESS); }