usr/src/uts/common/os/ddifm.c
author jpk
Fri, 24 Mar 2006 12:29:20 -0800
changeset 1676 37f4a3e2bd99
parent 1414 b4126407ac5b
child 1865 c8b524cdb631
permissions -rw-r--r--
PSARC/2002/762 Layered Trusted Solaris PSARC/2005/060 TSNET: Trusted Networking with Security Labels PSARC/2005/259 Layered Trusted Solaris Label Interfaces PSARC/2005/573 Solaris Trusted Extensions for Printing PSARC/2005/691 Trusted Extensions for Device Allocation PSARC/2005/723 Solaris Trusted Extensions Filesystem Labeling PSARC/2006/009 Labeled Auditing PSARC/2006/155 Trusted Extensions RBAC Changes PSARC/2006/191 is_system_labeled 6293271 Zone processes should use zone_kcred instead of kcred 6394554 integrate Solaris Trusted Extensions

/*
 * 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 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * Fault Management for Device Drivers
 *
 * Device drivers wishing to participate in fault management may do so by
 * first initializing their fault management state and capabilties via
 * ddi_fm_init(). If the system supports the requested FM capabilities,
 * the IO framework will intialize FM state and return a bit mask of the
 * requested capabilities.
 *
 * If the system does not support the requested FM capabilities,
 * the device driver must behave in accordance with the programming semantics
 * defined below for the capabilities returned from ddi_fm_init().
 * ddi_fm_init() must be called at attach(9E) time and ddi_fm_fini() must be
 * called from detach(9E) to perform FM clean-up.
 *
 * Driver Fault Management Capabilities
 *
 * DDI_FM_NOT_CAPABLE
 *
 *	This is the default fault management capability for drivers.  Drivers
 *	that implement no fault management capabilites or do not participate
 *	in fault management activities have their FM capability bitmask set
 *	to 0.
 *
 * DDI_FM_EREPORT_CAPABLE
 *
 *	When this capability bit is set, drivers are expected to generate error
 *	report events via ddi_ereport_post() for the associated faults
 *	that are diagnosed by the IO fault manager DE.  ddi_ereport_post()
 *	may be called in any context subject to the constraints specified
 *	by the interrupt iblock cookie	returned during initialization.
 *
 *	Error reports resulting from hardware component specific and common IO
 *	fault and driver defects must be accompanied by an Eversholt fault
 *	tree (.eft) by the Solaris fault manager (fmd(1M)) for
 *	diagnosis.
 *
 * DDI_FM_ERRCB_CAPABLE
 *
 *	Device drivers are expected to implement and register an error
 *	handler callback function.  ddi_fm_handler_register() and
 *	ddi_fm_handler_unregister() must be
 *	called in passive kernel context, typically during an attach(9E)
 *	or detach(9E) operation.  When called by the FM IO framework,
 *	the callback function should check for error conditions for the
 *	hardware and software under its control.  All detected errors
 *	should have ereport events generated for them.
 *
 *	Upon completion of the error handler callback, the driver should
 *	return one of the following values:
 *
 *	#define DDI_FM_OK - no error was detected
 *	#define DDI_FM_FATAL - a fatal error was detected
 *	#define DDI_FM_NONFATAL - a non-fatal error was detected
 *	#define DDI_FM_UNKNOWN - the error status is unknown
 *
 *	To insure single threaded access to error handling callbacks,
 *	the device driver may use i_ddi_fm_handler_enter() and
 *	i_ddi_fm_handler_exit() when entering and exiting the callback.
 *
 * DDI_FM_ACCCHK_CAPABLE/DDI_FM_DMACHK_CAPABLE
 *
 *	Device drivers are expected to set-up access and DMA handles
 *	with FM-specific attributes designed to allow nexus parent
 *	drivers to flag any errors seen during subsequent IO transactions.
 *	Drivers must set the devacc_attr_acc_flag member of their
 *	ddi_device_acc_attr_t structures to DDI_FLAGERR_ACC or DDI_CAUTIOUS_ACC.
 *	For DMA transactions, driver must set the dma_attr_flags of
 *	their ddi_dma_attr_t structures to DDI_DMA_FLAGERR.
 *
 *	Upon completion of an IO transaction, device drivers are expected
 *	to check the status of host-side hardware access and device-side
 *	dma completions by calling ddi_acc_err_check() or ddi_dma_err_check()
 *	respectively. If the handle is associated with an error detected by
 *	the nexus parent or FM IO framework, ddi_fm_error_t data (status, ena
 *	and error expectation) is returned.  If status of DDI_FM_NONFATAL or
 *	DDI_FM_FATAL is returned, the ena is valid and the expectation flag
 *	will be set to 1 if the error was unexpected (i.e. not the result
 *	of a peek or poke type operation).
 *
 *	ddi_acc_err_check() and ddi_dma_err_check() may be called in any
 *	context	subject to the constraints specified by the interrupt
 *	iblock cookie returned during initialization.
 *
 *	Device drivers should generate an access (DDI_FM_IO_ACC) or dma
 *	(DDI_FM_IO_DMA) data path error report if DDI_FM_NONFATAL or
 *	DDI_FM_FATAL is returned.
 *
 */

#include <sys/types.h>
#include <sys/sunddi.h>
#include <sys/sunndi.h>
#include <sys/kmem.h>
#include <sys/nvpair.h>
#include <sys/fm/protocol.h>
#include <sys/ndifm.h>
#include <sys/ddifm.h>
#include <sys/ddi_impldefs.h>
#include <sys/ddi_isa.h>
#include <sys/spl.h>
#include <sys/varargs.h>
#include <sys/systm.h>
#include <sys/disp.h>
#include <sys/atomic.h>
#include <sys/errorq_impl.h>
#include <sys/kobj.h>
#include <sys/fm/util.h>
#include <sys/fm/io/ddi.h>

#define	ERPT_CLASS_SZ	sizeof (DDI_IO_CLASS) + sizeof (FM_EREPORT_CLASS) + \
			    DDI_MAX_ERPT_CLASS + 2
/* Globals */
int default_dmacache_sz = DEFAULT_DMACACHE_SZ;
int default_acccache_sz = DEFAULT_ACCCACHE_SZ;
int ddi_system_fmcap = 0;

static struct i_ddi_fmkstat ddifm_kstat_template = {
	{"erpt_dropped", KSTAT_DATA_UINT64 },
	{"fm_cache_full", KSTAT_DATA_UINT64 },
	{"fm_cache_grew", KSTAT_DATA_UINT64 },
	{"acc_err", KSTAT_DATA_UINT64 },
	{"dma_err", KSTAT_DATA_UINT64 }
};

/*
 * Update the service state following the detection of an
 * error.
 */
void
ddi_fm_service_impact(dev_info_t *dip, int svc_impact)
{
	mutex_enter(&(DEVI(dip)->devi_lock));
	if (!DEVI_IS_DEVICE_OFFLINE(dip)) {
		switch (svc_impact) {
		case DDI_SERVICE_LOST:
			DEVI_SET_DEVICE_DOWN(dip);
			break;
		case DDI_SERVICE_DEGRADED:
			DEVI_SET_DEVICE_DEGRADED(dip);
			break;
		case DDI_SERVICE_RESTORED:
			DEVI_SET_DEVICE_UP(dip);
			break;
		case DDI_SERVICE_UNAFFECTED:
		default:
			break;
		}
	}
	mutex_exit(&(DEVI(dip)->devi_lock));
}

static int
erpt_post_sleep(dev_info_t *dip, const char *error_class, uint64_t ena,
    uint8_t version, va_list ap)
{
	char *devid, *name;
	char device_path[MAXPATHLEN];
	char ddi_error_class[ERPT_CLASS_SZ];
	nvlist_t *ereport, *detector = NULL;

	/*
	 * Driver defect - should not call with DDI_SLEEP while
	 * in interrupt context
	 */
	if (servicing_interrupt()) {
		i_ddi_drv_ereport_post(dip, DVR_ECONTEXT, NULL, DDI_NOSLEEP);
		return (1);
	}

	if ((ereport = fm_nvlist_create(NULL)) == NULL)
		return (1);

	/*
	 * Use the dev_path/devid for this device instance.
	 */
	detector = fm_nvlist_create(NULL);
	if (dip == ddi_root_node()) {
		device_path[0] = '/';
		device_path[1] = '\0';
	} else {
		(void) ddi_pathname(dip, device_path);
	}

	if (ddi_prop_lookup_string(DDI_DEV_T_ANY, dip,
		DDI_PROP_DONTPASS, DEVID_PROP_NAME, &devid) == DDI_SUCCESS) {
		fm_fmri_dev_set(detector, FM_DEV_SCHEME_VERSION, NULL,
		    device_path, devid);
		ddi_prop_free(devid);
	} else {
		fm_fmri_dev_set(detector, FM_DEV_SCHEME_VERSION, NULL,
		    device_path, NULL);
	}

	if (ena == 0)
		ena = fm_ena_generate(0, FM_ENA_FMT1);

	(void) snprintf(ddi_error_class, ERPT_CLASS_SZ, "%s.%s",
	    DDI_IO_CLASS, error_class);

	fm_ereport_set(ereport, version, ddi_error_class,
	    ena, detector, NULL);

	name = va_arg(ap, char *);
	(void) i_fm_payload_set(ereport, name, ap);

	fm_ereport_post(ereport, EVCH_SLEEP);
	fm_nvlist_destroy(ereport, FM_NVA_FREE);
	fm_nvlist_destroy(detector, FM_NVA_FREE);

	return (0);
}

static int
erpt_post_nosleep(dev_info_t *dip, struct i_ddi_fmhdl *fmhdl,
    const char *error_class, uint64_t ena, uint8_t version, va_list ap)
{
	char *name;
	char device_path[MAXPATHLEN];
	char ddi_error_class[ERPT_CLASS_SZ];
	nvlist_t *ereport, *detector;
	nv_alloc_t *nva;
	errorq_elem_t *eqep;

	eqep = errorq_reserve(fmhdl->fh_errorq);
	if (eqep == NULL)
		return (1);

	ereport = errorq_elem_nvl(fmhdl->fh_errorq, eqep);
	nva = errorq_elem_nva(fmhdl->fh_errorq, eqep);

	ASSERT(ereport);
	ASSERT(nva);

	/*
	 * Use the dev_path/devid for this device instance.
	 */
	detector = fm_nvlist_create(nva);
	if (dip == ddi_root_node()) {
		device_path[0] = '/';
		device_path[1] = '\0';
	} else {
		(void) ddi_pathname(dip, device_path);
	}

	fm_fmri_dev_set(detector, FM_DEV_SCHEME_VERSION, NULL,
	    device_path, NULL);

	if (ena == 0)
		ena = fm_ena_generate(0, FM_ENA_FMT1);

	(void) snprintf(ddi_error_class, ERPT_CLASS_SZ, "%s.%s",
	    DDI_IO_CLASS, error_class);

	fm_ereport_set(ereport, version, ddi_error_class,
	    ena, detector, NULL);

	name = va_arg(ap, char *);
	(void) i_fm_payload_set(ereport, name, ap);

	errorq_commit(fmhdl->fh_errorq, eqep, ERRORQ_ASYNC);

	return (0);
}

void
i_ddi_drv_ereport_post(dev_info_t *dip, const char *error_class,
    nvlist_t *errp, int sflag)
{
	int i;
	int depth;
	char classp[DDI_DVR_MAX_CLASS];
	caddr_t stkp;
	char *buf;
	char **stkpp;
	char *sym;
	pc_t stack[DDI_FM_STKDEPTH];
	ulong_t off;
	dev_info_t *root_dip = ddi_root_node();

	if (!DDI_FM_EREPORT_CAP(ddi_fm_capable(root_dip)))
		return;

	(void) snprintf(classp, DDI_DVR_MAX_CLASS, "%s%s", DVR_ERPT,
	    error_class);

	if (sflag == DDI_SLEEP) {
		depth = getpcstack(stack, DDI_FM_STKDEPTH);

		/* Allocate array of char * for nvlist payload */
		stkpp = (char **)kmem_alloc(depth * sizeof (char *), KM_SLEEP);

		/*
		 * Allocate temporary 64-bit aligned buffer for stack
		 * symbol strings
		 */
		buf = kmem_alloc(depth * DDI_FM_SYM_SZ, KM_SLEEP);

		stkp = buf;
		for (i = 0; i < depth; ++i) {
			sym = kobj_getsymname(stack[i], &off);
			(void) snprintf(stkp, DDI_FM_SYM_SZ,
			    "\t%s+%lx\n", sym ? sym : "?", off);
			stkpp[i] = stkp;
			stkp += DDI_FM_SYM_SZ;
		}

		if (errp)
			ddi_fm_ereport_post(root_dip,
			    classp, fm_ena_generate(0, FM_ENA_FMT1), sflag,
			    FM_VERSION, DATA_TYPE_UINT8, 0,
			    DVR_NAME, DATA_TYPE_STRING, ddi_driver_name(dip),
			    DVR_STACK_DEPTH, DATA_TYPE_UINT32, depth,
			    DVR_STACK, DATA_TYPE_STRING_ARRAY, depth, stkpp,
			    DVR_ERR_SPECIFIC, DATA_TYPE_NVLIST, errp, NULL);
		else
			ddi_fm_ereport_post(root_dip,
			    classp, fm_ena_generate(0, FM_ENA_FMT1), sflag,
			    FM_VERSION, DATA_TYPE_UINT8, 0,
			    DVR_NAME, DATA_TYPE_STRING, ddi_driver_name(dip),
			    DVR_STACK_DEPTH, DATA_TYPE_UINT32, depth,
			    DVR_STACK, DATA_TYPE_STRING_ARRAY, depth, stkpp,
			    NULL);

		kmem_free(stkpp, depth * sizeof (char *));
		kmem_free(buf, depth * DDI_FM_SYM_SZ);

	} else {
		if (errp)
			ddi_fm_ereport_post(root_dip,
			    classp, fm_ena_generate(0, FM_ENA_FMT1), sflag,
			    FM_VERSION, DATA_TYPE_UINT8, 0,
			    DVR_NAME, DATA_TYPE_STRING, ddi_driver_name(dip),
			    DVR_ERR_SPECIFIC, DATA_TYPE_NVLIST, errp, NULL);
		else
			ddi_fm_ereport_post(root_dip,
			    classp, fm_ena_generate(0, FM_ENA_FMT1), sflag,
			    FM_VERSION, DATA_TYPE_UINT8, 0,
			    DVR_NAME, DATA_TYPE_STRING, ddi_driver_name(dip),
			    NULL);
	}
}

/*
 * Generate an error report for consumption by the Solaris Fault Manager,
 * fmd(1M).  Valid ereport classes are defined in /usr/include/sys/fm/io.  The
 * ENA should be set if this error is a result of an error status returned
 * from ddi_dma_err_check() or ddi_acc_err_check().  Otherwise, an ENA
 * value of 0 is appropriate.
 *
 * If sflag == DDI_NOSLEEP, ddi_fm_ereport_post () may be called
 * from user, kernel, interrupt or high-interrupt context.  Otherwise,
 * ddi_fm_ereport_post() must be called from user or kernel context.
 */
void
ddi_fm_ereport_post(dev_info_t *dip, const char *error_class, uint64_t ena,
    int sflag, ...)
{
	int ret;
	char *name;
	data_type_t type;
	uint8_t version;
	va_list ap;
	struct i_ddi_fmhdl *fmhdl = DEVI(dip)->devi_fmhdl;

	if (!DDI_FM_EREPORT_CAP(ddi_fm_capable(dip))) {
		i_ddi_drv_ereport_post(dip, DVR_EFMCAP, NULL, sflag);
		return;
	}

	ASSERT(fmhdl);

	va_start(ap, sflag);

	/* First payload tuple should be the version */
	name = va_arg(ap, char *);
	type = va_arg(ap, data_type_t);
	version = va_arg(ap, uint_t);
	if (strcmp(name, FM_VERSION) != 0 && type != DATA_TYPE_UINT8) {
		va_end(ap);
		i_ddi_drv_ereport_post(dip, DVR_EVER, NULL, sflag);
		return;
	}

	if (sflag == DDI_SLEEP)
		ret = erpt_post_sleep(dip, error_class, ena, version, ap);
	else
		ret = erpt_post_nosleep(dip, fmhdl, error_class, ena, version,
		    ap);
	va_end(ap);

	if (ret != 0)
		atomic_add_64(&fmhdl->fh_kstat.fek_erpt_dropped.value.ui64, 1);

}

/*
 * Driver error handling entry.  Prevents multiple simultaneous calls into
 * driver error handling callback.
 *
 * May be called from a context consistent with the iblock_cookie returned
 * in ddi_fm_init().
 */
void
i_ddi_fm_handler_enter(dev_info_t *dip)
{
	struct i_ddi_fmhdl *hdl = DEVI(dip)->devi_fmhdl;

	mutex_enter(&hdl->fh_lock);
}

/*
 * Driver error handling exit.
 *
 * May be called from a context consistent with the iblock_cookie returned
 * in ddi_fm_init().
 */
void
i_ddi_fm_handler_exit(dev_info_t *dip)
{
	struct i_ddi_fmhdl *hdl = DEVI(dip)->devi_fmhdl;

	mutex_exit(&hdl->fh_lock);
}

/*
 * Register a fault manager error handler for this device instance
 *
 * This function must be called from a driver's attach(9E) routine.
 */
void
ddi_fm_handler_register(dev_info_t *dip, ddi_err_func_t handler,
    void *impl_data)
{
	dev_info_t *pdip;
	struct i_ddi_fmhdl *pfmhdl;
	struct i_ddi_errhdl *new_eh;
	struct i_ddi_fmtgt *tgt;

	/*
	 * Check for proper calling context.
	 * The DDI configuration framework does not support
	 * DR states to allow checking for proper invocation
	 * from a DDI_ATTACH or DDI_RESUME.  This limits context checking
	 * to interrupt only.
	 */
	if (servicing_interrupt()) {
		i_ddi_drv_ereport_post(dip, DVR_ECONTEXT, NULL, DDI_NOSLEEP);
		return;
	}

	if (dip == ddi_root_node())
		pdip = dip;
	else
		pdip = (dev_info_t *)DEVI(dip)->devi_parent;

	ASSERT(pdip);

	if (!(DDI_FM_ERRCB_CAP(ddi_fm_capable(dip)) &&
	    DDI_FM_ERRCB_CAP(ddi_fm_capable(pdip)))) {
		i_ddi_drv_ereport_post(dip, DVR_EFMCAP, NULL, DDI_SLEEP);
		return;
	}

	new_eh = kmem_zalloc(sizeof (struct i_ddi_errhdl), KM_SLEEP);
	new_eh->eh_func = handler;
	new_eh->eh_impl = impl_data;

	/* Add dip to parent's target list of registered error handlers */
	tgt = kmem_alloc(sizeof (struct i_ddi_fmtgt), KM_SLEEP);
	tgt->ft_dip = dip;
	tgt->ft_errhdl = new_eh;

	i_ddi_fm_handler_enter(pdip);
	pfmhdl = DEVI(pdip)->devi_fmhdl;
	ASSERT(pfmhdl);
	tgt->ft_next = pfmhdl->fh_tgts;
	pfmhdl->fh_tgts = tgt;
	i_ddi_fm_handler_exit(pdip);
}

/*
 * Unregister a fault manager error handler for this device instance
 *
 * This function must be called from a drivers attach(9E) or detach(9E)
 * routine.
 */
void
ddi_fm_handler_unregister(dev_info_t *dip)
{
	dev_info_t *pdip;
	struct i_ddi_fmhdl *pfmhdl;
	struct i_ddi_fmtgt *tgt, **ptgt;

	/*
	 * Check for proper calling context.
	 * The DDI configuration framework does not support
	 * DR states to allow checking for proper invocation
	 * from a DDI_DETACH or DDI_SUSPEND.  This limits context checking
	 * to interrupt only.
	 */
	if (servicing_interrupt()) {
		i_ddi_drv_ereport_post(dip, DVR_ECONTEXT, NULL, DDI_NOSLEEP);
		return;
	}

	if (dip == ddi_root_node())
		pdip = dip;
	else
		pdip = (dev_info_t *)DEVI(dip)->devi_parent;

	ASSERT(pdip);

	if (!(DDI_FM_ERRCB_CAP(ddi_fm_capable(dip)) &&
	    DDI_FM_ERRCB_CAP(ddi_fm_capable(pdip)))) {
		i_ddi_drv_ereport_post(dip, DVR_EFMCAP, NULL, DDI_SLEEP);
		return;
	}

	i_ddi_fm_handler_enter(pdip);
	pfmhdl = DEVI(pdip)->devi_fmhdl;
	ASSERT(pfmhdl);
	ptgt = &pfmhdl->fh_tgts;
	for (tgt = pfmhdl->fh_tgts; tgt != NULL; tgt = tgt->ft_next) {
		if (dip == tgt->ft_dip) {
			*ptgt = tgt->ft_next;
			kmem_free(tgt->ft_errhdl, sizeof (struct i_ddi_errhdl));
			kmem_free(tgt, sizeof (struct i_ddi_fmtgt));
			break;
		}
		ptgt = &tgt->ft_next;
	}
	i_ddi_fm_handler_exit(pdip);


}

/*
 * Initialize Fault Management capabilities for this device instance (dip).
 * When called with the following capabilities, data structures neccessary
 * for fault management activities are allocated and initialized.
 *
 *	DDI_FM_EREPORT_CAPABLE - initialize ereport errorq and ereport
 *				capable driver property.
 *
 *	DDI_FM_ERRCB_CAPABLE - check with parent for ability to register
 *				an error handler.
 *
 *	DDI_FM_ACCCHK_CAPABLE - initialize access handle cache and acc-chk
 *				driver property
 *
 *	DDI_FM_DMACHK_CAPABLE - initialize dma handle cache and dma-chk
 *				driver property
 *
 * A driver's FM capability level may not exceed that of its parent or
 * system-wide FM capability.  The available capability level for this
 * device instance is returned in *fmcap.
 *
 * This function must be called from a driver's attach(9E) entry point.
 */
void
ddi_fm_init(dev_info_t *dip, int *fmcap, ddi_iblock_cookie_t *ibcp)
{
	struct dev_info *devi = DEVI(dip);
	struct i_ddi_fmhdl *fmhdl;
	ddi_iblock_cookie_t ibc;
	int pcap, newcap = DDI_FM_NOT_CAPABLE;

	if (!DEVI_IS_ATTACHING(dip)) {
		i_ddi_drv_ereport_post(dip, DVR_ECONTEXT, NULL, DDI_NOSLEEP);
		*fmcap = DDI_FM_NOT_CAPABLE;
		return;
	}

	if (DDI_FM_DEFAULT_CAP(*fmcap))
		return;

	/*
	 * Check parent for supported FM level
	 * and correct error handling PIL
	 */
	if (dip != ddi_root_node()) {

		/*
		 * Initialize the default ibc.  The parent may change it
		 * depending upon its capabilities.
		 */
		ibc = (ddi_iblock_cookie_t)ipltospl(FM_ERR_PIL);

		pcap = i_ndi_busop_fm_init(dip, *fmcap, &ibc);
	} else {
		pcap = *fmcap;
		ibc = *ibcp;
	}

	/* Initialize the per-device instance FM handle */
	fmhdl = kmem_zalloc(sizeof (struct i_ddi_fmhdl), KM_SLEEP);

	if ((fmhdl->fh_ksp = kstat_create((char *)ddi_driver_name(dip),
	    ddi_get_instance(dip), "fm", "misc",
	    KSTAT_TYPE_NAMED, sizeof (struct i_ddi_fmkstat) /
	    sizeof (kstat_named_t), KSTAT_FLAG_VIRTUAL)) == NULL) {
		mutex_destroy(&fmhdl->fh_lock);
		kmem_free(fmhdl, sizeof (struct i_ddi_fmhdl));
		*fmcap = DDI_FM_NOT_CAPABLE;
		return;
	}

	bcopy(&ddifm_kstat_template, &fmhdl->fh_kstat,
	    sizeof (struct i_ddi_fmkstat));
	fmhdl->fh_ksp->ks_data = &fmhdl->fh_kstat;
	fmhdl->fh_ksp->ks_private = fmhdl;
	kstat_install(fmhdl->fh_ksp);

	fmhdl->fh_dma_cache = NULL;
	fmhdl->fh_acc_cache = NULL;
	fmhdl->fh_tgts = NULL;
	fmhdl->fh_dip = dip;
	fmhdl->fh_ibc = ibc;
	mutex_init(&fmhdl->fh_lock, NULL, MUTEX_DRIVER, fmhdl->fh_ibc);
	devi->devi_fmhdl = fmhdl;

	/*
	 * Initialize support for ereport generation
	 */
	if (DDI_FM_EREPORT_CAP(*fmcap) &&
	    DDI_FM_EREPORT_CAP(ddi_system_fmcap)) {
		fmhdl->fh_errorq = ereport_errorq;
		if (ddi_getprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
		    "fm-ereport-capable", 0) == 0)
			(void) ddi_prop_create(DDI_DEV_T_ANY, dip,
			    DDI_PROP_CANSLEEP, "fm-ereport-capable", NULL, 0);

		newcap |= DDI_FM_EREPORT_CAPABLE;
	}

	/*
	 * Need cooperation of the parent for error handling
	 */

	if (DDI_FM_ERRCB_CAP(*fmcap) && DDI_FM_ERRCB_CAP(pcap)) {
		if (ddi_getprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
		    "fm-errcb-capable", 0) == 0)
			(void) ddi_prop_create(DDI_DEV_T_ANY, dip,
			    DDI_PROP_CANSLEEP, "fm-errcb-capable", NULL, 0);

		newcap |= DDI_FM_ERRCB_CAPABLE;
	}

	/*
	 * Support for DMA and Access error handling
	 */

	if (DDI_FM_DMA_ERR_CAP(*fmcap) && DDI_FM_DMA_ERR_CAP(pcap)) {
		i_ndi_fmc_create(&fmhdl->fh_dma_cache, 2, ibc);

		/* Set-up dma chk capability prop */
		if (ddi_getprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
		    "fm-dmachk-capable", 0) == 0)
			(void) ddi_prop_create(DDI_DEV_T_ANY, dip,
			    DDI_PROP_CANSLEEP, "fm-dmachk-capable", NULL, 0);

		newcap |= DDI_FM_DMACHK_CAPABLE;
	}

	if (DDI_FM_ACC_ERR_CAP(*fmcap) && DDI_FM_ACC_ERR_CAP(pcap)) {
		i_ndi_fmc_create(&fmhdl->fh_acc_cache, 2, ibc);
		/* Set-up dma chk capability prop */
		if (ddi_getprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
		    "fm-accchk-capable", 0) == 0)
			(void) ddi_prop_create(DDI_DEV_T_ANY, dip,
			    DDI_PROP_CANSLEEP, "fm-accchk-capable", NULL, 0);

		newcap |= DDI_FM_ACCCHK_CAPABLE;
	}

	/*
	 * Return the capability support available
	 * to this driver instance
	 */
	fmhdl->fh_cap = newcap;
	*fmcap = newcap;

	if (ibcp != NULL)
		*ibcp = ibc;
}

/*
 * Finalize Fault Management activities for this device instance.
 * Outstanding IO transaction must be completed prior to calling
 * this routine.  All previously allocated resources and error handler
 * registration are cleared and deallocated.
 *
 * This function must be called from a driver's detach(9E) entry point.
 */
void
ddi_fm_fini(dev_info_t *dip)
{
	struct i_ddi_fmhdl *fmhdl = DEVI(dip)->devi_fmhdl;

	if (!(DEVI_IS_DETACHING(dip) || DEVI_IS_ATTACHING(dip))) {
		i_ddi_drv_ereport_post(dip, DVR_ECONTEXT, NULL, DDI_NOSLEEP);
		return;
	}

	if (DDI_FM_DEFAULT_CAP(fmhdl->fh_cap))
		return;

	ASSERT(fmhdl);

	kstat_delete(fmhdl->fh_ksp);

	if (DDI_FM_EREPORT_CAP(fmhdl->fh_cap)) {
		(void) ddi_prop_remove(DDI_DEV_T_ANY, dip,
		    "fm-ereport-capable");
	}

	if (dip != ddi_root_node()) {
		ddi_fm_handler_unregister(dip);

		if (DDI_FM_DMA_ERR_CAP(fmhdl->fh_cap) ||
		    DDI_FM_ACC_ERR_CAP(fmhdl->fh_cap)) {
			if (fmhdl->fh_dma_cache != NULL) {
				i_ndi_fmc_destroy(fmhdl->fh_dma_cache);
				(void) ddi_prop_remove(DDI_DEV_T_ANY, dip,
				    "fm-dmachk-capable");
			}
			if (fmhdl->fh_acc_cache != NULL) {
				i_ndi_fmc_destroy(fmhdl->fh_acc_cache);
				(void) ddi_prop_remove(DDI_DEV_T_ANY, dip,
				    "fm-accachk-capable");
			}
		}

		i_ndi_busop_fm_fini(dip);
	}

	kmem_free(fmhdl, sizeof (struct i_ddi_fmhdl));
	DEVI(dip)->devi_fmhdl = NULL;
}

/*
 * Return the fault management capability level for this device instance.
 *
 * This function may be called from user, kernel, or interrupt context.
 */
int
ddi_fm_capable(dev_info_t *dip)
{
	struct i_ddi_fmhdl *fmhdl = DEVI(dip)->devi_fmhdl;

	if (fmhdl == NULL)
		return (DDI_FM_NOT_CAPABLE);

	return (fmhdl->fh_cap);
}

/*
 * Routines to set and get error information for/from an access or dma handle
 *
 * These routines may be called from user, kernel, and interrupt contexts.
 */
void
ddi_fm_acc_err_get(ddi_acc_handle_t handle, ddi_fm_error_t *de, int version)
{
	ndi_err_t *errp = ((ddi_acc_impl_t *)handle)->ahi_err;

	if (version != DDI_FME_VER0) {
		ddi_acc_hdl_t *hp = impl_acc_hdl_get(handle);

		i_ddi_drv_ereport_post(hp->ah_dip, DVR_EVER, NULL, DDI_NOSLEEP);
		cmn_err(CE_PANIC, "ddi_fm_dma_err_get: "
		    "Invalid driver version\n");
	}

	de->fme_status = errp->err_status;
	de->fme_ena = errp->err_ena;
	de->fme_flag = errp->err_expected;
	de->fme_acc_handle = handle;
}

void
ddi_fm_dma_err_get(ddi_dma_handle_t handle, ddi_fm_error_t *de, int version)
{
	ndi_err_t *errp = &((ddi_dma_impl_t *)handle)->dmai_error;

	if (version != DDI_FME_VER0) {
		i_ddi_drv_ereport_post(((ddi_dma_impl_t *)handle)->dmai_rdip,
		    DVR_EVER, NULL, DDI_NOSLEEP);
		cmn_err(CE_PANIC, "ddi_fm_dma_err_get: "
		    "Invalid driver version\n");
	}

	de->fme_status = errp->err_status;
	de->fme_ena = errp->err_ena;
	de->fme_flag = errp->err_expected;
	de->fme_dma_handle = handle;
}

void
i_ddi_fm_acc_err_set(ddi_acc_handle_t handle, uint64_t ena, int status,
    int flag)
{
	ddi_acc_hdl_t *hdlp = impl_acc_hdl_get(handle);
	ddi_acc_impl_t *i_hdlp = (ddi_acc_impl_t *)handle;
	struct i_ddi_fmhdl *fmhdl = DEVI(hdlp->ah_dip)->devi_fmhdl;

	i_hdlp->ahi_err->err_ena = ena;
	i_hdlp->ahi_err->err_status = status;
	i_hdlp->ahi_err->err_expected = flag;
	atomic_add_64(&fmhdl->fh_kstat.fek_acc_err.value.ui64, 1);
}

void
i_ddi_fm_dma_err_set(ddi_dma_handle_t handle, uint64_t ena, int status,
    int flag)
{
	ddi_dma_impl_t *hdlp = (ddi_dma_impl_t *)handle;
	struct i_ddi_fmhdl *fmhdl = DEVI(hdlp->dmai_rdip)->devi_fmhdl;

	hdlp->dmai_error.err_ena = ena;
	hdlp->dmai_error.err_status = status;
	hdlp->dmai_error.err_expected = flag;
	atomic_add_64(&fmhdl->fh_kstat.fek_dma_err.value.ui64, 1);
}