/* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * 1394 mass storage SBP-2 driver routines */ #include #include #include #include #include #include #include #include #include #include #include #include static void scsa1394_sbp2_detect_symbios(scsa1394_state_t *); static void scsa1394_sbp2_worker_thread(void *); static void scsa1394_sbp2_status_cb(void *, sbp2_task_t *); static void scsa1394_sbp2_seg2pt_default(scsa1394_lun_t *, scsa1394_cmd_t *); static void scsa1394_sbp2_seg2pt_symbios(scsa1394_lun_t *, scsa1394_cmd_t *); static void scsa1394_sbp2_req_status(scsa1394_lun_t *); static void scsa1394_sbp2_status_proc(scsa1394_lun_t *, scsa1394_cmd_t *, scsa1394_status_t *); static int scsa1394_sbp2_conv_status(scsa1394_cmd_t *, scsa1394_status_t *); static void scsa1394_sbp2_reset_proc(scsa1394_lun_t *, int, scsa1394_cmd_t *); static boolean_t scsa1394_sbp2_logged_in(scsa1394_lun_t *); extern sbp2_bus_t scsa1394_sbp2_bus; /* tunables */ uint_t scsa1394_sbp2_max_payload_sub = 2; extern int scsa1394_symbios_size_max; extern int scsa1394_symbios_page_size; extern int scsa1394_wrka_symbios; /* symbios workaround will be applied unless device is on this list */ scsa1394_bw_list_t scsa1394_sbp2_symbios_whitelist[] = { { SCSA1394_BW_ONE, 0x0a27 }, /* Apple */ { SCSA1394_BW_ONE, 0xd04b } /* LaCie */ }; /* * * --- SBP-2 routines * */ int scsa1394_sbp2_attach(scsa1394_state_t *sp) { sbp2_tgt_t *tp; scsa1394_lun_t *lp; int i; /* * target */ if (sbp2_tgt_init(sp, &scsa1394_sbp2_bus, NLUNS_PER_TARGET, &sp->s_tgt) != SBP2_SUCCESS) { return (DDI_FAILURE); } tp = sp->s_tgt; /* * luns */ sp->s_nluns = tp->t_nluns; sp->s_lun = kmem_zalloc(sp->s_nluns * sizeof (scsa1394_lun_t), KM_SLEEP); for (i = 0; i < sp->s_nluns; i++) { lp = &sp->s_lun[i]; mutex_init(&lp->l_mutex, NULL, MUTEX_DRIVER, sp->s_attachinfo.iblock_cookie); lp->l_rmb_orig = -1; lp->l_lun = &tp->t_lun[i]; lp->l_sp = sp; lp->l_lba_size = DEV_BSIZE; } scsa1394_sbp2_detect_symbios(sp); return (DDI_SUCCESS); } void scsa1394_sbp2_detach(scsa1394_state_t *sp) { int i; scsa1394_lun_t *lp; for (i = 0; i < sp->s_nluns; i++) { lp = &sp->s_lun[i]; if (lp->l_sp != NULL) { mutex_destroy(&lp->l_mutex); } } kmem_free(sp->s_lun, sp->s_nluns * sizeof (scsa1394_lun_t)); sbp2_tgt_fini(sp->s_tgt); } static void scsa1394_sbp2_detect_symbios(scsa1394_state_t *sp) { sbp2_cfgrom_ent_t *root = &sp->s_tgt->t_cfgrom.cr_root; sbp2_cfgrom_ent_t *ent; scsa1394_bw_list_t *wl; int vid; int i; if (!scsa1394_wrka_symbios) { sp->s_symbios = B_FALSE; return; } else { sp->s_symbios = B_TRUE; } /* get device's vendor ID */ if ((ent = sbp2_cfgrom_ent_by_key(root, IEEE1212_IMMEDIATE_TYPE, IEEE1212_MODULE_VENDOR_ID, 0)) == NULL) { return; } vid = ent->ce_data.imm; /* find a match in the whitelist */ for (i = 0; i < NELEM(scsa1394_sbp2_symbios_whitelist); i++) { wl = &scsa1394_sbp2_symbios_whitelist[i]; if ((wl->vid_match == SCSA1394_BW_ONE) && (wl->vid == vid)) { sp->s_symbios = B_FALSE; break; } } } /* * functional equivalent of ddi_rep_get32() with big endian access handle */ static void bcopy_swap32(uint32_t *from, uint32_t *to, int count) { int i; uint32_t data; ASSERT((uintptr_t)to % 4 == 0); for (i = 0; i < count; i++) { data = *from++; *to++ = SBP2_SWAP32(data); } } /* * Build an inquiry for a given device that doesn't like inquiry commands. */ void scsa1394_sbp2_fake_inquiry(scsa1394_state_t *sp, struct scsi_inquiry *inq) { sbp2_cfgrom_ent_t *r = &sp->s_tgt->t_cfgrom.cr_root; sbp2_cfgrom_ent_t *e, *eref, *evid; int i, len; bzero(inq, sizeof (struct scsi_inquiry)); inq->inq_dtype = DTYPE_DIRECT; inq->inq_rmb = 1; inq->inq_ansi = 2; inq->inq_rdf = RDF_SCSI2; inq->inq_len = sizeof (struct scsi_inquiry) - 4; (void) memset(inq->inq_vid, ' ', sizeof (inq->inq_vid)); (void) memset(inq->inq_pid, ' ', sizeof (inq->inq_pid)); (void) memset(inq->inq_revision, ' ', sizeof (inq->inq_revision)); /* * vid/pid/rev can be derived from Config ROM textual descriptors */ for (i = 0; i < 256; i++) { if ((e = sbp2_cfgrom_ent_by_key(r, IEEE1212_LEAF_TYPE, IEEE1212_TEXTUAL_DESCRIPTOR, i)) == NULL) { break; } eref = e->ce_ref; if ((eref == NULL) || (eref->ce_len < 3) && (eref->ce_kt != IEEE1212_IMMEDIATE_TYPE)) { continue; } len = e->ce_len - 2; if (eref->ce_kv == IEEE1212_MODULE_VENDOR_ID) { evid = e; bcopy_swap32(&e->ce_data.leaf[2], (uint32_t *)inq->inq_vid, min(sizeof (inq->inq_vid) / 4, len)); } else if (eref->ce_kv == 0x17) { bcopy_swap32(&e->ce_data.leaf[2], (uint32_t *)inq->inq_pid, min(sizeof (inq->inq_pid) / 4, len)); } else if ((eref->ce_kv == IEEE1212_MODULE_HW_VERSION) || (eref == evid)) { bcopy_swap32(&e->ce_data.leaf[2], (uint32_t *)inq->inq_revision, min(sizeof (inq->inq_revision) / 4, len)); } } } int scsa1394_sbp2_threads_init(scsa1394_state_t *sp) { scsa1394_lun_t *lp; scsa1394_thread_t *thr; int i; for (i = 0; i < sp->s_nluns; i++) { lp = &sp->s_lun[i]; thr = &lp->l_worker_thread; thr->thr_func = scsa1394_sbp2_worker_thread; thr->thr_arg = thr; thr->thr_state = SCSA1394_THR_INIT; cv_init(&thr->thr_cv, NULL, CV_DRIVER, NULL); thr->thr_lun = lp; thr->thr_req = 0; mutex_enter(&lp->l_mutex); if (scsa1394_thr_dispatch(thr) != DDI_SUCCESS) { mutex_exit(&lp->l_mutex); scsa1394_sbp2_threads_fini(sp); return (DDI_FAILURE); } mutex_exit(&lp->l_mutex); } return (DDI_SUCCESS); } void scsa1394_sbp2_threads_fini(scsa1394_state_t *sp) { scsa1394_lun_t *lp; scsa1394_thread_t *thr; int i; for (i = 0; i < sp->s_nluns; i++) { lp = &sp->s_lun[i]; thr = &lp->l_worker_thread; /* if thread wasn't initialized, thr_lun will be NULL */ if (thr->thr_lun == lp) { mutex_enter(&lp->l_mutex); scsa1394_thr_cancel(thr); mutex_exit(&lp->l_mutex); ASSERT(thr->thr_state != SCSA1394_THR_RUN); cv_destroy(&thr->thr_cv); } } } int scsa1394_sbp2_get_lun_type(scsa1394_lun_t *lp) { return (lp->l_lun->l_type); } int scsa1394_sbp2_login(scsa1394_state_t *sp, int lun) { scsa1394_lun_t *lp = &sp->s_lun[lun]; int berr; if (sbp2_lun_login(lp->l_lun, &lp->l_ses, scsa1394_sbp2_status_cb, lp, &berr) != SBP2_SUCCESS) { return (DDI_FAILURE); } ASSERT(lp->l_ses != NULL); return (DDI_SUCCESS); } void scsa1394_sbp2_logout(scsa1394_state_t *sp, int lun, boolean_t phys) { scsa1394_lun_t *lp = &sp->s_lun[lun]; int berr; if (scsa1394_sbp2_logged_in(lp)) { (void) sbp2_lun_logout(lp->l_lun, &lp->l_ses, &berr, phys); } } void scsa1394_sbp2_req(scsa1394_state_t *sp, int lun, int req) { scsa1394_lun_t *lp = &sp->s_lun[lun]; if (lp != NULL) { mutex_enter(&lp->l_mutex); scsa1394_thr_wake(&lp->l_worker_thread, req); mutex_exit(&lp->l_mutex); } } void scsa1394_sbp2_req_bus_reset(scsa1394_lun_t *lp) { scsa1394_state_t *sp = lp->l_sp; int berr = 0; if (t1394_get_targetinfo(sp->s_t1394_hdl, SCSA1394_BUSGEN(sp), 0, &sp->s_targetinfo) != DDI_SUCCESS) { goto disconnect; } if (sp->s_targetinfo.target_nodeID == T1394_INVALID_NODEID) { goto disconnect; } if (!scsa1394_sbp2_logged_in(lp)) { /* reconnect procedure is only for logged in hosts */ return; } /* * Try SBP-2 RECONNECT procedure first. Note that we're passing * local Node ID, which might have changed during bus reset. * sbp2_ses_reconnect() will use it to update the ORBs. */ if (sbp2_ses_reconnect(lp->l_ses, &berr, SCSA1394_NODEID(sp)) == SBP2_SUCCESS) { mutex_enter(&sp->s_mutex); sp->s_dev_state = SCSA1394_DEV_ONLINE; mutex_exit(&sp->s_mutex); /* resume task processing */ scsa1394_sbp2_nudge(lp); return; } if (berr == CMD1394_EDEVICE_REMOVED) { goto disconnect; } /* reconnect failed, try to logout and login again */ scsa1394_sbp2_flush_cmds(lp, CMD_TRAN_ERR, 0, STAT_BUS_RESET); (void) sbp2_lun_logout(lp->l_lun, &lp->l_ses, &berr, B_FALSE); if (scsa1394_sbp2_login(sp, 0) != SBP2_SUCCESS) { goto disconnect; } mutex_enter(&sp->s_mutex); sp->s_dev_state = SCSA1394_DEV_ONLINE; mutex_exit(&sp->s_mutex); return; disconnect: mutex_enter(&sp->s_mutex); sp->s_dev_state = SCSA1394_DEV_DISCONNECTED; mutex_exit(&sp->s_mutex); if (scsa1394_sbp2_logged_in(lp)) { scsa1394_sbp2_flush_cmds(lp, CMD_DEV_GONE, 0, STAT_BUS_RESET); (void) sbp2_lun_logout(lp->l_lun, &lp->l_ses, &berr, B_FALSE); } } /*ARGSUSED*/ void scsa1394_sbp2_req_reconnect(scsa1394_lun_t *lp) { scsa1394_state_t *sp = lp->l_sp; if (t1394_get_targetinfo(sp->s_t1394_hdl, SCSA1394_BUSGEN(sp), 0, &sp->s_targetinfo) != DDI_SUCCESS) { return; } mutex_enter(&sp->s_mutex); sp->s_dev_state = SCSA1394_DEV_ONLINE; mutex_exit(&sp->s_mutex); if (sbp2_tgt_reconnect(sp->s_tgt) != SBP2_SUCCESS) { goto disconnect; } if (scsa1394_sbp2_login(sp, 0) != SBP2_SUCCESS) { goto disconnect; } cmn_err(CE_WARN, "scsa1394(%d): " "Reinserted device is accessible again.\n", sp->s_instance); return; disconnect: mutex_enter(&sp->s_mutex); sp->s_dev_state = SCSA1394_DEV_DISCONNECTED; mutex_exit(&sp->s_mutex); } void scsa1394_sbp2_disconnect(scsa1394_state_t *sp) { scsa1394_lun_t *lp = &sp->s_lun[0]; int berr; scsa1394_sbp2_flush_cmds(lp, CMD_DEV_GONE, 0, STAT_BUS_RESET); if (scsa1394_sbp2_logged_in(lp)) { (void) sbp2_lun_logout(lp->l_lun, &lp->l_ses, &berr, B_FALSE); } sbp2_tgt_disconnect(sp->s_tgt); } /* * convert segment array into DMA-mapped SBP-2 page table */ void scsa1394_sbp2_seg2pt(scsa1394_lun_t *lp, scsa1394_cmd_t *cmd) { scsa1394_state_t *sp = lp->l_sp; ASSERT(cmd->sc_flags & SCSA1394_CMD_DMA_BUF_PT_VALID); if (sp->s_symbios) { scsa1394_sbp2_seg2pt_symbios(lp, cmd); } else { scsa1394_sbp2_seg2pt_default(lp, cmd); } } /*ARGSUSED*/ static void scsa1394_sbp2_seg2pt_default(scsa1394_lun_t *lp, scsa1394_cmd_t *cmd) { sbp2_pt_unrestricted_t *pt; scsa1394_cmd_seg_t *seg; int i; pt = (sbp2_pt_unrestricted_t *)cmd->sc_pt_kaddr; seg = &cmd->sc_buf_seg[0]; for (i = 0; i < cmd->sc_buf_nsegs; i++) { pt->pt_seg_len = SBP2_SWAP16(seg->ss_len); pt->pt_seg_base_hi = SBP2_SWAP16(seg->ss_baddr >> 32); pt->pt_seg_base_lo = SBP2_SWAP32(seg->ss_baddr & 0xFFFFFFFF); pt++; seg++; } (void) ddi_dma_sync(cmd->sc_pt_dma_hdl, 0, 0, DDI_DMA_SYNC_FORDEV); cmd->sc_pt_cmd_size = cmd->sc_buf_nsegs; } /* * fill page table for Symbios workaround */ /*ARGSUSED*/ static void scsa1394_sbp2_seg2pt_symbios(scsa1394_lun_t *lp, scsa1394_cmd_t *cmd) { sbp2_pt_unrestricted_t *pt; scsa1394_cmd_seg_t *seg; int nsegs; size_t resid, skiplen, dataoff, segoff, seglen; uint64_t baddr; /* data offset within command */ if (cmd->sc_flags & SCSA1394_CMD_SYMBIOS_BREAKUP) { dataoff = (cmd->sc_total_blks - cmd->sc_resid_blks) * cmd->sc_blk_size; } else { dataoff = 0; } /* skip dataoff bytes */ seg = &cmd->sc_buf_seg[0]; skiplen = 0; while (skiplen + seg->ss_len <= dataoff) { skiplen += seg->ss_len; seg++; } segoff = dataoff - skiplen; /* offset within segment */ pt = (sbp2_pt_unrestricted_t *)cmd->sc_pt_kaddr; resid = cmd->sc_xfer_bytes; nsegs = 0; while (resid > 0) { ASSERT(seg->ss_len <= scsa1394_symbios_page_size); seglen = min(seg->ss_len, resid) - segoff; baddr = seg->ss_baddr + segoff; pt->pt_seg_len = SBP2_SWAP16(seglen); pt->pt_seg_base_hi = SBP2_SWAP16(baddr >> 32); pt->pt_seg_base_lo = SBP2_SWAP32(baddr & 0xFFFFFFFF); segoff = 0; resid -= seglen; nsegs++; pt++; seg++; } (void) ddi_dma_sync(cmd->sc_pt_dma_hdl, 0, 0, DDI_DMA_SYNC_FORDEV); cmd->sc_pt_cmd_size = nsegs; } /* * convert command into DMA-mapped SBP-2 ORB */ void scsa1394_sbp2_cmd2orb(scsa1394_lun_t *lp, scsa1394_cmd_t *cmd) { scsa1394_state_t *sp = lp->l_sp; scsa1394_cmd_orb_t *orb = sbp2_task_orb_kaddr(&cmd->sc_task); mutex_enter(&lp->l_mutex); lp->l_stat.stat_cmd_cnt++; bzero(orb->co_cdb, sizeof (orb->co_cdb)); /* CDB */ bcopy(cmd->sc_cdb, orb->co_cdb, cmd->sc_cdb_actual_len); /* * ORB parameters * * use max speed and max payload for this speed. * max async data transfer for a given speed is 512<co_params = SBP2_ORB_NOTIFY | SBP2_ORB_RQ_FMT_SBP2 | (sp->s_targetinfo.current_max_speed << SBP2_ORB_CMD_SPD_SHIFT) | ((7 + sp->s_targetinfo.current_max_speed - scsa1394_sbp2_max_payload_sub) << SBP2_ORB_CMD_MAX_PAYLOAD_SHIFT); /* direction: initiator's read is target's write (and vice versa) */ if (cmd->sc_flags & SCSA1394_CMD_READ) { orb->co_params |= SBP2_ORB_CMD_DIR; } /* * data_size and data_descriptor */ if (cmd->sc_buf_nsegs == 0) { /* no data */ orb->co_data_size = 0; SCSA1394_ADDR_SET(sp, orb->co_data_descr, 0); } else if (cmd->sc_buf_nsegs == 1) { /* contiguous buffer - use direct addressing */ ASSERT(cmd->sc_buf_seg[0].ss_len != 0); orb->co_data_size = SBP2_SWAP16(cmd->sc_buf_seg[0].ss_len); SCSA1394_ADDR_SET(sp, orb->co_data_descr, cmd->sc_buf_seg[0].ss_baddr); } else { /* non-contiguous s/g list - page table */ ASSERT(cmd->sc_pt_cmd_size > 0); orb->co_params |= SBP2_ORB_CMD_PT; orb->co_data_size = SBP2_SWAP16(cmd->sc_pt_cmd_size); SCSA1394_ADDR_SET(sp, orb->co_data_descr, cmd->sc_pt_baddr); } SBP2_SWAP16_1(orb->co_params); SBP2_ORBP_SET(orb->co_next_orb, SBP2_ORBP_NULL); mutex_exit(&lp->l_mutex); sbp2_task_orb_sync(lp->l_lun, &cmd->sc_task, DDI_DMA_SYNC_FORDEV); } /*ARGSUSED*/ int scsa1394_sbp2_start(scsa1394_lun_t *lp, scsa1394_cmd_t *cmd) { sbp2_task_t *task = CMD2TASK(cmd); int ret; ASSERT(lp->l_ses != NULL); task->ts_timeout = cmd->sc_timeout; task->ts_error = SBP2_TASK_ERR_NONE; task->ts_bus_error = 0; task->ts_state = SBP2_TASK_INIT; ret = sbp2_ses_submit_task(lp->l_ses, task); if ((ret == SBP2_SUCCESS) || (ret == SBP2_ECONTEXT)) { return (TRAN_ACCEPT); } if (task->ts_error == SBP2_TASK_ERR_BUS) { if (task->ts_bus_error == CMD1394_EDEVICE_BUSY) { return (TRAN_BUSY); } else { return (TRAN_FATAL_ERROR); } } else { return (TRAN_FATAL_ERROR); } } /* * This function is called by SBP-2 layer when task status is received, * typically from interrupt handler. Just wake the thread to do the actual work. */ /*ARGSUSED*/ static void scsa1394_sbp2_status_cb(void *arg, sbp2_task_t *task) { scsa1394_lun_t *lp = (scsa1394_lun_t *)arg; mutex_enter(&lp->l_mutex); scsa1394_thr_wake(&lp->l_worker_thread, SCSA1394_THREQ_TASK_STATUS); mutex_exit(&lp->l_mutex); } void scsa1394_sbp2_nudge(scsa1394_lun_t *lp) { mutex_enter(&lp->l_mutex); scsa1394_thr_wake(&lp->l_worker_thread, SCSA1394_THREQ_NUDGE); mutex_exit(&lp->l_mutex); } /* * worker thread */ static void scsa1394_sbp2_worker_thread(void *arg) { scsa1394_thread_t *thr = (scsa1394_thread_t *)arg; scsa1394_lun_t *lp = thr->thr_lun; mutex_enter(&lp->l_mutex); for (;;) { while (thr->thr_req == 0) { cv_wait(&thr->thr_cv, &lp->l_mutex); } if (thr->thr_req & SCSA1394_THREQ_EXIT) { break; } if (thr->thr_req & SCSA1394_THREQ_BUS_RESET) { thr->thr_req &= ~SCSA1394_THREQ_BUS_RESET; mutex_exit(&lp->l_mutex); scsa1394_sbp2_req_bus_reset(lp); mutex_enter(&lp->l_mutex); continue; } if (thr->thr_req & SCSA1394_THREQ_RECONNECT) { thr->thr_req &= ~SCSA1394_THREQ_RECONNECT; mutex_exit(&lp->l_mutex); scsa1394_sbp2_req_reconnect(lp); mutex_enter(&lp->l_mutex); continue; } if (thr->thr_req & SCSA1394_THREQ_TASK_STATUS) { thr->thr_req &= ~SCSA1394_THREQ_TASK_STATUS; mutex_exit(&lp->l_mutex); scsa1394_sbp2_req_status(lp); mutex_enter(&lp->l_mutex); continue; } if (thr->thr_req & SCSA1394_THREQ_NUDGE) { thr->thr_req &= ~SCSA1394_THREQ_NUDGE; mutex_exit(&lp->l_mutex); if (scsa1394_sbp2_logged_in(lp)) { sbp2_ses_nudge(lp->l_ses); } mutex_enter(&lp->l_mutex); continue; } } thr->thr_state = SCSA1394_THR_EXIT; cv_signal(&thr->thr_cv); mutex_exit(&lp->l_mutex); } /* * task status handler */ static void scsa1394_sbp2_req_status(scsa1394_lun_t *lp) { sbp2_ses_t *sp = lp->l_ses; sbp2_task_t *task; if (sp == NULL) { return; } /* * Process all tasks that received status. * This algorithm preserves callback order. */ while ((task = sbp2_ses_remove_first_task_state(sp, SBP2_TASK_COMP)) != NULL) { sbp2_ses_nudge(sp); ASSERT(task->ts_state == SBP2_TASK_COMP); task->ts_state = SBP2_TASK_PROC; scsa1394_sbp2_status_proc(lp, TASK2CMD(task), (scsa1394_status_t *)&task->ts_status); } sbp2_ses_nudge(sp); /* submit next task */ } static void scsa1394_sbp2_status_proc(scsa1394_lun_t *lp, scsa1394_cmd_t *cmd, scsa1394_status_t *st) { sbp2_task_t *task = CMD2TASK(cmd); struct scsi_pkt *pkt = CMD2PKT(cmd); uint64_t *p; if (cmd->sc_flags & SCSA1394_CMD_READ) { (void) ddi_dma_sync(cmd->sc_buf_dma_hdl, 0, 0, DDI_DMA_SYNC_FORKERNEL); } if (task->ts_error != SBP2_TASK_ERR_NONE) { pkt->pkt_state |= STATE_GOT_BUS; switch (task->ts_error) { case SBP2_TASK_ERR_ABORT: pkt->pkt_state |= STATE_GOT_TARGET; pkt->pkt_reason = CMD_ABORTED; break; case SBP2_TASK_ERR_LUN_RESET: pkt->pkt_state |= STATE_GOT_TARGET; pkt->pkt_reason = CMD_RESET; pkt->pkt_statistics |= STAT_DEV_RESET; break; case SBP2_TASK_ERR_TGT_RESET: pkt->pkt_state |= STATE_GOT_TARGET; pkt->pkt_reason = CMD_RESET; pkt->pkt_statistics |= STAT_DEV_RESET; break; case SBP2_TASK_ERR_TIMEOUT: (void) scsa1394_sbp2_reset(lp, RESET_TARGET, cmd); return; case SBP2_TASK_ERR_DEAD: case SBP2_TASK_ERR_BUS: default: pkt->pkt_reason = CMD_TRAN_ERR; break; } } else if ((st->st_param & SBP2_ST_RESP) == SBP2_ST_RESP_COMPLETE) { /* * SBP-2 status block has been received, now look at sbp_status. * * Note: ANSI NCITS 325-1998 B.2 requires that when status is * GOOD, length must be one, but some devices do not comply */ if (st->st_sbp_status == SBP2_ST_SBP_DUMMY_ORB) { pkt->pkt_state |= (STATE_GOT_BUS | STATE_GOT_TARGET); pkt->pkt_reason = CMD_ABORTED; pkt->pkt_statistics |= STAT_DEV_RESET; } else if ((st->st_status & SCSA1394_ST_STATUS) == STATUS_GOOD) { /* request complete */ *(pkt->pkt_scbp) = STATUS_GOOD; pkt->pkt_state |= (STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD | STATE_XFERRED_DATA | STATE_GOT_STATUS); pkt->pkt_reason = CMD_CMPLT; } else if (scsa1394_sbp2_conv_status(cmd, st) == DDI_SUCCESS) { pkt->pkt_state |= (STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD | STATE_XFERRED_DATA | STATE_GOT_STATUS | STATE_ARQ_DONE); pkt->pkt_reason = CMD_TRAN_ERR; } else { pkt->pkt_state |= (STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD | STATE_XFERRED_DATA | STATE_GOT_STATUS); pkt->pkt_reason = CMD_TRAN_ERR; lp->l_stat.stat_err_status_conv++; } } else { /* transport or serial bus failure */ pkt->pkt_state |= (STATE_GOT_BUS | STATE_GOT_TARGET); pkt->pkt_reason = CMD_TRAN_ERR; lp->l_stat.stat_err_status_resp++; } if (pkt->pkt_reason == CMD_TRAN_ERR) { lp->l_stat.stat_err_status_tran_err++; /* save the command */ p = &lp->l_stat.stat_cmd_last_fail[ lp->l_stat.stat_cmd_last_fail_idx][0]; bcopy(&pkt->pkt_cdbp[0], p, min(cmd->sc_cdb_len, 16)); *(clock_t *)&p[2] = ddi_get_lbolt(); lp->l_stat.stat_cmd_last_fail_idx = (lp->l_stat.stat_cmd_last_fail_idx + 1) % SCSA1394_STAT_NCMD_LAST; } /* generic HBA status processing */ scsa1394_cmd_status_proc(lp, cmd); } /* * Convert SBP-2 status block into SCSA status. * * Note: (ref: B.2) "SBP-2 permits the return of a status block between two * and quadlets in length. When a truncated status block is stored, the * omitted quadlets shall be interpreted as if zero values were stored." * We expect the sbp2 layer to do the zeroing for us. */ static int scsa1394_sbp2_conv_status(scsa1394_cmd_t *cmd, scsa1394_status_t *st) { struct scsi_pkt *pkt = CMD2PKT(cmd); uint8_t status = st->st_status; uint8_t bits = st->st_sense_bits; struct scsi_arq_status *arqp = (struct scsi_arq_status *)pkt->pkt_scbp; struct scsi_extended_sense *esp = &arqp->sts_sensedata; *(pkt->pkt_scbp) = (status & SCSA1394_ST_STATUS); *(uint8_t *)&arqp->sts_rqpkt_status = STATUS_GOOD; arqp->sts_rqpkt_reason = CMD_CMPLT; arqp->sts_rqpkt_resid = 0; arqp->sts_rqpkt_state |= STATE_XFERRED_DATA; arqp->sts_rqpkt_statistics = 0; esp->es_valid = (bits & SCSA1394_ST_VALID) >> SCSA1394_ST_VALID_SHIFT; esp->es_class = CLASS_EXTENDED_SENSE; esp->es_code = (status & SCSA1394_ST_SFMT) >> SCSA1394_ST_SFMT_SHIFT; esp->es_segnum = 0; esp->es_filmk = (bits & SCSA1394_ST_MARK) >> SCSA1394_ST_MARK_SHIFT; esp->es_eom = (bits & SCSA1394_ST_EOM) >> SCSA1394_ST_EOM_SHIFT; esp->es_ili = (bits & SCSA1394_ST_ILI) >> SCSA1394_ST_ILI_SHIFT; esp->es_key = (bits & SCSA1394_ST_SENSE_KEY); esp->es_info_1 = st->st_info[0]; esp->es_info_2 = st->st_info[1]; esp->es_info_3 = st->st_info[2]; esp->es_info_4 = st->st_info[3]; esp->es_add_len = 4; esp->es_cmd_info[0] = st->st_cdb[0]; esp->es_cmd_info[1] = st->st_cdb[1]; esp->es_cmd_info[2] = st->st_cdb[2]; esp->es_cmd_info[3] = st->st_cdb[3]; esp->es_add_code = st->st_sense_code; esp->es_qual_code = st->st_sense_qual; esp->es_fru_code = st->st_fru; esp->es_skey_specific[0] = st->st_sks[0]; esp->es_skey_specific[1] = st->st_sks[1]; esp->es_skey_specific[2] = st->st_sks[2]; esp->es_add_info[0] = esp->es_add_info[1] = 0; return (DDI_SUCCESS); } /* * Sends appropriate reset command to the target. LUN reset is optional, so it * can fail, in which case the SCSA target driver will use RESET_TARGET/ALL. * Target reset support is mandatory in SBP-2, if it fails, it means something's * terribly wrong with the device - blow away outstanding tasks in that case. */ int scsa1394_sbp2_reset(scsa1394_lun_t *lp, int level, scsa1394_cmd_t *cmd) { scsa1394_state_t *sp = lp->l_sp; sbp2_task_t *task; int berr; int ret = DDI_FAILURE; if (scsa1394_dev_is_online(sp)) { switch (level) { case RESET_LUN: ret = sbp2_lun_reset(lp->l_lun, &berr); if (ret != SBP2_SUCCESS) { return (ret); } break; case RESET_TARGET: case RESET_ALL: ret = sbp2_tgt_reset(sp->s_tgt, &berr); break; } } if (cmd != NULL) { scsa1394_sbp2_reset_proc(lp, level, cmd); } if (scsa1394_sbp2_logged_in(lp)) { while ((task = sbp2_ses_cancel_first_task(lp->l_ses)) != NULL) { ASSERT(task->ts_state < SBP2_TASK_PROC); scsa1394_sbp2_reset_proc(lp, level, TASK2CMD(task)); } } return (ret); } static void scsa1394_sbp2_reset_proc(scsa1394_lun_t *lp, int level, scsa1394_cmd_t *cmd) { sbp2_task_t *task = CMD2TASK(cmd); struct scsi_pkt *pkt = CMD2PKT(cmd); int ts_error; pkt->pkt_reason = CMD_RESET; if (level == RESET_LUN) { if (task->ts_state == SBP2_TASK_PEND) { pkt->pkt_statistics |= STAT_DEV_RESET; } else { pkt->pkt_statistics |= STAT_ABORTED; } ts_error = SBP2_TASK_ERR_LUN_RESET; } else { pkt->pkt_statistics |= STAT_BUS_RESET; ts_error = SBP2_TASK_ERR_TGT_RESET; } task->ts_error = ts_error; task->ts_state = SBP2_TASK_PROC; scsa1394_cmd_status_proc(lp, cmd); } /* * Cancel commands immediately. * * Caller's responsibility to set device state such that no new tasks are added. */ void scsa1394_sbp2_flush_cmds(scsa1394_lun_t *lp, int reason, int state, int statistics) { scsa1394_cmd_t *cmd; struct scsi_pkt *pkt; sbp2_ses_t *sp = lp->l_ses; sbp2_task_t *task; if (sp == NULL) { return; } while ((task = sbp2_ses_cancel_first_task(sp)) != NULL) { ASSERT(task->ts_state < SBP2_TASK_PROC); cmd = TASK2CMD(task); pkt = CMD2PKT(cmd); pkt->pkt_reason = reason; pkt->pkt_state |= state; pkt->pkt_statistics |= statistics; task->ts_state = SBP2_TASK_PROC; scsa1394_cmd_status_proc(lp, cmd); } scsa1394_thr_clear_req(&lp->l_worker_thread, SCSA1394_THREQ_TASK_STATUS | SCSA1394_THREQ_NUDGE); } static boolean_t scsa1394_sbp2_logged_in(scsa1394_lun_t *lp) { return (lp->l_ses != NULL); }