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
* 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 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#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/ddi_impldefs.h>
#include <sys/ndi_impldefs.h>
#include <sys/modctl.h>
#include <sys/dacf.h>
#include <sys/promif.h>
#include <sys/cpuvar.h>
#include <sys/pathname.h>
#include <sys/taskq.h>
#include <sys/sysevent.h>
#include <sys/sunmdi.h>
#include <sys/stream.h>
#include <sys/strsubr.h>
#include <sys/fs/snode.h>
#include <sys/fs/dv_node.h>
#include <sys/kobj.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 int devid_nvp2nvl(nvfd_t *, nvlist_t **);
static nvp_list_t *devid_nvl2nvp(nvlist_t *, char *);
static void devid_nvp_free(nvp_list_t *);
static int sdev_nvp2nvl(nvfd_t *, nvlist_t **);
static nvp_list_t *sdev_nvl2nvp(nvlist_t *, char *);
static void sdev_nvp_free(nvp_list_t *);
/*
* Descriptors for the /etc/devices cache files
*/
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 */
0, NULL, NULL, 0
};
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 */
0, NULL, NULL, 0
};
static nvfd_t *dcfd = &devid_cache_fd;
nvfd_t *sdevfd = &sdev_cache_fd;
static nvfd_t *cachefds[] = {
&devid_cache_fd,
&sdev_cache_fd
};
#define NCACHEFDS ((sizeof (cachefds)) / (sizeof (nvfd_t *)))
extern int modrootloaded;
extern struct bootops *bootops;
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)
{
dcfd->nvf_flags = 0;
dcfd->nvf_list = NULL;
dcfd->nvf_tail = NULL;
rw_init(&dcfd->nvf_lock, NULL, RW_DRIVER, NULL);
sdevfd->nvf_flags = 0;
sdevfd->nvf_list = NULL;
sdevfd->nvf_tail = NULL;
rw_init(&sdevfd->nvf_lock, NULL, RW_DRIVER, NULL);
mutex_init(&devid_discovery_mutex, NULL, MUTEX_DEFAULT, NULL);
cv_init(&devid_discovery_cv, NULL, CV_DRIVER, NULL);
}
static uint16_t
nvp_cksum(uchar_t *buf, int64_t buflen)
{
uint16_t cksum = 0;
uint16_t *p = (uint16_t *)buf;
int64_t n;
if ((buflen & 0x01) != 0) {
buflen--;
cksum = buf[buflen];
}
n = buflen / 2;
while (n-- > 0)
cksum ^= *p++;
return (cksum);
}
int
fread_nvlist(char *filename, nvlist_t **ret_nvlist)
{
struct _buf *file;
nvpf_hdr_t hdr;
char *buf;
nvlist_t *nvl;
int rval;
uint_t offset;
int n;
char c;
uint16_t cksum, hdrsum;
*ret_nvlist = NULL;
file = kobj_open_file(filename);
if (file == (struct _buf *)-1) {
KFDEBUG((CE_CONT, "cannot open file: %s\n", filename));
return (ENOENT);
}
offset = 0;
n = kobj_read_file(file, (char *)&hdr, sizeof (hdr), offset);
if (n != sizeof (hdr)) {
kobj_close_file(file);
if (n < 0) {
KFIOERR((CE_CONT,
"error reading header: %s\n", filename));
return (EIO);
} else if (n == 0) {
KFDEBUG((CE_CONT, "file empty: %s\n", filename));
} else {
KFIOERR((CE_CONT,
"header size incorrect: %s\n", filename));
}
return (EINVAL);
}
offset += n;
KFDEBUG2((CE_CONT, "nvpf_magic: 0x%x\n", hdr.nvpf_magic));
KFDEBUG2((CE_CONT, "nvpf_version: %d\n", hdr.nvpf_version));
KFDEBUG2((CE_CONT, "nvpf_size: %lld\n",
(longlong_t)hdr.nvpf_size));
KFDEBUG2((CE_CONT, "nvpf_hdr_chksum: 0x%x\n",
hdr.nvpf_hdr_chksum));
KFDEBUG2((CE_CONT, "nvpf_chksum: 0x%x\n", hdr.nvpf_chksum));
cksum = hdr.nvpf_hdr_chksum;
hdr.nvpf_hdr_chksum = 0;
hdrsum = nvp_cksum((uchar_t *)&hdr, sizeof (hdr));
if (hdr.nvpf_magic != NVPF_HDR_MAGIC ||
hdr.nvpf_version != NVPF_HDR_VERSION || hdrsum != cksum) {
kobj_close_file(file);
if (hdrsum != cksum) {
KFIOERR((CE_CONT,
"%s: checksum error "
"(actual 0x%x, expected 0x%x)\n",
filename, hdrsum, cksum));
}
KFIOERR((CE_CONT,
"%s: header information incorrect", filename));
return (EINVAL);
}
ASSERT(hdr.nvpf_size >= 0);
buf = kmem_alloc(hdr.nvpf_size, KM_SLEEP);
n = kobj_read_file(file, buf, hdr.nvpf_size, offset);
if (n != hdr.nvpf_size) {
kmem_free(buf, hdr.nvpf_size);
kobj_close_file(file);
if (n < 0) {
KFIOERR((CE_CONT, "%s: read error %d", filename, n));
} else {
KFIOERR((CE_CONT, "%s: incomplete read %d/%lld",
filename, n, (longlong_t)hdr.nvpf_size));
}
return (EINVAL);
}
offset += n;
rval = kobj_read_file(file, &c, 1, offset);
kobj_close_file(file);
if (rval > 0) {
KFIOERR((CE_CONT, "%s is larger than %lld\n",
filename, (longlong_t)hdr.nvpf_size));
kmem_free(buf, hdr.nvpf_size);
return (EINVAL);
}
cksum = nvp_cksum((uchar_t *)buf, hdr.nvpf_size);
if (hdr.nvpf_chksum != cksum) {
KFIOERR((CE_CONT,
"%s: checksum error (actual 0x%x, expected 0x%x)\n",
filename, hdr.nvpf_chksum, cksum));
kmem_free(buf, hdr.nvpf_size);
return (EINVAL);
}
nvl = NULL;
rval = nvlist_unpack(buf, hdr.nvpf_size, &nvl, 0);
if (rval != 0) {
KFIOERR((CE_CONT, "%s: error %d unpacking nvlist\n",
filename, rval));
kmem_free(buf, hdr.nvpf_size);
return (EINVAL);
}
kmem_free(buf, hdr.nvpf_size);
*ret_nvlist = nvl;
return (0);
}
static int
kfcreate(char *filename, kfile_t **kfilep)
{
kfile_t *fp;
int rval;
ASSERT(modrootloaded);
fp = kmem_alloc(sizeof (kfile_t), KM_SLEEP);
fp->kf_vnflags = FCREAT | FWRITE | FTRUNC;
fp->kf_fname = filename;
fp->kf_fpos = 0;
fp->kf_state = 0;
KFDEBUG((CE_CONT, "create: %s flags 0x%x\n",
filename, fp->kf_vnflags));
rval = vn_open(filename, UIO_SYSSPACE, fp->kf_vnflags,
0444, &fp->kf_vp, CRCREAT, 0);
if (rval != 0) {
kmem_free(fp, sizeof (kfile_t));
KFDEBUG((CE_CONT, "%s: create error %d\n",
filename, rval));
return (rval);
}
*kfilep = fp;
return (0);
}
static int
kfremove(char *filename)
{
int rval;
KFDEBUG((CE_CONT, "remove: %s\n", filename));
rval = vn_remove(filename, UIO_SYSSPACE, RMFILE);
if (rval != 0) {
KFDEBUG((CE_CONT, "%s: remove error %d\n",
filename, rval));
}
return (rval);
}
static int
kfread(kfile_t *fp, char *buf, ssize_t bufsiz, ssize_t *ret_n)
{
ssize_t resid;
int err;
ssize_t n;
ASSERT(modrootloaded);
if (fp->kf_state != 0)
return (fp->kf_state);
err = vn_rdwr(UIO_READ, fp->kf_vp, buf, bufsiz, fp->kf_fpos,
UIO_SYSSPACE, 0, (rlim64_t)0, kcred, &resid);
if (err != 0) {
KFDEBUG((CE_CONT, "%s: read error %d\n",
fp->kf_fname, err));
fp->kf_state = err;
return (err);
}
ASSERT(resid >= 0 && resid <= bufsiz);
n = bufsiz - resid;
KFDEBUG1((CE_CONT, "%s: read %ld bytes ok %ld bufsiz, %ld resid\n",
fp->kf_fname, n, bufsiz, resid));
fp->kf_fpos += n;
*ret_n = n;
return (0);
}
static int
kfwrite(kfile_t *fp, char *buf, ssize_t bufsiz, ssize_t *ret_n)
{
rlim64_t rlimit;
ssize_t resid;
int err;
ssize_t len;
ssize_t n = 0;
ASSERT(modrootloaded);
if (fp->kf_state != 0)
return (fp->kf_state);
len = bufsiz;
rlimit = bufsiz + 1;
for (;;) {
err = vn_rdwr(UIO_WRITE, fp->kf_vp, buf, len, fp->kf_fpos,
UIO_SYSSPACE, FSYNC, rlimit, kcred, &resid);
if (err) {
KFDEBUG((CE_CONT, "%s: write error %d\n",
fp->kf_fname, err));
fp->kf_state = err;
return (err);
}
KFDEBUG1((CE_CONT, "%s: write %ld bytes ok %ld resid\n",
fp->kf_fname, len-resid, resid));
ASSERT(resid >= 0 && resid <= len);
n += (len - resid);
if (resid == 0)
break;
if (resid == len) {
KFDEBUG((CE_CONT, "%s: filesystem full?\n",
fp->kf_fname));
fp->kf_state = ENOSPC;
return (ENOSPC);
}
len -= resid;
buf += len;
fp->kf_fpos += len;
len = resid;
}
ASSERT(n == bufsiz);
KFDEBUG1((CE_CONT, "%s: wrote %ld bytes ok\n", fp->kf_fname, n));
*ret_n = n;
return (0);
}
static int
kfclose(kfile_t *fp)
{
int rval;
KFDEBUG((CE_CONT, "close: %s\n", fp->kf_fname));
if ((fp->kf_vnflags & FWRITE) && fp->kf_state == 0) {
rval = VOP_FSYNC(fp->kf_vp, FSYNC, kcred);
if (rval != 0) {
KFIOERR((CE_CONT, "%s: sync error %d\n",
fp->kf_fname, rval));
}
KFDEBUG((CE_CONT, "%s: sync ok\n", fp->kf_fname));
}
rval = VOP_CLOSE(fp->kf_vp, fp->kf_vnflags, 1, (offset_t)0, kcred);
if (rval != 0) {
if (fp->kf_state == 0) {
KFIOERR((CE_CONT, "%s: close error %d\n",
fp->kf_fname, rval));
}
} else {
if (fp->kf_state == 0)
KFDEBUG((CE_CONT, "%s: close ok\n", fp->kf_fname));
}
VN_RELE(fp->kf_vp);
kmem_free(fp, sizeof (kfile_t));
return (rval);
}
static int
kfrename(char *oldname, char *newname)
{
int rval;
ASSERT(modrootloaded);
KFDEBUG((CE_CONT, "renaming %s to %s\n", oldname, newname));
if ((rval = vn_rename(oldname, newname, UIO_SYSSPACE)) != 0) {
KFDEBUG((CE_CONT, "rename %s to %s: %d\n",
oldname, newname, rval));
}
return (rval);
}
int
fwrite_nvlist(char *filename, nvlist_t *nvl)
{
char *buf;
char *nvbuf;
kfile_t *fp;
char *newname;
int len, err, err1;
size_t buflen;
ssize_t n;
ASSERT(modrootloaded);
nvbuf = NULL;
err = nvlist_pack(nvl, &nvbuf, &buflen, NV_ENCODE_NATIVE, 0);
if (err != 0) {
KFIOERR((CE_CONT, "%s: error %d packing nvlist\n",
filename, err));
return (err);
}
buf = kmem_alloc(sizeof (nvpf_hdr_t) + buflen, KM_SLEEP);
bzero(buf, sizeof (nvpf_hdr_t));
((nvpf_hdr_t *)buf)->nvpf_magic = NVPF_HDR_MAGIC;
((nvpf_hdr_t *)buf)->nvpf_version = NVPF_HDR_VERSION;
((nvpf_hdr_t *)buf)->nvpf_size = buflen;
((nvpf_hdr_t *)buf)->nvpf_chksum = nvp_cksum((uchar_t *)nvbuf, buflen);
((nvpf_hdr_t *)buf)->nvpf_hdr_chksum =
nvp_cksum((uchar_t *)buf, sizeof (nvpf_hdr_t));
bcopy(nvbuf, buf + sizeof (nvpf_hdr_t), buflen);
kmem_free(nvbuf, buflen);
buflen += sizeof (nvpf_hdr_t);
len = strlen(filename) + MAX_SUFFIX_LEN + 2;
newname = kmem_alloc(len, KM_SLEEP);
(void) sprintf(newname, "%s.%s",
filename, NEW_FILENAME_SUFFIX);
/*
* 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 = kfcreate(newname, &fp)) == 0) {
err = kfwrite(fp, buf, buflen, &n);
if (err) {
KFIOERR((CE_CONT, "%s: write error - %d\n",
newname, err));
} else {
if (n != buflen) {
KFIOERR((CE_CONT,
"%s: partial write %ld of %ld bytes\n",
newname, n, buflen));
KFIOERR((CE_CONT,
"%s: filesystem may be full?\n", newname));
err = EIO;
}
}
if ((err1 = kfclose(fp)) != 0) {
KFIOERR((CE_CONT, "%s: close error\n", newname));
if (err == 0)
err = err1;
}
if (err != 0) {
if (kfremove(newname) != 0) {
KFIOERR((CE_CONT, "%s: remove failed\n",
newname));
}
}
} else {
KFIOERR((CE_CONT, "%s: create failed - %d\n",
filename, err));
}
if (err == 0) {
if ((err = kfrename(newname, filename)) != 0) {
KFIOERR((CE_CONT, "%s: rename from %s failed\n",
newname, filename));
}
}
kmem_free(newname, len);
kmem_free(buf, buflen);
return (err);
}
static int
e_fwrite_nvlist(nvfd_t *nvfd, nvlist_t *nvl)
{
int err;
if ((err = fwrite_nvlist(nvfd->nvf_name, nvl)) == 0)
return (DDI_SUCCESS);
else {
if (err == EROFS)
NVF_MARK_READONLY(nvfd);
return (DDI_FAILURE);
}
}
static void
devid_nvp_free(nvp_list_t *np)
{
nvp_devid_t *dp = NVP2DEVID(np);
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
sdev_nvp_free(nvp_list_t *np)
{
nvp_devname_t *dp = NVP2DEVNAME(np);
int i;
char **p;
if (dp->nvp_npaths > 0) {
p = dp->nvp_paths;
for (i = 0; i < dp->nvp_npaths; i++, p++) {
kmem_free(*p, strlen(*p)+1);
}
kmem_free(dp->nvp_paths,
dp->nvp_npaths * sizeof (char *));
kmem_free(dp->nvp_expirecnts,
dp->nvp_npaths * sizeof (int));
}
kmem_free(dp, sizeof (nvp_devname_t));
}
static void
nvp_list_free(nvfd_t *nvf, nvp_list_t *nvp)
{
nvp_list_t *np;
nvp_list_t *next;
for (np = nvp; np; np = next) {
next = np->nvp_next;
(nvf->nvf_nvp_free)(np);
}
}
/*
* Free an nvp element in a list
*/
void
nfd_nvp_free_and_unlink(nvfd_t *nvf, nvp_list_t *np)
{
nvp_list_t *pv, *next;
pv = np->nvp_prev;
next = np->nvp_next;
(nvf->nvf_nvp_free)(np);
/* remove element at head */
if (pv == NULL) {
if (next)
next->nvp_prev = NULL;
nvf->nvf_list = next;
}
/* remove element at tail */
if (next == NULL) {
if (pv)
pv->nvp_next = NULL;
nvf->nvf_tail = pv;
}
/* remove element in the middle, neither head nor tail */
if (pv && next) {
pv->nvp_next = next;
next->nvp_prev = pv;
}
}
void
nfd_nvp_link(nvfd_t *nvf, nvp_list_t *np)
{
if (nvf->nvf_list == NULL) {
nvf->nvf_list = np;
} else {
nvf->nvf_tail->nvp_next = np;
}
np->nvp_next = NULL;
np->nvp_prev = nvf->nvf_tail;
nvf->nvf_tail = np;
}
/*
* Convert a device path/nvlist pair to an nvp_list_t
* Used to parse the nvlist format when reading
* /etc/devices/devid_cache
*/
static nvp_list_t *
devid_nvl2nvp(nvlist_t *nvl, char *name)
{
nvp_devid_t *np;
ddi_devid_t devidp;
int rval;
uint_t n;
np = kmem_zalloc(sizeof (nvp_devid_t), KM_SLEEP);
np->nvp_devpath = i_ddi_strdup(name, KM_SLEEP);
NVP_DEVID_DEBUG_PATH((np->nvp_devpath));
/*
* check path for a devid
*/
np->nvp_devid = NULL;
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->nvp_devid = kmem_alloc(n, KM_SLEEP);
(void) bcopy(devidp, np->nvp_devid, n);
NVP_DEVID_DEBUG_DEVID((np->nvp_devid));
} else {
DEVIDERR((CE_CONT,
"%s: invalid devid\n", np->nvp_devpath));
}
}
return (NVPLIST(np));
}
/*
* Convert a device path/nvlist pair to an nvp_list_t
* Used to parse the nvlist format when reading
* /etc/devices/devname_cache
*/
static nvp_list_t *
sdev_nvl2nvp(nvlist_t *nvl, char *name)
{
nvp_devname_t *np;
char **strs;
int *cnts;
uint_t nstrs, ncnts;
int rval, i;
/* name of the sublist must match what we created */
if (strcmp(name, DP_DEVNAME_ID) != 0) {
return (NULL);
}
np = kmem_zalloc(sizeof (nvp_devname_t), KM_SLEEP);
rval = nvlist_lookup_string_array(nvl,
DP_DEVNAME_NCACHE_ID, &strs, &nstrs);
if (rval) {
kmem_free(np, sizeof (nvp_devname_t));
return (NULL);
}
np->nvp_npaths = nstrs;
np->nvp_paths = kmem_zalloc(nstrs * sizeof (char *), KM_SLEEP);
for (i = 0; i < nstrs; i++) {
np->nvp_paths[i] = i_ddi_strdup(strs[i], KM_SLEEP);
}
np->nvp_expirecnts = kmem_zalloc(nstrs * sizeof (int), KM_SLEEP);
for (i = 0; i < nstrs; i++) {
np->nvp_expirecnts[i] = 4; /* XXX sdev_nc_expirecnt */
}
rval = nvlist_lookup_int32_array(nvl,
DP_DEVNAME_NC_EXPIRECNT_ID, &cnts, &ncnts);
if (rval == 0) {
ASSERT(ncnts == nstrs);
ncnts = max(ncnts, nstrs);
for (i = 0; i < nstrs; i++) {
np->nvp_expirecnts[i] = cnts[i];
}
}
return (NVPLIST(np));
}
/*
* Convert a list of nvp_list_t's to a single nvlist
* Used when writing the nvlist file.
*/
static int
devid_nvp2nvl(nvfd_t *nvfd, nvlist_t **ret_nvl)
{
nvlist_t *nvl, *sub_nvl;
nvp_devid_t *np;
int rval;
ASSERT(modrootloaded);
rval = nvlist_alloc(&nvl, NV_UNIQUE_NAME, KM_SLEEP);
if (rval != 0) {
KFIOERR((CE_CONT, "%s: nvlist alloc error %d\n",
nvfd->nvf_name, rval));
return (DDI_FAILURE);
}
for (np = NVF_DEVID_LIST(nvfd); np; np = NVP_DEVID_NEXT(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) {
KFIOERR((CE_CONT, "%s: nvlist alloc error %d\n",
nvfd->nvf_name, 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 {
KFIOERR((CE_CONT,
"%s: nvlist add error %d (devid)\n",
nvfd->nvf_name, rval));
goto err;
}
rval = nvlist_add_nvlist(nvl, np->nvp_devpath, sub_nvl);
if (rval != 0) {
KFIOERR((CE_CONT, "%s: nvlist add error %d (sublist)\n",
nvfd->nvf_name, rval));
goto err;
}
nvlist_free(sub_nvl);
}
*ret_nvl = nvl;
return (DDI_SUCCESS);
err:
if (sub_nvl)
nvlist_free(sub_nvl);
nvlist_free(nvl);
*ret_nvl = NULL;
return (DDI_FAILURE);
}
/*
* Convert a list of nvp_list_t's to a single nvlist
* Used when writing the nvlist file.
*/
static int
sdev_nvp2nvl(nvfd_t *nvfd, nvlist_t **ret_nvl)
{
nvlist_t *nvl, *sub_nvl;
nvp_devname_t *np;
int rval;
ASSERT(modrootloaded);
rval = nvlist_alloc(&nvl, NV_UNIQUE_NAME, KM_SLEEP);
if (rval != 0) {
KFIOERR((CE_CONT, "%s: nvlist alloc error %d\n",
nvfd->nvf_name, rval));
return (DDI_FAILURE);
}
if ((np = NVF_DEVNAME_LIST(nvfd)) != NULL) {
ASSERT(NVP_DEVNAME_NEXT(np) == NULL);
rval = nvlist_alloc(&sub_nvl, NV_UNIQUE_NAME, KM_SLEEP);
if (rval != 0) {
KFIOERR((CE_CONT, "%s: nvlist alloc error %d\n",
nvfd->nvf_name, rval));
sub_nvl = NULL;
goto err;
}
rval = nvlist_add_string_array(sub_nvl,
DP_DEVNAME_NCACHE_ID, np->nvp_paths, np->nvp_npaths);
if (rval != 0) {
KFIOERR((CE_CONT,
"%s: nvlist add error %d (sdev)\n",
nvfd->nvf_name, rval));
goto err;
}
rval = nvlist_add_int32_array(sub_nvl,
DP_DEVNAME_NC_EXPIRECNT_ID,
np->nvp_expirecnts, np->nvp_npaths);
if (rval != 0) {
KFIOERR((CE_CONT,
"%s: nvlist add error %d (sdev)\n",
nvfd->nvf_name, rval));
goto err;
}
rval = nvlist_add_nvlist(nvl, DP_DEVNAME_ID, sub_nvl);
if (rval != 0) {
KFIOERR((CE_CONT, "%s: nvlist add error %d (sublist)\n",
nvfd->nvf_name, rval));
goto err;
}
nvlist_free(sub_nvl);
}
*ret_nvl = nvl;
return (DDI_SUCCESS);
err:
if (sub_nvl)
nvlist_free(sub_nvl);
nvlist_free(nvl);
*ret_nvl = NULL;
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
fread_nvp_list(nvfd_t *nvfd)
{
nvlist_t *nvl;
nvpair_t *nvp;
char *name;
nvlist_t *sublist;
int rval;
nvp_list_t *np;
nvp_list_t *nvp_list = NULL;
nvp_list_t *nvp_tail = NULL;
nvfd->nvf_list = NULL;
nvfd->nvf_tail = NULL;
rval = fread_nvlist(nvfd->nvf_name, &nvl);
if (rval != 0)
return (rval);
ASSERT(nvl != NULL);
nvp = NULL;
while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) {
name = nvpair_name(nvp);
ASSERT(strlen(name) > 0);
switch (nvpair_type(nvp)) {
case DATA_TYPE_NVLIST:
rval = nvpair_value_nvlist(nvp, &sublist);
if (rval != 0) {
KFIOERR((CE_CONT,
"nvpair_value_nvlist error %s %d\n",
name, rval));
goto error;
}
/*
* convert nvlist for this device to
* an nvp_list_t struct
*/
np = (nvfd->nvf_nvl2nvp)(sublist, name);
if (np) {
np->nvp_next = NULL;
np->nvp_prev = nvp_tail;
if (nvp_list == NULL) {
nvp_list = np;
} else {
nvp_tail->nvp_next = np;
}
nvp_tail = np;
}
break;
default:
KFIOERR((CE_CONT, "%s: %s unsupported data type %d\n",
nvfd->nvf_name, name, nvpair_type(nvp)));
rval = EINVAL;
goto error;
}
}
nvlist_free(nvl);
nvfd->nvf_list = nvp_list;
nvfd->nvf_tail = nvp_tail;
return (0);
error:
nvlist_free(nvl);
if (nvp_list)
nvp_list_free(nvfd, nvp_list);
return (rval);
}
static int
i_ddi_read_one_nvfile(nvfd_t *nvfd)
{
int rval;
KFDEBUG((CE_CONT, "reading %s\n", nvfd->nvf_name));
rval = fread_nvp_list(nvfd);
if (rval) {
switch (rval) {
case EIO:
nvfd->nvf_flags |= NVF_REBUILD_MSG;
cmn_err(CE_WARN, "%s: I/O error",
nvfd->nvf_name);
break;
case ENOENT:
nvfd->nvf_flags |= NVF_CREATE_MSG;
KFIOERR((CE_CONT, "%s: not found\n",
nvfd->nvf_name));
break;
case EINVAL:
default:
nvfd->nvf_flags |= NVF_REBUILD_MSG;
cmn_err(CE_WARN, "%s: data file corrupted",
nvfd->nvf_name);
break;
}
}
return (rval);
}
/* for information possibly required to mount root */
void
i_ddi_read_devices_files(void)
{
mdi_read_devices_files();
if (devid_cache_read_disable == 0) {
ASSERT(dcfd->nvf_list == NULL);
(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) {
ASSERT(sdevfd->nvf_list == NULL);
(void) i_ddi_read_one_nvfile(sdevfd);
}
}
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);
}
static char *e_ddi_devid_hold_driver_list[] = { "sd", "ssd", "dad" };
#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];
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 != (major_t)-1) {
e_ddi_devid_hold_by_major(hint_major);
}
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 != (major_t)-1 && 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);
}
int
e_devid_cache_register(dev_info_t *dip, ddi_devid_t devid)
{
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;
ASSERT(ddi_devid_valid(devid) == DDI_SUCCESS);
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(&dcfd->nvf_lock, RW_WRITER);
for (np = NVF_DEVID_LIST(dcfd); np; np = NVP_DEVID_NEXT(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;
NVF_MARK_DIRTY(dcfd);
rw_exit(&dcfd->nvf_lock);
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));
/*
* 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.
*/
nfd_nvp_free_and_unlink(dcfd, NVPLIST(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(&dcfd->nvf_lock);
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;
NVF_MARK_DIRTY(dcfd);
nfd_nvp_link(dcfd, NVPLIST(new_nvp));
rw_exit(&dcfd->nvf_lock);
exit:
if (free_devid)
kmem_free(free_devid, ddi_devid_sizeof(free_devid));
if (!devid_cache_write_disable)
wake_nvpflush_daemon();
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
e_devid_cache_unregister(dev_info_t *dip)
{
nvp_devid_t *np;
rw_enter(&dcfd->nvf_lock, RW_WRITER);
for (np = NVF_DEVID_LIST(dcfd); np; np = NVP_DEVID_NEXT(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;
break;
}
}
rw_exit(&dcfd->nvf_lock);
}
void
e_devid_cache_cleanup(void)
{
nvp_devid_t *np, *next;
rw_enter(&dcfd->nvf_lock, RW_WRITER);
for (np = NVF_DEVID_LIST(dcfd); np; np = next) {
next = NVP_DEVID_NEXT(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));
NVF_MARK_DIRTY(dcfd);
nfd_nvp_free_and_unlink(dcfd, NVPLIST(np));
}
}
rw_exit(&dcfd->nvf_lock);
if (NVF_IS_DIRTY(dcfd))
wake_nvpflush_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,
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;
mutex_enter(&(DEVI(dip)->devi_lock));
/* Find matching minor names */
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++;
}
mutex_exit(&(DEVI(dip)->devi_lock));
*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 circ;
int maxdevis = 0;
int maxpaths = 0;
ndevis = 0;
npaths = 0;
for (np = NVF_DEVID_LIST(dcfd); np; np = NVP_DEVID_NEXT(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, &circ)) {
dip = np->nvp_dip;
ndi_hold_devi(dip);
ndi_devi_exit(pdip, circ);
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, 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(&dcfd->nvf_lock, RW_READER);
n = e_devid_cache_devi_path_lists(devid, nalloced,
&ndevis, devis, &npaths, paths);
if (n <= nalloced)
break;
rw_exit(&dcfd->nvf_lock);
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(&dcfd->nvf_lock);
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 *));
}
#include <sys/callb.h>
/*
* 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
#define TICKS_PER_SECOND (drv_usectohz(1000000))
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 clock_t nvpticks;
static void nvpflush_daemon(void);
void
nvf_register_write_complete(nvfd_t *fd, void (*f)(nvfd_t *))
{
fd->nvf_write_complete = f;
}
void
nvf_unregister_write_complete(nvfd_t *fd)
{
fd->nvf_write_complete = NULL;
}
static void
nvf_write_complete(nvfd_t *fd)
{
if (fd->nvf_write_complete) {
(*(fd->nvf_write_complete))(fd);
}
}
void
i_ddi_start_flush_daemon(void)
{
ASSERT(i_ddi_io_initialized());
mutex_init(&nvpflush_lock, NULL, MUTEX_DRIVER, NULL);
cv_init(&nvpflush_cv, NULL, CV_DRIVER, NULL);
if ((NVF_IS_DIRTY(dcfd) && !devid_cache_write_disable) ||
(NVF_IS_DIRTY(sdevfd) && !sdevfd && sdev_cache_write_disable)) {
wake_nvpflush_daemon();
}
}
/*ARGSUSED*/
static void
nvpflush_timeout(void *arg)
{
clock_t nticks;
mutex_enter(&nvpflush_lock);
nticks = nvpticks - ddi_get_lbolt();
if (nticks > 4) {
nvpflush_timer_busy = 1;
mutex_exit(&nvpflush_lock);
nvpflush_id = timeout(nvpflush_timeout, NULL, nticks);
} else {
do_nvpflush = 1;
NVPDAEMON_DEBUG((CE_CONT, "signal nvpdaemon\n"));
cv_signal(&nvpflush_cv);
nvpflush_id = 0;
nvpflush_timer_busy = 0;
mutex_exit(&nvpflush_lock);
}
}
void
wake_nvpflush_daemon()
{
clock_t nticks;
/*
* If the system isn't up yet
* don't even think about starting a flush.
*/
if (!i_ddi_io_initialized())
return;
mutex_enter(&nvpflush_lock);
if (nvpflush_daemon_active == 0) {
nvpflush_daemon_active = 1;
mutex_exit(&nvpflush_lock);
NVPDAEMON_DEBUG((CE_CONT, "starting nvpdaemon thread\n"));
nvpflush_thr_id = thread_create(NULL, 0,
(void (*)())nvpflush_daemon,
NULL, 0, &p0, TS_RUN, minclsyspri);
mutex_enter(&nvpflush_lock);
}
nticks = nvpflush_delay * TICKS_PER_SECOND;
nvpticks = ddi_get_lbolt() + nticks;
if (nvpflush_timer_busy == 0) {
nvpflush_timer_busy = 1;
mutex_exit(&nvpflush_lock);
nvpflush_id = timeout(nvpflush_timeout, NULL, nticks + 4);
} else
mutex_exit(&nvpflush_lock);
}
static int
nvpflush_one(nvfd_t *nvfd)
{
int rval = DDI_SUCCESS;
nvlist_t *nvl;
rw_enter(&nvfd->nvf_lock, RW_READER);
if (!NVF_IS_DIRTY(nvfd) || NVF_IS_READONLY(nvfd)) {
rw_exit(&nvfd->nvf_lock);
return (DDI_SUCCESS);
}
if (rw_tryupgrade(&nvfd->nvf_lock) == 0) {
KFIOERR((CE_CONT, "nvpflush: "
"%s rw upgrade failed\n", nvfd->nvf_name));
rw_exit(&nvfd->nvf_lock);
return (DDI_FAILURE);
}
if (((nvfd->nvf_nvp2nvl)(nvfd, &nvl)) != DDI_SUCCESS) {
KFIOERR((CE_CONT, "nvpflush: "
"%s nvlist construction failed\n", nvfd->nvf_name));
rw_exit(&nvfd->nvf_lock);
return (DDI_FAILURE);
}
NVF_CLEAR_DIRTY(nvfd);
nvfd->nvf_flags |= NVF_FLUSHING;
rw_exit(&nvfd->nvf_lock);
rval = e_fwrite_nvlist(nvfd, nvl);
nvlist_free(nvl);
rw_enter(&nvfd->nvf_lock, RW_WRITER);
nvfd->nvf_flags &= ~NVF_FLUSHING;
if (rval == DDI_FAILURE) {
if (NVF_IS_READONLY(nvfd)) {
rval = DDI_SUCCESS;
nvfd->nvf_flags &= ~(NVF_ERROR | NVF_DIRTY);
} else if ((nvfd->nvf_flags & NVF_ERROR) == 0) {
cmn_err(CE_CONT,
"%s: updated failed\n", nvfd->nvf_name);
nvfd->nvf_flags |= NVF_ERROR | NVF_DIRTY;
}
} else {
if (nvfd->nvf_flags & NVF_CREATE_MSG) {
cmn_err(CE_CONT, "!Creating %s\n", nvfd->nvf_name);
nvfd->nvf_flags &= ~NVF_CREATE_MSG;
}
if (nvfd->nvf_flags & NVF_REBUILD_MSG) {
cmn_err(CE_CONT, "!Rebuilding %s\n", nvfd->nvf_name);
nvfd->nvf_flags &= ~NVF_REBUILD_MSG;
}
if (nvfd->nvf_flags & NVF_ERROR) {
cmn_err(CE_CONT,
"%s: update now ok\n", nvfd->nvf_name);
nvfd->nvf_flags &= ~NVF_ERROR;
}
/*
* 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;
}
rw_exit(&nvfd->nvf_lock);
return (rval);
}
static void
nvpflush_daemon(void)
{
callb_cpr_t cprinfo;
clock_t clk;
int rval;
int i;
ASSERT(modrootloaded);
nvpflush_thread = curthread;
NVPDAEMON_DEBUG((CE_CONT, "nvpdaemon: init\n"));
CALLB_CPR_INIT(&cprinfo, &nvpflush_lock, callb_generic_cpr, "nvp");
mutex_enter(&nvpflush_lock);
for (;;) {
CALLB_CPR_SAFE_BEGIN(&cprinfo);
while (do_nvpflush == 0) {
clk = cv_timedwait(&nvpflush_cv, &nvpflush_lock,
ddi_get_lbolt() +
(nvpdaemon_idle_time * TICKS_PER_SECOND));
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.
*/
CALLB_CPR_SAFE_END(&cprinfo, &nvpflush_lock);
NVPDAEMON_DEBUG((CE_CONT, "nvpdaemon: exit\n"));
ASSERT(mutex_owned(&nvpflush_lock));
nvpflush_thr_id = NULL;
nvpflush_daemon_active = 0;
CALLB_CPR_EXIT(&cprinfo);
thread_exit();
}
}
CALLB_CPR_SAFE_END(&cprinfo, &nvpflush_lock);
nvpbusy = 1;
do_nvpflush = 0;
mutex_exit(&nvpflush_lock);
/*
* Try flushing what's dirty, reschedule if there's
* a failure or data gets marked as dirty again.
*/
for (i = 0; i < NCACHEFDS; i++) {
rw_enter(&cachefds[i]->nvf_lock, RW_READER);
if (NVF_IS_DIRTY(cachefds[i])) {
NVPDAEMON_DEBUG((CE_CONT,
"nvpdaemon: flush %s\n",
cachefds[i]->nvf_name));
rw_exit(&cachefds[i]->nvf_lock);
rval = nvpflush_one(cachefds[i]);
rw_enter(&cachefds[i]->nvf_lock, RW_READER);
if (rval != DDI_SUCCESS ||
NVF_IS_DIRTY(cachefds[i])) {
rw_exit(&cachefds[i]->nvf_lock);
NVPDAEMON_DEBUG((CE_CONT,
"nvpdaemon: %s dirty again\n",
cachefds[i]->nvf_name));
wake_nvpflush_daemon();
} else {
rw_exit(&cachefds[i]->nvf_lock);
nvf_write_complete(cachefds[i]);
}
} else {
NVPDAEMON_DEBUG((CE_CONT,
"nvpdaemon: not dirty %s\n",
cachefds[i]->nvf_name));
rw_exit(&cachefds[i]->nvf_lock);
}
}
mutex_enter(&nvpflush_lock);
nvpbusy = 0;
}
}
void
i_ddi_clean_devices_files(void)
{
e_devid_cache_cleanup();
mdi_clean_vhcache();
}
#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 */