devcache.c revision 83c4dfe9546fd839e7a52bca7e9920da918f916e
/*
* 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/devcache.h>
#include <sys/devcache_impl.h>
#include <sys/sysmacros.h>
/*
* This facility provides interfaces to clients to register,
* read and update cache data in persisted backing store files,
* mechanism should be stateless data, functioning in the sense
* of a cache. Writes are performed by a background daemon
* thread, permitting a client to schedule an update without
* blocking, then continue updating the data state in
* parallel. The data is only locked by the daemon thread
* to pack the data in preparation for the write.
*
* Data persisted through this mechanism should be capable
* of being regenerated through normal system operation,
* for example attaching all disk devices would cause all
* devids to be registered for those devices. By caching
* a devid-device tuple, the system can operate in a
* more optimal way, directly attaching the device mapped
* to a devid, rather than burdensomely driving attach of
* the entire device tree to discover a single device.
*
* Note that a client should only need to include
* <sys/devcache.h> for the supported interfaces.
*
* The data per client is entirely within the control of
* the client. When reading, data unpacked from the backing
* store should be inserted in the list. The pointer to
* the list can be retreived via nvf_list(). When writing,
* the data on the list is to be packed and returned to the
* nvpdaemon as an nvlist.
*
* Obvious restrictions are imposed by the limits of the
* nvlist format. The data cannot be read or written
* piecemeal, and large amounts of data aren't recommended.
* However, nvlists do allow that data be named and typed
* and can be size-of-int invariant, and the cached data
* can be versioned conveniently.
*
* The registration involves two steps: a handle is
* allocated by calling the registration function.
* This sets up the data referenced by the handle and
* initializes the lock. Following registration, the
* client must initialize the data list. The list
* interfaces require that the list element with offset
* to the node link be provided. The format of the
* list element is under the control of the client.
*
* Locking: the address of the data list r/w lock provided
* can be accessed with nvf_lock(). The lock must be held
* as reader when traversing the list or checking state,
* such as nvf_is_dirty(). The lock must be held as
* writer when updating the list or marking it dirty.
* The lock must not be held when waking the daemon.
*
* The data r/w lock is held as writer when the pack,
* unpack and free list handlers are called. The
* lock should not be dropped and must be still held
* upon return. The client should also hold the lock
* as reader when checking if the list is dirty, and
* as writer when marking the list dirty or initiating
* a read.
*
* The asynchronous nature of updates allows for the
* possibility that the data may continue to be updated
* once the daemon has been notified that an update is
* desired. The data only needs to be locked against
* updates when packing the data into the form to be
* written. When the write of the packed data has
* completed, the daemon will automatically reschedule
* an update if the data was marked dirty after the
* point at which it was packed. Before beginning an
* update, the daemon attempts to lock the data as
* writer; if the writer lock is already held, it
* backs off and retries later. The model is to give
* priority to the kernel processes generating the
* data, and that the nature of the data is that
* it does not change often, can be re-generated when
* needed, so updates should not happen often and
* can be delayed until the data stops changing.
* The client may update the list or mark it dirty
* any time it is able to acquire the lock as
* writer first.
*
* A failed write will be retried after some delay,
* in the hope that the cause of the error will be
* transient, for example a filesystem with no space
* available. An update on a read-only filesystem
* is failed silently and not retried; this would be
* the case when booted off install media.
*
* There is no unregister mechanism as of yet, as it
* hasn't been needed so far.
*/
/*
* Global list of files registered and updated by the nvpflush
* daemon, protected by the nvf_cache_mutex. While an
* update is taking place, a file is temporarily moved to
* the dirty list to avoid locking the primary list for
* the duration of the update.
*/
/*
* 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 or hot-plug of a new adapter.
*
* kfio_report_error (default 0)
* Set to 1 to enable some error messages related to low-level
* kernel file i/o operations.
*
* nvpflush_delay (default 10)
* The number of seconds after data is marked dirty before the
* flush daemon is triggered to flush the data. A longer period
* of time permits more data updates per write. Note that
* every update resets the timer so no repository write will
* occur while data is being updated continuously.
*
* nvpdaemon_idle_time (default 60)
* The number of seconds the daemon will sleep idle before exiting.
*
*/
#define NVPFLUSH_DELAY 10
#define NVPDAEMON_IDLE_TIME 60
/*
* Tunables
*/
int kfio_report_error = 0; /* kernel file i/o operations */
int kfio_disable_read = 0; /* disable all reads */
int kfio_disable_write = 0; /* disable all writes */
int nvpflush_delay = NVPFLUSH_DELAY;
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);
#ifdef DEBUG
int nvpdaemon_debug = 0;
int kfio_debug = 0;
#endif /* DEBUG */
extern int modrootloaded;
extern void mdi_read_devices_files(void);
extern void mdi_clean_vhcache(void);
/*
* Initialize the overall cache file management
*/
void
i_ddi_devices_init(void)
{
}
/*
* Read cache files
* The files read here should be restricted to those
* that may be required to mount root.
*/
void
i_ddi_read_devices_files(void)
{
if (!kfio_disable_read) {
}
}
void
i_ddi_start_flush_daemon(void)
{
if (NVF_IS_DIRTY(nvfdp)) {
break;
}
}
}
void
{
}
/*
* Register a cache file to be managed and updated by the nvpflush daemon.
* All operations are performed through the returned handle.
* There is no unregister mechanism for now.
*/
{
return ((nvf_handle_t)nvfdp);
}
/*PRINTFLIKE1*/
void
{
if (kfio_report_error) {
}
}
/*
* Some operations clients may use to manage the data
* to be persisted in a cache file.
*/
char *
{
}
{
}
list_t *
{
}
void
{
}
int
{
}
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) {
return (EIO);
} else if (n == 0) {
} else {
}
return (EINVAL);
}
offset += n;
hdr.nvpf_hdr_chksum = 0;
nvf_error("%s: checksum error "
"(actual 0x%x, expected 0x%x)\n",
}
return (EINVAL);
}
if (n < 0) {
} else {
nvf_error("%s: incomplete read %d/%lld",
}
return (EINVAL);
}
offset += n;
if (rval > 0) {
nvf_error("%s is larger than %lld\n",
return (EINVAL);
}
nvf_error("%s: checksum error (actual 0x%x, expected 0x%x)\n",
return (EINVAL);
}
if (rval != 0) {
nvf_error("%s: error %d unpacking nvlist\n",
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) {
nvf_error("%s: sync error %d\n",
}
}
if (rval != 0) {
nvf_error("%s: close error %d\n",
}
} else {
}
return (rval);
}
static int
{
int rval;
}
return (rval);
}
int
{
char *buf;
char *nvbuf;
char *newname;
ssize_t n;
if (err != 0) {
nvf_error("%s: error %d packing nvlist\n",
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) {
nvf_error("%s: write error - %d\n",
} else {
if (n != buflen) {
"%s: partial write %ld of %ld bytes\n",
nvf_error("%s: filesystem may be full?\n",
newname);
}
}
if (err == 0)
}
if (err != 0) {
nvf_error("%s: remove failed\n",
newname);
}
}
} else {
}
if (err == 0) {
nvf_error("%s: rename from %s failed\n",
}
}
return (err);
}
static int
{
int err;
return (DDI_SUCCESS);
else {
return (DDI_FAILURE);
}
}
static void
{
}
/*
* 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;
int rv;
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;
}
/*
* unpack nvlist for this device and
* add elements to data list.
*/
if (rv != 0) {
"%s: %s invalid list element\n",
goto error;
}
break;
default:
nvf_error("%s: %s unsupported data type %d\n",
goto error;
}
}
return (0);
return (rval);
}
int
{
int rval;
if (kfio_disable_read)
return (0);
if (rval) {
switch (rval) {
case EIO:
break;
case ENOENT:
nvf_error("%s: not found\n",
break;
case EINVAL:
default:
break;
}
}
return (rval);
}
static void
{
if (fd->nvf_write_complete) {
}
}
/*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;
}
}
/*
* After marking a list as dirty, wake the nvpflush daemon
* to perform the update.
*/
void
nvf_wake_daemon(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;
if (!NVF_IS_DIRTY(nvfd) ||
return (DDI_SUCCESS);
}
nvf_error("nvpflush: "
return (DDI_FAILURE);
}
if (((nvfd->nvf_pack_list)
nvf_error("nvpflush: "
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 want_wakeup;
int is_now_clean;
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;
want_wakeup = 0;
do_nvpflush = 0;
/*
* Try flushing what's dirty, reschedule if there's
* a failure or data gets marked as dirty again.
* First move each file marked dirty to the dirty
* list to avoid locking the list across the write.
*/
if (NVF_IS_DIRTY(nvfdp)) {
} else {
"nvpdaemon: not dirty %s\n",
nvfdp->nvf_cache_path));
}
}
/*
* Now go through the dirty list
*/
is_now_clean = 0;
if (NVF_IS_DIRTY(nvfdp)) {
"nvpdaemon: flush %s\n",
nvfdp->nvf_cache_path));
if (rval != DDI_SUCCESS ||
NVF_IS_DIRTY(nvfdp)) {
"nvpdaemon: %s dirty again\n",
nvfdp->nvf_cache_path));
want_wakeup = 1;
} else {
is_now_clean = 1;
}
} else {
"nvpdaemon: not dirty %s\n",
nvfdp->nvf_cache_path));
is_now_clean = 1;
}
if (is_now_clean) {
nvfdp);
}
}
if (want_wakeup)
nvpbusy = 0;
}
}