Home | History | Annotate | Download | only in os
      1 /*
      2  * CDDL HEADER START
      3  *
      4  * The contents of this file are subject to the terms of the
      5  * Common Development and Distribution License (the "License").
      6  * You may not use this file except in compliance with the License.
      7  *
      8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
      9  * or http://www.opensolaris.org/os/licensing.
     10  * See the License for the specific language governing permissions
     11  * and limitations under the License.
     12  *
     13  * When distributing Covered Code, include this CDDL HEADER in each
     14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
     15  * If applicable, add the following below this CDDL HEADER, with the
     16  * fields enclosed by brackets "[]" replaced with your own identifying
     17  * information: Portions Copyright [yyyy] [name of copyright owner]
     18  *
     19  * CDDL HEADER END
     20  */
     21 /*
     22  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
     23  * Use is subject to license terms.
     24  */
     25 
     26 #pragma ident	"@(#)ndifm.c	1.10	07/03/02 SMI"
     27 
     28 /*
     29  * Fault Management for Nexus Device Drivers
     30  *
     31  * In addition to implementing and supporting Fault Management for Device
     32  * Drivers (ddifm.c), nexus drivers must support their children by
     33  * reporting FM capabilities, intializing interrupt block cookies
     34  * for error handling callbacks and caching mapped resources for lookup
     35  * during the detection of an IO transaction error.
     36  *
     37  * It is typically the nexus driver that receives an error indication
     38  * for a fault that may have occurred in the data path of an IO transaction.
     39  * Errors may be detected or received via an interrupt, a callback from
     40  * another subsystem (e.g. a cpu trap) or examination of control data.
     41  *
     42  * Upon detection of an error, the nexus has a responsibility to alert
     43  * its children of the error and the transaction associated with that
     44  * error.  The actual implementation may vary depending upon the capabilities
     45  * of the nexus, its underlying hardware and its children.  In this file,
     46  * we provide support for typical nexus driver fault management tasks.
     47  *
     48  * Fault Management Initialization
     49  *
     50  *      Nexus drivers must implement two new busops, bus_fm_init() and
     51  *      bus_fm_fini().  bus_fm_init() is called from a child nexus or device
     52  *      driver and is expected to initialize any per-child state and return
     53  *      the FM and error interrupt priority levels of the nexus driver.
     54  *      Similarly, bus_fm_fini() is called by child drivers and should
     55  *      clean-up any resources allocated during bus_fm_init().
     56  *      These functions are called from passive kernel context, typically from
     57  *      driver attach(9F) and detach(9F) entry points.
     58  *
     59  * Error Handler Dispatching
     60  *
     61  *      Nexus drivers implemented to support error handler capabilities
     62  *	should invoke registered error handler callbacks for child drivers
     63  *	thought to be involved in the error.
     64  *	ndi_fm_handler_dispatch() is used to invoke
     65  *      all error handlers and returns one of the following status
     66  *      indications:
     67  *
     68  *      DDI_FM_OK - No errors found by any child
     69  *      DDI_FM_FATAL - one or more children have detected a fatal error
     70  *      DDI_FM_NONFATAL - no fatal errors, but one or more children have
     71  *                            detected a non-fatal error
     72  *
     73  *      ndi_fm_handler_dispatch() may be called in any context
     74  *      subject to the constraints specified by the interrupt iblock cookie
     75  *      returned during initialization.
     76  *
     77  * Protected Accesses
     78  *
     79  *      When an access handle is mapped or a DMA handle is bound via the
     80  *      standard busops, bus_map() or bus_dma_bindhdl(), a child driver
     81  *      implemented to support DDI_FM_ACCCHK_CAPABLE or
     82  *	DDI_FM_DMACHK_CAPABLE capabilites
     83  *	expects the nexus to flag any errors detected for transactions
     84  *	associated with the mapped or bound handles.
     85  *
     86  *      Children nexus or device drivers will set the following flags
     87  *      in their ddi_device_access or dma_attr_flags when requesting
     88  *      the an access or DMA handle mapping:
     89  *
     90  *      DDI_DMA_FLAGERR - nexus should set error status for any errors
     91  *                              detected for a failed DMA transaction.
     92  *      DDI_ACC_FLAGERR - nexus should set error status for any errors
     93  *                              detected for a failed PIO transaction.
     94  *
     95  *      A nexus is expected to provide additional error detection and
     96  *      handling for handles with these flags set.
     97  *
     98  * Exclusive Bus Access
     99  *
    100  *      In cases where a driver requires a high level of fault tolerance
    101  *      for a programmed IO transaction, it is neccessary to grant exclusive
    102  *      access to the bus resource.  Exclusivity guarantees that a fault
    103  *      resulting from a transaction on the bus can be easily traced and
    104  *      reported to the driver requesting the transaction.
    105  *
    106  *      Nexus drivers must implement two new busops to support exclusive
    107  *      access, bus_fm_access_enter() and bus_fm_access_exit().  The IO
    108  *      framework will use these functions when it must set-up access
    109  *      handles that set devacc_attr_access to DDI_ACC_CAUTIOUS in
    110  *      their ddi_device_acc_attr_t request.
    111  *
    112  *      Upon receipt of a bus_fm_access_enter() request, the nexus must prevent
    113  *      all other access requests until it receives bus_fm_access_exit()
    114  *      for the requested bus instance. bus_fm_access_enter() and
    115  *	bus_fm_access_exit() may be called from user, kernel or kernel
    116  *	interrupt context.
    117  *
    118  * Access and DMA Handle Caching
    119  *
    120  *      To aid a nexus driver in associating access or DMA handles with
    121  *      a detected error, the nexus should cache all handles that are
    122  *      associated with DDI_ACC_FLAGERR, DDI_ACC_CAUTIOUS_ACC or
    123  *	DDI_DMA_FLAGERR requests from its children.  ndi_fmc_insert() is
    124  *	called by a nexus to cache handles with the above protection flags
    125  *	and ndi_fmc_remove() is called when that handle is unmapped or
    126  *	unbound by the requesting child.  ndi_fmc_insert() and
    127  *	ndi_fmc_remove() may be called from any user or kernel context.
    128  *
    129  *	FM caches are allocated during ddi_fm_init() and maintained
    130  *	as an array of elements that may be on one of two lists:
    131  *	free or active.  The free list is a singly-linked list of
    132  *	elements available for activity.  ndi_fm_insert() moves the
    133  *	element at the head of the free to the active list.  The active
    134  *	list is a doubly-linked searchable list.
    135  *	When a handle is unmapped or unbound, its associated cache
    136  *	entry is removed from the active list back to the free list.
    137  *
    138  *      Upon detection of an error, the nexus may invoke ndi_fmc_error() to
    139  *      iterate over the handle cache of one or more of its FM compliant
    140  *      children.  A comparison callback function is provided upon each
    141  *      invocation of ndi_fmc_error() to tell the IO framework if a
    142  *      handle is associated with an error.  If so, the framework will
    143  *      set the error status for that handle before returning from
    144  *      ndi_fmc_error().
    145  *
    146  *      ndi_fmc_error() may be called in any context
    147  *      subject to the constraints specified by the interrupt iblock cookie
    148  *      returned during initialization of the nexus and its children.
    149  *
    150  */
    151 
    152 #include <sys/types.h>
    153 #include <sys/param.h>
    154 #include <sys/debug.h>
    155 #include <sys/sunddi.h>
    156 #include <sys/sunndi.h>
    157 #include <sys/ddi.h>
    158 #include <sys/ndi_impldefs.h>
    159 #include <sys/devctl.h>
    160 #include <sys/nvpair.h>
    161 #include <sys/ddifm.h>
    162 #include <sys/ndifm.h>
    163 #include <sys/spl.h>
    164 #include <sys/sysmacros.h>
    165 #include <sys/devops.h>
    166 #include <sys/atomic.h>
    167 #include <sys/fm/io/ddi.h>
    168 
    169 /*
    170  * Allocate and initialize a fault management resource cache
    171  * A fault management cache consists of a set of cache elements that
    172  * may be on one of two lists: free or active.
    173  *
    174  * At creation time, every element but one is placed on the free list
    175  * except for the first element.  This element is reserved as the first
    176  * element of the active list and serves as an anchor for the active
    177  * list in ndi_fmc_insert() and ndi_fmc_remove().  In these functions,
    178  * it is not neccessary to check for the existence or validity of
    179  * the active list.
    180  */
    181 void
    182 i_ndi_fmc_create(ndi_fmc_t **fcpp, int qlen, ddi_iblock_cookie_t ibc)
    183 {
    184 	ndi_fmc_t *fcp;
    185 	ndi_fmcentry_t *fep;
    186 
    187 	ASSERT(qlen > 1);
    188 
    189 	fcp = kmem_zalloc(sizeof (ndi_fmc_t), KM_SLEEP);
    190 	mutex_init(&fcp->fc_lock, NULL, MUTEX_DRIVER, ibc);
    191 	mutex_init(&fcp->fc_free_lock, NULL, MUTEX_DRIVER, NULL);
    192 
    193 	/* Preallocate and initialize entries for this fm cache */
    194 	fcp->fc_elems = kmem_zalloc(qlen * sizeof (ndi_fmcentry_t), KM_SLEEP);
    195 
    196 	fcp->fc_len = qlen;
    197 
    198 	/* Intialize the active and free lists */
    199 	fcp->fc_active = fcp->fc_tail = fcp->fc_elems;
    200 	fcp->fc_free = fcp->fc_elems + 1;
    201 	qlen--;
    202 	for (fep = fcp->fc_free; qlen > 1; qlen--) {
    203 		fep->fce_prev = fep + 1;
    204 		fep++;
    205 	}
    206 
    207 	*fcpp = fcp;
    208 }
    209 
    210 /*
    211  * Destroy and resources associated with the given fault management cache.
    212  */
    213 void
    214 i_ndi_fmc_destroy(ndi_fmc_t *fcp)
    215 {
    216 	if (fcp == NULL)
    217 		return;
    218 
    219 	kmem_free(fcp->fc_elems, fcp->fc_len * sizeof (ndi_fmcentry_t));
    220 	kmem_free(fcp, sizeof (ndi_fmc_t));
    221 }
    222 
    223 /*
    224  * Grow an existing fault management cache by grow_sz number of entries
    225  */
    226 static int
    227 fmc_grow(ndi_fmc_t *fcp, int flag, int grow_sz)
    228 {
    229 	int olen, nlen;
    230 	void *resource;
    231 	ndi_fmcentry_t *ncp, *oep, *nep, *nnep;
    232 
    233 	ASSERT(grow_sz);
    234 	ASSERT(MUTEX_HELD(&fcp->fc_free_lock));
    235 
    236 	/* Allocate a new cache */
    237 	nlen = grow_sz + fcp->fc_len;
    238 	if ((ncp = kmem_zalloc(nlen * sizeof (ndi_fmcentry_t),
    239 	    KM_NOSLEEP)) == NULL)
    240 		return (1);
    241 
    242 	/* Migrate old cache to new cache */
    243 	oep = fcp->fc_elems;
    244 	olen = fcp->fc_len;
    245 	for (nep = ncp; ; olen--) {
    246 		resource = nep->fce_resource = oep->fce_resource;
    247 		nep->fce_bus_specific = oep->fce_bus_specific;
    248 		if (resource) {
    249 			if (flag == DMA_HANDLE) {
    250 				((ddi_dma_impl_t *)resource)->
    251 				    dmai_error.err_fep = nep;
    252 			} else if (flag == ACC_HANDLE) {
    253 				((ddi_acc_impl_t *)resource)->
    254 				    ahi_err->err_fep = nep;
    255 			}
    256 		}
    257 
    258 		/*
    259 		 * This is the last entry.  Set the tail pointer and
    260 		 * terminate processing of the old cache.
    261 		 */
    262 		if (olen == 1) {
    263 			fcp->fc_tail = nep;
    264 			++nep;
    265 			break;
    266 		}
    267 
    268 		/*
    269 		 * Set the next and previous pointer for the new cache
    270 		 * entry.
    271 		 */
    272 		nnep = nep + 1;
    273 		nep->fce_next = nnep;
    274 		nnep->fce_prev = nep;
    275 
    276 		/* Advance to the next entry */
    277 		++oep;
    278 		nep = nnep;
    279 	}
    280 
    281 	/* Initialize and add remaining new cache entries to the free list */
    282 	for (fcp->fc_free = nep; nlen > fcp->fc_len + 1; nlen--) {
    283 		nep->fce_prev = nep + 1;
    284 		nep++;
    285 	}
    286 
    287 	oep = fcp->fc_elems;
    288 	olen = fcp->fc_len;
    289 	nlen = grow_sz + olen;
    290 
    291 	/*
    292 	 * Update the FM cache array and active list pointers.
    293 	 * Updates to these pointers require us to acquire the
    294 	 * FMA cache lock to prevent accesses to a stale active
    295 	 * list in ndi_fmc_error().
    296 	 */
    297 
    298 	mutex_enter(&fcp->fc_lock);
    299 	fcp->fc_active = ncp;
    300 	fcp->fc_elems = ncp;
    301 	fcp->fc_len = nlen;
    302 	mutex_exit(&fcp->fc_lock);
    303 
    304 	kmem_free(oep, olen * sizeof (ndi_fmcentry_t));
    305 
    306 	return (0);
    307 }
    308 
    309 /*
    310  * ndi_fmc_insert -
    311  * 	Add a new entry to the specified cache.
    312  *
    313  * 	This function must be called at or below LOCK_LEVEL
    314  */
    315 void
    316 ndi_fmc_insert(dev_info_t *dip, int flag, void *resource, void *bus_specific)
    317 {
    318 	struct dev_info *devi = DEVI(dip);
    319 	ndi_fmc_t *fcp;
    320 	ndi_fmcentry_t *fep, **fpp;
    321 	struct i_ddi_fmhdl *fmhdl;
    322 
    323 	ASSERT(devi);
    324 	ASSERT(flag == DMA_HANDLE || flag == ACC_HANDLE);
    325 
    326 	fmhdl = devi->devi_fmhdl;
    327 	if (fmhdl == NULL) {
    328 		i_ddi_drv_ereport_post(dip, DVR_EFMCAP, NULL, DDI_NOSLEEP);
    329 		return;
    330 	}
    331 
    332 	if (flag == DMA_HANDLE) {
    333 		if (!DDI_FM_DMA_ERR_CAP(fmhdl->fh_cap)) {
    334 			i_ddi_drv_ereport_post(dip, DVR_EFMCAP, NULL,
    335 			    DDI_NOSLEEP);
    336 			return;
    337 		}
    338 		fcp = fmhdl->fh_dma_cache;
    339 		fpp = &((ddi_dma_impl_t *)resource)->dmai_error.err_fep;
    340 	} else if (flag == ACC_HANDLE) {
    341 		if (!DDI_FM_ACC_ERR_CAP(fmhdl->fh_cap)) {
    342 			i_ddi_drv_ereport_post(dip, DVR_EFMCAP, NULL,
    343 			    DDI_NOSLEEP);
    344 			return;
    345 		}
    346 		fcp = fmhdl->fh_acc_cache;
    347 		fpp = &((ddi_acc_impl_t *)resource)->ahi_err->err_fep;
    348 	}
    349 	ASSERT(*fpp == NULL);
    350 
    351 	mutex_enter(&fcp->fc_free_lock);
    352 
    353 	/* Get an entry from the free list */
    354 	fep = fcp->fc_free;
    355 	if (fep == NULL) {
    356 		if (fmc_grow(fcp, flag,
    357 		    (flag == ACC_HANDLE ? default_acccache_sz :
    358 		    default_dmacache_sz)) != 0) {
    359 
    360 			/* Unable to get an entry or grow this cache */
    361 			atomic_add_64(
    362 			    &fmhdl->fh_kstat.fek_fmc_full.value.ui64, 1);
    363 			mutex_exit(&fcp->fc_free_lock);
    364 			return;
    365 		}
    366 		atomic_add_64(&fmhdl->fh_kstat.fek_fmc_grew.value.ui64, 1);
    367 		fep = fcp->fc_free;
    368 	}
    369 	fcp->fc_free = fep->fce_prev;
    370 	mutex_exit(&fcp->fc_free_lock);
    371 
    372 	/*
    373 	 * Set-up the handle resource and bus_specific information.
    374 	 * Also remember the pointer back to the cache for quick removal.
    375 	 */
    376 	fep->fce_bus_specific = bus_specific;
    377 	fep->fce_resource = resource;
    378 	fep->fce_next = NULL;
    379 	*fpp = fep;
    380 
    381 	/* Add entry to the end of the active list */
    382 	mutex_enter(&fcp->fc_lock);
    383 	fep->fce_prev = fcp->fc_tail;
    384 	fcp->fc_tail->fce_next = fep;
    385 	fcp->fc_tail = fep;
    386 	mutex_exit(&fcp->fc_lock);
    387 }
    388 
    389 /*
    390  * 	Remove an entry from the specified cache of access or dma mappings
    391  *
    392  * 	This function must be called at or below LOCK_LEVEL.
    393  */
    394 void
    395 ndi_fmc_remove(dev_info_t *dip, int flag, const void *resource)
    396 {
    397 	ndi_fmc_t *fcp;
    398 	ndi_fmcentry_t *fep;
    399 	struct dev_info *devi = DEVI(dip);
    400 	struct i_ddi_fmhdl *fmhdl;
    401 
    402 	ASSERT(devi);
    403 	ASSERT(flag == DMA_HANDLE || flag == ACC_HANDLE);
    404 
    405 	fmhdl = devi->devi_fmhdl;
    406 	if (fmhdl == NULL) {
    407 		i_ddi_drv_ereport_post(dip, DVR_EFMCAP, NULL, DDI_NOSLEEP);
    408 		return;
    409 	}
    410 
    411 	/* Find cache entry pointer for this resource */
    412 	if (flag == DMA_HANDLE) {
    413 		if (!DDI_FM_DMA_ERR_CAP(fmhdl->fh_cap)) {
    414 			i_ddi_drv_ereport_post(dip, DVR_EFMCAP, NULL,
    415 			    DDI_NOSLEEP);
    416 			return;
    417 		}
    418 		fcp = fmhdl->fh_dma_cache;
    419 
    420 		ASSERT(fcp);
    421 
    422 		mutex_enter(&fcp->fc_free_lock);
    423 		fep = ((ddi_dma_impl_t *)resource)->dmai_error.err_fep;
    424 		((ddi_dma_impl_t *)resource)->dmai_error.err_fep = NULL;
    425 	} else if (flag == ACC_HANDLE) {
    426 		if (!DDI_FM_ACC_ERR_CAP(fmhdl->fh_cap)) {
    427 			i_ddi_drv_ereport_post(dip, DVR_EFMCAP, NULL,
    428 			    DDI_NOSLEEP);
    429 			return;
    430 		}
    431 		fcp = fmhdl->fh_acc_cache;
    432 
    433 		ASSERT(fcp);
    434 
    435 		mutex_enter(&fcp->fc_free_lock);
    436 		fep = ((ddi_acc_impl_t *)resource)->ahi_err->err_fep;
    437 		((ddi_acc_impl_t *)resource)->ahi_err->err_fep = NULL;
    438 	} else {
    439 		return;
    440 	}
    441 
    442 	/*
    443 	 * Resource not in cache, return
    444 	 */
    445 	if (fep == NULL) {
    446 		mutex_exit(&fcp->fc_free_lock);
    447 		return;
    448 	}
    449 
    450 	/*
    451 	 * Updates to FM cache pointers require us to grab fmc_lock
    452 	 * to synchronize access to the cache for ndi_fmc_insert()
    453 	 * and ndi_fmc_error()
    454 	 */
    455 	mutex_enter(&fcp->fc_lock);
    456 	fep->fce_prev->fce_next = fep->fce_next;
    457 	if (fep == fcp->fc_tail)
    458 		fcp->fc_tail = fep->fce_prev;
    459 	else
    460 		fep->fce_next->fce_prev = fep->fce_prev;
    461 	mutex_exit(&fcp->fc_lock);
    462 
    463 	/* Add entry back to the free list */
    464 	fep->fce_prev = fcp->fc_free;
    465 	fcp->fc_free = fep;
    466 	mutex_exit(&fcp->fc_free_lock);
    467 }
    468 
    469 int
    470 ndi_fmc_entry_error(dev_info_t *dip, int flag, ddi_fm_error_t *derr,
    471     const void *bus_err_state)
    472 {
    473 	int status, fatal = 0, nonfatal = 0;
    474 	ndi_fmc_t *fcp = NULL;
    475 	ndi_fmcentry_t *fep;
    476 	struct i_ddi_fmhdl *fmhdl;
    477 
    478 	ASSERT(flag == DMA_HANDLE || flag == ACC_HANDLE);
    479 
    480 	fmhdl = DEVI(dip)->devi_fmhdl;
    481 	ASSERT(fmhdl);
    482 	status = DDI_FM_UNKNOWN;
    483 
    484 	if (flag == DMA_HANDLE && DDI_FM_DMA_ERR_CAP(fmhdl->fh_cap)) {
    485 		fcp = fmhdl->fh_dma_cache;
    486 		ASSERT(fcp);
    487 	} else if (flag == ACC_HANDLE && DDI_FM_ACC_ERR_CAP(fmhdl->fh_cap)) {
    488 		fcp = fmhdl->fh_acc_cache;
    489 		ASSERT(fcp);
    490 	}
    491 
    492 	if (fcp != NULL) {
    493 
    494 		/*
    495 		 * Check active resource entries
    496 		 */
    497 		mutex_enter(&fcp->fc_lock);
    498 		for (fep = fcp->fc_active->fce_next; fep != NULL;
    499 		    fep = fep->fce_next) {
    500 			ddi_fmcompare_t compare_func;
    501 
    502 			/*
    503 			 * Compare captured error state with handle
    504 			 * resources.  During the comparison and
    505 			 * subsequent error handling, we block
    506 			 * attempts to free the cache entry.
    507 			 */
    508 			compare_func = (flag == ACC_HANDLE) ?
    509 			    i_ddi_fm_acc_err_cf_get((ddi_acc_handle_t)
    510 				fep->fce_resource) :
    511 			    i_ddi_fm_dma_err_cf_get((ddi_dma_handle_t)
    512 				fep->fce_resource);
    513 
    514 			status = compare_func(dip, fep->fce_resource,
    515 			    bus_err_state, fep->fce_bus_specific);
    516 			if (status == DDI_FM_UNKNOWN || status == DDI_FM_OK)
    517 				continue;
    518 
    519 			if (status == DDI_FM_FATAL)
    520 				++fatal;
    521 			else if (status == DDI_FM_NONFATAL)
    522 				++nonfatal;
    523 
    524 			/* Set the error for this resource handle */
    525 			if (flag == ACC_HANDLE) {
    526 				ddi_acc_handle_t ap = fep->fce_resource;
    527 
    528 				i_ddi_fm_acc_err_set(ap, derr->fme_ena, status,
    529 				    DDI_FM_ERR_UNEXPECTED);
    530 				ddi_fm_acc_err_get(ap, derr, DDI_FME_VERSION);
    531 				derr->fme_acc_handle = ap;
    532 			} else {
    533 				ddi_dma_handle_t dp = fep->fce_resource;
    534 
    535 				i_ddi_fm_dma_err_set(dp, derr->fme_ena, status,
    536 				    DDI_FM_ERR_UNEXPECTED);
    537 				ddi_fm_dma_err_get(dp, derr, DDI_FME_VERSION);
    538 				derr->fme_dma_handle = dp;
    539 			}
    540 			break;
    541 		}
    542 		mutex_exit(&fcp->fc_lock);
    543 	}
    544 	return (fatal ? DDI_FM_FATAL : nonfatal ? DDI_FM_NONFATAL :
    545 	    DDI_FM_UNKNOWN);
    546 }
    547 
    548 /*
    549  * Check error state against the handle resource stored in the specified
    550  * FM cache.  If tdip != NULL, we check only the cache entries for tdip.
    551  * The caller must ensure that tdip is valid throughout the call and
    552  * all FM data structures can be safely accesses.
    553  *
    554  * If tdip == NULL, we check all children that have registered their
    555  * FM_DMA_CHK or FM_ACC_CHK capabilities.
    556  *
    557  * The following status values may be returned:
    558  *
    559  *	DDI_FM_FATAL - if at least one cache entry comparison yields a
    560  *			fatal error.
    561  *
    562  *	DDI_FM_NONFATAL - if at least one cache entry comparison yields a
    563  *			non-fatal error and no comparison yields a fatal error.
    564  *
    565  *	DDI_FM_UNKNOWN - cache entry comparisons did not yield fatal or
    566  *			non-fatal errors.
    567  *
    568  */
    569 int
    570 ndi_fmc_error(dev_info_t *dip, dev_info_t *tdip, int flag, uint64_t ena,
    571     const void *bus_err_state)
    572 {
    573 	int status, fatal = 0, nonfatal = 0;
    574 	ddi_fm_error_t derr;
    575 	struct i_ddi_fmhdl *fmhdl;
    576 	struct i_ddi_fmtgt *tgt;
    577 
    578 	ASSERT(flag == DMA_HANDLE || flag == ACC_HANDLE);
    579 
    580 	i_ddi_fm_handler_enter(dip);
    581 	fmhdl = DEVI(dip)->devi_fmhdl;
    582 	ASSERT(fmhdl);
    583 
    584 	bzero(&derr, sizeof (ddi_fm_error_t));
    585 	derr.fme_version = DDI_FME_VERSION;
    586 	derr.fme_flag = DDI_FM_ERR_UNEXPECTED;
    587 	derr.fme_ena = ena;
    588 
    589 	for (tgt = fmhdl->fh_tgts; tgt != NULL; tgt = tgt->ft_next) {
    590 
    591 		if (tdip != NULL && tdip != tgt->ft_dip)
    592 			continue;
    593 
    594 		/*
    595 		 * Attempt to find the entry in this childs handle cache
    596 		 */
    597 		status = ndi_fmc_entry_error(tgt->ft_dip, flag, &derr,
    598 		    bus_err_state);
    599 
    600 		if (status == DDI_FM_FATAL)
    601 			++fatal;
    602 		else if (status == DDI_FM_NONFATAL)
    603 			++nonfatal;
    604 		else
    605 			continue;
    606 
    607 		/*
    608 		 * Call our child to process this error.
    609 		 */
    610 		status = tgt->ft_errhdl->eh_func(tgt->ft_dip, &derr,
    611 		    tgt->ft_errhdl->eh_impl);
    612 
    613 		if (status == DDI_FM_FATAL)
    614 			++fatal;
    615 		else if (status == DDI_FM_NONFATAL)
    616 			++nonfatal;
    617 	}
    618 
    619 	i_ddi_fm_handler_exit(dip);
    620 
    621 	if (fatal)
    622 		return (DDI_FM_FATAL);
    623 	else if (nonfatal)
    624 		return (DDI_FM_NONFATAL);
    625 
    626 	return (DDI_FM_UNKNOWN);
    627 }
    628 
    629 int
    630 ndi_fmc_entry_error_all(dev_info_t *dip, int flag, ddi_fm_error_t *derr)
    631 {
    632 	ndi_fmc_t *fcp = NULL;
    633 	ndi_fmcentry_t *fep;
    634 	struct i_ddi_fmhdl *fmhdl;
    635 	int nonfatal = 0;
    636 
    637 	ASSERT(flag == DMA_HANDLE || flag == ACC_HANDLE);
    638 
    639 	fmhdl = DEVI(dip)->devi_fmhdl;
    640 	ASSERT(fmhdl);
    641 
    642 	if (flag == DMA_HANDLE && DDI_FM_DMA_ERR_CAP(fmhdl->fh_cap)) {
    643 		fcp = fmhdl->fh_dma_cache;
    644 		ASSERT(fcp);
    645 	} else if (flag == ACC_HANDLE && DDI_FM_ACC_ERR_CAP(fmhdl->fh_cap)) {
    646 		fcp = fmhdl->fh_acc_cache;
    647 		ASSERT(fcp);
    648 	}
    649 
    650 	if (fcp != NULL) {
    651 		/*
    652 		 * Check active resource entries
    653 		 */
    654 		mutex_enter(&fcp->fc_lock);
    655 		for (fep = fcp->fc_active->fce_next; fep != NULL;
    656 		    fep = fep->fce_next) {
    657 			/* Set the error for this resource handle */
    658 			nonfatal++;
    659 			if (flag == ACC_HANDLE) {
    660 				ddi_acc_handle_t ap = fep->fce_resource;
    661 
    662 				i_ddi_fm_acc_err_set(ap, derr->fme_ena,
    663 				    DDI_FM_NONFATAL, DDI_FM_ERR_UNEXPECTED);
    664 				ddi_fm_acc_err_get(ap, derr, DDI_FME_VERSION);
    665 				derr->fme_acc_handle = ap;
    666 			} else {
    667 				ddi_dma_handle_t dp = fep->fce_resource;
    668 
    669 				i_ddi_fm_dma_err_set(dp, derr->fme_ena,
    670 				    DDI_FM_NONFATAL, DDI_FM_ERR_UNEXPECTED);
    671 				ddi_fm_dma_err_get(dp, derr, DDI_FME_VERSION);
    672 				derr->fme_dma_handle = dp;
    673 			}
    674 		}
    675 		mutex_exit(&fcp->fc_lock);
    676 	}
    677 	return (nonfatal ? DDI_FM_NONFATAL : DDI_FM_UNKNOWN);
    678 }
    679 
    680 /*
    681  * Dispatch registered error handlers for dip.  If tdip != NULL, only
    682  * the error handler (if available) for tdip is invoked.  Otherwise,
    683  * all registered error handlers are invoked.
    684  *
    685  * The following status values may be returned:
    686  *
    687  *	DDI_FM_FATAL - if at least one error handler returns a
    688  *			fatal error.
    689  *
    690  *	DDI_FM_NONFATAL - if at least one error handler returns a
    691  *			non-fatal error and none returned a fatal error.
    692  *
    693  *	DDI_FM_UNKNOWN - if at least one error handler returns
    694  *			unknown status and none return fatal or non-fatal.
    695  *
    696  *	DDI_FM_OK - if all error handlers return DDI_FM_OK
    697  */
    698 int
    699 ndi_fm_handler_dispatch(dev_info_t *dip, dev_info_t *tdip,
    700     const ddi_fm_error_t *nerr)
    701 {
    702 	int status;
    703 	int unknown = 0, fatal = 0, nonfatal = 0;
    704 	struct i_ddi_fmhdl *hdl;
    705 	struct i_ddi_fmtgt *tgt;
    706 
    707 	status = DDI_FM_UNKNOWN;
    708 
    709 	i_ddi_fm_handler_enter(dip);
    710 	hdl = DEVI(dip)->devi_fmhdl;
    711 	tgt = hdl->fh_tgts;
    712 	while (tgt != NULL) {
    713 		if (tdip == NULL || tdip == tgt->ft_dip) {
    714 			struct i_ddi_errhdl *errhdl;
    715 
    716 			errhdl = tgt->ft_errhdl;
    717 			status = errhdl->eh_func(tgt->ft_dip, nerr,
    718 			    errhdl->eh_impl);
    719 
    720 			if (status == DDI_FM_FATAL)
    721 				++fatal;
    722 			else if (status == DDI_FM_NONFATAL)
    723 				++nonfatal;
    724 			else if (status == DDI_FM_UNKNOWN)
    725 				++unknown;
    726 
    727 			/* Only interested in one target */
    728 			if (tdip != NULL)
    729 				break;
    730 		}
    731 		tgt = tgt->ft_next;
    732 	}
    733 	i_ddi_fm_handler_exit(dip);
    734 
    735 	if (fatal)
    736 		return (DDI_FM_FATAL);
    737 	else if (nonfatal)
    738 		return (DDI_FM_NONFATAL);
    739 	else if (unknown)
    740 		return (DDI_FM_UNKNOWN);
    741 	else
    742 		return (DDI_FM_OK);
    743 }
    744 
    745 /*
    746  * Set error status for specified access or DMA handle
    747  *
    748  * May be called in any context but caller must insure validity of
    749  * handle.
    750  */
    751 void
    752 ndi_fm_acc_err_set(ddi_acc_handle_t handle, ddi_fm_error_t *dfe)
    753 {
    754 	i_ddi_fm_acc_err_set(handle, dfe->fme_ena, dfe->fme_status,
    755 	    dfe->fme_flag);
    756 }
    757 
    758 void
    759 ndi_fm_dma_err_set(ddi_dma_handle_t handle, ddi_fm_error_t *dfe)
    760 {
    761 	i_ddi_fm_dma_err_set(handle, dfe->fme_ena, dfe->fme_status,
    762 	    dfe->fme_flag);
    763 }
    764 
    765 /*
    766  * Call parent busop fm initialization routine.
    767  *
    768  * Called during driver attach(1M)
    769  */
    770 int
    771 i_ndi_busop_fm_init(dev_info_t *dip, int tcap, ddi_iblock_cookie_t *ibc)
    772 {
    773 	int pcap;
    774 	dev_info_t *pdip = (dev_info_t *)DEVI(dip)->devi_parent;
    775 
    776 	if (dip == ddi_root_node())
    777 		return (ddi_system_fmcap | DDI_FM_EREPORT_CAPABLE);
    778 
    779 	/* Valid operation for BUSO_REV_6 and above */
    780 	if (DEVI(pdip)->devi_ops->devo_bus_ops->busops_rev < BUSO_REV_6)
    781 		return (DDI_FM_NOT_CAPABLE);
    782 
    783 	if (DEVI(pdip)->devi_ops->devo_bus_ops->bus_fm_init == NULL)
    784 		return (DDI_FM_NOT_CAPABLE);
    785 
    786 	pcap = (*DEVI(pdip)->devi_ops->devo_bus_ops->bus_fm_init)
    787 	    (pdip, dip, tcap, ibc);
    788 
    789 	return (pcap);
    790 }
    791 
    792 /*
    793  * Call parent busop fm clean-up routine.
    794  *
    795  * Called during driver detach(1M)
    796  */
    797 void
    798 i_ndi_busop_fm_fini(dev_info_t *dip)
    799 {
    800 	dev_info_t *pdip = (dev_info_t *)DEVI(dip)->devi_parent;
    801 
    802 	if (dip == ddi_root_node())
    803 		return;
    804 
    805 	/* Valid operation for BUSO_REV_6 and above */
    806 	if (DEVI(pdip)->devi_ops->devo_bus_ops->busops_rev < BUSO_REV_6)
    807 		return;
    808 
    809 	if (DEVI(pdip)->devi_ops->devo_bus_ops->bus_fm_fini == NULL)
    810 		return;
    811 
    812 	(*DEVI(pdip)->devi_ops->devo_bus_ops->bus_fm_fini)(pdip, dip);
    813 }
    814 
    815 /*
    816  * The following routines provide exclusive access to a nexus resource
    817  *
    818  * These busops may be called in user or kernel driver context.
    819  */
    820 void
    821 i_ndi_busop_access_enter(dev_info_t *dip, ddi_acc_handle_t handle)
    822 {
    823 	dev_info_t *pdip = (dev_info_t *)DEVI(dip)->devi_parent;
    824 
    825 	/* Valid operation for BUSO_REV_6 and above */
    826 	if (DEVI(pdip)->devi_ops->devo_bus_ops->busops_rev < BUSO_REV_6)
    827 		return;
    828 
    829 	if (DEVI(pdip)->devi_ops->devo_bus_ops->bus_fm_access_enter == NULL)
    830 		return;
    831 
    832 	(*DEVI(pdip)->devi_ops->devo_bus_ops->bus_fm_access_enter)
    833 	    (pdip, handle);
    834 }
    835 
    836 void
    837 i_ndi_busop_access_exit(dev_info_t *dip, ddi_acc_handle_t handle)
    838 {
    839 	dev_info_t *pdip = (dev_info_t *)DEVI(dip)->devi_parent;
    840 
    841 	/* Valid operation for BUSO_REV_6 and above */
    842 	if (DEVI(pdip)->devi_ops->devo_bus_ops->busops_rev < BUSO_REV_6)
    843 		return;
    844 
    845 	if (DEVI(pdip)->devi_ops->devo_bus_ops->bus_fm_access_exit == NULL)
    846 		return;
    847 
    848 	(*DEVI(pdip)->devi_ops->devo_bus_ops->bus_fm_access_exit)(pdip, handle);
    849 }
    850