summaryrefslogtreecommitdiff
path: root/usr/src/uts/common/os/ddifm.c
diff options
context:
space:
mode:
authorstevel@tonic-gate <none@none>2005-06-14 00:00:00 -0700
committerstevel@tonic-gate <none@none>2005-06-14 00:00:00 -0700
commit7c478bd95313f5f23a4c958a745db2134aa03244 (patch)
treec871e58545497667cbb4b0a4f2daf204743e1fe7 /usr/src/uts/common/os/ddifm.c
downloadillumos-joyent-7c478bd95313f5f23a4c958a745db2134aa03244.tar.gz
OpenSolaris Launch
Diffstat (limited to 'usr/src/uts/common/os/ddifm.c')
-rw-r--r--usr/src/uts/common/os/ddifm.c842
1 files changed, 842 insertions, 0 deletions
diff --git a/usr/src/uts/common/os/ddifm.c b/usr/src/uts/common/os/ddifm.c
new file mode 100644
index 0000000000..911a7d9a2f
--- /dev/null
+++ b/usr/src/uts/common/os/ddifm.c
@@ -0,0 +1,842 @@
+/*
+ * 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"
+
+/*
+ * 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;
+ }
+
+ 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;
+ }
+
+ 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 *ibc)
+{
+ struct dev_info *devi = DEVI(dip);
+ struct i_ddi_fmhdl *fmhdl;
+ 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;
+ }
+
+ /* 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;
+}
+
+/*
+ * 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);
+}