| /* |
| * 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 2009 Sun Microsystems, Inc. All rights reserved. |
| * Use is subject to license terms. |
| */ |
| |
| /* |
| * Layered driver support. |
| */ |
| |
| #include <sys/atomic.h> |
| #include <sys/types.h> |
| #include <sys/t_lock.h> |
| #include <sys/param.h> |
| #include <sys/conf.h> |
| #include <sys/systm.h> |
| #include <sys/sysmacros.h> |
| #include <sys/buf.h> |
| #include <sys/cred.h> |
| #include <sys/uio.h> |
| #include <sys/vnode.h> |
| #include <sys/fs/snode.h> |
| #include <sys/open.h> |
| #include <sys/kmem.h> |
| #include <sys/file.h> |
| #include <sys/bootconf.h> |
| #include <sys/pathname.h> |
| #include <sys/bitmap.h> |
| #include <sys/stat.h> |
| #include <sys/dditypes.h> |
| #include <sys/ddi_impldefs.h> |
| #include <sys/ddi.h> |
| #include <sys/sunddi.h> |
| #include <sys/sunndi.h> |
| #include <sys/esunddi.h> |
| #include <sys/autoconf.h> |
| #include <sys/sunldi.h> |
| #include <sys/sunldi_impl.h> |
| #include <sys/errno.h> |
| #include <sys/debug.h> |
| #include <sys/modctl.h> |
| #include <sys/var.h> |
| #include <vm/seg_vn.h> |
| |
| #include <sys/stropts.h> |
| #include <sys/strsubr.h> |
| #include <sys/socket.h> |
| #include <sys/socketvar.h> |
| #include <sys/kstr.h> |
| |
| /* |
| * Device contract related |
| */ |
| #include <sys/contract_impl.h> |
| #include <sys/contract/device_impl.h> |
| |
| /* |
| * 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; |
| |
| 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}, |
| { 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; |
| 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; |
| 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 indent in the hash */ |
| ASSERT(strcmp((*lipp)->li_modname, mod_name) == 0); |
| (*lipp)->li_ref++; |
| mutex_exit(&ldi_ident_hash_lock[index]); |
| kmem_free(lip, sizeof (struct ldi_ident)); |
| return (*lipp); |
| } |
| |
| /* 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; |
| int index = LH_HASH(vp); |
| |
| mutex_enter(&ldi_handle_hash_lock[index]); |
| lhpp = handle_find_ref_nolock(vp, ident); |
| mutex_exit(&ldi_handle_hash_lock[index]); |
| ASSERT(lhpp != NULL); |
| return (*lhpp); |
| } |
| |
| static struct ldi_handle * |
| handle_alloc(vnode_t *vp, struct ldi_ident *ident) |
| { |
| struct ldi_handle *lhp, **lhpp; |
| 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++; |
| 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 *)*lhpp, (void *)ident, (void *)vp, |
| mod_major_to_name(getmajor(vp->v_rdev)), |
| getminor(vp->v_rdev))); |
| |
| kmem_free(lhp, sizeof (struct ldi_handle)); |
| return (*lhpp); |
| } |
| |
| /* 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 (STREAMSTAB(getmajor(vp->v_rdev))) { |
| 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_add_long(&ldi_handle_hash_count, 1); |
| |
| 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_add_long(&ldi_handle_hash_count, -1); |
| 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); |
| |
| if (STREAMSTAB(getmajor(dev)) && (otyp != OTYP_CHR)) { |
| ddi_release_devi(dip); /* from e_ddi_hold_devi_by_dev */ |
| return (ENXIO); |
| } |
| |
| 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 */ |
| static 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 */ |
| static 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)); |
| } |
| |
| /* Flush back any dirty pages associated with the device. */ |
| if (nlhp->lh_type & LH_CBDEV) { |
| vnode_t *cvp = common_specvp(nlhp->lh_vp); |
| dev_t dev = cvp->v_rdev; |
| |
| (void) VOP_PUTPAGE(cvp, 0, 0, B_INVAL, kcred, NULL); |
| bflush(dev); |
| } |
| |
| *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; |
| |
| ASSERT((major >= 0) && (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. |
| */ |
| void |
| 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 */ |
| if (!vn_matchops(vp, spec_getvnodeops())) |
| return; |
| |
| 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); |
| |
| /* 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. |
| */ |
| mutex_enter(&csp->s_lock); |
| ASSERT(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); |
| ASSERT(!(sp->s_flag & SMUXED)); |
| sp->s_flag |= SMUXED; |
| mutex_exit(&sp->s_lock); |
| |
| /* 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 compleated, 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. |
| */ |
| ASSERT(type == LINKPERSIST); |
| major = getmajor(stp->sd_vnode->v_rdev); |
| ret = ldi_ident_from_major(major, &li); |
| } |
| |
| ASSERT(ret == 0); |
| (void) handle_alloc(vp, (struct ldi_ident *)li); |
| ldi_ident_release(li); |
| } |
| |
| void |
| 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 */ |
| if (!vn_matchops(vp, spec_getvnodeops())) |
| return; |
| |
| ASSERT(!servicing_interrupt()); |
| ASSERT((type == LINKNORMAL) || (type == LINKPERSIST)); |
| |
| LDI_STREAMS_LNK((CE_NOTE, "%s: unlinking streams " |
| "stp=0x%p, fpdown=0x%p", "ldi_munlink_fp", |
| (void *)stp, (void *)fpdown)); |
| |
| /* |
| * 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); |
| ASSERT(sp->s_flag & SMUXED); |
| sp->s_flag &= ~SMUXED; |
| mutex_exit(&sp->s_lock); |
| |
| /* |
| * 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 { |
| ASSERT(type == LINKPERSIST); |
| major = getmajor(stp->sd_vnode->v_rdev); |
| ret = ldi_ident_from_major(major, &li); |
| } |
| |
| ASSERT(ret == 0); |
| lhp = handle_find(vp, (struct ldi_ident *)li); |
| handle_release(lhp); |
| ldi_ident_release(li); |
| } |
| |
| /* |
| * 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()); |
| |
| /* Flush back any dirty pages associated with the device. */ |
| if (handlep->lh_type & LH_CBDEV) { |
| vnode_t *cvp = common_specvp(handlep->lh_vp); |
| dev_t dev = cvp->v_rdev; |
| |
| (void) VOP_PUTPAGE(cvp, 0, 0, B_INVAL, kcred, NULL); |
| bflush(dev); |
| } |
| |
| #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; |
| |
| 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; |
| |
| 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 |