root/usr/src/uts/common/os/devid_cache.c
/*
 * 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) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2018 by Delphix. All rights reserved.
 * Copyright 2023 Oxide Computer Company
 */

#include <sys/note.h>
#include <sys/t_lock.h>
#include <sys/cmn_err.h>
#include <sys/instance.h>
#include <sys/conf.h>
#include <sys/stat.h>
#include <sys/ddi.h>
#include <sys/hwconf.h>
#include <sys/sunddi.h>
#include <sys/sunndi.h>
#include <sys/sunmdi.h>
#include <sys/ddi_impldefs.h>
#include <sys/ndi_impldefs.h>
#include <sys/kobj.h>
#include <sys/devcache.h>
#include <sys/devid_cache.h>
#include <sys/sysmacros.h>

/*
 * Discovery refers to the heroic effort made to discover a device which
 * cannot be accessed at the physical path where it once resided.  Discovery
 * involves walking the entire device tree attaching all possible disk
 * instances, to search for the device referenced by a devid.  Obviously,
 * full device discovery is something to be avoided where possible.
 * Note that simply invoking devfsadm(8) is equivalent to running full
 * discovery at the devid cache level.
 *
 * Reasons why a disk may not be accessible:
 *      disk powered off
 *      disk removed or cable disconnected
 *      disk or adapter broken
 *
 * Note that discovery is not needed and cannot succeed in any of these
 * cases.
 *
 * When discovery may succeed:
 *      Discovery will result in success when a device has been moved
 *      to a different address.  Note that it's recommended that
 *      devfsadm(8) be invoked (no arguments required) whenever a system's
 *      h/w configuration has been updated.  Alternatively, a
 *      reconfiguration boot can be used to accomplish the same result.
 *
 * Note that discovery is not necessary to be able to correct an access
 * failure for a device which was powered off.  Assuming the cache has an
 * entry for such a device, simply powering it on should permit the system
 * to access it.  If problems persist after powering it on, invoke
 * devfsadm(8).
 *
 * Discovery prior to mounting root is only of interest when booting
 * from a filesystem which accesses devices by device id, which of
 * not all do.
 *
 * Tunables
 *
 * devid_discovery_boot (default 1)
 *      Number of times discovery will be attempted prior to mounting root.
 *      Must be done at least once to recover from corrupted or missing
 *      devid cache backing store.  Probably there's no reason to ever
 *      set this to greater than one as a missing device will remain
 *      unavailable no matter how often the system searches for it.
 *
 * devid_discovery_postboot (default 1)
 *      Number of times discovery will be attempted after mounting root.
 *      This must be performed at least once to discover any devices
 *      needed after root is mounted which may have been powered
 *      off and moved before booting.
 *      Setting this to a larger positive number will introduce
 *      some inconsistency in system operation.  Searching for a device
 *      will take an indeterminate amount of time, sometimes slower,
 *      sometimes faster.  In addition, the system will sometimes
 *      discover a newly powered on device, sometimes it won't.
 *      Use of this option is not therefore recommended.
 *
 * devid_discovery_postboot_always (default 0)
 *      Set to 1, the system will always attempt full discovery.
 *
 * devid_discovery_secs (default 0)
 *      Set to a positive value, the system will attempt full discovery
 *      but with a minimum delay between attempts.  A device search
 *      within the period of time specified will result in failure.
 *
 * devid_cache_read_disable (default 0)
 *      Set to 1 to disable reading /etc/devices/devid_cache.
 *      Devid cache will continue to operate normally but
 *      at least one discovery attempt will be required.
 *
 * devid_cache_write_disable (default 0)
 *      Set to 1 to disable updates to /etc/devices/devid_cache.
 *      Any updates to the devid cache will not be preserved across a reboot.
 *
 * devid_report_error (default 0)
 *      Set to 1 to enable some error messages related to devid
 *      cache failures.
 *
 * The devid is packed in the cache file as a byte array.  For
 * portability, this could be done in the encoded string format.
 */


int devid_discovery_boot = 1;
int devid_discovery_postboot = 1;
int devid_discovery_postboot_always = 0;
int devid_discovery_secs = 0;

int devid_cache_read_disable = 0;
int devid_cache_write_disable = 0;

int devid_report_error = 0;


/*
 * State to manage discovery of devices providing a devid
 */
static int              devid_discovery_busy = 0;
static kmutex_t         devid_discovery_mutex;
static kcondvar_t       devid_discovery_cv;
static clock_t          devid_last_discovery = 0;


#ifdef  DEBUG
int nvp_devid_debug = 0;
int devid_debug = 0;
int devid_log_registers = 0;
int devid_log_finds = 0;
int devid_log_lookups = 0;
int devid_log_discovery = 0;
int devid_log_matches = 0;
int devid_log_paths = 0;
int devid_log_failures = 0;
int devid_log_hold = 0;
int devid_log_unregisters = 0;
int devid_log_removes = 0;
int devid_register_debug = 0;
int devid_log_stale = 0;
int devid_log_detaches = 0;
#endif  /* DEBUG */

/*
 * devid cache file registration for cache reads and updates
 */
static nvf_ops_t devid_cache_ops = {
        "/etc/devices/devid_cache",             /* path to cache */
        devid_cache_unpack_nvlist,              /* read: nvlist to nvp */
        devid_cache_pack_list,                  /* write: nvp to nvlist */
        devid_list_free,                        /* free data list */
        NULL                                    /* write complete callback */
};

/*
 * handle to registered devid cache handlers
 */
nvf_handle_t    dcfd_handle;


/*
 * Initialize devid cache file management
 */
void
devid_cache_init(void)
{
        dcfd_handle = nvf_register_file(&devid_cache_ops);
        ASSERT(dcfd_handle);

        list_create(nvf_list(dcfd_handle), sizeof (nvp_devid_t),
            offsetof(nvp_devid_t, nvp_link));

        mutex_init(&devid_discovery_mutex, NULL, MUTEX_DEFAULT, NULL);
        cv_init(&devid_discovery_cv, NULL, CV_DRIVER, NULL);
}

/*
 * Read and initialize the devid cache from the persistent store
 */
void
devid_cache_read(void)
{
        if (!devid_cache_read_disable) {
                rw_enter(nvf_lock(dcfd_handle), RW_WRITER);
                ASSERT(list_head(nvf_list(dcfd_handle)) == NULL);
                (void) nvf_read_file(dcfd_handle);
                rw_exit(nvf_lock(dcfd_handle));
        }
}

static void
devid_nvp_free(nvp_devid_t *dp)
{
        if (dp->nvp_devpath)
                kmem_free(dp->nvp_devpath, strlen(dp->nvp_devpath)+1);
        if (dp->nvp_devid)
                kmem_free(dp->nvp_devid, ddi_devid_sizeof(dp->nvp_devid));

        kmem_free(dp, sizeof (nvp_devid_t));
}

static void
devid_list_free(nvf_handle_t fd)
{
        list_t          *listp;
        nvp_devid_t     *np;

        ASSERT(RW_WRITE_HELD(nvf_lock(dcfd_handle)));

        listp = nvf_list(fd);
        while (np = list_head(listp)) {
                list_remove(listp, np);
                devid_nvp_free(np);
        }
}

/*
 * Free an nvp element in a list
 */
static void
devid_nvp_unlink_and_free(nvf_handle_t fd, nvp_devid_t *np)
{
        list_remove(nvf_list(fd), np);
        devid_nvp_free(np);
}

/*
 * Unpack a device path/nvlist pair to the list of devid cache elements.
 * Used to parse the nvlist format when reading
 * /etc/devices/devid_cache
 */
static int
devid_cache_unpack_nvlist(nvf_handle_t fd, nvlist_t *nvl, char *name)
{
        nvp_devid_t *np;
        ddi_devid_t devidp;
        int rval;
        uint_t n;

        NVP_DEVID_DEBUG_PATH((name));
        ASSERT(RW_WRITE_HELD(nvf_lock(dcfd_handle)));

        /*
         * check path for a devid
         */
        rval = nvlist_lookup_byte_array(nvl,
            DP_DEVID_ID, (uchar_t **)&devidp, &n);
        if (rval == 0) {
                if (ddi_devid_valid(devidp) == DDI_SUCCESS) {
                        ASSERT(n == ddi_devid_sizeof(devidp));
                        np = kmem_zalloc(sizeof (nvp_devid_t), KM_SLEEP);
                        np->nvp_devpath = i_ddi_strdup(name, KM_SLEEP);
                        np->nvp_devid = kmem_alloc(n, KM_SLEEP);
                        (void) bcopy(devidp, np->nvp_devid, n);
                        list_insert_tail(nvf_list(fd), np);
                        NVP_DEVID_DEBUG_DEVID((np->nvp_devid));
                } else {
                        DEVIDERR((CE_CONT,
                            "%s: invalid devid\n", name));
                }
        } else {
                DEVIDERR((CE_CONT,
                    "%s: devid not available\n", name));
        }

        return (0);
}

/*
 * Pack the list of devid cache elements into a single nvlist
 * Used when writing the nvlist file.
 */
static int
devid_cache_pack_list(nvf_handle_t fd, nvlist_t **ret_nvl)
{
        nvlist_t        *nvl, *sub_nvl;
        nvp_devid_t     *np;
        int             rval;
        list_t          *listp;

        ASSERT(RW_WRITE_HELD(nvf_lock(dcfd_handle)));

        rval = nvlist_alloc(&nvl, NV_UNIQUE_NAME, KM_SLEEP);
        if (rval != 0) {
                nvf_error("%s: nvlist alloc error %d\n",
                    nvf_cache_name(fd), rval);
                return (DDI_FAILURE);
        }

        listp = nvf_list(fd);
        for (np = list_head(listp); np; np = list_next(listp, np)) {
                if (np->nvp_devid == NULL)
                        continue;
                NVP_DEVID_DEBUG_PATH(np->nvp_devpath);
                rval = nvlist_alloc(&sub_nvl, NV_UNIQUE_NAME, KM_SLEEP);
                if (rval != 0) {
                        nvf_error("%s: nvlist alloc error %d\n",
                            nvf_cache_name(fd), rval);
                        sub_nvl = NULL;
                        goto err;
                }

                rval = nvlist_add_byte_array(sub_nvl, DP_DEVID_ID,
                    (uchar_t *)np->nvp_devid,
                    ddi_devid_sizeof(np->nvp_devid));
                if (rval == 0) {
                        NVP_DEVID_DEBUG_DEVID(np->nvp_devid);
                } else {
                        nvf_error(
                            "%s: nvlist add error %d (devid)\n",
                            nvf_cache_name(fd), rval);
                        goto err;
                }

                rval = nvlist_add_nvlist(nvl, np->nvp_devpath, sub_nvl);
                if (rval != 0) {
                        nvf_error("%s: nvlist add error %d (sublist)\n",
                            nvf_cache_name(fd), rval);
                        goto err;
                }
                nvlist_free(sub_nvl);
        }

        *ret_nvl = nvl;
        return (DDI_SUCCESS);

err:
        nvlist_free(sub_nvl);
        nvlist_free(nvl);
        *ret_nvl = NULL;
        return (DDI_FAILURE);
}

static int
e_devid_do_discovery(void)
{
        ASSERT(mutex_owned(&devid_discovery_mutex));

        if (i_ddi_io_initialized() == 0) {
                if (devid_discovery_boot > 0) {
                        devid_discovery_boot--;
                        return (1);
                }
        } else {
                if (devid_discovery_postboot_always > 0)
                        return (1);
                if (devid_discovery_postboot > 0) {
                        devid_discovery_postboot--;
                        return (1);
                }
                if (devid_discovery_secs > 0) {
                        if ((ddi_get_lbolt() - devid_last_discovery) >
                            drv_usectohz(devid_discovery_secs * MICROSEC)) {
                                return (1);
                        }
                }
        }

        DEVID_LOG_DISC((CE_CONT, "devid_discovery: no discovery\n"));
        return (0);
}

static void
e_ddi_devid_hold_by_major(major_t major)
{
        DEVID_LOG_DISC((CE_CONT,
            "devid_discovery: ddi_hold_installed_driver %d\n", major));

        if (ddi_hold_installed_driver(major) == NULL)
                return;

        ddi_rele_driver(major);
}

/* legacy support - see below */
static char *e_ddi_devid_hold_driver_list[] = { "sd", "ssd" };

#define N_DRIVERS_TO_HOLD       \
        (sizeof (e_ddi_devid_hold_driver_list) / sizeof (char *))

static void
e_ddi_devid_hold_installed_driver(ddi_devid_t devid)
{
        impl_devid_t    *id = (impl_devid_t *)devid;
        major_t         major, hint_major;
        char            hint[DEVID_HINT_SIZE + 1];
        struct devnames *dnp;
        char            **drvp;
        int             i;

        /* Count non-null bytes */
        for (i = 0; i < DEVID_HINT_SIZE; i++)
                if (id->did_driver[i] == '\0')
                        break;

        /* Make a copy of the driver hint */
        bcopy(id->did_driver, hint, i);
        hint[i] = '\0';

        /* search for the devid using the hint driver */
        hint_major = ddi_name_to_major(hint);
        if (hint_major != DDI_MAJOR_T_NONE) {
                e_ddi_devid_hold_by_major(hint_major);
        }

        /*
         * search for the devid with each driver declaring
         * itself as a devid registrant.
         */
        for (major = 0; major < devcnt; major++) {
                if (major == hint_major)
                        continue;
                dnp = &devnamesp[major];
                if (dnp->dn_flags & DN_DEVID_REGISTRANT) {
                        e_ddi_devid_hold_by_major(major);
                }
        }

        /*
         * Legacy support: may be removed once an upgrade mechanism
         * for driver conf files is available.
         */
        drvp = e_ddi_devid_hold_driver_list;
        for (i = 0; i < N_DRIVERS_TO_HOLD; i++, drvp++) {
                major = ddi_name_to_major(*drvp);
                if (major != DDI_MAJOR_T_NONE && major != hint_major) {
                        e_ddi_devid_hold_by_major(major);
                }
        }
}

/*
 * Return success if discovery was attempted, to indicate
 * that the desired device may now be available.
 */
int
e_ddi_devid_discovery(ddi_devid_t devid)
{
        int flags;
        int rval = DDI_SUCCESS;

        mutex_enter(&devid_discovery_mutex);

        if (devid_discovery_busy) {
                DEVID_LOG_DISC((CE_CONT, "devid_discovery: busy\n"));
                while (devid_discovery_busy) {
                        cv_wait(&devid_discovery_cv, &devid_discovery_mutex);
                }
        } else if (e_devid_do_discovery()) {
                devid_discovery_busy = 1;
                mutex_exit(&devid_discovery_mutex);

                if (i_ddi_io_initialized() == 0) {
                        e_ddi_devid_hold_installed_driver(devid);
                } else {
                        DEVID_LOG_DISC((CE_CONT,
                            "devid_discovery: ndi_devi_config\n"));
                        flags = NDI_DEVI_PERSIST | NDI_CONFIG | NDI_NO_EVENT;
                        if (i_ddi_io_initialized())
                                flags |= NDI_DRV_CONF_REPROBE;
                        (void) ndi_devi_config(ddi_root_node(), flags);
                }

                mutex_enter(&devid_discovery_mutex);
                devid_discovery_busy = 0;
                cv_broadcast(&devid_discovery_cv);
                if (devid_discovery_secs > 0)
                        devid_last_discovery = ddi_get_lbolt();
                DEVID_LOG_DISC((CE_CONT, "devid_discovery: done\n"));
        } else {
                rval = DDI_FAILURE;
                DEVID_LOG_DISC((CE_CONT, "no devid discovery\n"));
        }

        mutex_exit(&devid_discovery_mutex);

        return (rval);
}

/*
 * As part of registering a devid for a device,
 * update the devid cache with this device/devid pair
 * or note that this combination has registered.
 *
 * If a devpath is provided it will be used as the path to register the
 * devid against, otherwise we use ddi_pathname(dip).  In both cases
 * we duplicate the path string so that it can be cached/freed indepdently
 * of the original owner.
 */
static int
e_devid_cache_register_cmn(dev_info_t *dip, ddi_devid_t devid, char *devpath)
{
        nvp_devid_t *np;
        nvp_devid_t *new_nvp;
        ddi_devid_t new_devid;
        int new_devid_size;
        char *path, *fullpath;
        ddi_devid_t free_devid = NULL;
        int pathlen;
        list_t *listp;
        int is_dirty = 0;


        ASSERT(ddi_devid_valid(devid) == DDI_SUCCESS);

        if (devpath) {
                pathlen = strlen(devpath) + 1;
                path = kmem_alloc(pathlen, KM_SLEEP);
                bcopy(devpath, path, pathlen);
        } else {
                /*
                 * We are willing to accept DS_BOUND nodes if we can form a full
                 * ddi_pathname (i.e. the node is part way to becomming
                 * DS_INITIALIZED and devi_addr/ddi_get_name_addr are non-NULL).
                 */
                if (ddi_get_name_addr(dip) == NULL)
                        return (DDI_FAILURE);

                fullpath = kmem_alloc(MAXPATHLEN, KM_SLEEP);
                (void) ddi_pathname(dip, fullpath);
                pathlen = strlen(fullpath) + 1;
                path = kmem_alloc(pathlen, KM_SLEEP);
                bcopy(fullpath, path, pathlen);
                kmem_free(fullpath, MAXPATHLEN);
        }

        DEVID_LOG_REG(("register", devid, path));

        new_nvp = kmem_zalloc(sizeof (nvp_devid_t), KM_SLEEP);
        new_devid_size = ddi_devid_sizeof(devid);
        new_devid = kmem_alloc(new_devid_size, KM_SLEEP);
        (void) bcopy(devid, new_devid, new_devid_size);

        rw_enter(nvf_lock(dcfd_handle), RW_WRITER);

        listp = nvf_list(dcfd_handle);
        for (np = list_head(listp); np; np = list_next(listp, np)) {
                if (strcmp(path, np->nvp_devpath) == 0) {
                        DEVID_DEBUG2((CE_CONT,
                            "register: %s path match\n", path));
                        if (np->nvp_devid == NULL) {
replace:                        np->nvp_devid = new_devid;
                                np->nvp_flags |=
                                    NVP_DEVID_DIP | NVP_DEVID_REGISTERED;
                                np->nvp_dip = dip;
                                if (!devid_cache_write_disable) {
                                        nvf_mark_dirty(dcfd_handle);
                                        is_dirty = 1;
                                }
                                rw_exit(nvf_lock(dcfd_handle));
                                kmem_free(new_nvp, sizeof (nvp_devid_t));
                                kmem_free(path, pathlen);
                                goto exit;
                        }
                        if (ddi_devid_valid(np->nvp_devid) != DDI_SUCCESS) {
                                /* replace invalid devid */
                                free_devid = np->nvp_devid;
                                goto replace;
                        }
                        /*
                         * We're registering an already-cached path
                         * Does the device's devid match the cache?
                         */
                        if (ddi_devid_compare(devid, np->nvp_devid) != 0) {
                                DEVID_DEBUG((CE_CONT, "devid register: "
                                    "devid %s does not match\n", path));
                                /*
                                 * We do not expect devids to change, log it.
                                 */
                                char *devid_stored =
                                    ddi_devid_str_encode(np->nvp_devid, NULL);
                                char *devid_new =
                                    ddi_devid_str_encode(devid, NULL);

                                cmn_err(CE_CONT, "devid register: devid for "
                                    "%s does not match. stored: %s, new: %s.",
                                    path, devid_stored, devid_new);

                                ddi_devid_str_free(devid_stored);
                                ddi_devid_str_free(devid_new);

                                /*
                                 * Replace cached devid for this path
                                 * with newly registered devid.  A devid
                                 * may map to multiple paths but one path
                                 * should only map to one devid.
                                 */
                                devid_nvp_unlink_and_free(dcfd_handle, np);
                                np = NULL;
                                break;
                        } else {
                                DEVID_DEBUG2((CE_CONT,
                                    "devid register: %s devid match\n", path));
                                np->nvp_flags |=
                                    NVP_DEVID_DIP | NVP_DEVID_REGISTERED;
                                np->nvp_dip = dip;
                                rw_exit(nvf_lock(dcfd_handle));
                                kmem_free(new_nvp, sizeof (nvp_devid_t));
                                kmem_free(path, pathlen);
                                kmem_free(new_devid, new_devid_size);
                                return (DDI_SUCCESS);
                        }
                }
        }

        /*
         * Add newly registered devid to the cache
         */
        ASSERT(np == NULL);

        new_nvp->nvp_devpath = path;
        new_nvp->nvp_flags = NVP_DEVID_DIP | NVP_DEVID_REGISTERED;
        new_nvp->nvp_dip = dip;
        new_nvp->nvp_devid = new_devid;

        if (!devid_cache_write_disable) {
                is_dirty = 1;
                nvf_mark_dirty(dcfd_handle);
        }
        list_insert_tail(nvf_list(dcfd_handle), new_nvp);

        rw_exit(nvf_lock(dcfd_handle));

exit:
        if (free_devid)
                kmem_free(free_devid, ddi_devid_sizeof(free_devid));

        if (is_dirty)
                nvf_wake_daemon();

        return (DDI_SUCCESS);
}

int
e_devid_cache_register(dev_info_t *dip, ddi_devid_t devid)
{
        return (e_devid_cache_register_cmn(dip, devid, NULL));
}

/*
 * Unregister a device's devid; the devinfo may hit on multiple entries
 * arising from both pHCI and vHCI paths.
 * Called as an instance detachs.
 * Invalidate the devid's devinfo reference.
 * Devid-path remains in the cache.
 */

void
e_devid_cache_unregister(dev_info_t *dip)
{
        nvp_devid_t *np;
        list_t *listp;

        rw_enter(nvf_lock(dcfd_handle), RW_WRITER);

        listp = nvf_list(dcfd_handle);
        for (np = list_head(listp); np; np = list_next(listp, np)) {
                if (np->nvp_devid == NULL)
                        continue;
                if ((np->nvp_flags & NVP_DEVID_DIP) && np->nvp_dip == dip) {
                        DEVID_LOG_UNREG((CE_CONT,
                            "unregister: %s\n", np->nvp_devpath));
                        np->nvp_flags &= ~NVP_DEVID_DIP;
                        np->nvp_dip = NULL;
                }
        }

        rw_exit(nvf_lock(dcfd_handle));
}

int
e_devid_cache_pathinfo(mdi_pathinfo_t *pip, ddi_devid_t devid)
{
        char *path = mdi_pi_pathname(pip);

        return (e_devid_cache_register_cmn(mdi_pi_get_client(pip), devid,
            path));
}

/*
 * Purge devid cache of stale devids
 */
void
devid_cache_cleanup(void)
{
        nvp_devid_t *np, *next;
        list_t *listp;
        int is_dirty = 0;

        rw_enter(nvf_lock(dcfd_handle), RW_WRITER);

        listp = nvf_list(dcfd_handle);
        for (np = list_head(listp); np; np = next) {
                next = list_next(listp, np);
                if (np->nvp_devid == NULL)
                        continue;
                if ((np->nvp_flags & NVP_DEVID_REGISTERED) == 0) {
                        DEVID_LOG_REMOVE((CE_CONT,
                            "cleanup: %s\n", np->nvp_devpath));
                        if (!devid_cache_write_disable) {
                                nvf_mark_dirty(dcfd_handle);
                                is_dirty = 0;
                        }
                        devid_nvp_unlink_and_free(dcfd_handle, np);
                }
        }

        rw_exit(nvf_lock(dcfd_handle));

        if (is_dirty)
                nvf_wake_daemon();
}


/*
 * Build a list of dev_t's for a device/devid
 *
 * The effect of this function is cumulative, adding dev_t's
 * for the device to the list of all dev_t's for a given
 * devid.
 */
static void
e_devid_minor_to_devlist(
        dev_info_t      *dip,
        const char      *minor_name,
        int             ndevts_alloced,
        int             *devtcntp,
        dev_t           *devtsp)
{
        struct ddi_minor_data   *dmdp;
        int                     minor_all = 0;
        int                     ndevts = *devtcntp;

        ASSERT(i_ddi_devi_attached(dip));

        /* are we looking for a set of minor nodes? */
        if ((minor_name == DEVID_MINOR_NAME_ALL) ||
            (minor_name == DEVID_MINOR_NAME_ALL_CHR) ||
            (minor_name == DEVID_MINOR_NAME_ALL_BLK))
                minor_all = 1;

        /* Find matching minor names */
        ndi_devi_enter(dip);
        for (dmdp = DEVI(dip)->devi_minor; dmdp; dmdp = dmdp->next) {

                /* Skip non-minors, and non matching minor names */
                if ((dmdp->type != DDM_MINOR) || ((minor_all == 0) &&
                    strcmp(dmdp->ddm_name, minor_name)))
                        continue;

                /* filter out minor_all mismatches */
                if (minor_all &&
                    (((minor_name == DEVID_MINOR_NAME_ALL_CHR) &&
                    (dmdp->ddm_spec_type != S_IFCHR)) ||
                    ((minor_name == DEVID_MINOR_NAME_ALL_BLK) &&
                    (dmdp->ddm_spec_type != S_IFBLK))))
                        continue;

                if (ndevts < ndevts_alloced)
                        devtsp[ndevts] = dmdp->ddm_dev;
                ndevts++;
        }
        ndi_devi_exit(dip);

        *devtcntp = ndevts;
}

/*
 * Search for cached entries matching a devid
 * Return two lists:
 *      a list of dev_info nodes, for those devices in the attached state
 *      a list of pathnames whose instances registered the given devid
 * If the lists passed in are not sufficient to return the matching
 * references, return the size of lists required.
 * The dev_info nodes are returned with a hold that the caller must release.
 */
static int
e_devid_cache_devi_path_lists(ddi_devid_t devid, int retmax,
    int *retndevis, dev_info_t **retdevis, int *retnpaths, char **retpaths)
{
        nvp_devid_t *np;
        int ndevis, npaths;
        dev_info_t *dip, *pdip;
        int maxdevis = 0;
        int maxpaths = 0;
        list_t *listp;

        ndevis = 0;
        npaths = 0;
        listp = nvf_list(dcfd_handle);
        for (np = list_head(listp); np; np = list_next(listp, np)) {
                if (np->nvp_devid == NULL)
                        continue;
                if (ddi_devid_valid(np->nvp_devid) != DDI_SUCCESS) {
                        DEVIDERR((CE_CONT,
                            "find: invalid devid %s\n",
                            np->nvp_devpath));
                        continue;
                }
                if (ddi_devid_compare(devid, np->nvp_devid) == 0) {
                        DEVID_DEBUG2((CE_CONT,
                            "find: devid match: %s 0x%x\n",
                            np->nvp_devpath, np->nvp_flags));
                        DEVID_LOG_MATCH(("find", devid, np->nvp_devpath));
                        DEVID_LOG_PATHS((CE_CONT, "%s\n", np->nvp_devpath));

                        /*
                         * Check if we have a cached devinfo reference for this
                         * devid.  Place a hold on it to prevent detach
                         * Otherwise, use the path instead.
                         * Note: returns with a hold on each dev_info
                         * node in the list.
                         */
                        dip = NULL;
                        if (np->nvp_flags & NVP_DEVID_DIP) {
                                pdip = ddi_get_parent(np->nvp_dip);
                                if (ndi_devi_tryenter(pdip)) {
                                        dip = np->nvp_dip;
                                        ndi_hold_devi(dip);
                                        ndi_devi_exit(pdip);
                                        ASSERT(!DEVI_IS_ATTACHING(dip));
                                        ASSERT(!DEVI_IS_DETACHING(dip));
                                } else {
                                        DEVID_LOG_DETACH((CE_CONT,
                                            "may be detaching: %s\n",
                                            np->nvp_devpath));
                                }
                        }

                        if (dip) {
                                if (ndevis < retmax) {
                                        retdevis[ndevis++] = dip;
                                } else {
                                        ndi_rele_devi(dip);
                                }
                                maxdevis++;
                        } else {
                                if (npaths < retmax)
                                        retpaths[npaths++] = np->nvp_devpath;
                                maxpaths++;
                        }
                }
        }

        *retndevis = ndevis;
        *retnpaths = npaths;
        return (maxdevis > maxpaths ? maxdevis : maxpaths);
}


/*
 * Search the devid cache, returning dev_t list for all
 * device paths mapping to the device identified by the
 * given devid.
 *
 * Primary interface used by ddi_lyr_devid_to_devlist()
 */
int
e_devid_cache_to_devt_list(ddi_devid_t devid, const char *minor_name,
    int *retndevts, dev_t **retdevts)
{
        char            *path, **paths;
        int             i, j, n;
        dev_t           *devts, *udevts;
        dev_t           tdevt;
        int             ndevts, undevts, ndevts_alloced;
        dev_info_t      *devi, **devis;
        int             ndevis, npaths, nalloced;
        ddi_devid_t     match_devid;

        DEVID_LOG_FIND(("find", devid, NULL));

        ASSERT(ddi_devid_valid(devid) == DDI_SUCCESS);
        if (ddi_devid_valid(devid) != DDI_SUCCESS) {
                DEVID_LOG_ERR(("invalid devid", devid, NULL));
                return (DDI_FAILURE);
        }

        nalloced = 128;

        for (;;) {
                paths = kmem_zalloc(nalloced * sizeof (char *), KM_SLEEP);
                devis = kmem_zalloc(nalloced * sizeof (dev_info_t *), KM_SLEEP);

                rw_enter(nvf_lock(dcfd_handle), RW_READER);
                n = e_devid_cache_devi_path_lists(devid, nalloced,
                    &ndevis, devis, &npaths, paths);
                if (n <= nalloced)
                        break;
                rw_exit(nvf_lock(dcfd_handle));
                for (i = 0; i < ndevis; i++)
                        ndi_rele_devi(devis[i]);
                kmem_free(paths, nalloced * sizeof (char *));
                kmem_free(devis, nalloced * sizeof (dev_info_t *));
                nalloced = n + 128;
        }

        for (i = 0; i < npaths; i++) {
                path = i_ddi_strdup(paths[i], KM_SLEEP);
                paths[i] = path;
        }
        rw_exit(nvf_lock(dcfd_handle));

        if (ndevis == 0 && npaths == 0) {
                DEVID_LOG_ERR(("no devid found", devid, NULL));
                kmem_free(paths, nalloced * sizeof (char *));
                kmem_free(devis, nalloced * sizeof (dev_info_t *));
                return (DDI_FAILURE);
        }

        ndevts_alloced = 128;
restart:
        ndevts = 0;
        devts = kmem_alloc(ndevts_alloced * sizeof (dev_t), KM_SLEEP);
        for (i = 0; i < ndevis; i++) {
                ASSERT(!DEVI_IS_ATTACHING(devis[i]));
                ASSERT(!DEVI_IS_DETACHING(devis[i]));
                e_devid_minor_to_devlist(devis[i], minor_name,
                    ndevts_alloced, &ndevts, devts);
                if (ndevts > ndevts_alloced) {
                        kmem_free(devts, ndevts_alloced * sizeof (dev_t));
                        ndevts_alloced += 128;
                        goto restart;
                }
        }
        for (i = 0; i < npaths; i++) {
                DEVID_LOG_LOOKUP((CE_CONT, "lookup %s\n", paths[i]));
                devi = e_ddi_hold_devi_by_path(paths[i], 0);
                if (devi == NULL) {
                        DEVID_LOG_STALE(("stale device reference",
                            devid, paths[i]));
                        continue;
                }
                /*
                 * Verify the newly attached device registered a matching devid
                 */
                if (i_ddi_devi_get_devid(DDI_DEV_T_ANY, devi,
                    &match_devid) != DDI_SUCCESS) {
                        DEVIDERR((CE_CONT,
                            "%s: no devid registered on attach\n",
                            paths[i]));
                        ddi_release_devi(devi);
                        continue;
                }

                if (ddi_devid_compare(devid, match_devid) != 0) {
                        DEVID_LOG_STALE(("new devid registered",
                            devid, paths[i]));
                        ddi_release_devi(devi);
                        ddi_devid_free(match_devid);
                        continue;
                }
                ddi_devid_free(match_devid);

                e_devid_minor_to_devlist(devi, minor_name,
                    ndevts_alloced, &ndevts, devts);
                ddi_release_devi(devi);
                if (ndevts > ndevts_alloced) {
                        kmem_free(devts,
                            ndevts_alloced * sizeof (dev_t));
                        ndevts_alloced += 128;
                        goto restart;
                }
        }

        /* drop hold from e_devid_cache_devi_path_lists */
        for (i = 0; i < ndevis; i++) {
                ndi_rele_devi(devis[i]);
        }
        for (i = 0; i < npaths; i++) {
                kmem_free(paths[i], strlen(paths[i]) + 1);
        }
        kmem_free(paths, nalloced * sizeof (char *));
        kmem_free(devis, nalloced * sizeof (dev_info_t *));

        if (ndevts == 0) {
                DEVID_LOG_ERR(("no devid found", devid, NULL));
                kmem_free(devts, ndevts_alloced * sizeof (dev_t));
                return (DDI_FAILURE);
        }

        /*
         * Build the final list of sorted dev_t's with duplicates collapsed so
         * returned results are consistent. This prevents implementation
         * artifacts from causing unnecessary changes in SVM namespace.
         */
        /* bubble sort */
        for (i = 0; i < (ndevts - 1); i++) {
                for (j = 0; j < ((ndevts - 1) - i); j++) {
                        if (devts[j + 1] < devts[j]) {
                                tdevt = devts[j];
                                devts[j] = devts[j + 1];
                                devts[j + 1] = tdevt;
                        }
                }
        }

        /* determine number of unique values */
        for (undevts = ndevts, i = 1; i < ndevts; i++) {
                if (devts[i - 1] == devts[i])
                        undevts--;
        }

        /* allocate unique */
        udevts = kmem_alloc(undevts * sizeof (dev_t), KM_SLEEP);

        /* copy unique */
        udevts[0] = devts[0];
        for (i = 1, j = 1; i < ndevts; i++) {
                if (devts[i - 1] != devts[i])
                        udevts[j++] = devts[i];
        }
        ASSERT(j == undevts);

        kmem_free(devts, ndevts_alloced * sizeof (dev_t));

        *retndevts = undevts;
        *retdevts = udevts;

        return (DDI_SUCCESS);
}

void
e_devid_cache_free_devt_list(int ndevts, dev_t *devt_list)
{
        kmem_free(devt_list, ndevts * sizeof (dev_t *));
}

/*
 * If given a full path and NULL ua, search for a cache entry
 * whose path matches the full path.  On a cache hit duplicate the
 * devid of the matched entry into the given devid (caller
 * must free);  nodenamebuf is not touched for this usage.
 *
 * Given a path and a non-NULL unit address, search the cache for any entry
 * matching "<path>/%@<unit-address>" where '%' is a wildcard meaning
 * any node name.  The path should not end a '/'.  On a cache hit
 * duplicate the devid as before (caller must free) and copy into
 * the caller-provided nodenamebuf (if not NULL) the nodename of the
 * matched entry.
 *
 * We must not make use of nvp_dip since that may be NULL for cached
 * entries that are not present in the current tree.
 */
int
e_devid_cache_path_to_devid(char *path, char *ua,
    char *nodenamebuf, ddi_devid_t *devidp)
{
        size_t pathlen, ualen;
        int rv = DDI_FAILURE;
        nvp_devid_t *np;
        list_t *listp;
        char *cand;

        if (path == NULL || *path == '\0' || (ua && *ua == '\0') ||
            devidp == NULL)
                return (DDI_FAILURE);

        *devidp = NULL;

        pathlen = 0;
        ualen = 0;
        if (ua) {
                pathlen = strlen(path);
                ualen = strlen(ua);
        }

        rw_enter(nvf_lock(dcfd_handle), RW_READER);

        listp = nvf_list(dcfd_handle);
        for (np = list_head(listp); np; np = list_next(listp, np)) {
                size_t nodelen, candlen, n;
                ddi_devid_t devid_dup;
                char *uasep, *node;

                if (np->nvp_devid == NULL)
                        continue;

                if (ddi_devid_valid(np->nvp_devid) != DDI_SUCCESS) {
                        DEVIDERR((CE_CONT,
                            "pathsearch: invalid devid %s\n",
                            np->nvp_devpath));
                        continue;
                }

                cand = np->nvp_devpath;         /* candidate path */

                /* If a full pathname was provided the compare is easy */
                if (ua == NULL) {
                        if (strcmp(cand, path) == 0)
                                goto match;
                        else
                                continue;
                }

                /*
                 * The compare for initial path plus ua and unknown nodename
                 * is trickier.
                 *
                 * Does the initial path component match 'path'?
                 */
                if (strncmp(path, cand, pathlen) != 0)
                        continue;

                candlen = strlen(cand);

                /*
                 * The next character must be a '/' and there must be no
                 * further '/' thereafter.  Begin by checking that the
                 * candidate is long enough to include at mininum a
                 * "/<nodename>@<ua>" after the initial portion already
                 * matched assuming a nodename length of 1.
                 */
                if (candlen < pathlen + 1 + 1 + 1 + ualen ||
                    cand[pathlen] != '/' ||
                    strchr(cand + pathlen + 1, '/') != NULL)
                        continue;

                node = cand + pathlen + 1;      /* <node>@<ua> string */

                /*
                 * Find the '@' before the unit address.  Check for
                 * unit address match.
                 */
                if ((uasep = strchr(node, '@')) == NULL)
                        continue;

                /*
                 * Check we still have enough length and that ua matches
                 */
                nodelen = (uintptr_t)uasep - (uintptr_t)node;
                if (candlen < pathlen + 1 + nodelen + 1 + ualen ||
                    strncmp(ua, uasep + 1, ualen) != 0)
                        continue;
match:
                n = ddi_devid_sizeof(np->nvp_devid);
                devid_dup = kmem_alloc(n, KM_SLEEP);    /* caller must free */
                (void) bcopy(np->nvp_devid, devid_dup, n);
                *devidp = devid_dup;

                if (ua && nodenamebuf) {
                        (void) strncpy(nodenamebuf, node, nodelen);
                        nodenamebuf[nodelen] = '\0';
                }

                rv = DDI_SUCCESS;
                break;
        }

        rw_exit(nvf_lock(dcfd_handle));

        return (rv);
}

#ifdef  DEBUG
static void
devid_log(char *fmt, ddi_devid_t devid, char *path)
{
        char *devidstr = ddi_devid_str_encode(devid, NULL);
        if (path) {
                cmn_err(CE_CONT, "%s: %s %s\n", fmt, path, devidstr);
        } else {
                cmn_err(CE_CONT, "%s: %s\n", fmt, devidstr);
        }
        ddi_devid_str_free(devidstr);
}
#endif  /* DEBUG */