devctl.c revision facf4a8d7b59fde89a8662b4f4c73a758e6c402c
/*
* 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
*/
/*
* Copyright 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <sys/instance.h>
#include <sys/ddi_impldefs.h>
#include <sys/ndi_impldefs.h>
#include <sys/pathname.h>
#include <sys/sysevent.h>
#include <sys/devctl_impl.h>
/*
* Tunables - see devctl_impl.h for more thorough explanation
*/
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 sdev_cache_read_disable = 0;
int sdev_cache_write_disable = 0;
int kfio_report_error = 0; /* kernel file i/o operations */
int devid_report_error = 0; /* devid cache operations */
/*
* State to manage discovery
*/
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;
static void devid_nvp_free(nvp_list_t *);
static void sdev_nvp_free(nvp_list_t *);
/*
*/
static nvfd_t devid_cache_fd = {
"/etc/devices/devid_cache",
devid_nvp2nvl, /* nvf_nvp2nvl */
devid_nvl2nvp, /* nvf_nvl2nvp */
devid_nvp_free, /* nvf_nvp_free */
NULL, /* nvf_write_complete */
};
static nvfd_t sdev_cache_fd = {
"/etc/devices/devname_cache",
sdev_nvp2nvl, /* nvf_nvp2nvl */
sdev_nvl2nvp, /* nvf_nvl2nvp */
sdev_nvp_free, /* nvf_nvp_free */
NULL, /* nvf_write_complete */
};
};
extern int modrootloaded;
extern void mdi_read_devices_files(void);
extern void mdi_clean_vhcache(void);
#ifdef DEBUG
int nvp_devid_debug = 0;
int nvpdaemon_debug = 0;
int kfio_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 */
void
i_ddi_devices_init(void)
{
}
static uint16_t
{
int64_t n;
if ((buflen & 0x01) != 0) {
buflen--;
}
n = buflen / 2;
while (n-- > 0)
cksum ^= *p++;
return (cksum);
}
int
{
char *buf;
int rval;
int n;
char c;
*ret_nvlist = NULL;
return (ENOENT);
}
offset = 0;
if (n != sizeof (hdr)) {
if (n < 0) {
"error reading header: %s\n", filename));
return (EIO);
} else if (n == 0) {
} else {
"header size incorrect: %s\n", filename));
}
return (EINVAL);
}
offset += n;
hdr.nvpf_hdr_chksum = 0;
"%s: checksum error "
"(actual 0x%x, expected 0x%x)\n",
}
"%s: header information incorrect", filename));
return (EINVAL);
}
if (n < 0) {
} else {
}
return (EINVAL);
}
offset += n;
if (rval > 0) {
return (EINVAL);
}
"%s: checksum error (actual 0x%x, expected 0x%x)\n",
return (EINVAL);
}
if (rval != 0) {
return (EINVAL);
}
*ret_nvlist = nvl;
return (0);
}
static int
{
int rval;
if (rval != 0) {
return (rval);
}
return (0);
}
static int
{
int rval;
if (rval != 0) {
}
return (rval);
}
static int
{
int err;
ssize_t n;
if (err != 0) {
return (err);
}
*ret_n = n;
return (0);
}
static int
{
int err;
ssize_t n = 0;
for (;;) {
if (err) {
return (err);
}
if (resid == 0)
break;
return (ENOSPC);
}
}
*ret_n = n;
return (0);
}
static int
{
int rval;
if (rval != 0) {
}
}
if (rval != 0) {
}
} else {
}
return (rval);
}
static int
{
int rval;
}
return (rval);
}
int
{
char *buf;
char *nvbuf;
char *newname;
ssize_t n;
if (err != 0) {
return (err);
}
buflen += sizeof (nvpf_hdr_t);
/*
* To make it unlikely we suffer data loss, write
* data to the new temporary file. Once successful
* complete the transaction by renaming the new file
* to replace the previous.
*/
if (err) {
} else {
if (n != buflen) {
"%s: partial write %ld of %ld bytes\n",
"%s: filesystem may be full?\n", newname));
}
}
if (err == 0)
}
if (err != 0) {
newname));
}
}
} else {
}
if (err == 0) {
}
}
return (err);
}
static int
{
int err;
return (DDI_SUCCESS);
else {
return (DDI_FAILURE);
}
}
static void
{
if (dp->nvp_devpath)
}
static void
{
int i;
char **p;
if (dp->nvp_npaths > 0) {
for (i = 0; i < dp->nvp_npaths; i++, p++) {
}
dp->nvp_npaths * sizeof (char *));
dp->nvp_npaths * sizeof (int));
}
}
static void
{
nvp_list_t *np;
}
}
/*
* Free an nvp element in a list
*/
void
{
/* remove element at head */
if (next)
}
/* remove element at tail */
if (pv)
}
/* remove element in the middle, neither head nor tail */
}
}
void
{
} else {
}
}
/*
* Used to parse the nvlist format when reading
*/
static nvp_list_t *
{
int rval;
uint_t n;
/*
* check path for a devid
*/
if (rval == 0) {
} else {
}
}
}
/*
* Used to parse the nvlist format when reading
*/
static nvp_list_t *
{
char **strs;
int *cnts;
int rval, i;
/* name of the sublist must match what we created */
return (NULL);
}
if (rval) {
return (NULL);
}
for (i = 0; i < nstrs; i++) {
}
for (i = 0; i < nstrs; i++) {
}
if (rval == 0) {
for (i = 0; i < nstrs; i++) {
}
}
}
/*
* Convert a list of nvp_list_t's to a single nvlist
* Used when writing the nvlist file.
*/
static int
{
int rval;
if (rval != 0) {
return (DDI_FAILURE);
}
continue;
if (rval != 0) {
goto err;
}
if (rval == 0) {
} else {
"%s: nvlist add error %d (devid)\n",
goto err;
}
if (rval != 0) {
goto err;
}
}
return (DDI_SUCCESS);
err:
if (sub_nvl)
return (DDI_FAILURE);
}
/*
* Convert a list of nvp_list_t's to a single nvlist
* Used when writing the nvlist file.
*/
static int
{
int rval;
if (rval != 0) {
return (DDI_FAILURE);
}
if (rval != 0) {
goto err;
}
if (rval != 0) {
"%s: nvlist add error %d (sdev)\n",
goto err;
}
if (rval != 0) {
"%s: nvlist add error %d (sdev)\n",
goto err;
}
if (rval != 0) {
goto err;
}
}
return (DDI_SUCCESS);
err:
if (sub_nvl)
return (DDI_FAILURE);
}
/*
* Read a file in the nvlist format
* EIO - i/o error during read
* ENOENT - file not found
* EINVAL - file contents corrupted
*/
static int
{
char *name;
int rval;
nvp_list_t *np;
if (rval != 0)
return (rval);
switch (nvpair_type(nvp)) {
case DATA_TYPE_NVLIST:
if (rval != 0) {
"nvpair_value_nvlist error %s %d\n",
goto error;
}
/*
* convert nvlist for this device to
* an nvp_list_t struct
*/
if (np) {
} else {
}
}
break;
default:
goto error;
}
}
return (0);
if (nvp_list)
return (rval);
}
static int
{
int rval;
if (rval) {
switch (rval) {
case EIO:
break;
case ENOENT:
break;
case EINVAL:
default:
break;
}
}
return (rval);
}
/* for information possibly required to mount root */
void
i_ddi_read_devices_files(void)
{
if (devid_cache_read_disable == 0) {
(void) i_ddi_read_one_nvfile(dcfd);
}
}
/* may be done after root is mounted */
void
i_ddi_read_devname_file(void)
{
if (sdev_cache_read_disable == 0) {
(void) i_ddi_read_one_nvfile(sdevfd);
}
}
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;
}
#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 */
}
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;
int rval = DDI_SUCCESS;
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);
}
int
{
int new_devid_size;
int pathlen;
"register: %s path match\n", path));
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
*/
exit:
if (free_devid)
return (DDI_SUCCESS);
}
/*
* Unregister a device's devid
* Called as an instance detachs
* Invalidate the devid's devinfo reference
* Devid-path remains in the cache
*/
void
{
continue;
break;
}
}
}
void
e_devid_cache_cleanup(void)
{
continue;
}
}
if (NVF_IS_DIRTY(dcfd))
}
/*
*
* 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,
{
struct ddi_minor_data *dmdp;
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
{
}
/*
* Allow some delay from an update of the data before flushing
* to permit simultaneous updates of multiple changes.
* Changes in the data are expected to be bursty, ie
* reconfig boot or hot-plug of a new adapter.
*
* nvpflush_delay is in units of seconds.
* The data should be "quiet" for this interval before
* the repository update is triggered.
*
* nvpdaemon_idle_time is the number of seconds the
* daemon will sleep idle before exiting.
*/
#define NVPFLUSH_DELAY 10
#define NVPDAEMON_IDLE_TIME 60
static int nvpflush_delay = NVPFLUSH_DELAY;
static int nvpdaemon_idle_time = NVPDAEMON_IDLE_TIME;
static timeout_id_t nvpflush_id = 0;
static int nvpflush_timer_busy = 0;
static int nvpflush_daemon_active = 0;
static kthread_t *nvpflush_thr_id = 0;
static int do_nvpflush = 0;
static int nvpbusy = 0;
static kmutex_t nvpflush_lock;
static kcondvar_t nvpflush_cv;
static kthread_id_t nvpflush_thread;
static void nvpflush_daemon(void);
void
{
fd->nvf_write_complete = f;
}
void
{
}
static void
{
if (fd->nvf_write_complete) {
}
}
void
i_ddi_start_flush_daemon(void)
{
}
}
/*ARGSUSED*/
static void
nvpflush_timeout(void *arg)
{
if (nticks > 4) {
nvpflush_timer_busy = 1;
} else {
do_nvpflush = 1;
nvpflush_id = 0;
nvpflush_timer_busy = 0;
}
}
void
{
/*
* If the system isn't up yet
* don't even think about starting a flush.
*/
if (!i_ddi_io_initialized())
return;
if (nvpflush_daemon_active == 0) {
(void (*)())nvpflush_daemon,
}
if (nvpflush_timer_busy == 0) {
nvpflush_timer_busy = 1;
} else
}
static int
{
int rval = DDI_SUCCESS;
return (DDI_SUCCESS);
}
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
if (rval == DDI_FAILURE) {
if (NVF_IS_READONLY(nvfd)) {
rval = DDI_SUCCESS;
}
} else {
}
}
}
/*
* The file may need to be flushed again if the cached
* data was touched while writing the earlier contents.
*/
if (NVF_IS_DIRTY(nvfd))
rval = DDI_FAILURE;
}
return (rval);
}
static void
nvpflush_daemon(void)
{
int rval;
int i;
for (;;) {
while (do_nvpflush == 0) {
ddi_get_lbolt() +
if (clk == -1 &&
do_nvpflush == 0 && nvpflush_timer_busy == 0) {
/*
* Note that CALLB_CPR_EXIT calls mutex_exit()
* on the lock passed in to CALLB_CPR_INIT,
* so the lock must be held when invoking it.
*/
thread_exit();
}
}
nvpbusy = 1;
do_nvpflush = 0;
/*
* Try flushing what's dirty, reschedule if there's
* a failure or data gets marked as dirty again.
*/
for (i = 0; i < NCACHEFDS; i++) {
if (NVF_IS_DIRTY(cachefds[i])) {
"nvpdaemon: flush %s\n",
if (rval != DDI_SUCCESS ||
NVF_IS_DIRTY(cachefds[i])) {
"nvpdaemon: %s dirty again\n",
} else {
}
} else {
"nvpdaemon: not dirty %s\n",
}
}
nvpbusy = 0;
}
}
void
{
}
#ifdef DEBUG
static void
{
if (path) {
} else {
}
}
#endif /* DEBUG */