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