/* * 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 * or http://www.opensolaris.org/os/licensing. * 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. */ #include #include #include #include #include #include #include #include "ghd.h" /* * Local functions */ static gcmd_t *ghd_timeout_get(ccc_t *cccp); static int ghd_timeout_loop(ccc_t *cccp); static uint_t ghd_timeout_softintr(caddr_t arg); static void ghd_timeout(void *arg); static void ghd_timeout_disable(tmr_t *tmrp); static void ghd_timeout_enable(tmr_t *tmrp); /* * Local data */ long ghd_HZ; static kmutex_t tglobal_mutex; /* table of timeouts for abort processing steps */ cmdstate_t ghd_timeout_table[GCMD_NSTATES]; /* This table indirectly initializes the ghd_timeout_table */ struct { int valid; cmdstate_t state; long value; } ghd_time_inits[] = { { TRUE, GCMD_STATE_ABORTING_CMD, 3 }, { TRUE, GCMD_STATE_ABORTING_DEV, 3 }, { TRUE, GCMD_STATE_RESETTING_DEV, 5 }, { TRUE, GCMD_STATE_RESETTING_BUS, 10 }, { TRUE, GCMD_STATE_HUNG, 60}, { 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) */ #define GCMD_SAME_DEV(gcmdp1, gcmdp2) \ (GCMDP2GDEVP(gcmdp1) == GCMDP2GDEVP(gcmdp2)) /* * Compare two gcmd_t's to see if they're for the same bus (same HBA inst) */ #define GCMD_SAME_BUS(gcmdp1, gcmdp2) \ (GCMDP2CCCP(gcmdp1) == GCMDP2CCCP(gcmdp2)) /* * Update state of gcmdp (in one direction, increasing state number, only) */ #define GCMD_UPDATE_STATE(gcmdp, newstate) \ { \ if ((gcmdp)->cmd_state < (newstate)) { \ ((gcmdp)->cmd_state = (newstate)); \ } \ } #ifdef ___notyet___ #include extern struct mod_ops mod_miscops; static struct modlmisc modlmisc = { &mod_miscops, /* Type of module */ "CCB Timeout Utility Routines" }; static struct modlinkage modlinkage = { MODREV_1, (void *)&modlmisc, NULL }; /* * 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). */ static tmr_t tmr_conf; int _init() { int err; ghd_timer_init(&tmr_conf, 0); return ((err = mod_install(&modlinkage)) != 0) ghd_timer_fini(&tmr_conf); return (err); } int _fini() { int err; if ((err = mod_remove(&modlinkage)) == 0) ghd_timer_fini(&tmr_conf); return (err); } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); } #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 ghd_timeout_loop(ccc_t *cccp) { int got_any = FALSE; gcmd_t *gcmdp; ulong_t lbolt; mutex_enter(&cccp->ccc_activel_mutex); lbolt = ddi_get_lbolt(); gcmdp = (gcmd_t *)L2_next(&cccp->ccc_activel); while (gcmdp) { /* * check to see if this one has timed out */ if ((gcmdp->cmd_timeout > 0) && (lbolt - gcmdp->cmd_start_time >= gcmdp->cmd_timeout)) { got_any = TRUE; } gcmdp = (gcmd_t *)L2_next(&gcmdp->cmd_timer_link); } mutex_exit(&cccp->ccc_activel_mutex); 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) { tmr_t *tmrp = (tmr_t *)arg; ccc_t *cccp; clock_t ufdelay_curr; clock_t lbolt, delay_in_hz; clock_t resched = (clock_t)0x7FFFFFFF; /* * 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.) */ mutex_enter(&tmrp->t_mutex); if ((cccp = tmrp->t_ccc_listp) == NULL) { mutex_exit(&tmrp->t_mutex); 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)) { cccp->ccc_timeout_pending = 1; ddi_trigger_softintr(cccp->ccc_soft_id); } /* Record closest unfreeze time for use in next timeout */ mutex_enter(&cccp->ccc_waitq_mutex); if (cccp->ccc_waitq_frozen) { delay_in_hz = drv_usectohz(cccp->ccc_waitq_freezedelay * 1000); ufdelay_curr = delay_in_hz - (lbolt - cccp->ccc_waitq_freezetime); if (ufdelay_curr < resched) resched = ufdelay_curr; /* frozen; trigger softintr to maybe unfreeze */ ddi_trigger_softintr(cccp->ccc_soft_id); } mutex_exit(&cccp->ccc_waitq_mutex); } while ((cccp = cccp->ccc_nextp) != NULL); /* don't allow any unfreeze delays to increase the timeout delay */ if (resched > tmrp->t_ticks) resched = tmrp->t_ticks; /* re-establish the timeout callback */ tmrp->t_timeout_id = timeout(ghd_timeout, (void *)tmrp, resched); mutex_exit(&tmrp->t_mutex); } /* * * ghd_timer_newstate() * * The HBA mutex is held by my caller. * */ void ghd_timer_newstate(ccc_t *cccp, gcmd_t *gcmdp, gtgt_t *gtgtp, gact_t action, int calltype) { gact_t next_action; cmdstate_t next_state; char *msgp; long new_timeout = 0; int (*func)(void *, gcmd_t *, gtgt_t *, gact_t, int); void *hba_handle; gcmd_t gsav; int gsav_used = 0; gcmd_t *gcmdp_scan; ASSERT(mutex_owned(&cccp->ccc_hba_mutex)); #ifdef DEBUG /* it shouldn't be on the timer active list */ if (gcmdp != NULL) { L2el_t *lp = &gcmdp->cmd_timer_link; ASSERT(lp->l2_nextp == lp); ASSERT(lp->l2_prevp == lp); } #endif bzero(&gsav, sizeof (gsav)); func = cccp->ccc_timeout_func; hba_handle = cccp->ccc_hba_handle; for (;;) { switch (action) { case GACTION_EARLY_ABORT: /* done before it started */ ASSERT(gcmdp != NULL); msgp = "early abort"; next_state = GCMD_STATE_DONEQ; next_action = GACTION_ABORT_CMD; break; case GACTION_EARLY_TIMEOUT: /* done before it started */ ASSERT(gcmdp != NULL); msgp = "early timeout"; next_state = GCMD_STATE_DONEQ; next_action = GACTION_ABORT_CMD; break; case GACTION_ABORT_CMD: msgp = "abort request"; ASSERT(gcmdp != NULL); next_state = GCMD_STATE_ABORTING_CMD; next_action = GACTION_ABORT_DEV; break; case GACTION_ABORT_DEV: msgp = "abort device"; next_state = GCMD_STATE_ABORTING_DEV; next_action = GACTION_RESET_TARGET; break; case GACTION_RESET_TARGET: msgp = "reset target"; next_state = GCMD_STATE_RESETTING_DEV; next_action = GACTION_RESET_BUS; break; case GACTION_RESET_BUS: msgp = "reset bus"; next_state = GCMD_STATE_RESETTING_BUS; next_action = GACTION_INCOMPLETE; 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", (void *)hba_handle, (void *)gcmdp, (void *)gtgtp)); /* * 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"; next_state = GCMD_STATE_HUNG; next_action = GACTION_INCOMPLETE; 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)) { scsi_log(cccp->ccc_hba_dip, cccp->ccc_label, CE_WARN, "target request: %s, target=%d lun=%d", msgp, gtgtp->gt_target, gtgtp->gt_lun); } } else { scsi_log(cccp->ccc_hba_dip, cccp->ccc_label, CE_WARN, "timeout: %s, target=%d lun=%d", msgp, gtgtp->gt_target, gtgtp->gt_lun); } /* * 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) { gcmdp->cmd_state = next_state; new_timeout = ghd_timeout_table[gcmdp->cmd_state]; if (new_timeout != 0) ghd_timer_start(cccp, gcmdp, new_timeout); /* save a copy in case action function frees it */ gsav = *gcmdp; gsav_used = 1; } if (action == GACTION_RESET_BUS && cccp->ccc_waitq_frozen) { GDBG_WARN(("avoiding bus reset while waitq frozen\n")); break; } /* invoke the HBA's action function */ if ((*func)(hba_handle, gcmdp, gtgtp, action, calltype)) { /* 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 */ if (gcmdp != NULL && new_timeout != 0) { /* * But stop the old timer prior to * restarting a new timer because each step may * have a different timeout value. */ GHD_TIMER_STOP(cccp, gcmdp); } action = next_action; } /* * HBA action function is done with gsav (if used) * or gtgtp/cccp (if gsav not used). We need to mark other * outstanding requests if they were affected by this action * (say, a device reset which also cancels all outstanding * requests on this device) to prevent multiple timeouts/HBA * 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 * for either same dev/bus as gsav (if used) or for same * dev/bus as gtgtp or cccp (if gsav is not used). */ mutex_enter(&cccp->ccc_activel_mutex); for (gcmdp_scan = (gcmd_t *)L2_next(&cccp->ccc_activel); gcmdp_scan != NULL; gcmdp_scan = (gcmd_t *)L2_next(&gcmdp_scan->cmd_timer_link)) { /* skip idle or waitq commands */ if (gcmdp_scan->cmd_state <= GCMD_STATE_WAITQ) continue; switch (action) { case GACTION_ABORT_DEV: if ((gsav_used && GCMD_SAME_DEV(&gsav, gcmdp_scan)) || (GCMDP2GDEVP(gcmdp_scan) == GTGTP2GDEVP(gtgtp))) { GCMD_UPDATE_STATE(gcmdp_scan, GCMD_STATE_ABORTING_DEV); } break; case GACTION_RESET_TARGET: if ((gsav_used && GCMD_SAME_DEV(&gsav, gcmdp_scan)) || (GCMDP2GDEVP(gcmdp_scan) == GTGTP2GDEVP(gtgtp))) { GCMD_UPDATE_STATE(gcmdp_scan, GCMD_STATE_RESETTING_DEV); } break; case GACTION_RESET_BUS: if ((gsav_used && GCMD_SAME_BUS(&gsav, gcmdp_scan)) || (GCMDP2CCCP(gcmdp_scan) == cccp)) { GCMD_UPDATE_STATE(gcmdp_scan, GCMD_STATE_RESETTING_BUS); } break; default: break; } } mutex_exit(&cccp->ccc_activel_mutex); } /* * * 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 ghd_timeout_softintr(caddr_t arg) { ccc_t *cccp = (ccc_t *)arg; if (cccp->ccc_timeout_pending) { /* grab this HBA instance's mutex */ mutex_enter(&cccp->ccc_hba_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 */ ghd_timer_poll(cccp, GHD_TIMER_POLL_ALL); mutex_enter(&cccp->ccc_waitq_mutex); ghd_waitq_process_and_mutex_exit(cccp); } else if (cccp->ccc_waitq_frozen) { mutex_enter(&cccp->ccc_hba_mutex); mutex_enter(&cccp->ccc_waitq_mutex); ghd_waitq_process_and_mutex_exit(cccp); } 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 ghd_timer_poll(ccc_t *cccp, gtimer_poll_t calltype) { gcmd_t *gcmdp; gact_t action; ASSERT(mutex_owned(&cccp->ccc_hba_mutex)); /* abort each expired CCB */ while (gcmdp = ghd_timeout_get(cccp)) { GDBG_INTR(("?ghd_timer_poll: cccp=0x%p gcmdp=0x%p\n", (void *)cccp, (void *)gcmdp)); switch (gcmdp->cmd_state) { case GCMD_STATE_IDLE: case GCMD_STATE_DONEQ: default: /* not supposed to happen */ GDBG_ERROR(("ghd_timer_poll: invalid state %d\n", gcmdp->cmd_state)); return; case GCMD_STATE_WAITQ: action = GACTION_EARLY_TIMEOUT; break; case GCMD_STATE_ACTIVE: action = GACTION_ABORT_CMD; break; case GCMD_STATE_ABORTING_CMD: action = GACTION_ABORT_DEV; break; case GCMD_STATE_ABORTING_DEV: action = GACTION_RESET_TARGET; break; case GCMD_STATE_RESETTING_DEV: action = GACTION_RESET_BUS; break; case GCMD_STATE_RESETTING_BUS: action = GACTION_INCOMPLETE; break; case GCMD_STATE_HUNG: action = GACTION_INCOMPLETE; break; } ghd_timer_newstate(cccp, gcmdp, gcmdp->cmd_gtgtp, action, GHD_TIMEOUT); /* 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 * ghd_timeout_get(ccc_t *cccp) { gcmd_t *gcmdp; ulong_t lbolt; ASSERT(mutex_owned(&cccp->ccc_hba_mutex)); mutex_enter(&cccp->ccc_activel_mutex); lbolt = ddi_get_lbolt(); gcmdp = (gcmd_t *)L2_next(&cccp->ccc_activel); while (gcmdp != NULL) { if ((gcmdp->cmd_timeout > 0) && (lbolt - gcmdp->cmd_start_time >= gcmdp->cmd_timeout)) goto expired; gcmdp = (gcmd_t *)L2_next(&gcmdp->cmd_timer_link); } mutex_exit(&cccp->ccc_activel_mutex); return (NULL); expired: /* unlink if from the CCB timer list */ L2_delete(&gcmdp->cmd_timer_link); mutex_exit(&cccp->ccc_activel_mutex); 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 ghd_timeout_enable(tmr_t *tmrp) { mutex_enter(&tglobal_mutex); if (tmrp->t_refs++ == 0) { /* establish the timeout callback */ tmrp->t_timeout_id = timeout(ghd_timeout, (void *)tmrp, tmrp->t_ticks); } mutex_exit(&tglobal_mutex); } static void ghd_timeout_disable(tmr_t *tmrp) { ASSERT(tmrp != NULL); mutex_enter(&tglobal_mutex); if (tmrp->t_refs-- <= 1) { (void) untimeout(tmrp->t_timeout_id); } mutex_exit(&tglobal_mutex); } /* ************************************************************************ */ /* these are the externally callable routines */ void ghd_timer_init(tmr_t *tmrp, long ticks) { int indx; mutex_init(&tglobal_mutex, NULL, MUTEX_DRIVER, NULL); mutex_init(&tmrp->t_mutex, NULL, MUTEX_DRIVER, NULL); /* * determine default timeout value */ ghd_HZ = drv_usectohz(1000000); if (ticks == 0) ticks = scsi_watchdog_tick * ghd_HZ; tmrp->t_ticks = ticks; /* * 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. */ for (indx = 0; indx < ghd_ntime_inits; indx++) { int state; ulong_t value; if (!ghd_time_inits[indx].valid) continue; state = ghd_time_inits[indx].state; value = ghd_time_inits[indx].value; ghd_timeout_table[state] = (cmdstate_t)value; } } void ghd_timer_fini(tmr_t *tmrp) { mutex_destroy(&tmrp->t_mutex); mutex_destroy(&tglobal_mutex); } int ghd_timer_attach(ccc_t *cccp, tmr_t *tmrp, int (*timeout_func)(void *, gcmd_t *, gtgt_t *, gact_t, int)) { ddi_iblock_cookie_t iblock; if (ddi_add_softintr(cccp->ccc_hba_dip, DDI_SOFTINT_LOW, &cccp->ccc_soft_id, &iblock, NULL, ghd_timeout_softintr, (caddr_t)cccp) != DDI_SUCCESS) { GDBG_ERROR(( "ghd_timer_attach: add softintr failed cccp 0x%p\n", (void *)cccp)); return (FALSE); } /* init the per HBA-instance control fields */ mutex_init(&cccp->ccc_activel_mutex, NULL, MUTEX_DRIVER, iblock); L2_INIT(&cccp->ccc_activel); cccp->ccc_timeout_func = timeout_func; /* stick this HBA's control structure on the master list */ mutex_enter(&tmrp->t_mutex); cccp->ccc_nextp = tmrp->t_ccc_listp; tmrp->t_ccc_listp = cccp; cccp->ccc_tmrp = tmrp; mutex_exit(&tmrp->t_mutex); /* * 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. */ ghd_timeout_enable(tmrp); return (TRUE); } /* * * ghd_timer_detach() * * clean up for a detaching HBA instance * */ void ghd_timer_detach(ccc_t *cccp) { tmr_t *tmrp = cccp->ccc_tmrp; ccc_t **prevpp; /* make certain the CCB list is empty */ ASSERT(cccp->ccc_activel.l2_nextp == &cccp->ccc_activel); ASSERT(cccp->ccc_activel.l2_nextp == cccp->ccc_activel.l2_prevp); mutex_enter(&tmrp->t_mutex); prevpp = &tmrp->t_ccc_listp; ASSERT(*prevpp != NULL); /* run down the linked list to find the entry that preceeds this one */ do { if (*prevpp == cccp) goto remove_it; prevpp = &(*prevpp)->ccc_nextp; } while (*prevpp != NULL); /* fell off the end of the list */ GDBG_ERROR(("ghd_timer_detach: corrupt list, cccp=0x%p\n", (void *)cccp)); remove_it: *prevpp = cccp->ccc_nextp; mutex_exit(&tmrp->t_mutex); mutex_destroy(&cccp->ccc_activel_mutex); ddi_remove_softintr(cccp->ccc_soft_id); ghd_timeout_disable(tmrp); } /* * * ghd_timer_start() * * Add a CCB to the CCB timer list. */ void ghd_timer_start(ccc_t *cccp, gcmd_t *gcmdp, long cmd_timeout) { ulong_t lbolt; mutex_enter(&cccp->ccc_activel_mutex); lbolt = ddi_get_lbolt(); /* initialize this CCB's timer */ gcmdp->cmd_start_time = lbolt; gcmdp->cmd_timeout = (cmd_timeout * ghd_HZ); /* add it to the list */ L2_add(&cccp->ccc_activel, &gcmdp->cmd_timer_link, gcmdp); mutex_exit(&cccp->ccc_activel_mutex); } /* * * 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 ghd_timer_stop(ccc_t *cccp, gcmd_t *gcmdp) { GHD_TIMER_STOP_INLINE(cccp, gcmdp); }