#include <sys/types.h>
#include <sys/kmem.h>
#include <sys/debug.h>
#include <sys/scsi/scsi.h>
#include "ghd.h"
typedef enum {
GHD_POLL_REQUEST,
GHD_POLL_DEVICE,
GHD_POLL_ALL
} gpoll_t;
static gcmd_t *ghd_doneq_get(ccc_t *cccp);
static void ghd_doneq_pollmode_enter(ccc_t *cccp);
static void ghd_doneq_pollmode_exit(ccc_t *cccp);
static uint_t ghd_doneq_process(caddr_t arg);
static void ghd_do_reset_notify_callbacks(ccc_t *cccp);
static int ghd_poll(ccc_t *cccp, gpoll_t polltype, ulong_t polltime,
gcmd_t *poll_gcmdp, gtgt_t *gtgtp, void *intr_status);
#define DEFAULT_GHD_TIMEOUT 50000
ulong_t ghd_tran_abort_timeout = DEFAULT_GHD_TIMEOUT;
ulong_t ghd_tran_abort_lun_timeout = DEFAULT_GHD_TIMEOUT;
ulong_t ghd_tran_reset_target_timeout = DEFAULT_GHD_TIMEOUT;
ulong_t ghd_tran_reset_bus_timeout = DEFAULT_GHD_TIMEOUT;
static int
ghd_doneq_init(ccc_t *cccp)
{
ddi_iblock_cookie_t iblock;
L2_INIT(&cccp->ccc_doneq);
cccp->ccc_hba_pollmode = TRUE;
if (ddi_add_softintr(cccp->ccc_hba_dip, DDI_SOFTINT_LOW,
&cccp->ccc_doneq_softid, &iblock, NULL,
ghd_doneq_process, (caddr_t)cccp) != DDI_SUCCESS) {
GDBG_ERROR(("ghd_doneq_init: add softintr failed cccp 0x%p\n",
(void *)cccp));
return (FALSE);
}
mutex_init(&cccp->ccc_doneq_mutex, NULL, MUTEX_DRIVER, iblock);
ghd_doneq_pollmode_exit(cccp);
return (TRUE);
}
void
ghd_complete(ccc_t *cccp, gcmd_t *gcmdp)
{
ASSERT(mutex_owned(&cccp->ccc_hba_mutex));
GHD_COMPLETE_INLINE(cccp, gcmdp);
}
void
ghd_doneq_put_head(ccc_t *cccp, gcmd_t *gcmdp)
{
GHD_DONEQ_PUT_HEAD_INLINE(cccp, gcmdp)
}
void
ghd_doneq_put_tail(ccc_t *cccp, gcmd_t *gcmdp)
{
GHD_DONEQ_PUT_TAIL_INLINE(cccp, gcmdp)
}
static gcmd_t *
ghd_doneq_get(ccc_t *cccp)
{
kmutex_t *doneq_mutexp = &cccp->ccc_doneq_mutex;
gcmd_t *gcmdp;
mutex_enter(doneq_mutexp);
if ((gcmdp = L2_next(&cccp->ccc_doneq)) != NULL)
L2_delete(&gcmdp->cmd_q);
mutex_exit(doneq_mutexp);
return (gcmdp);
}
static void
ghd_doneq_pollmode_enter(ccc_t *cccp)
{
kmutex_t *doneq_mutexp = &cccp->ccc_doneq_mutex;
mutex_enter(doneq_mutexp);
cccp->ccc_hba_pollmode = TRUE;
mutex_exit(doneq_mutexp);
}
static void
ghd_doneq_pollmode_exit(ccc_t *cccp)
{
kmutex_t *doneq_mutexp = &cccp->ccc_doneq_mutex;
mutex_enter(doneq_mutexp);
cccp->ccc_hba_pollmode = FALSE;
mutex_exit(doneq_mutexp);
if (!L2_EMPTY(&cccp->ccc_doneq)) {
if (!ddi_in_panic())
ddi_trigger_softintr(cccp->ccc_doneq_softid);
else
(void) ghd_doneq_process((caddr_t)cccp);
}
}
static uint_t
ghd_doneq_process(caddr_t arg)
{
ccc_t *cccp = (ccc_t *)arg;
kmutex_t *doneq_mutexp;
gcmd_t *gcmdp;
int rc = DDI_INTR_UNCLAIMED;
doneq_mutexp = &cccp->ccc_doneq_mutex;
for (;;) {
mutex_enter(doneq_mutexp);
if (cccp->ccc_hba_pollmode)
break;
if ((gcmdp = L2_next(&cccp->ccc_doneq)) == NULL)
break;
L2_delete(&gcmdp->cmd_q);
if (gcmdp->cmd_flags & GCMDFLG_RESET_NOTIFY) {
ghd_do_reset_notify_callbacks(cccp);
ghd_gcmd_free(gcmdp);
mutex_exit(doneq_mutexp);
continue;
}
mutex_exit(doneq_mutexp);
gcmdp->cmd_state = GCMD_STATE_IDLE;
(*cccp->ccc_hba_complete)(cccp->ccc_hba_handle, gcmdp, TRUE);
#ifdef notyet
rc = DDI_INTR_CLAIMED;
#endif
}
mutex_exit(doneq_mutexp);
return (rc);
}
static void
ghd_do_reset_notify_callbacks(ccc_t *cccp)
{
ghd_reset_notify_list_t *rnp;
L2el_t *rnl = &cccp->ccc_reset_notify_list;
ASSERT(mutex_owned(&cccp->ccc_doneq_mutex));
mutex_enter(&cccp->ccc_reset_notify_mutex);
for (rnp = (ghd_reset_notify_list_t *)L2_next(rnl);
rnp != NULL;
rnp = (ghd_reset_notify_list_t *)L2_next(&rnp->l2_link)) {
if (cccp->ccc_hba_reset_notify_callback) {
(*cccp->ccc_hba_reset_notify_callback)(rnp->gtgtp,
rnp->callback, rnp->arg);
}
}
mutex_exit(&cccp->ccc_reset_notify_mutex);
}
int
ghd_register(char *labelp,
ccc_t *cccp,
dev_info_t *dip,
int inumber,
void *hba_handle,
int (*ccballoc)(gtgt_t *, gcmd_t *, int, int, int, int),
void (*ccbfree)(gcmd_t *),
void (*sg_func)(gcmd_t *, ddi_dma_cookie_t *, int, int),
int (*hba_start)(void *, gcmd_t *),
void (*hba_complete)(void *, gcmd_t *, int),
uint_t (*int_handler)(caddr_t),
int (*get_status)(void *, void *),
void (*process_intr)(void *, void *),
int (*timeout_func)(void *, gcmd_t *, gtgt_t *, gact_t, int),
tmr_t *tmrp,
void (*hba_reset_notify_callback)(gtgt_t *,
void (*)(caddr_t), caddr_t))
{
cccp->ccc_label = labelp;
cccp->ccc_hba_dip = dip;
cccp->ccc_ccballoc = ccballoc;
cccp->ccc_ccbfree = ccbfree;
cccp->ccc_sg_func = sg_func;
cccp->ccc_hba_start = hba_start;
cccp->ccc_hba_complete = hba_complete;
cccp->ccc_process_intr = process_intr;
cccp->ccc_get_status = get_status;
cccp->ccc_hba_handle = hba_handle;
cccp->ccc_hba_reset_notify_callback = hba_reset_notify_callback;
CCCP_INIT(cccp);
if (ddi_get_iblock_cookie(dip, inumber, &cccp->ccc_iblock)
!= DDI_SUCCESS) {
return (FALSE);
}
mutex_init(&cccp->ccc_hba_mutex, NULL, MUTEX_DRIVER, cccp->ccc_iblock);
mutex_init(&cccp->ccc_waitq_mutex, NULL, MUTEX_DRIVER,
cccp->ccc_iblock);
mutex_init(&cccp->ccc_reset_notify_mutex, NULL, MUTEX_DRIVER,
cccp->ccc_iblock);
if (ddi_add_intr(dip, inumber, &cccp->ccc_iblock, NULL,
int_handler, (caddr_t)hba_handle) != DDI_SUCCESS) {
mutex_destroy(&cccp->ccc_hba_mutex);
mutex_destroy(&cccp->ccc_waitq_mutex);
mutex_destroy(&cccp->ccc_reset_notify_mutex);
return (FALSE);
}
if (ghd_timer_attach(cccp, tmrp, timeout_func) == FALSE) {
ddi_remove_intr(cccp->ccc_hba_dip, 0, cccp->ccc_iblock);
mutex_destroy(&cccp->ccc_hba_mutex);
mutex_destroy(&cccp->ccc_waitq_mutex);
mutex_destroy(&cccp->ccc_reset_notify_mutex);
return (FALSE);
}
if (ghd_doneq_init(cccp)) {
return (TRUE);
}
ghd_timer_detach(cccp);
ddi_remove_intr(cccp->ccc_hba_dip, 0, cccp->ccc_iblock);
mutex_destroy(&cccp->ccc_hba_mutex);
mutex_destroy(&cccp->ccc_waitq_mutex);
mutex_destroy(&cccp->ccc_reset_notify_mutex);
return (FALSE);
}
void
ghd_unregister(ccc_t *cccp)
{
ghd_timer_detach(cccp);
ddi_remove_intr(cccp->ccc_hba_dip, 0, cccp->ccc_iblock);
ddi_remove_softintr(cccp->ccc_doneq_softid);
mutex_destroy(&cccp->ccc_hba_mutex);
mutex_destroy(&cccp->ccc_waitq_mutex);
mutex_destroy(&cccp->ccc_doneq_mutex);
}
int
ghd_intr(ccc_t *cccp, void *intr_status)
{
int (*statfunc)(void *, void *) = cccp->ccc_get_status;
void (*processfunc)(void *, void *) = cccp->ccc_process_intr;
kmutex_t *waitq_mutexp = &cccp->ccc_waitq_mutex;
kmutex_t *hba_mutexp = &cccp->ccc_hba_mutex;
void *handle = cccp->ccc_hba_handle;
int rc = DDI_INTR_UNCLAIMED;
int more;
mutex_enter(hba_mutexp);
GDBG_INTR(("ghd_intr(): cccp=0x%p status=0x%p\n",
(void *)cccp, intr_status));
for (;;) {
more = FALSE;
while ((*statfunc)(handle, intr_status)) {
(*processfunc)(handle, intr_status);
rc = DDI_INTR_CLAIMED;
more = TRUE;
}
mutex_enter(waitq_mutexp);
if (ghd_waitq_process_and_mutex_hold(cccp)) {
ASSERT(mutex_owned(hba_mutexp));
mutex_exit(waitq_mutexp);
continue;
}
if (more) {
mutex_exit(waitq_mutexp);
continue;
}
GDBG_INTR(("ghd_intr(): done cccp=0x%p status=0x%p rc %d\n",
(void *)cccp, intr_status, rc));
mutex_exit(hba_mutexp);
mutex_exit(waitq_mutexp);
return (rc);
}
}
static int
ghd_poll(ccc_t *cccp,
gpoll_t polltype,
ulong_t polltime,
gcmd_t *poll_gcmdp,
gtgt_t *gtgtp,
void *intr_status)
{
gcmd_t *gcmdp;
L2el_t gcmd_hold_queue;
int got_it = FALSE;
clock_t poll_lbolt;
clock_t start_lbolt;
clock_t current_lbolt;
ASSERT(mutex_owned(&cccp->ccc_hba_mutex));
L2_INIT(&gcmd_hold_queue);
poll_lbolt = drv_usectohz((clock_t)polltime);
start_lbolt = ddi_get_lbolt();
while (!got_it) {
current_lbolt = ddi_get_lbolt();
if (poll_lbolt && (current_lbolt - start_lbolt >= poll_lbolt))
break;
drv_usecwait(1000);
if ((*cccp->ccc_get_status)(cccp->ccc_hba_handle, intr_status))
(*cccp->ccc_process_intr)(cccp->ccc_hba_handle,
intr_status);
mutex_enter(&cccp->ccc_waitq_mutex);
(void) ghd_waitq_process_and_mutex_hold(cccp);
mutex_exit(&cccp->ccc_waitq_mutex);
ghd_timer_poll(cccp, GHD_TIMER_POLL_ONE);
while (gcmdp = ghd_doneq_get(cccp)) {
if (gcmdp == poll_gcmdp) {
poll_gcmdp->cmd_state = GCMD_STATE_IDLE;
got_it = TRUE;
continue;
}
L2_add(&gcmd_hold_queue, &gcmdp->cmd_q, gcmdp);
}
switch (polltype) {
case GHD_POLL_DEVICE:
if (GDEV_NACTIVE(gtgtp->gt_gdevp) == 0)
got_it = TRUE;
break;
case GHD_POLL_ALL:
if (GHBA_NACTIVE(cccp) == 0)
got_it = TRUE;
break;
case GHD_POLL_REQUEST:
break;
}
}
if (L2_EMPTY(&gcmd_hold_queue)) {
ASSERT(!mutex_owned(&cccp->ccc_waitq_mutex));
ASSERT(mutex_owned(&cccp->ccc_hba_mutex));
return (got_it);
}
while (gcmdp = L2_next(&gcmd_hold_queue)) {
L2_delete(&gcmdp->cmd_q);
GHD_DONEQ_PUT_TAIL(cccp, gcmdp);
}
ASSERT(!mutex_owned(&cccp->ccc_waitq_mutex));
ASSERT(mutex_owned(&cccp->ccc_hba_mutex));
return (got_it);
}
int
ghd_tran_abort(ccc_t *cccp, gcmd_t *gcmdp, gtgt_t *gtgtp, void *intr_status)
{
gact_t action;
int rc;
mutex_enter(&cccp->ccc_hba_mutex);
ghd_doneq_pollmode_enter(cccp);
switch (gcmdp->cmd_state) {
case GCMD_STATE_WAITQ:
action = GACTION_EARLY_ABORT;
break;
case GCMD_STATE_ACTIVE:
action = GACTION_ABORT_CMD;
break;
default:
rc = FALSE;
goto exit;
}
GHD_TIMER_STOP(cccp, gcmdp);
ghd_timer_newstate(cccp, gcmdp, gtgtp, action, GHD_TGTREQ);
if (rc = ghd_poll(cccp, GHD_POLL_REQUEST, ghd_tran_abort_timeout,
gcmdp, gtgtp, intr_status)) {
gcmdp->cmd_state = GCMD_STATE_DONEQ;
GHD_DONEQ_PUT_TAIL(cccp, gcmdp);
}
exit:
ghd_doneq_pollmode_exit(cccp);
mutex_enter(&cccp->ccc_waitq_mutex);
ghd_waitq_process_and_mutex_exit(cccp);
return (rc);
}
int
ghd_tran_abort_lun(ccc_t *cccp, gtgt_t *gtgtp, void *intr_status)
{
int rc;
mutex_enter(&cccp->ccc_hba_mutex);
ghd_doneq_pollmode_enter(cccp);
ghd_timer_newstate(cccp, NULL, gtgtp, GACTION_ABORT_DEV, GHD_TGTREQ);
rc = ghd_poll(cccp, GHD_POLL_DEVICE, ghd_tran_abort_lun_timeout,
NULL, gtgtp, intr_status);
ghd_doneq_pollmode_exit(cccp);
mutex_enter(&cccp->ccc_waitq_mutex);
ghd_waitq_process_and_mutex_exit(cccp);
return (rc);
}
int
ghd_tran_reset_target(ccc_t *cccp, gtgt_t *gtgtp, void *intr_status)
{
int rc = TRUE;
mutex_enter(&cccp->ccc_hba_mutex);
ghd_doneq_pollmode_enter(cccp);
ghd_timer_newstate(cccp, NULL, gtgtp, GACTION_RESET_TARGET, GHD_TGTREQ);
rc = ghd_poll(cccp, GHD_POLL_DEVICE, ghd_tran_reset_target_timeout,
NULL, gtgtp, intr_status);
ghd_doneq_pollmode_exit(cccp);
mutex_enter(&cccp->ccc_waitq_mutex);
ghd_waitq_process_and_mutex_exit(cccp);
return (rc);
}
int
ghd_tran_reset_bus(ccc_t *cccp, gtgt_t *gtgtp, void *intr_status)
{
int rc;
mutex_enter(&cccp->ccc_hba_mutex);
ghd_doneq_pollmode_enter(cccp);
ghd_timer_newstate(cccp, NULL, gtgtp, GACTION_RESET_BUS, GHD_TGTREQ);
rc = ghd_poll(cccp, GHD_POLL_ALL, ghd_tran_reset_bus_timeout,
NULL, NULL, intr_status);
ghd_doneq_pollmode_exit(cccp);
mutex_enter(&cccp->ccc_waitq_mutex);
ghd_waitq_process_and_mutex_exit(cccp);
return (rc);
}
int
ghd_transport(ccc_t *cccp,
gcmd_t *gcmdp,
gtgt_t *gtgtp,
ulong_t timeout,
int polled,
void *intr_status)
{
gdev_t *gdevp = gtgtp->gt_gdevp;
ASSERT(!mutex_owned(&cccp->ccc_hba_mutex));
ASSERT(!mutex_owned(&cccp->ccc_waitq_mutex));
if (polled) {
mutex_enter(&cccp->ccc_hba_mutex);
GDBG_START(("ghd_transport: polled"
" cccp 0x%p gdevp 0x%p gtgtp 0x%p gcmdp 0x%p\n",
(void *)cccp, (void *)gdevp, (void *)gtgtp, (void *)gcmdp));
ghd_doneq_pollmode_enter(cccp);
}
#if defined(GHD_DEBUG) || defined(__lint)
else {
GDBG_START(("ghd_transport: non-polled"
" cccp 0x%p gdevp 0x%p gtgtp 0x%p gcmdp 0x%p\n",
(void *)cccp, (void *)gdevp, (void *)gtgtp, (void *)gcmdp));
}
#endif
gcmdp->cmd_waitq_level = 1;
mutex_enter(&cccp->ccc_waitq_mutex);
L2_add(&GDEV_QHEAD(gdevp), &gcmdp->cmd_q, gcmdp);
gcmdp->cmd_state = GCMD_STATE_WAITQ;
ghd_timer_start(cccp, gcmdp, timeout);
ghd_waitq_shuffle_up(cccp, gdevp);
if (!polled) {
if (!mutex_tryenter(&cccp->ccc_hba_mutex)) {
GDBG_START(("ghd_transport: !mutex cccp 0x%p\n",
(void *)cccp));
mutex_exit(&cccp->ccc_waitq_mutex);
return (TRAN_ACCEPT);
}
GDBG_START(("ghd_transport: got mutex cccp 0x%p\n",
(void *)cccp));
ghd_waitq_process_and_mutex_exit(cccp);
ASSERT(!mutex_owned(&cccp->ccc_hba_mutex));
ASSERT(!mutex_owned(&cccp->ccc_waitq_mutex));
return (TRAN_ACCEPT);
}
mutex_exit(&cccp->ccc_waitq_mutex);
(void) ghd_poll(cccp, GHD_POLL_REQUEST, 0, gcmdp, gtgtp, intr_status);
ghd_doneq_pollmode_exit(cccp);
mutex_enter(&cccp->ccc_waitq_mutex);
ghd_waitq_process_and_mutex_exit(cccp);
(*cccp->ccc_hba_complete)(cccp->ccc_hba_handle, gcmdp, FALSE);
GDBG_START(("ghd_transport: polled done cccp 0x%p\n", (void *)cccp));
return (TRAN_ACCEPT);
}
int ghd_reset_notify(ccc_t *cccp,
gtgt_t *gtgtp,
int flag,
void (*callback)(caddr_t),
caddr_t arg)
{
ghd_reset_notify_list_t *rnp;
int rc = FALSE;
switch (flag) {
case SCSI_RESET_NOTIFY:
rnp = (ghd_reset_notify_list_t *)kmem_zalloc(sizeof (*rnp),
KM_SLEEP);
rnp->gtgtp = gtgtp;
rnp->callback = callback;
rnp->arg = arg;
mutex_enter(&cccp->ccc_reset_notify_mutex);
L2_add(&cccp->ccc_reset_notify_list, &rnp->l2_link,
(void *)rnp);
mutex_exit(&cccp->ccc_reset_notify_mutex);
rc = TRUE;
break;
case SCSI_RESET_CANCEL:
mutex_enter(&cccp->ccc_reset_notify_mutex);
for (rnp = (ghd_reset_notify_list_t *)
L2_next(&cccp->ccc_reset_notify_list);
rnp != NULL;
rnp = (ghd_reset_notify_list_t *)L2_next(&rnp->l2_link)) {
if (rnp->gtgtp == gtgtp &&
rnp->callback == callback &&
rnp->arg == arg) {
L2_delete(&rnp->l2_link);
kmem_free(rnp, sizeof (*rnp));
rc = TRUE;
}
}
mutex_exit(&cccp->ccc_reset_notify_mutex);
break;
default:
rc = FALSE;
break;
}
return (rc);
}
void
ghd_freeze_waitq(ccc_t *cccp, int delay)
{
ASSERT(mutex_owned(&cccp->ccc_hba_mutex));
mutex_enter(&cccp->ccc_waitq_mutex);
cccp->ccc_waitq_freezetime = ddi_get_lbolt();
cccp->ccc_waitq_freezedelay = delay;
cccp->ccc_waitq_frozen = 1;
mutex_exit(&cccp->ccc_waitq_mutex);
}
void
ghd_queue_hold(ccc_t *cccp)
{
ASSERT(mutex_owned(&cccp->ccc_hba_mutex));
mutex_enter(&cccp->ccc_waitq_mutex);
cccp->ccc_waitq_held = 1;
mutex_exit(&cccp->ccc_waitq_mutex);
}
void
ghd_queue_unhold(ccc_t *cccp)
{
ASSERT(mutex_owned(&cccp->ccc_hba_mutex));
mutex_enter(&cccp->ccc_waitq_mutex);
cccp->ccc_waitq_held = 0;
mutex_exit(&cccp->ccc_waitq_mutex);
}
void
ghd_trigger_reset_notify(ccc_t *cccp)
{
gcmd_t *gcmdp;
ASSERT(mutex_owned(&cccp->ccc_hba_mutex));
gcmdp = ghd_gcmd_alloc((gtgt_t *)NULL, 0, TRUE);
gcmdp->cmd_flags = GCMDFLG_RESET_NOTIFY;
GHD_DONEQ_PUT_HEAD(cccp, gcmdp);
}