ghd_timer.c revision 903a11ebdc8df157c4700150f41f1f262f4a8ae8
/*
* 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"
#include "ghd.h"
/*
* Local functions
*/
static void ghd_timeout(void *arg);
/*
* Local data
*/
long ghd_HZ;
static kmutex_t tglobal_mutex;
/* table of timeouts for abort processing steps */
/* This table indirectly initializes the ghd_timeout_table */
struct {
int valid;
long value;
} ghd_time_inits[] = {
{ FALSE, 0, 0 }, /* spare entry */
{ FALSE, 0, 0 }, /* spare entry */
{ FALSE, 0, 0 }, /* spare entry */
{ FALSE, 0, 0 }, /* spare entry */
{ FALSE, 0, 0 } /* spare entry */
};
int ghd_ntime_inits = sizeof (ghd_time_inits)
/ sizeof (ghd_time_inits[0]);
/*
* Locally-used macros
*/
/*
* Compare two gcmd_t's to see if they're for the same device (same gdev_t)
*/
/*
* Compare two gcmd_t's to see if they're for the same bus (same HBA inst)
*/
/*
* Update state of gcmdp (in one direction, increasing state number, only)
*/
{ \
} \
}
#ifdef ___notyet___
extern struct mod_ops mod_miscops;
&mod_miscops, /* Type of module */
"CCB Timeout Utility Routines"
};
static struct modlinkage modlinkage = {
};
/*
* If this is a loadable module then there's a single CCB timer configure
* structure for all HBA drivers (rather than one per HBA driver).
*/
int
_init()
{
int err;
ghd_timer_init(&tmr_conf, 0);
return (err);
}
int
_fini()
{
int err;
return (err);
}
int
{
}
#endif /* ___notyet___ */
/*
*
* ghd_timeout_loop()
*
* Check the CCB timer value for every active CCB for this
* HBA driver instance.
*
* This function is called both by the ghd_timeout() interrupt
* handler when called via the timer callout, and by ghd_timer_poll()
* while procesing "polled" (FLAG_NOINTR) requests.
*
* The ccc_activel_mutex is held while a CCB list is being scanned.
* This prevents the HBA driver's transport or interrupt functions
* from changing the active CCB list. But we wake up very infrequently
* and do as little as possible so it shouldn't affect performance.
*
*/
static int
{
lbolt = ddi_get_lbolt();
while (gcmdp) {
/*
* check to see if this one has timed out
*/
if ((gcmdp->cmd_timeout > 0) &&
}
}
return (got_any);
}
/*
*
* ghd_timeout()
*
* Called every t_ticks ticks to scan the CCB timer lists
*
* The t_mutex mutex is held the entire time this routine is active.
* It protects the list of ccc_t's.
*
* The list of cmd_t's is protected by the ccc_activel_mutex mutex
* in the ghd_timeout_loop() routine.
*
* We also check to see if the waitq is frozen, and if so,
* adjust our timeout to call back sooner if necessary (to
* unfreeze the waitq as soon as possible).
*
*
* +------------+
* | tmr_t |----+
* +------------+ |
* |
* V
* +---------+
* | ccc_t |----+
* +---------+ |
* | V
* | +--------+ +--------+
* | | gcmd_t |-->| gcmd_t |--> ...
* | +--------+ +--------+
* V
* +---------+
* | ccc_t |----+
* +---------+ |
* | V
* | +--------+
* | | gcmd_t |
* V +--------+
* ...
*
*
*
*/
static void
ghd_timeout(void *arg)
{
/*
* Each HBA driver instance has a separate CCB timer list. Skip
* timeout processing if there are no more active timeout lists
* to process. (There are no lists only if there are no attached
* HBA instances; the list still exists if there are no outstanding
* active commands.)
*/
return;
}
lbolt = ddi_get_lbolt();
do {
/*
* If any active CCBs on this HBA have timed out
* then kick off the HBA driver's softintr
* handler to do the timeout processing
*/
if (ghd_timeout_loop(cccp)) {
}
/* Record closest unfreeze time for use in next timeout */
if (cccp->ccc_waitq_frozen) {
if (ufdelay_curr < resched)
/* frozen; trigger softintr to maybe unfreeze */
}
/* don't allow any unfreeze delays to increase the timeout delay */
/* re-establish the timeout callback */
}
/*
*
* ghd_timer_newstate()
*
* The HBA mutex is held by my caller.
*
*/
void
{
char *msgp;
long new_timeout;
void *hba_handle;
int gsav_used = 0;
#ifdef DEBUG
/* it shouldn't be on the timer active list */
}
#endif
for (;;) {
switch (action) {
case GACTION_EARLY_ABORT:
/* done before it started */
msgp = "early abort";
break;
case GACTION_EARLY_TIMEOUT:
/* done before it started */
msgp = "early timeout";
break;
case GACTION_ABORT_CMD:
msgp = "abort request";
break;
case GACTION_ABORT_DEV:
msgp = "abort device";
break;
case GACTION_RESET_TARGET:
msgp = "reset target";
break;
case GACTION_RESET_BUS:
msgp = "reset bus";
break;
case GACTION_INCOMPLETE:
default:
/* be verbose about HBA resets */
GDBG_ERROR(("?ghd_timer_newstate: HBA reset failed "
"hba 0x%p gcmdp 0x%p gtgtp 0x%p\n",
/*
* When all else fails, punt.
*
* We're in big trouble if we get to this point.
* Maybe we should try to re-initialize the HBA.
*/
msgp = "HBA reset";
break;
}
/*
* I want to see target requests only if verbose, but
* scsi_log() only prints the device pathname if level
* is CE_WARN or CE_PANIC...so I guess we can't use
* scsi_log for TGTREQ messages, or they must come to
* the console. How silly. Looking for "verbose boot"
* is non-DDI-compliant, but let's do it anyway.
*/
if (calltype == GHD_TGTREQ) {
if ((boothowto & RB_VERBOSE)) {
"target request: %s, target=%d lun=%d",
}
} else {
"timeout: %s, target=%d lun=%d", msgp,
}
/*
* Before firing off the HBA action, restart the timer
* using the timeout value from ghd_timeout_table[].
*
* The table entries should never restart the timer
* for the GHD_STATE_IDLE and GHD_STATE_DONEQ states.
*
*/
if (gcmdp) {
if (new_timeout != 0)
/* save a copy in case action function frees it */
gsav_used = 1;
}
GDBG_WARN(("avoiding bus reset while waitq frozen\n"));
break;
}
/* invoke the HBA's action function */
/* if it took wait for an interrupt or timeout */
break;
}
/*
* if the HBA reset fails leave the retry
* timer running and just exit.
*/
if (action == GACTION_INCOMPLETE)
return;
/* all other failures cause transition to next action */
/*
* But stop the old timer prior to
* restarting a new timer because each step may
* have a different timeout value.
*/
}
}
/*
* HBA action function is done with gsav (if used)
* outstanding requests if they were affected by this action
* (say, a device reset which also cancels all outstanding
* actions for the same device or bus condition. Scan the timer
* list (all active requests) and update states as necessary.
* Hold the activel_mutex while scanning the active list. Check
*/
gcmdp_scan != NULL;
/* skip idle or waitq commands */
continue;
switch (action) {
case GACTION_ABORT_DEV:
}
break;
case GACTION_RESET_TARGET:
}
break;
case GACTION_RESET_BUS:
}
break;
default:
break;
}
}
}
/*
*
* ghd_timeout_softintr()
*
* This interrupt is scheduled if a particular HBA instance's
* CCB timer list has a timed out CCB, or if the waitq is in a
* frozen state.
*
* Find the timed out CCB and then call the HBA driver's timeout
* function.
*
* In order to avoid race conditions all processing must be done
* while holding the HBA instance's mutex. If the mutex wasn't
* held the HBA driver's hardware interrupt routine could be
* triggered and it might try to remove a CCB from the list at
* same time as were trying to abort it.
*
* For frozen-waitq processing, just call ghd_waitq_process...
* it takes care of the time calculations.
*
*/
static uint_t
{
if (cccp->ccc_timeout_pending) {
/* grab this HBA instance's mutex */
/*
* The claim is we could reset "pending" outside the mutex, but
* since we have to acquire the mutex anyway, it doesn't hurt
*/
cccp->ccc_timeout_pending = 0;
/* timeout each expired CCB */
} else if (cccp->ccc_waitq_frozen) {
}
return (DDI_INTR_UNCLAIMED);
}
/*
* ghd_timer_poll()
*
* This function steps a packet to the next action in the recovery
* procedure.
*
* The caller must be already holding the HBA mutex and take care of
* running the pkt completion functions.
*
*/
void
{
/* abort each expired CCB */
GDBG_INTR(("?ghd_timer_poll: cccp=0x%p gcmdp=0x%p\n",
case GCMD_STATE_IDLE:
case GCMD_STATE_DONEQ:
default:
/* not supposed to happen */
GDBG_ERROR(("ghd_timer_poll: invalid state %d\n",
return;
case GCMD_STATE_WAITQ:
break;
case GCMD_STATE_ACTIVE:
break;
case GCMD_STATE_ABORTING_CMD:
break;
case GCMD_STATE_ABORTING_DEV:
break;
case GCMD_STATE_RESETTING_DEV:
break;
case GCMD_STATE_RESETTING_BUS:
break;
case GCMD_STATE_HUNG:
break;
}
/* return after processing first cmd if requested */
if (calltype == GHD_TIMER_POLL_ONE)
return;
}
}
/*
*
* ghd_timeout_get()
*
* Remove the first expired CCB from a particular timer list.
*
*/
static gcmd_t *
{
lbolt = ddi_get_lbolt();
if ((gcmdp->cmd_timeout > 0) &&
goto expired;
}
return (NULL);
/* unlink if from the CCB timer list */
return (gcmdp);
}
/*
*
* ghd_timeout_enable()
*
* Only start a single timeout callback for each HBA driver
* regardless of the number of boards it supports.
*
*/
static void
{
/* establish the timeout callback */
}
}
static void
{
}
}
/* ************************************************************************ */
/* these are the externally callable routines */
void
{
int indx;
/*
* determine default timeout value
*/
if (ticks == 0)
/*
* Initialize the table of abort timer values using an
* indirect lookup table so that this code isn't dependant
* on the cmdstate_t enum values or order.
*/
int state;
continue;
}
}
void
{
}
int
{
"ghd_timer_attach: add softintr failed cccp 0x%p\n",
(void *)cccp));
return (FALSE);
}
/* init the per HBA-instance control fields */
/* stick this HBA's control structure on the master list */
/*
* The enable and disable routines use a separate mutex than
* t_mutex which is used by the timeout callback function.
* This is to avoid a deadlock when calling untimeout() from
* the disable routine.
*/
return (TRUE);
}
/*
*
* ghd_timer_detach()
*
* clean up for a detaching HBA instance
*
*/
void
{
/* make certain the CCB list is empty */
/* run down the linked list to find the entry that preceeds this one */
do {
goto remove_it;
/* fell off the end of the list */
GDBG_ERROR(("ghd_timer_detach: corrupt list, cccp=0x%p\n",
(void *)cccp));
}
/*
*
* ghd_timer_start()
*
* Add a CCB to the CCB timer list.
*/
void
{
lbolt = ddi_get_lbolt();
/* initialize this CCB's timer */
/* add it to the list */
}
/*
*
* ghd_timer_stop()
*
* Remove a completed CCB from the CCB timer list.
*
* See the GHD_TIMER_STOP_INLINE() macro in ghd.h for
* the actual code.
*/
void
{
}