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