diff options
Diffstat (limited to 'usr/src/uts/common/os/devctl.c')
-rw-r--r-- | usr/src/uts/common/os/devctl.c | 1726 |
1 files changed, 1726 insertions, 0 deletions
diff --git a/usr/src/uts/common/os/devctl.c b/usr/src/uts/common/os/devctl.c new file mode 100644 index 0000000000..8541128985 --- /dev/null +++ b/usr/src/uts/common/os/devctl.c @@ -0,0 +1,1726 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License, Version 1.0 only + * (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 2004 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 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; + +/* + * Descriptor for /etc/devices/devid_cache + */ +nvfd_t devid_cache_fd = { + "/etc/devices/devid_cache", +}; +static nvfd_t *dcfd = &devid_cache_fd; + + +extern int modrootloaded; +extern struct bootops *bootops; + +#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); + + 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); +} + +static 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); +} + +static int +fwrite_nvlist(nvfd_t *nvfd, nvlist_t *nvl) +{ + char *buf; + char *nvbuf; + kfile_t *fp; + char *newname; + int len, err; + int rval; + size_t buflen; + ssize_t n; + + ASSERT(modrootloaded); + + nvbuf = NULL; + rval = nvlist_pack(nvl, &nvbuf, &buflen, NV_ENCODE_NATIVE, 0); + if (rval != 0) { + KFIOERR((CE_CONT, "%s: error %d packing nvlist\n", + nvfd->nvf_name, rval)); + return (DDI_FAILURE); + } + + 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(nvfd->nvf_name) + MAX_SUFFIX_LEN + 2; + newname = kmem_alloc(len, KM_SLEEP); + + + (void) sprintf(newname, "%s.%s", + nvfd->nvf_name, 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. + */ + + rval = DDI_SUCCESS; + if ((err = kfcreate(newname, &fp)) == 0) { + err = kfwrite(fp, buf, buflen, &n); + if (err) { + KFIOERR((CE_CONT, "%s: write error - %d\n", + newname, err)); + if (err == EROFS) + NVF_MARK_READONLY(nvfd); + rval = DDI_FAILURE; + } 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)); + rval = DDI_FAILURE; + } + } + if ((err = kfclose(fp)) != 0) { + KFIOERR((CE_CONT, "%s: close error\n", newname)); + rval = DDI_FAILURE; + } + if (rval != DDI_SUCCESS) { + if (kfremove(newname) != 0) { + KFIOERR((CE_CONT, "%s: remove failed\n", + newname)); + } + } + } else { + KFIOERR((CE_CONT, "%s: create failed - %d\n", + nvfd->nvf_name, err)); + if (err == EROFS) + NVF_MARK_READONLY(nvfd); + rval = DDI_FAILURE; + } + + if (rval == DDI_SUCCESS) { + if (kfrename(newname, nvfd->nvf_name) != 0) { + KFIOERR((CE_CONT, "%s: rename from %s failed\n", + newname, nvfd->nvf_name)); + rval = DDI_FAILURE; + } + } + + kmem_free(newname, len); + kmem_free(buf, buflen); + + return (rval); +} + + +static void +nvp_free(nvp_list_t *np) +{ + if (np->nvp_devpath) + kmem_free(np->nvp_devpath, strlen(np->nvp_devpath)+1); + if (np->nvp_devid) + kmem_free(np->nvp_devid, ddi_devid_sizeof(np->nvp_devid)); + + kmem_free(np, sizeof (nvp_list_t)); +} + +static void +nvp_list_free(nvp_list_t *nvp) +{ + nvp_list_t *np; + nvp_list_t *next; + + for (np = nvp; np; np = next) { + next = np->nvp_next; + nvp_free(np); + } +} + +/* + * Free the devid-related information in an nvp element + * If no more data is stored in the nvp element, free + * it and unlink it from the list + * + * Since at present there is no further use of nvp's, + * there's nothing to check. + */ +static nvp_list_t * +nfd_devid_free_and_unlink(nvfd_t *nvf, nvp_list_t *np) +{ + nvp_list_t *pv, *next; + + pv = np->nvp_prev; + next = np->nvp_next; + 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; + } + + return (next); +} + +static void +nfd_devid_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 + */ +static nvp_list_t * +nvlist_to_nvp(nvlist_t *nvl, char *name) +{ + nvp_list_t *np; + ddi_devid_t devidp; + int rval; + uint_t n; + + np = kmem_zalloc(sizeof (nvp_list_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 (np); +} + +/* + * Convert a list of nvp_list_t's to a single nvlist + * Used when writing the nvlist file + */ +static int +nvp_to_nvlist(nvfd_t *nvfd, nvlist_t **ret_nvl) +{ + nvlist_t *nvl, *sub_nvl; + nvp_list_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 = nvfd->nvf_list; np; np = np->nvp_next) { + 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; + } + + if (np->nvp_devid) { + 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); +} + + +/* + * 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 = nvlist_to_nvp(sublist, name); + 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(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); +} + +void +i_ddi_read_devices_files(void) +{ + nvfd_t nvfd; + int rval; + + if (devid_cache_read_disable) + return; + + nvfd.nvf_name = dcfd->nvf_name; + nvfd.nvf_flags = 0; + nvfd.nvf_list = NULL; + nvfd.nvf_tail = NULL; + rw_init(&nvfd.nvf_lock, NULL, RW_DRIVER, NULL); + + rval = i_ddi_read_one_nvfile(&nvfd); + + rw_enter(&dcfd->nvf_lock, RW_WRITER); + + if (rval == 0) { + if (dcfd->nvf_list != NULL) { + nvp_list_free(dcfd->nvf_list); + } + dcfd->nvf_list = nvfd.nvf_list; + dcfd->nvf_tail = nvfd.nvf_tail; + } + dcfd->nvf_flags = nvfd.nvf_flags; + + rw_exit(&dcfd->nvf_lock); + + rw_destroy(&nvfd.nvf_lock); +} + +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_list_t *np; + nvp_list_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_list_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 = dcfd->nvf_list; np != NULL; np = np->nvp_next) { + 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_list_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. + */ + (void) nfd_devid_free_and_unlink( + dcfd, 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_list_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_devid_link(dcfd, new_nvp); + + rw_exit(&dcfd->nvf_lock); + +exit: + if (free_devid) + kmem_free(free_devid, ddi_devid_sizeof(free_devid)); + + wake_nvpflush_daemon(dcfd); + + 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_list_t *np; + + rw_enter(&dcfd->nvf_lock, RW_WRITER); + + for (np = dcfd->nvf_list; np != NULL; np = np->nvp_next) { + 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_list_t *np, *next; + + rw_enter(&dcfd->nvf_lock, RW_WRITER); + + for (np = dcfd->nvf_list; np != NULL; np = next) { + next = np->nvp_next; + 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); + next = nfd_devid_free_and_unlink(dcfd, np); + } + } + + rw_exit(&dcfd->nvf_lock); + + if (NVF_IS_DIRTY(dcfd)) + wake_nvpflush_daemon(dcfd); +} + + +/* + * 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_node_state(dip) >= DS_ATTACHED); + + /* 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_list_t *np; + int ndevis, npaths; + dev_info_t *dip, *pdip; + int circ; + int maxdevis = 0; + int maxpaths = 0; + + ndevis = 0; + npaths = 0; + for (np = dcfd->nvf_list; np != NULL; np = np->nvp_next) { + 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, x; + dev_t *devts, *udevts; + int ndevts, ndevts_alloced; + dev_info_t *devi, **devis; + int ndevis, npaths, nalloced; + ddi_devid_t match_devid; + int undevts; + + 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 dev_t's with duplicate collapsed + * Also build the final list in sorted order so we hand + * off a consistent list of dev_t's to avoid SVM complaints + * about volumes moving around. + */ + undevts = 1; + for (i = 1; i < ndevts; i++) { + for (j = 0; j < i; j++) { + if (devts[i] == devts[j]) + break; + } + if (j == i) + undevts++; + } + + udevts = kmem_alloc(undevts * sizeof (dev_t), KM_SLEEP); + + n = 1; + udevts[0] = devts[0]; + for (i = 1; i < ndevts; i++) { + for (j = 0; j < n; j++) { + if (udevts[j] == devts[i]) + break; + if (udevts[j] > devts[i]) { + for (x = n; x > j; x--) + udevts[x] = udevts[x-1]; + udevts[j] = devts[i]; + n++; + break; + } + } + if (j == i) { + udevts[n++] = devts[i]; + } + } + ASSERT(n == 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 +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)) { + wake_nvpflush_daemon(dcfd); + } +} + +/*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; + cv_signal(&nvpflush_cv); + nvpflush_id = 0; + nvpflush_timer_busy = 0; + mutex_exit(&nvpflush_lock); + } +} + +static void +wake_nvpflush_daemon(nvfd_t *nvfp) +{ + clock_t nticks; + + /* + * If root is readonly or the system isn't up yet + * don't even think about starting a flush. + */ + if (devid_cache_write_disable || + !i_ddi_io_initialized() || NVF_IS_READONLY(nvfp)) + 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 (nvp_to_nvlist(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 = 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; + + 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. + */ + NVPDAEMON_DEBUG((CE_CONT, "nvpdaemon: flush\n")); + rval = nvpflush_one(dcfd); + + rw_enter(&dcfd->nvf_lock, RW_READER); + if (rval != DDI_SUCCESS || NVF_IS_DIRTY(dcfd)) { + rw_exit(&dcfd->nvf_lock); + NVPDAEMON_DEBUG((CE_CONT, "nvpdaemon: dirty again\n")); + wake_nvpflush_daemon(dcfd); + } else + rw_exit(&dcfd->nvf_lock); + mutex_enter(&nvpflush_lock); + nvpbusy = 0; + } +} + +#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 */ |