ndifm.c revision 6b804b7dd19910467b5d3bdaca7ed2f66cc5ad58
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (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
* 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 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* Fault Management for Nexus Device Drivers
*
* In addition to implementing and supporting Fault Management for Device
* Drivers (ddifm.c), nexus drivers must support their children by
* reporting FM capabilities, intializing interrupt block cookies
* for error handling callbacks and caching mapped resources for lookup
* during the detection of an IO transaction error.
*
* It is typically the nexus driver that receives an error indication
* for a fault that may have occurred in the data path of an IO transaction.
* Errors may be detected or received via an interrupt, a callback from
* another subsystem (e.g. a cpu trap) or examination of control data.
*
* Upon detection of an error, the nexus has a responsibility to alert
* its children of the error and the transaction associated with that
* error. The actual implementation may vary depending upon the capabilities
* of the nexus, its underlying hardware and its children. In this file,
* we provide support for typical nexus driver fault management tasks.
*
* Fault Management Initialization
*
* Nexus drivers must implement two new busops, bus_fm_init() and
* bus_fm_fini(). bus_fm_init() is called from a child nexus or device
* driver and is expected to initialize any per-child state and return
* the FM and error interrupt priority levels of the nexus driver.
* Similarly, bus_fm_fini() is called by child drivers and should
* clean-up any resources allocated during bus_fm_init().
* These functions are called from passive kernel context, typically from
* driver attach(9F) and detach(9F) entry points.
*
* Error Handler Dispatching
*
* Nexus drivers implemented to support error handler capabilities
* should invoke registered error handler callbacks for child drivers
* thought to be involved in the error.
* ndi_fm_handler_dispatch() is used to invoke
* all error handlers and returns one of the following status
* indications:
*
* DDI_FM_OK - No errors found by any child
* DDI_FM_FATAL - one or more children have detected a fatal error
* DDI_FM_NONFATAL - no fatal errors, but one or more children have
* detected a non-fatal error
*
* ndi_fm_handler_dispatch() may be called in any context
* subject to the constraints specified by the interrupt iblock cookie
* returned during initialization.
*
* Protected Accesses
*
* When an access handle is mapped or a DMA handle is bound via the
* standard busops, bus_map() or bus_dma_bindhdl(), a child driver
* implemented to support DDI_FM_ACCCHK_CAPABLE or
* DDI_FM_DMACHK_CAPABLE capabilites
* expects the nexus to flag any errors detected for transactions
* associated with the mapped or bound handles.
*
* Children nexus or device drivers will set the following flags
* in their ddi_device_access or dma_attr_flags when requesting
* the an access or DMA handle mapping:
*
* DDI_DMA_FLAGERR - nexus should set error status for any errors
* detected for a failed DMA transaction.
* DDI_ACC_FLAGERR - nexus should set error status for any errors
* detected for a failed PIO transaction.
*
* A nexus is expected to provide additional error detection and
* handling for handles with these flags set.
*
* Exclusive Bus Access
*
* In cases where a driver requires a high level of fault tolerance
* for a programmed IO transaction, it is neccessary to grant exclusive
* access to the bus resource. Exclusivity guarantees that a fault
* resulting from a transaction on the bus can be easily traced and
* reported to the driver requesting the transaction.
*
* Nexus drivers must implement two new busops to support exclusive
* access, bus_fm_access_enter() and bus_fm_access_exit(). The IO
* framework will use these functions when it must set-up access
* handles that set devacc_attr_access to DDI_ACC_CAUTIOUS in
* their ddi_device_acc_attr_t request.
*
* Upon receipt of a bus_fm_access_enter() request, the nexus must prevent
* all other access requests until it receives bus_fm_access_exit()
* for the requested bus instance. bus_fm_access_enter() and
* bus_fm_access_exit() may be called from user, kernel or kernel
* interrupt context.
*
* Access and DMA Handle Caching
*
* To aid a nexus driver in associating access or DMA handles with
* a detected error, the nexus should cache all handles that are
* associated with DDI_ACC_FLAGERR, DDI_ACC_CAUTIOUS_ACC or
* DDI_DMA_FLAGERR requests from its children. ndi_fmc_insert() is
* called by a nexus to cache handles with the above protection flags
* and ndi_fmc_remove() is called when that handle is unmapped or
* unbound by the requesting child. ndi_fmc_insert() and
* ndi_fmc_remove() may be called from any user or kernel context.
*
* FM caches are allocated during ddi_fm_init() and maintained
* as an array of elements that may be on one of two lists:
* free or active. The free list is a singly-linked list of
* elements available for activity. ndi_fm_insert() moves the
* element at the head of the free to the active list. The active
* list is a doubly-linked searchable list.
* When a handle is unmapped or unbound, its associated cache
* entry is removed from the active list back to the free list.
*
* Upon detection of an error, the nexus may invoke ndi_fmc_error() to
* iterate over the handle cache of one or more of its FM compliant
* children. A comparison callback function is provided upon each
* invocation of ndi_fmc_error() to tell the IO framework if a
* handle is associated with an error. If so, the framework will
* set the error status for that handle before returning from
* ndi_fmc_error().
*
* ndi_fmc_error() may be called in any context
* subject to the constraints specified by the interrupt iblock cookie
* returned during initialization of the nexus and its children.
*
*/
#include <sys/ndi_impldefs.h>
#include <sys/sysmacros.h>
/*
* Allocate and initialize a fault management resource cache
* A fault management cache consists of a set of cache elements that
* may be on one of two lists: free or active.
*
* At creation time, every element but one is placed on the free list
* except for the first element. This element is reserved as the first
* element of the active list and serves as an anchor for the active
* list in ndi_fmc_insert() and ndi_fmc_remove(). In these functions,
* it is not neccessary to check for the existence or validity of
* the active list.
*/
void
{
/* Preallocate and initialize entries for this fm cache */
/* Intialize the active and free lists */
qlen--;
fep++;
}
}
/*
* Destroy and resources associated with the given fault management cache.
*/
void
{
return;
}
/*
* Grow an existing fault management cache by grow_sz number of entries
*/
static int
{
void *resource;
/* Allocate a new cache */
KM_NOSLEEP)) == NULL)
return (1);
/* Migrate old cache to new cache */
if (resource) {
if (flag == DMA_HANDLE) {
((ddi_dma_impl_t *)resource)->
} else if (flag == ACC_HANDLE) {
((ddi_acc_impl_t *)resource)->
}
}
/*
* This is the last entry. Set the tail pointer and
* terminate processing of the old cache.
*/
if (olen == 1) {
++nep;
break;
}
/*
* Set the next and previous pointer for the new cache
* entry.
*/
/* Advance to the next entry */
++oep;
}
/* Initialize and add remaining new cache entries to the free list */
nep++;
}
/*
* Update the FM cache array and active list pointers.
* Updates to these pointers require us to acquire the
* FMA cache lock to prevent accesses to a stale active
* list in ndi_fmc_error().
*/
return (0);
}
/*
* ndi_fmc_insert -
* Add a new entry to the specified cache.
*
* This function must be called at or below LOCK_LEVEL
*/
void
{
struct i_ddi_fmhdl *fmhdl;
return;
}
if (flag == DMA_HANDLE) {
return;
}
} else if (flag == ACC_HANDLE) {
return;
}
}
/* Get an entry from the free list */
default_dmacache_sz)) != 0) {
/* Unable to get an entry or grow this cache */
return;
}
}
/*
* Set-up the handle resource and bus_specific information.
* Also remember the pointer back to the cache for quick removal.
*/
/* Add entry to the end of the active list */
}
/*
* Remove an entry from the specified cache of access or dma mappings
*
* This function must be called at or below LOCK_LEVEL.
*/
void
{
struct i_ddi_fmhdl *fmhdl;
return;
}
/* Find cache entry pointer for this resource */
if (flag == DMA_HANDLE) {
return;
}
} else if (flag == ACC_HANDLE) {
return;
}
} else {
return;
}
/*
* Resource not in cache, return
*/
return;
}
/*
* Updates to FM cache pointers require us to grab fmc_lock
* to synchronize access to the cache for ndi_fmc_insert()
* and ndi_fmc_error()
*/
else
/* Add entry back to the free list */
}
int
const void *bus_err_state)
{
struct i_ddi_fmhdl *fmhdl;
}
/*
* Check active resource entries
*/
/*
* Compare captured error state with handle
* resources. During the comparison and
* subsequent error handling, we block
* attempts to free the cache entry.
*/
fep->fce_resource) :
fep->fce_resource);
continue;
if (status == DDI_FM_FATAL)
++fatal;
else if (status == DDI_FM_NONFATAL)
++nonfatal;
/* Set the error for this resource handle */
if (flag == ACC_HANDLE) {
} else {
}
break;
}
}
}
/*
* Check error state against the handle resource stored in the specified
* FM cache. If tdip != NULL, we check only the cache entries for tdip.
* The caller must ensure that tdip is valid throughout the call and
* all FM data structures can be safely accesses.
*
* If tdip == NULL, we check all children that have registered their
* FM_DMA_CHK or FM_ACC_CHK capabilities.
*
* The following status values may be returned:
*
* DDI_FM_FATAL - if at least one cache entry comparison yields a
* fatal error.
*
* DDI_FM_NONFATAL - if at least one cache entry comparison yields a
* non-fatal error and no comparison yields a fatal error.
*
* DDI_FM_UNKNOWN - cache entry comparisons did not yield fatal or
* non-fatal errors.
*
*/
int
const void *bus_err_state)
{
struct i_ddi_fmhdl *fmhdl;
struct i_ddi_fmtgt *tgt;
continue;
/*
* Attempt to find the entry in this childs handle cache
*/
if (status == DDI_FM_FATAL)
++fatal;
else if (status == DDI_FM_NONFATAL)
++nonfatal;
else
continue;
/*
* Call our child to process this error.
*/
if (status == DDI_FM_FATAL)
++fatal;
else if (status == DDI_FM_NONFATAL)
++nonfatal;
}
if (fatal)
return (DDI_FM_FATAL);
else if (nonfatal)
return (DDI_FM_NONFATAL);
return (DDI_FM_UNKNOWN);
}
int
{
struct i_ddi_fmhdl *fmhdl;
int nonfatal = 0;
}
/*
* Check active resource entries
*/
/* Set the error for this resource handle */
nonfatal++;
if (flag == ACC_HANDLE) {
} else {
}
}
}
}
/*
* Dispatch registered error handlers for dip. If tdip != NULL, only
* the error handler (if available) for tdip is invoked. Otherwise,
* all registered error handlers are invoked.
*
* The following status values may be returned:
*
* DDI_FM_FATAL - if at least one error handler returns a
* fatal error.
*
* DDI_FM_NONFATAL - if at least one error handler returns a
* non-fatal error and none returned a fatal error.
*
* DDI_FM_UNKNOWN - if at least one error handler returns
* unknown status and none return fatal or non-fatal.
*
* DDI_FM_OK - if all error handlers return DDI_FM_OK
*/
int
const ddi_fm_error_t *nerr)
{
int status;
struct i_ddi_fmhdl *hdl;
struct i_ddi_fmtgt *tgt;
struct i_ddi_errhdl *errhdl;
if (status == DDI_FM_FATAL)
++fatal;
else if (status == DDI_FM_NONFATAL)
++nonfatal;
else if (status == DDI_FM_UNKNOWN)
++unknown;
/* Only interested in one target */
break;
}
}
if (fatal)
return (DDI_FM_FATAL);
else if (nonfatal)
return (DDI_FM_NONFATAL);
else if (unknown)
return (DDI_FM_UNKNOWN);
else
return (DDI_FM_OK);
}
/*
* Set error status for specified access or DMA handle
*
* May be called in any context but caller must insure validity of
* handle.
*/
void
{
}
void
{
}
/*
* Call parent busop fm initialization routine.
*
* Called during driver attach(1M)
*/
int
{
int pcap;
if (dip == ddi_root_node())
return (ddi_system_fmcap | DDI_FM_EREPORT_CAPABLE);
/* Valid operation for BUSO_REV_6 and above */
return (DDI_FM_NOT_CAPABLE);
return (DDI_FM_NOT_CAPABLE);
return (pcap);
}
/*
* Call parent busop fm clean-up routine.
*
* Called during driver detach(1M)
*/
void
{
if (dip == ddi_root_node())
return;
/* Valid operation for BUSO_REV_6 and above */
return;
return;
}
/*
* The following routines provide exclusive access to a nexus resource
*
* These busops may be called in user or kernel driver context.
*/
void
{
/* Valid operation for BUSO_REV_6 and above */
return;
return;
}
void
{
/* Valid operation for BUSO_REV_6 and above */
return;
return;
}