#include <sys/types.h>
#include <sys/conf.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/ksynch.h>
#include <sys/scsi/conf/autoconf.h>
#include <sys/reboot.h>
#include "ghd.h"
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);
long ghd_HZ;
static kmutex_t tglobal_mutex;
cmdstate_t ghd_timeout_table[GCMD_NSTATES];
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 },
{ FALSE, 0, 0 },
{ FALSE, 0, 0 },
{ FALSE, 0, 0 },
{ FALSE, 0, 0 }
};
int ghd_ntime_inits = sizeof (ghd_time_inits)
/ sizeof (ghd_time_inits[0]);
#define GCMD_SAME_DEV(gcmdp1, gcmdp2) \
(GCMDP2GDEVP(gcmdp1) == GCMDP2GDEVP(gcmdp2))
#define GCMD_SAME_BUS(gcmdp1, gcmdp2) \
(GCMDP2CCCP(gcmdp1) == GCMDP2CCCP(gcmdp2))
#define GCMD_UPDATE_STATE(gcmdp, newstate) \
{ \
if ((gcmdp)->cmd_state < (newstate)) { \
((gcmdp)->cmd_state = (newstate)); \
} \
}
#ifdef ___notyet___
#include <sys/modctl.h>
extern struct mod_ops mod_miscops;
static struct modlmisc modlmisc = {
&mod_miscops,
"CCB Timeout Utility Routines"
};
static struct modlinkage modlinkage = {
MODREV_1, (void *)&modlmisc, NULL
};
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
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) {
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);
}
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;
mutex_enter(&tmrp->t_mutex);
if ((cccp = tmrp->t_ccc_listp) == NULL) {
mutex_exit(&tmrp->t_mutex);
return;
}
lbolt = ddi_get_lbolt();
do {
if (ghd_timeout_loop(cccp)) {
cccp->ccc_timeout_pending = 1;
ddi_trigger_softintr(cccp->ccc_soft_id);
}
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;
ddi_trigger_softintr(cccp->ccc_soft_id);
}
mutex_exit(&cccp->ccc_waitq_mutex);
} while ((cccp = cccp->ccc_nextp) != NULL);
if (resched > tmrp->t_ticks)
resched = tmrp->t_ticks;
tmrp->t_timeout_id = timeout(ghd_timeout, (void *)tmrp, resched);
mutex_exit(&tmrp->t_mutex);
}
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
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:
ASSERT(gcmdp != NULL);
msgp = "early abort";
next_state = GCMD_STATE_DONEQ;
next_action = GACTION_ABORT_CMD;
break;
case GACTION_EARLY_TIMEOUT:
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:
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));
msgp = "HBA reset";
next_state = GCMD_STATE_HUNG;
next_action = GACTION_INCOMPLETE;
break;
}
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);
}
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);
gsav = *gcmdp;
gsav_used = 1;
}
if (action == GACTION_RESET_BUS && cccp->ccc_waitq_frozen) {
GDBG_WARN(("avoiding bus reset while waitq frozen\n"));
break;
}
if ((*func)(hba_handle, gcmdp, gtgtp, action, calltype)) {
break;
}
if (action == GACTION_INCOMPLETE)
return;
if (gcmdp != NULL && new_timeout != 0) {
GHD_TIMER_STOP(cccp, gcmdp);
}
action = next_action;
}
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)) {
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);
}
static uint_t
ghd_timeout_softintr(caddr_t arg)
{
ccc_t *cccp = (ccc_t *)arg;
if (cccp->ccc_timeout_pending) {
mutex_enter(&cccp->ccc_hba_mutex);
cccp->ccc_timeout_pending = 0;
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);
}
void
ghd_timer_poll(ccc_t *cccp, gtimer_poll_t calltype)
{
gcmd_t *gcmdp;
gact_t action;
ASSERT(mutex_owned(&cccp->ccc_hba_mutex));
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:
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);
if (calltype == GHD_TIMER_POLL_ONE)
return;
}
}
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:
L2_delete(&gcmdp->cmd_timer_link);
mutex_exit(&cccp->ccc_activel_mutex);
return (gcmdp);
}
static void
ghd_timeout_enable(tmr_t *tmrp)
{
mutex_enter(&tglobal_mutex);
if (tmrp->t_refs++ == 0) {
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);
}
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);
ghd_HZ = drv_usectohz(1000000);
if (ticks == 0)
ticks = scsi_watchdog_tick * ghd_HZ;
tmrp->t_ticks = ticks;
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);
}
mutex_init(&cccp->ccc_activel_mutex, NULL, MUTEX_DRIVER, iblock);
L2_INIT(&cccp->ccc_activel);
cccp->ccc_timeout_func = timeout_func;
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);
ghd_timeout_enable(tmrp);
return (TRUE);
}
void
ghd_timer_detach(ccc_t *cccp)
{
tmr_t *tmrp = cccp->ccc_tmrp;
ccc_t **prevpp;
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);
do {
if (*prevpp == cccp)
goto remove_it;
prevpp = &(*prevpp)->ccc_nextp;
} while (*prevpp != NULL);
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);
}
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();
gcmdp->cmd_start_time = lbolt;
gcmdp->cmd_timeout = (cmd_timeout * ghd_HZ);
L2_add(&cccp->ccc_activel, &gcmdp->cmd_timer_link, gcmdp);
mutex_exit(&cccp->ccc_activel_mutex);
}
void
ghd_timer_stop(ccc_t *cccp, gcmd_t *gcmdp)
{
GHD_TIMER_STOP_INLINE(cccp, gcmdp);
}