/*
* 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
* 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
*/
/*
*/
#include <sys/instance.h>
#include <sys/ddi_impldefs.h>
#include <sys/ndi_impldefs.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(1M) 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(1M) 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(1M).
*
* 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_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;
#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
*/
"/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
*/
/*
* Initialize devid cache file management
*/
void
devid_cache_init(void)
{
}
/*
* Read and initialize the devid cache from the persistent store
*/
void
devid_cache_read(void)
{
if (!devid_cache_read_disable) {
(void) nvf_read_file(dcfd_handle);
}
}
static void
{
if (dp->nvp_devpath)
}
static void
{
}
}
/*
* Free an nvp element in a list
*/
static void
{
}
/*
* Used to parse the nvlist format when reading
*/
static int
{
int rval;
uint_t n;
/*
* check path for a devid
*/
if (rval == 0) {
} else {
"%s: invalid devid\n", name));
}
} else {
"%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
{
int rval;
if (rval != 0) {
nvf_error("%s: nvlist alloc error %d\n",
return (DDI_FAILURE);
}
continue;
if (rval != 0) {
nvf_error("%s: nvlist alloc error %d\n",
goto err;
}
if (rval == 0) {
} else {
"%s: nvlist add error %d (devid)\n",
goto err;
}
if (rval != 0) {
nvf_error("%s: nvlist add error %d (sublist)\n",
goto err;
}
}
return (DDI_SUCCESS);
err:
return (DDI_FAILURE);
}
static int
e_devid_do_discovery(void)
{
if (i_ddi_io_initialized() == 0) {
if (devid_discovery_boot > 0) {
return (1);
}
} else {
if (devid_discovery_postboot_always > 0)
return (1);
if (devid_discovery_postboot > 0) {
return (1);
}
if (devid_discovery_secs > 0) {
if ((ddi_get_lbolt() - devid_last_discovery) >
return (1);
}
}
}
return (0);
}
static void
{
"devid_discovery: ddi_hold_installed_driver %d\n", major));
return;
}
/* legacy support - see below */
#define N_DRIVERS_TO_HOLD \
(sizeof (e_ddi_devid_hold_driver_list) / sizeof (char *))
static void
{
char **drvp;
int i;
/* Count non-null bytes */
for (i = 0; i < DEVID_HINT_SIZE; i++)
break;
/* Make a copy of the driver hint */
hint[i] = '\0';
/* search for the devid using the hint driver */
if (hint_major != DDI_MAJOR_T_NONE) {
}
/*
* search for the devid with each driver declaring
* itself as a devid registrant.
*/
if (major == hint_major)
continue;
}
}
/*
* Legacy support: may be removed once an upgrade mechanism
* for driver conf files is available.
*/
for (i = 0; i < N_DRIVERS_TO_HOLD; i++, drvp++) {
}
}
}
/*
* Return success if discovery was attempted, to indicate
* that the desired device may now be available.
*/
int
{
int flags;
if (devid_discovery_busy) {
while (devid_discovery_busy) {
}
} else if (e_devid_do_discovery()) {
devid_discovery_busy = 1;
if (i_ddi_io_initialized() == 0) {
} else {
"devid_discovery: ndi_devi_config\n"));
if (i_ddi_io_initialized())
}
devid_discovery_busy = 0;
if (devid_discovery_secs > 0)
} else {
rval = DDI_FAILURE;
}
return (rval);
}
/*
* As part of registering a devid for a device,
* 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
* of the original owner.
*/
static int
{
int new_devid_size;
int pathlen;
int is_dirty = 0;
if (devpath) {
} 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).
*/
return (DDI_FAILURE);
}
"register: %s path match\n", path));
if (!devid_cache_write_disable) {
is_dirty = 1;
}
goto exit;
}
/* replace invalid devid */
goto replace;
}
/*
* We're registering an already-cached path
* Does the device's devid match the cache?
*/
"devid %s does not match\n", path));
/*
* 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.
*/
break;
} else {
"devid register: %s devid match\n", path));
return (DDI_SUCCESS);
}
}
}
/*
* Add newly registered devid to the cache
*/
if (!devid_cache_write_disable) {
is_dirty = 1;
}
exit:
if (free_devid)
if (is_dirty)
return (DDI_SUCCESS);
}
int
{
}
/*
* 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
{
continue;
}
}
}
int
{
path));
}
/*
* Purge devid cache of stale devids
*/
void
devid_cache_cleanup(void)
{
int is_dirty = 0;
continue;
if (!devid_cache_write_disable) {
is_dirty = 0;
}
}
}
if (is_dirty)
}
/*
*
* 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
char *minor_name,
int ndevts_alloced,
int *devtcntp,
{
int circ;
int minor_all = 0;
/* are we looking for a set of minor nodes? */
if ((minor_name == DEVID_MINOR_NAME_ALL) ||
(minor_name == DEVID_MINOR_NAME_ALL_CHR) ||
minor_all = 1;
/* Find matching minor names */
/* Skip non-minors, and non matching minor names */
continue;
/* filter out minor_all mismatches */
if (minor_all &&
(((minor_name == DEVID_MINOR_NAME_ALL_CHR) &&
((minor_name == DEVID_MINOR_NAME_ALL_BLK) &&
continue;
if (ndevts < ndevts_alloced)
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
{
int circ;
int maxdevis = 0;
int maxpaths = 0;
ndevis = 0;
npaths = 0;
continue;
"find: invalid devid %s\n",
np->nvp_devpath));
continue;
}
"find: devid match: %s 0x%x\n",
/*
* 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.
*/
} else {
"may be detaching: %s\n",
np->nvp_devpath));
}
}
if (dip) {
} else {
}
maxdevis++;
} else {
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
{
int i, j, n;
return (DDI_FAILURE);
}
nalloced = 128;
for (;;) {
if (n <= nalloced)
break;
for (i = 0; i < ndevis; i++)
ndi_rele_devi(devis[i]);
nalloced = n + 128;
}
for (i = 0; i < npaths; i++) {
}
return (DDI_FAILURE);
}
ndevts_alloced = 128;
ndevts = 0;
for (i = 0; i < ndevis; i++) {
if (ndevts > ndevts_alloced) {
ndevts_alloced += 128;
goto restart;
}
}
for (i = 0; i < npaths; i++) {
DEVID_LOG_STALE(("stale device reference",
continue;
}
/*
* Verify the newly attached device registered a matching devid
*/
&match_devid) != DDI_SUCCESS) {
"%s: no devid registered on attach\n",
paths[i]));
continue;
}
DEVID_LOG_STALE(("new devid registered",
continue;
}
if (ndevts > ndevts_alloced) {
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++) {
}
if (ndevts == 0) {
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++) {
}
}
}
/* determine number of unique values */
undevts--;
}
/* allocate unique */
/* copy unique */
}
return (DDI_SUCCESS);
}
void
{
}
/*
* 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
{
char *cand;
return (DDI_FAILURE);
if (ua) {
}
continue;
"pathsearch: invalid devid %s\n",
np->nvp_devpath));
continue;
}
/* If a full pathname was provided the compare is easy */
goto match;
else
continue;
}
/*
* The compare for initial path plus ua and unknown nodename
* is trickier.
*
* Does the initial path component match 'path'?
*/
continue;
/*
* 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.
*/
continue;
/*
* Find the '@' before the unit address. Check for
* unit address match.
*/
continue;
/*
* Check we still have enough length and that ua matches
*/
continue;
if (ua && nodenamebuf) {
}
rv = DDI_SUCCESS;
break;
}
return (rv);
}
#ifdef DEBUG
static void
{
if (path) {
} else {
}
}
#endif /* DEBUG */