Files
scst/qla_isp/linux/isp_linux.c
2009-09-19 20:18:03 +00:00

4847 lines
161 KiB
C

/* $Id: isp_linux.c,v 1.252 2009/09/08 01:22:53 mjacob Exp $ */
/*
* Copyright (c) 1997-2009 by Matthew Jacob
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
*
* Alternatively, this software may be distributed under the terms of the
* the GNU Public License ("GPL") with platforms where the prevalant license
* is the GNU Public License:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of The Version 2 GNU General Public License as published
* by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*
* Matthew Jacob
* Feral Software
* 421 Laurel Avenue
* Menlo Park, CA 94025
* USA
*
* gplbsd at feral com
*/
/*
* Qlogic ISP Host Adapter Common Bus Linux routies
*
* Bug fixes from Janice McLaughlin (janus@somemore.com)
* gratefully acknowledged.
*
*/
#define ISP_MODULE 1
#include "isp_linux.h"
#include "linux/smp_lock.h"
static int isp_task_thread(void *);
ispsoftc_t *isplist[MAX_ISP] = { NULL };
ispsoftc_t *api_isp = NULL;
int api_channel = 0;
int isp_debug = 0;
int isp_throttle = 0;
int isp_cmd_per_lun = 0;
int isp_maxsectors = 1024;
int isp_unit_seed = 0;
int isp_disable = 0;
int isp_nofwreload = 0;
int isp_nonvram = 0;
int isp_maxluns = 8;
int isp_fcduplex = 0;
int isp_nport_only = 0;
int isp_loop_only = 0;
int isp_deadloop_time = 10; /* how long to wait before assume loop dead */
int isp_fc_id = 111;
int isp_spi_id = 7;
int isp_own_id = 0;
int isp_default_frame_size;
int isp_default_exec_throttle;
int isp_vports = 0;
static char *isp_roles;
static char *isp_wwpns;
static char *isp_wwnns;
#ifdef ISP_TARGET_MODE
#ifndef ISP_PARENT_TARGET
#define ISP_PARENT_TARGET scsi_target_handler
#endif
#define CALL_PARENT_TMD(hba, tmd, action) \
tmd->cd_action = action; \
tmd->cd_next = hba->isp_osinfo.pending_t; \
hba->isp_osinfo.pending_t = tmd
#define CALL_PARENT_NOTIFY(hba, ins) \
ins->notify.nt_lreserved = hba->isp_osinfo.pending_n; \
hba->isp_osinfo.pending_n = ins
#define CALL_PARENT_XFR(hba, xact) \
xact->td_lprivate = hba->isp_osinfo.pending_x; \
hba->isp_osinfo.pending_x = xact
extern void ISP_PARENT_TARGET (qact_e, void *);
static ISP_INLINE tmd_cmd_t *isp_find_tmd(ispsoftc_t *, uint64_t);
static void isp_taction(qact_e, void *);
static void isp_target_start_ctio(ispsoftc_t *, tmd_xact_t *);
static void isp_handle_platform_atio(ispsoftc_t *, at_entry_t *);
static void isp_handle_platform_atio2(ispsoftc_t *, at2_entry_t *);
static void isp_handle_platform_atio7(ispsoftc_t *, at7_entry_t *);
static int isp_terminate_cmd(ispsoftc_t *, tmd_cmd_t *);
static void isp_handle_platform_ctio(ispsoftc_t *, void *);
static int isp_target_putback_atio(ispsoftc_t *, tmd_cmd_t *);
static void isp_complete_ctio(ispsoftc_t *, tmd_xact_t *);
static void isp_tgt_tq(ispsoftc_t *);
#endif
const char *
isplinux_info(struct Scsi_Host *host)
{
ispsoftc_t *isp = ISP_HOST2ISP(host);
if (IS_FC(isp)) {
static char *foo = "Driver for a Qlogic ISP 2X00 Host Adapter";
foo[26] = '0';
foo[27] = '0';
if (isp->isp_type == ISP_HA_FC_2100) {
foo[25] = '1';
} else if (isp->isp_type == ISP_HA_FC_2200) {
foo[25] = '2';
} else if (isp->isp_type == ISP_HA_FC_2300) {
foo[25] = '3';
} else if (isp->isp_type == ISP_HA_FC_2312) {
foo[25] = '3';
foo[26] = '1';
foo[27] = '2';
} else if (isp->isp_type == ISP_HA_FC_2322) {
foo[25] = '3';
foo[26] = '2';
foo[27] = '2';
} else if (isp->isp_type == ISP_HA_FC_2400) {
foo[25] = '4';
foo[26] = '2';
foo[27] = '2';
} else if (isp->isp_type == ISP_HA_FC_2500) {
foo[25] = '5';
foo[26] = '3';
foo[27] = '2';
}
return (foo);
} else if (IS_1240(isp)) {
return ("Driver for a Qlogic ISP 1240 Host Adapter");
} else if (IS_1080(isp)) {
return ("Driver for a Qlogic ISP 1080 Host Adapter");
} else if (IS_1280(isp)) {
return ("Driver for a Qlogic ISP 1280 Host Adapter");
} else if (IS_10160(isp)) {
return ("Driver for a Qlogic ISP 10160 Host Adapter");
} else if (IS_12160(isp)) {
return ("Driver for a Qlogic ISP 12160 Host Adapter");
} else {
return ("Driver for a Qlogic ISP 1020/1040 Host Adapter");
}
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28)
static ISP_INLINE void
isplinux_eh_timer_off(Scsi_Cmnd *Cmnd)
{
if (Cmnd->eh_timeout.function) {
del_timer(&Cmnd->eh_timeout);
}
}
static ISP_INLINE void
isplinux_eh_timer_on(Scsi_Cmnd *Cmnd)
{
if (Cmnd->eh_timeout.function) {
mod_timer(&Cmnd->eh_timeout, jiffies + Cmnd->timeout_per_command);
}
}
#else
static ISP_INLINE void
isplinux_eh_timer_on(Scsi_Cmnd *Cmnd)
{
Cmnd->SCp.ptr = (void *) jiffies;
Cmnd->SCp.Message = 1;
}
static ISP_INLINE void
isplinux_eh_timer_off(Scsi_Cmnd *Cmnd)
{
Cmnd->SCp.Message = 0;
}
static enum blk_eh_timer_return isplinux_eh_timed_out(Scsi_Cmnd *Cmnd)
{
/*
* Give us more time if command is on our internal wait queue
* or if time elapsed after removing from wait queue is too small
*/
if (Cmnd->SCp.Message == 0) {
return (BLK_EH_RESET_TIMER);
} else {
unsigned long start = (unsigned long) Cmnd->SCp.ptr;
if (time_before(jiffies, start + Cmnd->request->timeout)) {
return (BLK_EH_RESET_TIMER);
}
}
/*
* We do not do any error handling here, instruct scsi layer do it
*/
return (BLK_EH_NOT_HANDLED);
}
#endif
static ISP_INLINE void
isplinux_append_to_waitq(ispsoftc_t *isp, Scsi_Cmnd *Cmnd)
{
/*
* If we're a fibre channel card and we consider the loop to be
* down, we just finish the command here and now.
*/
if ((IS_FC(isp) && ISP_DATA(isp, XS_CHANNEL(Cmnd))->deadloop) || isp->isp_dead) {
XS_INITERR(Cmnd);
XS_SETERR(Cmnd, DID_NO_CONNECT);
/*
* Add back a timer else scsi_done drops this on the floor.
*/
isplinux_eh_timer_on(Cmnd);
isp_prt(isp, ISP_LOGDEBUG0, "giving up on target %d", XS_TGT(Cmnd));
ISP_DROP_LK_SOFTC(isp);
ISP_LOCK_SCSI_DONE(isp);
(*Cmnd->scsi_done)(Cmnd);
ISP_UNLK_SCSI_DONE(isp);
ISP_IGET_LK_SOFTC(isp);
return;
}
isp->isp_osinfo.wqcnt++;
if (isp->isp_osinfo.wqhiwater < isp->isp_osinfo.wqcnt) {
isp->isp_osinfo.wqhiwater = isp->isp_osinfo.wqcnt;
}
if (isp->isp_osinfo.wqnext == NULL) {
isp->isp_osinfo.wqtail = isp->isp_osinfo.wqnext = Cmnd;
} else {
isp->isp_osinfo.wqtail->host_scribble = (unsigned char *) Cmnd;
isp->isp_osinfo.wqtail = Cmnd;
}
Cmnd->host_scribble = NULL;
/*
* Stop the clock for this command.
*/
isplinux_eh_timer_off(Cmnd);
}
static ISP_INLINE void
isplinux_insert_head_waitq(ispsoftc_t *isp, Scsi_Cmnd *Cmnd)
{
isp->isp_osinfo.wqcnt++;
if (isp->isp_osinfo.wqnext == NULL) {
isp->isp_osinfo.wqtail = isp->isp_osinfo.wqnext = Cmnd;
Cmnd->host_scribble = NULL;
} else {
Cmnd->host_scribble = (unsigned char *) isp->isp_osinfo.wqnext;
isp->isp_osinfo.wqnext = Cmnd;
}
}
static ISP_INLINE Scsi_Cmnd *
isp_remove_from_waitq(Scsi_Cmnd *Cmnd)
{
ispsoftc_t *isp;
Scsi_Cmnd *f;
if (Cmnd == NULL) {
return (Cmnd);
}
isp = XS_ISP(Cmnd);
if ((f = isp->isp_osinfo.wqnext) == Cmnd) {
isp->isp_osinfo.wqnext = (Scsi_Cmnd *) Cmnd->host_scribble;
} else {
Scsi_Cmnd *b = f;
while (f) {
f = (Scsi_Cmnd *) b->host_scribble;
if (f == Cmnd) {
b->host_scribble = f->host_scribble;
if (isp->isp_osinfo.wqtail == Cmnd) {
isp->isp_osinfo.wqtail = b;
}
break;
}
b = f;
}
}
if (f) {
f->host_scribble = NULL;
isp->isp_osinfo.wqcnt -= 1;
}
return (f);
}
static ISP_INLINE void
isplinux_runwaitq(ispsoftc_t *isp)
{
Scsi_Cmnd *f;
int chan, result;
for (chan = 0; chan < isp->isp_nchan; chan++) {
if (ISP_DATA(isp, chan)->blocked || ISP_DATA(isp, chan)->qfdelay) {
continue;
}
f = isp->isp_osinfo.wqnext;
while (f != NULL) {
Scsi_Cmnd *nxt = (Scsi_Cmnd *) f->host_scribble;
if (XS_CHANNEL(f) != chan) {
f = nxt;
continue;
}
f = isp_remove_from_waitq(f);
result = isp_start(f);
/*
* Restart the timer for this command if it is queued or completing.
*/
if (result == CMD_QUEUED || result == CMD_COMPLETE) {
isplinux_eh_timer_on(f);
if (result == CMD_QUEUED) {
if (isp->isp_osinfo.hiwater < isp->isp_nactive) {
isp->isp_osinfo.hiwater = isp->isp_nactive;
}
f = isp->isp_osinfo.wqnext;
continue;
}
}
/*
* If we cannot start a command on a fibre channel card, it means
* that loop state isn't ready for us to do so. Activate the FC
* thread to rediscover loop and fabric residency- but not if
* we consider the loop to be dead. If the loop is considered dead,
* we wait until a PDB Changed after a Loop UP activates the FC
* thread.
*/
if (IS_FC(isp)) {
if (result == CMD_RQLATER && ISP_DATA(isp, XS_CHANNEL(f))->deadloop == 0) {
isp_thread_event(isp, ISP_THREAD_FC_RESCAN, FCPARAM(isp, chan), 0, __func__, __LINE__);
}
}
/*
* Put the command back on the wait queue. Don't change any
* timer parameters for it because they were established
* when we originally put the command on the waitq in the first
* place.
*/
if (result == CMD_EAGAIN || result == CMD_RQLATER) {
isplinux_insert_head_waitq(isp, f);
break;
}
if (result == CMD_COMPLETE) {
isp_done(f);
} else {
isp_prt(isp, ISP_LOGERR, "isplinux_runwaitq: result %d", result);
}
f = isp->isp_osinfo.wqnext;
}
}
}
static ISP_INLINE void
isplinux_flushwaitq(ispsoftc_t *isp)
{
Scsi_Cmnd *Cmnd, *Ncmnd;
if ((Cmnd = isp->isp_osinfo.wqnext) == NULL) {
return;
}
isp->isp_osinfo.wqnext = isp->isp_osinfo.wqtail = NULL;
isp->isp_osinfo.wqcnt = 0;
ISP_DROP_LK_SOFTC(isp);
do {
Ncmnd = (Scsi_Cmnd *) Cmnd->host_scribble;
Cmnd->host_scribble = NULL;
XS_INITERR(Cmnd);
XS_SETERR(Cmnd, DID_NO_CONNECT);
/*
* Add back a timer else scsi_done drops this on the floor.
*/
isplinux_eh_timer_on(Cmnd);
ISP_LOCK_SCSI_DONE(isp);
(*Cmnd->scsi_done)(Cmnd);
ISP_UNLK_SCSI_DONE(isp);
} while ((Cmnd = Ncmnd) != NULL);
ISP_IGET_LK_SOFTC(isp);
}
static ISP_INLINE Scsi_Cmnd *
isplinux_remove_from_doneq(Scsi_Cmnd *Cmnd)
{
Scsi_Cmnd *f;
ispsoftc_t *isp;
if (Cmnd == NULL) {
return (NULL);
}
isp = XS_ISP(Cmnd);
if (isp->isp_osinfo.dqnext == NULL) {
return (NULL);
}
if ((f = isp->isp_osinfo.dqnext) == Cmnd) {
isp->isp_osinfo.dqnext = (Scsi_Cmnd *) Cmnd->host_scribble;
} else {
Scsi_Cmnd *b = f;
while (f) {
f = (Scsi_Cmnd *) b->host_scribble;
if (f == Cmnd) {
b->host_scribble = f->host_scribble;
if (isp->isp_osinfo.dqtail == Cmnd) {
isp->isp_osinfo.dqtail = b;
}
break;
}
b = f;
}
}
if (f) {
f->host_scribble = NULL;
}
return (f);
}
int
isplinux_queuecommand(Scsi_Cmnd *Cmnd, void (*donecmd)(Scsi_Cmnd *))
{
struct Scsi_Host *host = XS_HOST(Cmnd);
ispsoftc_t *isp = ISP_HOST2ISP(host);
int result, chan;
unsigned long flags;
chan = XS_CHANNEL(Cmnd);
Cmnd->scsi_done = donecmd;
ISP_DRIVER_ENTRY_LOCK(isp);
ISP_LOCK_SOFTC(isp);
/*
* See if we're currently blocked. If we are, just queue up the command to be run later.
*/
if (ISP_DATA(isp, chan)->blocked || ISP_DATA(isp, chan)->qfdelay) {
isp_prt(isp, ISP_LOGDEBUG0, "appending cmd to waitq due to %d/%d", ISP_DATA(isp, chan)->blocked, ISP_DATA(isp, chan)->qfdelay);
isplinux_append_to_waitq(isp, Cmnd);
ISP_UNLK_SOFTC(isp);
ISP_DRIVER_EXIT_LOCK(isp);
return (0);
}
/*
* If we get past the above, and we're not at RUNSTATE, we're broken or out of service.
*/
if (isp->isp_state != ISP_RUNSTATE) {
isp_prt(isp, ISP_LOGDEBUG0, "DID_NOCONNECT because isp not at RUNSTATE");
ISP_UNLK_SOFTC(isp);
ISP_DRIVER_EXIT_LOCK(isp);
XS_INITERR(Cmnd);
XS_SETERR(Cmnd, DID_NO_CONNECT);
ISP_LOCK_SCSI_DONE(isp);
(*Cmnd->scsi_done)(Cmnd);
ISP_UNLK_SCSI_DONE(isp);
return (0);
}
/*
* Next see if we have any stored up commands to run. If so, run them.
* If we get back from this with commands still ready to run, put the
* current command at the tail of waiting commands to be run later.
*/
isplinux_runwaitq(isp);
if (isp->isp_osinfo.wqnext) {
isp_prt(isp, ISP_LOGDEBUG0, "appending cmd to waitq");
isplinux_append_to_waitq(isp, Cmnd);
ISP_UNLK_SOFTC(isp);
ISP_DRIVER_EXIT_LOCK(isp);
return (0);
}
/*
* Finally, try and run this command.
*/
result = isp_start(Cmnd);
if (result == CMD_QUEUED) {
if (isp->isp_osinfo.hiwater < isp->isp_nactive) {
isp->isp_osinfo.hiwater = isp->isp_nactive;
}
result = 0;
} else if (result == CMD_EAGAIN) {
/*
* We ran out of request queue space (or could not
* get DMA resources). Tell the upper layer to try
* later.
*/
result = 1;
} else if (result == CMD_RQLATER) {
/*
* Temporarily hold off on this one.
* Typically this means for fibre channel
* that the loop is down or we're processing
* some other change (e.g., fabric membership
* change)
*/
isplinux_append_to_waitq(isp, Cmnd);
if (IS_FC(isp) && ISP_DATA(isp, XS_CHANNEL(Cmnd))->deadloop == 0) {
isp_thread_event(isp, ISP_THREAD_FC_RESCAN, FCPARAM(isp, XS_CHANNEL(Cmnd)), 0, __func__, __LINE__);
}
result = 0;
} else if (result == CMD_COMPLETE) {
result = -1;
} else {
isp_prt(isp, ISP_LOGERR, "unknown return code %d from isp_start", result);
XS_INITERR(Cmnd);
XS_SETERR(Cmnd, DID_ERROR);
ISP_LOCK_SCSI_DONE(isp);
(*Cmnd->scsi_done)(Cmnd);
ISP_UNLK_SCSI_DONE(isp);
return (0);
}
ISP_UNLK_SOFTC(isp);
ISP_DRIVER_EXIT_LOCK(isp);
if (result == -1) {
Cmnd->result &= ~0xff;
Cmnd->result |= Cmnd->SCp.Status;
Cmnd->host_scribble = NULL;
ISP_LOCK_SCSI_DONE(isp);
(*Cmnd->scsi_done)(Cmnd);
ISP_UNLK_SCSI_DONE(isp);
result = 0;
}
return (result);
}
static ISP_INLINE void
isplinux_scsi_probe_done(Scsi_Cmnd *Cmnd)
{
ispsoftc_t *isp = XS_ISP(Cmnd);
isp_data *idp;
/*
* If we haven't seen this target yet, check the command result. If
* it was an inquiry and it succeeded okay, then we can update our
* notions about this target's capabilities.
*
* If the command did *not* succeed, we also update our notions about
* this target's capabilities (pessimistically) - it's probably not there.
* All of this so we can know when we're done so we stop wasting cycles
* seeing whether we can enable sync mode or not.
*/
idp = ISP_DATA(isp, XS_CHANNEL(Cmnd));
if ((idp->tgts_tested & (1 << XS_TGT(Cmnd))) == 0) {
sdparam *sdp;
caddr_t iqd;
sdp = SDPARAM(isp, XS_CHANNEL(Cmnd));
if (Cmnd->cmnd[0] == 0x12 && host_byte(Cmnd->result) == DID_OK) {
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25)
if (Cmnd->use_sg == 0) {
iqd = (caddr_t) Cmnd->request_buffer;
} else {
struct scatterlist *sg = (struct scatterlist *) Cmnd->request_buffer;
iqd = page_address(sg_page(sg)) + sg->offset;
}
#else
{
struct scatterlist *sg = scsi_sglist(Cmnd);
iqd = page_address(sg_page(sg)) + sg->offset;
}
#endif
sdp->isp_devparam[XS_TGT(Cmnd)].goal_flags &= ~(DPARM_TQING|DPARM_SYNC|DPARM_WIDE);
if (iqd[7] & 0x2) {
sdp->isp_devparam[XS_TGT(Cmnd)].goal_flags |= DPARM_TQING;
}
if (iqd[7] & 0x10) {
sdp->isp_devparam[XS_TGT(Cmnd)].goal_flags |= DPARM_SYNC;
}
if (iqd[7] & 0x20) {
sdp->isp_devparam[XS_TGT(Cmnd)].goal_flags |= DPARM_WIDE;
}
sdp->isp_devparam[XS_TGT(Cmnd)].dev_update = 1;
idp->tgts_tested |= (1 << XS_TGT(Cmnd));
} else if (host_byte(Cmnd->result) != DID_OK) {
idp->tgts_tested |= (1 << XS_TGT(Cmnd));
}
if ((idp->tgts_tested & ~sdp->isp_initiator_id) == (0xffff & ~sdp->isp_initiator_id)) {
idp->tgts_tested = 0xffff;
sdp->update = 1;
}
}
}
void
isp_done(Scsi_Cmnd *Cmnd)
{
ispsoftc_t *isp = XS_ISP(Cmnd);
if (IS_SCSI(isp) && unlikely(ISP_DATA(isp, XS_CHANNEL(Cmnd))->tgts_tested != 0xffff)) {
isplinux_scsi_probe_done(Cmnd);
}
Cmnd->result &= ~0xff;
Cmnd->result |= Cmnd->SCp.Status;
if (Cmnd->SCp.Status != GOOD) {
isp_prt(isp, ISP_LOGDEBUG0, "%d.%d.%d: cmd 0x%x finishes with status 0x%x", XS_CHANNEL(Cmnd), XS_TGT(Cmnd), XS_LUN(Cmnd), XS_CDBP(Cmnd)[0] & 0xff, Cmnd->SCp.Status);
if (Cmnd->SCp.Status == SCSI_QFULL) {
ISP_DATA(isp, XS_CHANNEL(Cmnd))->qfdelay = 2 * ISP_WATCH_TPS;
/*
* Too many hangups in the midlayer
*/
isplinux_append_to_waitq(isp, Cmnd);
return;
}
}
if (Cmnd->underflow > (XS_XFRLEN(Cmnd) - XS_GET_RESID(Cmnd))) {
XS_SETERR(Cmnd, DID_ERROR);
}
/*
* Queue command on completion queue.
*/
if (isp->isp_osinfo.dqnext == NULL) {
isp->isp_osinfo.dqnext = Cmnd;
} else {
isp->isp_osinfo.dqtail->host_scribble = (unsigned char *) Cmnd;
}
isp->isp_osinfo.dqtail = Cmnd;
Cmnd->host_scribble = NULL;
}
/*
* Error handling routines
*/
int
isplinux_abort(Scsi_Cmnd *Cmnd)
{
ispsoftc_t *isp;
uint32_t handle;
unsigned long flags;
if (Cmnd == NULL || XS_HOST(Cmnd) == NULL) {
return (FAILED);
}
isp = XS_ISP(Cmnd);
ISP_DRIVER_CTL_ENTRY_LOCK(isp);
ISP_LOCKU_SOFTC(isp);
handle = isp_find_handle(isp, Cmnd);
if (handle == 0) {
int wqfnd = 0;
Scsi_Cmnd *NewCmnd = isp_remove_from_waitq(Cmnd);
if (NewCmnd == NULL) {
NewCmnd = isplinux_remove_from_doneq(Cmnd);
wqfnd++;
}
ISP_UNLKU_SOFTC(isp);
isp_prt(isp, ISP_LOGINFO, "isplinux_abort: found %d:%p for non-running cmd for %d.%d.%d",
wqfnd, NewCmnd, XS_CHANNEL(Cmnd), XS_TGT(Cmnd), XS_LUN(Cmnd));
if (NewCmnd == NULL) {
ISP_DRIVER_CTL_EXIT_LOCK(isp);
return (FAILED);
}
} else {
ISP_DATA(isp, XS_CHANNEL(Cmnd))->qfdelay = ISP_WATCH_TPS;
if (isp_control(isp, ISPCTL_ABORT_CMD, Cmnd)) {
ISP_UNLKU_SOFTC(isp);
ISP_DRIVER_CTL_EXIT_LOCK(isp);
return (FAILED);
}
if (isp->isp_nactive > 0) {
isp->isp_nactive--;
}
isp_destroy_handle(isp, handle);
ISP_UNLKU_SOFTC(isp);
ISP_DRIVER_CTL_EXIT_LOCK(isp);
isp_prt(isp, ISP_LOGINFO, "isplinux_abort: aborted running cmd (handle 0x%x) for %d.%d.%d",
handle, XS_CHANNEL(Cmnd), XS_TGT(Cmnd), XS_LUN(Cmnd));
}
Cmnd->result = DID_ABORT << 16;
ISP_LOCK_SCSI_DONE(isp);
(*Cmnd->scsi_done)(Cmnd);
ISP_UNLK_SCSI_DONE(isp);
return (SUCCESS);
}
int
isplinux_bdr(Scsi_Cmnd *Cmnd)
{
ispsoftc_t *isp;
int r;
unsigned long flags;
if (Cmnd == NULL || XS_HOST(Cmnd) == NULL) {
return (FAILED);
}
isp = XS_ISP(Cmnd);
ISP_DRIVER_CTL_ENTRY_LOCK(isp);
ISP_LOCKU_SOFTC(isp);
r = isp_control(isp, ISPCTL_RESET_DEV, XS_CHANNEL(Cmnd), XS_TGT(Cmnd));
ISP_UNLKU_SOFTC(isp);
ISP_DRIVER_CTL_EXIT_LOCK(isp);
isp_prt(isp, ISP_LOGINFO, "Bus Device Reset %successfully sent to %d.%d.%d",
r == 0? "s" : "uns", XS_CHANNEL(Cmnd), XS_TGT(Cmnd), XS_LUN(Cmnd));
return ((r == 0)? SUCCESS : FAILED);
}
int
isplinux_sreset(Scsi_Cmnd *Cmnd)
{
ispsoftc_t *isp;
int r;
fcparam *fcp;
unsigned long flags;
if (Cmnd == NULL || XS_HOST(Cmnd) == NULL) {
return (FAILED);
}
isp = XS_ISP(Cmnd);
fcp = FCPARAM(isp, XS_CHANNEL(Cmnd));
ISP_DRIVER_CTL_ENTRY_LOCK(isp);
ISP_LOCKU_SOFTC(isp);
ISP_DATA(isp, XS_CHANNEL(Cmnd))->qfdelay = ISP_WATCH_TPS;
if (IS_FC(isp) && fcp->isp_fwstate == FW_READY && fcp->isp_loopstate == LOOP_READY && fcp->isp_topo == TOPO_F_PORT) {
ISP_UNLKU_SOFTC(isp);
ISP_DRIVER_CTL_EXIT_LOCK(isp);
isp_prt(isp, ISP_LOGINFO, "SCSI Bus Reset request ignored");
return (SUCCESS);
}
r = isp_control(isp, ISPCTL_RESET_DEV, XS_CHANNEL(Cmnd), XS_TGT(Cmnd));
ISP_UNLKU_SOFTC(isp);
ISP_DRIVER_CTL_EXIT_LOCK(isp);
isp_prt(isp, ISP_LOGINFO, "Chan %d SCSI Bus Reset %successful", XS_CHANNEL(Cmnd), r == 0? "s" : "uns");
return ((r == 0)? SUCCESS : FAILED);
}
/*
* We call completion on any commands owned here-
* except the one we were called with.
*/
int
isplinux_hreset(Scsi_Cmnd *Cmnd)
{
Scsi_Cmnd *tmp, *dq, *wq, *xqf, *xql;
ispsoftc_t *isp;
uint32_t handle;
unsigned long flags;
if (Cmnd == NULL || XS_HOST(Cmnd) == NULL) {
return (FAILED);
}
isp = XS_ISP(Cmnd);
isp_prt(isp, ISP_LOGINFO, "Resetting Host Adapter");
ISP_DRIVER_CTL_ENTRY_LOCK(isp);
ISP_LOCKU_SOFTC(isp);
/*
* Save pending, running, and completed commands.
*/
xql = xqf = NULL;
for (handle = 1; handle <= isp->isp_maxcmds; handle++) {
tmp = isp_find_xs(isp, handle);
if (tmp == NULL) {
continue;
}
isp_destroy_handle(isp, handle);
tmp->host_scribble = NULL;
if (xqf) {
xql->host_scribble = (unsigned char *) tmp;
} else {
xqf = xql = tmp;
}
xql = tmp;
}
dq = isp->isp_osinfo.dqnext;
isp->isp_osinfo.dqnext = NULL;
wq = isp->isp_osinfo.wqnext;
isp->isp_osinfo.wqnext = NULL;
isp->isp_nactive = 0;
(void) isplinux_reinit(isp, 0);
ISP_UNLKU_SOFTC(isp);
ISP_DRIVER_CTL_EXIT_LOCK(isp);
/*
* Call completion on the detritus, skipping the one we were called with.
*/
while ((tmp = xqf) != NULL) {
xqf = (Scsi_Cmnd *) tmp->host_scribble;
tmp->host_scribble = NULL;
if (tmp == Cmnd) {
continue;
}
tmp->result = DID_RESET << 16;
if (tmp->scsi_done) {
ISP_LOCK_SCSI_DONE(isp);
(*tmp->scsi_done)(tmp);
ISP_UNLK_SCSI_DONE(isp);
}
}
while ((tmp = wq) != NULL) {
wq = (Scsi_Cmnd *) tmp->host_scribble;
tmp->host_scribble = NULL;
if (tmp == Cmnd) {
continue;
}
tmp->result = DID_RESET << 16;
if (tmp->scsi_done) {
ISP_LOCK_SCSI_DONE(isp);
(*tmp->scsi_done)(tmp);
ISP_UNLK_SCSI_DONE(isp);
}
}
while ((tmp = dq) != NULL) {
dq = (Scsi_Cmnd *) tmp->host_scribble;
tmp->host_scribble = NULL;
if (tmp == Cmnd) {
continue;
}
tmp->result = DID_RESET << 16;
if (tmp->scsi_done) {
ISP_LOCK_SCSI_DONE(isp);
(*tmp->scsi_done)(tmp);
ISP_UNLK_SCSI_DONE(isp);
}
}
Cmnd->result = DID_RESET << 16;
return (SUCCESS);
}
#ifdef ISP_TARGET_MODE
int
isp_init_target(ispsoftc_t *isp)
{
int i;
void *pool, *npool;
unsigned long flags;
static const uint8_t inqdsd[DEFAULT_INQSIZE] = {
0x7f, 0x00, 0x03, 0x02, 0x1c, 0x00, 0x00, 0x00,
'L', 'I', 'N', 'U', 'X', 'I', 'S', 'P',
' ', 'T', 'A', 'R', 'G', 'E', 'T', ' ',
'D', 'E', 'V', 'I', 'C', 'E', ' ', ' '
};
pool = isp_kzalloc(NTGT_CMDS * TMD_SIZE, GFP_KERNEL);
if (pool == NULL) {
isp_prt(isp, ISP_LOGERR, "cannot allocate TMD structures");
return (-ENOMEM);
}
npool = isp_kzalloc(N_NOTIFIES * sizeof (notify_t), GFP_KERNEL);
if (npool == NULL) {
isp_prt(isp, ISP_LOGERR, "cannot allocate TMD NOTIFY structures");
isp_kfree(pool, NTGT_CMDS * TMD_SIZE);
return (-ENOMEM);
}
sema_init(&isp->isp_osinfo.tgt_inisem, 1);
ISP_LOCK_SOFTC(isp);
isp->isp_osinfo.pool = pool;
for (i = 0; i < NTGT_CMDS-1; i++) {
isp->isp_osinfo.pool[i].cd_next = &isp->isp_osinfo.pool[i+1];
}
isp->isp_osinfo.npool = npool;
for (i = 0; i < N_NOTIFIES-1; i++) {
isp->isp_osinfo.npool[i].notify.nt_lreserved = &isp->isp_osinfo.npool[i+1];
}
memset(isp->isp_osinfo.auxbmap, 0, sizeof (isp->isp_osinfo.auxbmap));
memcpy(isp->isp_osinfo.inqdata, inqdsd, DEFAULT_INQSIZE);
isp->isp_osinfo.pending_t = NULL;
isp->isp_osinfo.tfreelist = isp->isp_osinfo.pool;
isp->isp_osinfo.bfreelist = &isp->isp_osinfo.pool[NTGT_CMDS-1];
isp->isp_osinfo.nfreelist = isp->isp_osinfo.npool;
ISP_UNLK_SOFTC(isp);
return (0);
}
void
isp_attach_target(ispsoftc_t *isp)
{
hba_register_t hba;
hba.r_identity = isp;
snprintf(hba.r_name, sizeof (hba.r_name), ISP_NAME);
hba.r_inst = isp->isp_unit;
hba.r_version = QR_VERSION;
hba.r_action = isp_taction;
hba.r_locator = isp->isp_osinfo.device_id;
hba.r_nchannels = isp->isp_nchan;
if (IS_FC(isp)) {
hba.r_type = R_FC;
} else{
hba.r_type = R_SPI;
}
hba.r_private = NULL;
ISP_PARENT_TARGET(QOUT_HBA_REG, &hba);
}
void
isp_deinit_target(ispsoftc_t *isp)
{
void *pool, *npool;
unsigned long flags;
ISP_LOCK_SOFTC(isp);
pool = isp->isp_osinfo.pool;
isp->isp_osinfo.pool = NULL;
npool = isp->isp_osinfo.npool;
isp->isp_osinfo.npool = NULL;
ISP_UNLK_SOFTC(isp);
if (pool) {
isp_kfree(pool, NTGT_CMDS * TMD_SIZE);
}
if (npool) {
isp_kfree(npool, N_NOTIFIES * sizeof (notify_t));
}
}
void
isp_detach_target(ispsoftc_t *isp)
{
hba_register_t hba;
struct semaphore rsem;
hba.r_identity = isp;
snprintf(hba.r_name, sizeof (hba.r_name), ISP_NAME);
hba.r_inst = isp->isp_unit;
hba.r_version = QR_VERSION;
hba.r_action = isp_taction;
hba.r_nchannels = isp->isp_nchan;
if (IS_FC(isp)) {
hba.r_type = R_FC;
} else{
hba.r_type = R_SPI;
}
hba.r_private = &rsem;
sema_init(&rsem, 0);
ISP_PARENT_TARGET(QOUT_HBA_UNREG, &hba);
down(&rsem);
}
static void
isp_tgt_tq(ispsoftc_t *isp)
{
notify_t *ins;
tmd_cmd_t *tmd;
tmd_xact_t *xact;
unsigned long flags;
ISP_LOCK_SOFTC(isp);
ins = isp->isp_osinfo.pending_n;
if (ins) {
isp->isp_osinfo.pending_n = NULL;
}
tmd = isp->isp_osinfo.pending_t;
if (tmd) {
isp->isp_osinfo.pending_t = NULL;
}
xact = isp->isp_osinfo.pending_x;
if (xact) {
isp->isp_osinfo.pending_x = NULL;
}
ISP_UNLK_SOFTC(isp);
while (ins != NULL) {
notify_t *next = ins->notify.nt_lreserved;
ins->notify.nt_lreserved = NULL;
isp_prt(isp, ISP_LOGTDEBUG2, "isp_tgt_tq -> notify 0x%x", ins->notify.nt_ncode);
ISP_PARENT_TARGET(QOUT_NOTIFY, ins);
ins = next;
}
while (tmd != NULL) {
tmd_cmd_t *next = tmd->cd_next;
tmd->cd_next = NULL;
isp_prt(isp, ISP_LOGTDEBUG2, "isp_tgt_tq[%llx] -> code 0x%x", (ull) tmd->cd_tagval, tmd->cd_action);
ISP_PARENT_TARGET(tmd->cd_action, tmd);
tmd = next;
}
while (xact != NULL) {
tmd_xact_t *next = xact->td_lprivate;
xact->td_lprivate = NULL;
ISP_PARENT_TARGET(QOUT_TMD_DONE, xact);
xact = next;
}
}
static ISP_INLINE tmd_cmd_t *
isp_find_tmd(ispsoftc_t *isp, uint64_t tagval)
{
int i;
tmd_cmd_t *tmd = isp->isp_osinfo.pool;
if (tmd == NULL || tagval == TAG_ANY) {
return (NULL);
}
for (i = 0; i < NTGT_CMDS; i++) {
if ((tmd->cd_lflags & CDFL_BUSY) && tmd->cd_tagval == tagval) {
return (tmd);
}
tmd++;
}
return (NULL);
}
static void
isp_tgt_dump_pdb(ispsoftc_t *isp, int chan)
{
fcparam *fcp;
int i;
if (chan >= isp->isp_nchan) {
return;
}
fcp = FCPARAM(isp, chan);
for (i = MAX_FC_TARG-1; i >= 0; i--) {
fcportdb_t *lp = &fcp->portdb[i];
if (lp->target_mode == 0) {
continue;
}
isp_prt(isp, ISP_LOGTINFO, "PDB[%d]: Chan %d 0x%016llx Port-ID 0x%06x N-Port Handle 0x%04x", i, chan, (ull) lp->port_wwn, lp->portid, lp->handle);
}
}
static void
isp_taction(qact_e action, void *arg)
{
tmd_cmd_t *tmd;
hba_register_t *hp;
enadis_t *ep;
ispsoftc_t *isp = NULL;
unsigned long flags;
switch (action) {
case QIN_HBA_REG:
hp = (hba_register_t *) arg;
isp = hp->r_identity;
if (isp == NULL) {
printk(KERN_ERR "null isp @ %s:%s:%d\n", __FILE__, __func__, __LINE__);
break;
}
isp_prt(isp, ISP_LOGINFO, "completed target registration");
ISP_LOCK_SOFTC(isp);
isp->isp_osinfo.hcb = 1;
ISP_UNLK_SOFTC(isp);
break;
case QIN_GETINFO:
{
info_t *ip = arg;
isp = ip->i_identity;
if (ip->i_channel >= isp->isp_nchan) {
ip->i_error = -ENODEV;
} else if (IS_FC(isp)) {
fcparam *fcp = FCPARAM(isp, ip->i_channel);
ip->i_type = I_FC;
ip->i_id.fc.wwnn_nvram = fcp->isp_wwnn_nvram;
ip->i_id.fc.wwpn_nvram = fcp->isp_wwpn_nvram;
ip->i_id.fc.wwnn = fcp->isp_wwnn;
ip->i_id.fc.wwpn = fcp->isp_wwpn;
ip->i_error = 0;
} else {
sdparam *sdp = SDPARAM(isp, ip->i_channel);
ip->i_type = I_SPI;
ip->i_id.spi.iid = sdp->isp_initiator_id;
ip->i_error = 0;
}
break;
}
case QIN_SETINFO:
{
info_t *ip = arg;
isp = ip->i_identity;
if (ip->i_type == I_FC) {
FCPARAM(isp, ip->i_channel)->isp_wwnn = ip->i_id.fc.wwnn;
FCPARAM(isp, ip->i_channel)->isp_wwpn = ip->i_id.fc.wwpn;
ip->i_error = 0;
} else {
ip->i_error = -EINVAL;
}
break;
}
case QIN_GETDLIST:
{
fc_dlist_t *ua = arg;
int rv, nph, nphe, lim, chan;
uint64_t wwpn;
isp = ua->d_identity;
if (IS_SCSI(isp)) {
ua->d_error = -EINVAL;
break;
}
lim = ua->d_count;
chan = ua->d_channel;
ua->d_count = 0;
if (ISP_CAP_2KLOGIN(isp)) {
nphe = NPH_MAX_2K;
} else {
nphe = NPH_MAX;
}
for (rv = 0, nph = 1; ua->d_count < lim && nph != nphe; nph++) {
ISP_LOCKU_SOFTC(isp);
rv = isp_control(isp, ISPCTL_GET_NAMES, chan, nph, NULL, &wwpn);
ISP_UNLKU_SOFTC(isp);
if (rv == 0 && wwpn != (uint64_t) 0) {
ua->d_wwpns[ua->d_count++] = wwpn;
}
}
ua->d_error = 0;
break;
}
case QIN_ENABLE:
ep = arg;
isp = ep->en_hba;
if (isp == NULL) {
printk(KERN_ERR "null isp @ %s:%s:%d\n", __FILE__, __func__, __LINE__);
break;
}
ep->en_error = isp_enable_lun(isp, ep->en_chan, ep->en_lun);
ISP_PARENT_TARGET(QOUT_ENABLE, ep);
break;
case QIN_DISABLE:
ep = arg;
isp = ep->en_hba;
if (isp == NULL) {
printk(KERN_ERR "null isp @ %s:%s:%d\n", __FILE__, __func__, __LINE__);
break;
}
ep->en_error = isp_disable_lun(isp, ep->en_chan, ep->en_lun);
ISP_PARENT_TARGET(QOUT_DISABLE, ep);
ISP_LOCK_SOFTC(isp);
if (ep->en_error == 0) {
(void) isp_target_async(isp, 0, ASYNC_LOOP_DOWN);
}
ISP_UNLK_SOFTC(isp);
break;
case QIN_TMD_CONT:
{
tmd_xact_t *xact = arg;
tmd = xact->td_cmd;
isp = tmd->cd_hba;
isp_target_start_ctio(isp, arg);
break;
}
case QIN_TMD_FIN:
tmd = (tmd_cmd_t *) arg;
isp = tmd->cd_hba;
if (isp == NULL) {
printk(KERN_ERR "null isp @ %s:%s:%d\n", __FILE__, __func__, __LINE__);
break;
}
ISP_LOCK_SOFTC(isp);
isp_prt(isp, ISP_LOGTDEBUG1, "freeing tmd %p [%llx]", tmd, (ull) tmd->cd_tagval);
if (tmd->cd_lflags & CDFL_RESRC_FILL) {
if (isp_target_putback_atio(isp, tmd)) {
isp_thread_event(isp, ISP_THREAD_FC_PUTBACK, tmd, 0, __func__, __LINE__);
ISP_UNLK_SOFTC(isp);
break;
}
}
if (tmd->cd_lflags & CDFL_NEED_CLNUP) {
tmd->cd_lflags &= ~CDFL_NEED_CLNUP;
isp_prt(isp, ISP_LOGTINFO, "Terminating [%llx] on FIN", (ull) tmd->cd_tagval);
(void) isp_terminate_cmd(isp, tmd);
}
tmd->cd_next = NULL;
if (isp->isp_osinfo.tfreelist) {
isp->isp_osinfo.bfreelist->cd_next = tmd;
} else {
isp->isp_osinfo.tfreelist = tmd;
}
isp->isp_osinfo.bfreelist = tmd; /* remember to move the list tail pointer */
isp_prt(isp, ISP_LOGTDEBUG1, "DONE freeing tmd %p [%llx]", tmd, (ull) tmd->cd_tagval);
ISP_UNLK_SOFTC(isp);
break;
case QIN_NOTIFY_ACK:
{
notify_t *ins = (notify_t *) arg;
isp = ins->notify.nt_hba;
if (isp == NULL) {
printk(KERN_ERR "null isp @ %s:%s:%d\n", __FILE__, __func__, __LINE__);
break;
}
ISP_LOCK_SOFTC(isp);
switch (ins->notify.nt_ncode) {
case NT_HBA_RESET:
case NT_LINK_UP:
case NT_LINK_DOWN:
isp_prt(isp, ISP_LOGINFO, "s/w notify code %x acked", ins->notify.nt_ncode);
break;
default:
if (ins->notify.nt_failed) {
isp_prt(isp, ISP_LOGWARN, "[%llx] notify code 0x%x returned back with failure", (ull) ins->notify.nt_tagval, ins->notify.nt_ncode);
}
if (isp->isp_state != ISP_RUNSTATE) {
isp_prt(isp, ISP_LOGTINFO, "[%llx] Notify Code 0x%x (qevalid=%d) acked- h/w not ready (dropping)",
(ull) ins->notify.nt_tagval, ins->notify.nt_ncode, ins->qevalid);
}
/*
* This case is for a Task Management Function, which shows up as an ATIO7 entry.
*/
if (IS_24XX(isp) && ins->qevalid && ((isphdr_t *)ins->qentry)->rqs_entry_type == RQSTYPE_ATIO) {
ct7_entry_t local, *cto = &local;
at7_entry_t *aep = (at7_entry_t *)ins->qentry;
fcportdb_t *lp;
uint32_t sid;
uint16_t nphdl;
sid = (aep->at_hdr.s_id[0] << 16) | (aep->at_hdr.s_id[1] << 8) | aep->at_hdr.s_id[2];
if (isp_find_pdb_by_sid(isp, ins->notify.nt_channel, sid, &lp)) {
nphdl = lp->handle;
} else {
nphdl = NIL_HANDLE;
}
memset(&local, 0, sizeof (local));
cto->ct_header.rqs_entry_type = RQSTYPE_CTIO7;
cto->ct_header.rqs_entry_count = 1;
cto->ct_nphdl = nphdl;
cto->ct_rxid = aep->at_rxid;
cto->ct_vpidx = ins->notify.nt_channel;
cto->ct_iid_lo = sid;
cto->ct_iid_hi = sid >> 16;
cto->ct_oxid = aep->at_hdr.ox_id;
cto->ct_flags = CT7_SENDSTATUS|CT7_NOACK|CT7_NO_DATA|CT7_FLAG_MODE1;
cto->ct_flags |= (aep->at_ta_len >> 12) << CT7_TASK_ATTR_SHIFT;
/* set response */
cto->ct_scsi_status = (FCP_RSPLEN_VALID << 8);
cto->rsp.m1.ct_resplen = FCP_MAX_RSPLEN;
ISP_MEMZERO(cto->rsp.m1.ct_resp, FCP_MAX_RSPLEN);
cto->rsp.m1.ct_resp[3] = ins->tmf_resp;
isp_prt(isp, ISP_LOGINFO, "[%llx] TMF response. status %d",
(ull)ins->notify.nt_tagval, ins->tmf_resp);
WARN_ON(isp_target_put_entry(isp, &local)); /* XXX FIX ME XXX */
break;
}
/*
* This case is for a responding to an ABTS frame
*/
if (IS_24XX(isp) && ins->qevalid && ((isphdr_t *)ins->qentry)->rqs_entry_type == RQSTYPE_ABTS_RCVD) {
uint8_t storage[QENTRY_LEN];
ct7_entry_t *cto = (ct7_entry_t *) storage;
abts_t *abts = (abts_t *)ins->qentry;
ISP_MEMZERO(cto, sizeof (ct7_entry_t));
isp_prt(isp, ISP_LOGTDEBUG0, "%s: [%x] terminating after ABTS received", __func__, abts->abts_rxid_task);
cto->ct_header.rqs_entry_type = RQSTYPE_CTIO7;
cto->ct_header.rqs_entry_count = 1;
cto->ct_nphdl = ins->notify.nt_nphdl;
cto->ct_rxid = abts->abts_rxid_task;
cto->ct_iid_lo = ins->notify.nt_sid;
cto->ct_iid_hi = ins->notify.nt_sid >> 16;
cto->ct_oxid = abts->abts_ox_id;
cto->ct_vpidx = ins->notify.nt_channel;
cto->ct_flags = CT7_NOACK|CT7_TERMINATE;
WARN_ON(isp_target_put_entry(isp, cto));
WARN_ON(isp_acknak_abts(isp, ins->qentry, 0));
break;
}
/*
* General purpose acknowledgement
*/
if (ins->notify.nt_need_ack) {
isp_prt(isp, ISP_LOGTINFO, "[%llx] Notify Code 0x%x (qevalid=%d) being acked", (ull) ins->notify.nt_tagval, ins->notify.nt_ncode, ins->qevalid);
WARN_ON(isp_notify_ack(isp, ins->qevalid? ins->qentry : NULL));
}
break;
}
ins->notify.nt_lreserved = isp->isp_osinfo.nfreelist;
isp->isp_osinfo.nfreelist = ins;
ISP_UNLK_SOFTC(isp);
break;
}
case QIN_HBA_UNREG:
hp = (hba_register_t *) arg;
isp = hp->r_identity;
if (isp == NULL) {
printk(KERN_ERR "null isp @ %s:%s:%d\n", __FILE__, __func__, __LINE__);
break;
}
isp_prt(isp, ISP_LOGINFO, "completed target unregistration");
ISP_LOCK_SOFTC(isp);
isp->isp_osinfo.hcb = 0;
if (hp->r_private) {
struct semaphore *rsemap = hp->r_private;
up(rsemap);
}
ISP_UNLK_SOFTC(isp);
/* force ourselves to not run any queues now */
isp = NULL;
break;
default:
printk(KERN_ERR "isp_taction: unknown action %x, arg %p\n", action, arg);
break;
}
if (isp) {
isp_tgt_tq(isp);
}
}
static int
lunenabled(ispsoftc_t *isp, uint16_t bus, uint16_t lun)
{
tgt_enalun_t *axl = isp->isp_osinfo.luns;
while (axl) {
if (axl->bus == bus && axl->lun == lun) {
return (1);
}
axl = axl->next;
}
return (0);
}
static int
nolunsenabled(ispsoftc_t *isp, uint16_t bus)
{
tgt_enalun_t *axl = isp->isp_osinfo.luns;
while (axl) {
if (axl->bus == bus) {
return (0);
}
axl = axl->next;
}
return (1);
}
static ISP_INLINE void
addlun(ispsoftc_t *isp, tgt_enalun_t *axl, uint16_t bus, uint16_t lun)
{
axl->lun = lun;
axl->bus = bus;
axl->next = isp->isp_osinfo.luns;
isp->isp_osinfo.luns = axl;
}
static ISP_INLINE tgt_enalun_t *
remlun(ispsoftc_t *isp, uint16_t bus, uint16_t lun)
{
tgt_enalun_t *axl, *axy = NULL;
axl = isp->isp_osinfo.luns;
if (axl == NULL) {
return (axy);
}
if (axl->lun == lun && axl->bus == bus) {
isp->isp_osinfo.luns = axl->next;
axy = axl;
} else {
while (axl->next) {
if (axl->next->lun == lun && axl->next->bus == bus) {
axy = axl->next;
axl->next = axy->next;
break;
}
axl = axl->next;
}
}
return (axy);
}
static void
isp_target_start_ctio(ispsoftc_t *isp, tmd_xact_t *xact)
{
void *qe;
uint32_t handle, orig_xfrlen = 0;
uint8_t local[QENTRY_LEN];
unsigned long flags;
int32_t resid;
tmd_cmd_t *tmd = xact->td_cmd;
xact->td_lflags &= ~TDFL_ERROR;
xact->td_error = 0;
/*
* Use this lock to protect tmd fields
*/
ISP_LOCK_SOFTC(isp);
/*
* Pre-increment cd_moved so we know how many bytes are actually in transit. If we actually fail to move
* the bytes, we'll subtract things out when we collect status. If we fail to even start the transfer
* (due to inability to even get queue space), we'll subtract that as well too.
*/
orig_xfrlen = xact->td_xfrlen;
tmd->cd_moved += orig_xfrlen;
/*
* Set the residual to be equal to the total length less the amount previously moved plus this transfer size
*/
resid = tmd->cd_totlen - tmd->cd_moved;
/*
* Check for commands that are already dead
*/
if (tmd->cd_lflags & CDFL_ABORTED) {
isp_prt(isp, ISP_LOGTINFO, "%s: [%llx] already ABORTED- not sending a CTIO", __func__, (ull) tmd->cd_tagval);
xact->td_error = -ENXIO;
goto out;
}
/*
* If the transfer length is zero, we have to be sending status.
* If we're sending data, we have to have one and only one data
* direction set.
*/
if (xact->td_xfrlen == 0) {
if ((xact->td_hflags & TDFH_STSVALID) == 0) {
isp_prt(isp, ISP_LOGERR, "%s: a CTIO, no data, and no status is wrong", __func__);
dump_stack();
xact->td_error = -EINVAL;
goto out;
}
} else {
if ((xact->td_hflags & TDFH_DATA_MASK) == 0) {
isp_prt(isp, ISP_LOGERR, "%s: a data CTIO with no direction is wrong", __func__);
dump_stack();
xact->td_error = -EINVAL;
goto out;
}
if ((xact->td_hflags & TDFH_DATA_MASK) == TDFH_DATA_MASK) {
isp_prt(isp, ISP_LOGERR, "%s: a data CTIO with both directions is wrong (for now)", __func__);
dump_stack();
xact->td_error = -EINVAL;
goto out;
}
}
if ((xact->td_hflags & TDFH_STSVALID) && tmd->cd_scsi_status == SCSI_CHECK && (xact->td_hflags & TDFH_SNSVALID) && tmd->cd_sense[0] == 0) {
isp_prt(isp, ISP_LOGWARN, "%s: [%llx] cdb0 0x%02x CHECK CONDITION but bogus sense 0x%x/0x%x/0x%x", __func__, (ull) tmd->cd_tagval, tmd->cd_cdb[0], tmd->cd_sense[0], tmd->cd_sense[12], tmd->cd_sense[13]);
}
memset(local, 0, QENTRY_LEN);
/*
* We're either moving data or completing a command here (or both).
*/
if (IS_24XX(isp)) {
ct7_entry_t *cto = (ct7_entry_t *) local;
int tattr;
cto->ct_header.rqs_entry_type = RQSTYPE_CTIO7;
cto->ct_header.rqs_entry_count = 1;
cto->ct_header.rqs_seqno = 1;
cto->ct_nphdl = tmd->cd_nphdl;
cto->ct_rxid = tmd->cd_tagval;
cto->ct_iid_lo = tmd->cd_portid;
cto->ct_iid_hi = tmd->cd_portid >> 16;
cto->ct_oxid = tmd->cd_oxid;
cto->ct_vpidx = tmd->cd_channel;
cto->ct_scsi_status = tmd->cd_scsi_status;
cto->ct_timeout = ISP_CT_TIMEOUT;
switch (tmd->cd_tagtype) {
case CD_SIMPLE_TAG:
tattr = FCP_CMND_TASK_ATTR_SIMPLE;
break;
case CD_HEAD_TAG:
tattr = FCP_CMND_TASK_ATTR_HEAD;
break;
case CD_ORDERED_TAG:
tattr = FCP_CMND_TASK_ATTR_ORDERED;
break;
case CD_ACA_TAG:
tattr = FCP_CMND_TASK_ATTR_ACA;
break;
default:
tattr = FCP_CMND_TASK_ATTR_UNTAGGED;
break;
}
cto->ct_flags = tattr << CT7_TASK_ATTR_SHIFT;
if (xact->td_xfrlen == 0) {
cto->ct_flags |= CT7_FLAG_MODE1 | CT7_NO_DATA | CT7_SENDSTATUS;
if ((xact->td_hflags & TDFH_SNSVALID) != 0) {
cto->rsp.m1.ct_resplen = cto->ct_senselen = min(TMD_SENSELEN, MAXRESPLEN_24XX);
memcpy(cto->rsp.m1.ct_resp, tmd->cd_sense, cto->ct_senselen);
cto->ct_scsi_status |= (FCP_SNSLEN_VALID << 8);
}
} else {
cto->rsp.m0.ct_xfrlen = xact->td_xfrlen;
cto->rsp.m0.reloff = tmd->cd_moved - orig_xfrlen;
cto->ct_flags |= CT7_FLAG_MODE0;
if (xact->td_hflags & TDFH_DATA_IN) {
cto->ct_flags |= CT7_DATA_IN;
} else {
cto->ct_flags |= CT7_DATA_OUT;
}
if (xact->td_hflags & TDFH_STSVALID) {
cto->ct_flags |= CT7_SENDSTATUS;
}
}
if ((cto->ct_flags & CT7_SENDSTATUS) && resid) {
cto->ct_resid = resid;
if (resid < 0) {
cto->ct_scsi_status |= (FCP_RESID_OVERFLOW << 8);
} else {
cto->ct_scsi_status |= (FCP_RESID_UNDERFLOW << 8);
}
}
isp_prt(isp, ISP_LOGTDEBUG0, "%s: CTIO7[%llx] scsi sts %x flags %x resid %d offset %u", __func__, (ull) tmd->cd_tagval, tmd->cd_scsi_status, cto->ct_flags, resid, xact->td_offset);
} else if (IS_FC(isp)) {
ct2_entry_t *cto = (ct2_entry_t *) local;
uint16_t *ssptr = NULL;
cto->ct_header.rqs_entry_type = RQSTYPE_CTIO2;
cto->ct_header.rqs_entry_count = 1;
cto->ct_header.rqs_seqno = 1;
if (ISP_CAP_2KLOGIN(isp)) {
((ct2e_entry_t *)cto)->ct_iid = tmd->cd_nphdl;
} else {
cto->ct_iid = tmd->cd_nphdl;
}
if (ISP_CAP_SCCFW(isp)) {
cto->ct_lun = L0LUN_TO_FLATLUN(tmd->cd_lun);
}
cto->ct_rxid = tmd->cd_tagval;
if (cto->ct_rxid == 0) {
isp_prt(isp, ISP_LOGERR, "a tagval of zero is not acceptable");
xact->td_error = -EINVAL;
goto out;
}
cto->ct_timeout = ISP_CT_TIMEOUT;
#if 0
/*
* I've had problems with this at varying times- dunno why
*/
cto->ct_flags = CT2_FASTPOST;
#endif
if (xact->td_xfrlen == 0) {
cto->ct_flags |= CT2_FLAG_MODE1 | CT2_NO_DATA | CT2_SENDSTATUS;
ssptr = &cto->rsp.m1.ct_scsi_status;
*ssptr = tmd->cd_scsi_status;
if ((xact->td_hflags & TDFH_SNSVALID) != 0) {
cto->rsp.m1.ct_senselen = min(TMD_SENSELEN, MAXRESPLEN);
memcpy(cto->rsp.m1.ct_resp, tmd->cd_sense, cto->rsp.m1.ct_senselen);
cto->rsp.m1.ct_scsi_status |= CT2_SNSLEN_VALID;
}
} else {
cto->ct_reloff = tmd->cd_moved - orig_xfrlen;
cto->ct_flags |= CT2_FLAG_MODE0;
if (xact->td_hflags & TDFH_DATA_IN) {
cto->ct_flags |= CT2_DATA_IN;
} else {
cto->ct_flags |= CT2_DATA_OUT;
}
cto->rsp.m0.ct_xfrlen = xact->td_xfrlen;
if (xact->td_hflags & TDFH_STSVALID) {
ssptr = &cto->rsp.m0.ct_scsi_status;
cto->ct_flags |= CT2_SENDSTATUS;
cto->rsp.m0.ct_scsi_status = tmd->cd_scsi_status;
/*
* It will be up to the low level mapping routine
* to check for sense data.
*/
}
}
if (ssptr && resid) {
cto->ct_resid = resid;
if (resid < 0) {
*ssptr |= CT2_DATA_OVER;
} else {
*ssptr |= CT2_DATA_UNDER;
}
}
if (cto->ct_flags & CT2_SENDSTATUS) {
cto->ct_flags |= CT2_CCINCR;
}
isp_prt(isp, ISP_LOGTDEBUG0, "%s: CTIO2[%llx] scsi sts %x flags %x resid %d", __func__, (ull) tmd->cd_tagval, tmd->cd_scsi_status, cto->ct_flags, resid);
} else {
ct_entry_t *cto = (ct_entry_t *) local;
cto->ct_header.rqs_entry_type = RQSTYPE_CTIO;
cto->ct_header.rqs_entry_count = 1;
cto->ct_header.rqs_seqno = 1;
cto->ct_iid = tmd->cd_iid;
cto->ct_tgt = tmd->cd_tgt;
cto->ct_lun = L0LUN_TO_FLATLUN(tmd->cd_lun);
cto->ct_fwhandle = AT_GET_HANDLE(tmd->cd_tagval);
cto->ct_timeout = ISP_CT_TIMEOUT;
if (AT_HAS_TAG(tmd->cd_tagval)) {
cto->ct_tag_val = AT_GET_TAG(tmd->cd_tagval);
cto->ct_flags |= CT_TQAE;
}
if (tmd->cd_flags & CDF_NODISC) {
cto->ct_flags |= CT_NODISC;
}
if (xact->td_xfrlen == 0) {
cto->ct_flags |= CT_NO_DATA | CT_SENDSTATUS;
cto->ct_scsi_status = tmd->cd_scsi_status;
} else {
if (xact->td_hflags & TDFH_STSVALID) {
cto->ct_flags |= CT_SENDSTATUS;
}
if (xact->td_hflags & TDFH_DATA_IN) {
cto->ct_flags |= CT_DATA_IN;
} else {
cto->ct_flags |= CT_DATA_OUT;
}
/*
* We assume we'll transfer what we say we'll transfer.
* Otherwise, the command is dead.
*/
if (xact->td_hflags & TDFH_STSVALID) {
cto->ct_resid = resid;
}
}
if (cto->ct_flags & CT_SENDSTATUS) {
cto->ct_flags |= CT_CCINCR;
}
isp_prt(isp, ISP_LOGTDEBUG0, "%s: CTIO[%llx] scsi sts %x resid %d cd_lflags %x", __func__, (ull) tmd->cd_tagval, tmd->cd_scsi_status, resid, xact->td_hflags);
}
qe = isp_getrqentry(isp);
if (qe == NULL) {
isp_prt(isp, ISP_LOGWARN, "%s: request queue overflow", __func__);
xact->td_error = -ENOMEM;
goto out;
}
if (isp_save_xs_tgt(isp, xact, &handle)) {
isp_prt(isp, ISP_LOGERR, "%s: No XFLIST pointers", __func__);
xact->td_error = -ENOMEM;
goto out;
}
if (IS_24XX(isp)) {
((ct7_entry_t *) local)->ct_syshandle = handle;
} else if (IS_FC(isp)) {
((ct2_entry_t *) local)->ct_syshandle = handle;
} else {
((ct_entry_t *) local)->ct_syshandle = handle;
}
/*
* Call the dma setup routines for this entry (and any subsequent
* CTIOs) if there's data to move, and then tell the f/w it's got
* new things to play with. As with isp_start's usage of DMA setup,
* any swizzling is done in the machine dependent layer. Because
* of this, we put the request onto the queue area first in native
* format.
*/
switch (ISP_DMASETUP(isp, (XS_T *)xact, local)) {
case CMD_QUEUED:
tmd->cd_req_cnt += 1;
ISP_UNLK_SOFTC(isp);
return;
case CMD_EAGAIN:
xact->td_error = -ENOMEM;
/* FALLTHROUGH */
case CMD_COMPLETE:
BUG_ON(xact->td_error == 0);
isp_destroy_tgt_handle(isp, handle);
break;
default:
BUG();
break;
}
out:
/*
* XXX: Note that this is different from the vanilla Feral driver
* XXX: which requires a callback no matter what. SCST requires
* XXX: synchronous error returns if you can't start a command
* XXX: and to avoid race conditions and locks we don't do an
* XXX: upcall in this case.
*/
if (xact->td_error) {
xact->td_lflags |= TDFL_ERROR|TDFL_SYNCERROR;
tmd->cd_moved -= orig_xfrlen;
} else if ((tmd->cd_lflags & CDFL_LCL) == 0) {
CALL_PARENT_XFR(isp, xact);
}
ISP_UNLK_SOFTC(isp);
}
static void
isp_lcl_respond(ispsoftc_t *isp, void *aep, tmd_cmd_t *tmd)
{
uint8_t *cdbp;
if (IS_24XX(isp)) {
cdbp = ((at7_entry_t *)aep)->at_cmnd.cdb_dl.sf.fcp_cmnd_cdb;
} else if (IS_FC(isp)) {
cdbp = ((at2_entry_t *)aep)->at_cdb;
} else {
cdbp = ((at_entry_t *)aep)->at_cdb;
}
if (cdbp[0] == INQUIRY && L0LUN_TO_FLATLUN(tmd->cd_lun) == 0) {
if (cdbp[1] == 0 && cdbp[2] == 0 && cdbp[3] == 0 && cdbp[5] == 0) {
tmd_xact_t *xact;
struct scatterlist *dp;
int amt, i;
for (i = 0; i < N_TGT_AUX; i++) {
if (ISP_BTST(isp->isp_osinfo.auxbmap, i)) {
break;
}
}
if (i == N_TGT_AUX) {
if (IS_24XX(isp)) {
isp_endcmd(isp, aep, tmd->cd_nphdl, tmd->cd_channel, SCSI_BUSY, 0);
} else {
isp_endcmd(isp, aep, SCSI_BUSY, 0);
}
return;
}
ISP_BSET(isp->isp_osinfo.auxbmap, i);
xact = &isp->isp_osinfo.auxinfo[i].xact;
dp = &isp->isp_osinfo.auxinfo[i].sg;
memset(dp, 0, sizeof (*dp));
sg_assign_page(dp, virt_to_page(isp->isp_osinfo.inqdata));
dp->offset = offset_in_page(isp->isp_osinfo.inqdata);
dp->length = DEFAULT_INQSIZE;
xact->td_data = dp;
xact->td_xfrlen = min(DEFAULT_INQSIZE, tmd->cd_totlen);
if ((amt = cdbp[4]) == 0) {
amt = 256;
}
if (xact->td_xfrlen > amt) {
xact->td_xfrlen = amt;
}
xact->td_hflags |= TDFH_DATA_IN|TDFH_STSVALID;
xact->td_cmd = tmd;
xact->td_offset = 0;
xact->td_error = 0;
xact->td_lflags = 0;
tmd->cd_scsi_status = 0;
tmd->cd_lflags |= CDFL_LCL;
ISP_DROP_LK_SOFTC(isp);
isp_target_start_ctio(isp, xact);
ISP_IGET_LK_SOFTC(isp);
return;
}
/*
* Illegal field in CDB
* 0x24 << 24 | 0x5 << 12 | ECMD_SVALID | SCSI_CHECK
*/
if (IS_24XX(isp)) {
isp_endcmd(isp, aep, tmd->cd_nphdl, tmd->cd_channel, 0x24005102, 0);
} else {
isp_endcmd(isp, aep, 0x24005102, 0);
}
} else if (L0LUN_TO_FLATLUN(tmd->cd_lun) == 0) {
/*
* Not Ready, Cause Not Reportable
*
* 0x4 << 24 | 0x2 << 12 | ECMD_SVALID | SCSI_CHECK
*/
if (IS_24XX(isp)) {
isp_endcmd(isp, aep, tmd->cd_nphdl, tmd->cd_channel, 0x04002102, 0);
} else {
isp_endcmd(isp, aep, 0x04002102, 0);
}
} else {
/*
* Logical Unit Not Supported:
* 0x25 << 24 | 0x5 << 12 | ECMD_SVALID | SCSI_CHECK
*/
if (IS_24XX(isp)) {
isp_endcmd(isp, aep, tmd->cd_nphdl, tmd->cd_channel, 0x25005102, 0);
} else {
isp_endcmd(isp, aep, 0x25005102, 0);
}
}
memset(tmd, 0, TMD_SIZE);
if (isp->isp_osinfo.tfreelist) {
isp->isp_osinfo.bfreelist->cd_next = tmd;
} else {
isp->isp_osinfo.tfreelist = tmd;
}
isp->isp_osinfo.bfreelist = tmd;
}
static void
isp_handle_platform_atio(ispsoftc_t *isp, at_entry_t *aep)
{
tmd_cmd_t *tmd;
int status;
isp->isp_osinfo.cmds_started++;
/*
* The firmware status (except for the QLTM_SVALID bit)
* indicates why this ATIO was sent to us.
*
* If QLTM_SVALID is set, the firware has recommended Sense Data.
*
* If the DISCONNECTS DISABLED bit is set in the flags field,
* we're still connected on the SCSI bus.
*/
status = aep->at_status;
if ((status & ~QLTM_SVALID) == AT_PHASE_ERROR) {
/*
* Bus Phase Sequence error. We should have sense data
* suggested by the f/w. I'm not sure quite yet what
* to do about this.
*/
isp_prt(isp, ISP_LOGERR, "PHASE ERROR in atio");
isp_endcmd(isp, aep, SCSI_BUSY, 0);
return;
}
if ((status & ~QLTM_SVALID) != AT_CDB) {
isp_prt(isp, ISP_LOGERR, "bad atio (0x%x) leaked to platform", status);
isp_endcmd(isp, aep, SCSI_BUSY, 0);
return;
}
if ((tmd = isp->isp_osinfo.tfreelist) == NULL) {
if (isp->isp_osinfo.out_of_tmds == 0) {
isp_prt(isp, ISP_LOGWARN, "out of TMDs");
isp->isp_osinfo.out_of_tmds = jiffies;
}
isp_endcmd(isp, aep, SCSI_BUSY, 0);
if (jiffies - isp->isp_osinfo.out_of_tmds > 30 * HZ) {
isp_prt(isp, ISP_LOGERR, "out of TMDs too long: disabling port");
isp_thread_event(isp, ISP_THREAD_REINIT, NULL, 0, __func__, __LINE__);
}
return;
}
isp->isp_osinfo.out_of_tmds = 0;
if ((isp->isp_osinfo.tfreelist = tmd->cd_next) == NULL) {
isp->isp_osinfo.bfreelist = NULL;
}
memset(tmd, 0, TMD_SIZE);
/*
* Set the local flags to BUSY. Also set the flags
* to force a resource replenish just in case we never
* get around to sending a CTIO.
*/
tmd->cd_lflags = CDFL_BUSY|CDFL_RESRC_FILL;
tmd->cd_channel = GET_BUS_VAL(aep->at_iid);
tmd->cd_iid = GET_IID_VAL(aep->at_iid);
tmd->cd_tgt = aep->at_tgt;
FLATLUN_TO_L0LUN(tmd->cd_lun, aep->at_lun);
if (aep->at_flags & AT_NODISC) {
tmd->cd_flags |= CDF_NODISC;
}
if (status & QLTM_SVALID) {
memcpy(tmd->cd_sense, aep->at_sense, QLTM_SENSELEN);
tmd->cd_flags |= CDF_SNSVALID;
}
memcpy(tmd->cd_cdb, aep->at_cdb, min(TMD_CDBLEN, ATIO_CDBLEN));
AT_MAKE_TAGID(tmd->cd_tagval, aep);
tmd->cd_tagtype = aep->at_tag_type;
tmd->cd_hba = isp;
isp_prt(isp, ISP_LOGTDEBUG0, "ATIO[%llx] CDB=0x%x bus %d iid%d->lun%d ttype 0x%x %s", (ull) tmd->cd_tagval, aep->at_cdb[0] & 0xff,
GET_BUS_VAL(aep->at_iid), GET_IID_VAL(aep->at_iid), aep->at_lun, aep->at_tag_type,
(aep->at_flags & AT_NODISC)? "nondisc" : "disconnecting");
if (isp->isp_osinfo.hcb == 0) {
tmd->cd_next = NULL;
if (isp->isp_osinfo.tfreelist) {
isp->isp_osinfo.bfreelist->cd_next = tmd;
} else {
isp->isp_osinfo.tfreelist = tmd;
}
isp->isp_osinfo.bfreelist = tmd;
isp_endcmd(isp, aep, SCSI_BUSY, 0);
} else {
CALL_PARENT_TMD(isp, tmd, QOUT_TMD_START);
}
}
static void
isp_handle_platform_atio2(ispsoftc_t *isp, at2_entry_t *aep)
{
tmd_cmd_t *tmd;
uint16_t lun, loopid;
isp->isp_osinfo.cmds_started++;
/*
* The firmware status (except for the QLTM_SVALID bit)
* indicates why this ATIO was sent to us.
*
* If QLTM_SVALID is set, the firware has recommended Sense Data.
*/
if ((aep->at_status & ~QLTM_SVALID) != AT_CDB) {
isp_prt(isp, ISP_LOGERR, "bad atio (0x%x) leaked to platform", aep->at_status);
isp_endcmd(isp, aep, SCSI_BUSY, 0);
return;
}
if (ISP_CAP_2KLOGIN(isp)) {
at2e_entry_t *aep2 = (at2e_entry_t *) aep;
lun = aep2->at_scclun;
loopid = aep2->at_iid;
} else {
loopid = aep->at_iid;
if (ISP_CAP_SCCFW(isp)) {
lun = aep->at_scclun;
} else {
lun = aep->at_lun;
}
}
/*
* If we're out of resources, just send a BUSY status back.
*/
if ((tmd = isp->isp_osinfo.tfreelist) == NULL) {
if (isp->isp_osinfo.out_of_tmds == 0) {
isp_prt(isp, ISP_LOGWARN, "out of TMDs");
isp->isp_osinfo.out_of_tmds = jiffies;
}
isp_endcmd(isp, aep, SCSI_BUSY, 0);
if (jiffies - isp->isp_osinfo.out_of_tmds > 30 * HZ) {
isp_prt(isp, ISP_LOGERR, "out of TMDs too long: disabling port");
isp_thread_event(isp, ISP_THREAD_REINIT, NULL, 0, __func__, __LINE__);
}
return;
}
isp->isp_osinfo.out_of_tmds = 0;
if ((isp->isp_osinfo.tfreelist = tmd->cd_next) == NULL) {
isp->isp_osinfo.bfreelist = NULL;
}
memset(tmd, 0, TMD_SIZE);
/*
* Set the local flags to BUSY. Also set the flags
* to force a resource replenish just in case we never
* get around to sending a CTIO.
*/
tmd->cd_lflags = CDFL_BUSY|CDFL_RESRC_FILL;
tmd->cd_iid =
(((uint64_t) aep->at_wwpn[0]) << 48) |
(((uint64_t) aep->at_wwpn[1]) << 32) |
(((uint64_t) aep->at_wwpn[2]) << 16) |
(((uint64_t) aep->at_wwpn[3]) << 0);
tmd->cd_oxid = aep->at_oxid;
tmd->cd_nphdl = loopid;
tmd->cd_tgt = FCPARAM(isp, 0)->isp_wwpn;
FLATLUN_TO_L0LUN(tmd->cd_lun, lun);
memcpy(tmd->cd_cdb, aep->at_cdb, min(TMD_CDBLEN, ATIO2_CDBLEN));
switch (aep->at_taskflags & ATIO2_TC_ATTR_MASK) {
case ATIO2_TC_ATTR_SIMPLEQ:
tmd->cd_tagtype = CD_SIMPLE_TAG;
break;
case ATIO2_TC_ATTR_HEADOFQ:
tmd->cd_tagtype = CD_HEAD_TAG;
break;
case ATIO2_TC_ATTR_ORDERED:
tmd->cd_tagtype = CD_ORDERED_TAG;
break;
case ATIO2_TC_ATTR_ACAQ:
tmd->cd_tagtype = CD_ACA_TAG;
break;
case ATIO2_TC_ATTR_UNTAGGED:
default:
tmd->cd_tagtype = CD_UNTAGGED;
break;
}
switch (aep->at_execodes & (ATIO2_EX_WRITE|ATIO2_EX_READ)) {
case ATIO2_EX_WRITE:
tmd->cd_flags |= CDF_DATA_OUT;
break;
case ATIO2_EX_READ:
tmd->cd_flags |= CDF_DATA_IN;
break;
case ATIO2_EX_WRITE|ATIO2_EX_READ:
tmd->cd_flags |= CDF_BIDIR;
isp_prt(isp, ISP_LOGWARN, "ATIO2 with both read/write set");
break;
default:
break;
}
tmd->cd_tagval = aep->at_rxid;
tmd->cd_hba = isp;
tmd->cd_totlen = aep->at_datalen;
if ((isp->isp_dblev & ISP_LOGTDEBUG0) || isp->isp_osinfo.hcb == 0) {
const char *sstr;
switch (tmd->cd_flags & CDF_BIDIR) {
default:
sstr = "nodatadir";
break;
case CDF_DATA_OUT:
sstr = "DATA OUT";
break;
case CDF_DATA_IN:
sstr = "DATA IN";
break;
case CDF_DATA_OUT|CDF_DATA_IN:
sstr = "BIDIR";
break;
}
isp_prt(isp, ISP_LOGALL, "ATIO2[%llx] CDB=0x%x 0x%016llx for lun %d tcode 0x%x dlen %d %s", (ull) tmd->cd_tagval,
aep->at_cdb[0] & 0xff, tmd->cd_iid, lun, aep->at_taskcodes, aep->at_datalen, sstr);
}
if (isp->isp_osinfo.hcb == 0) {
isp_lcl_respond(isp, aep, tmd);
} else {
fcportdb_t *lp;
if (isp_find_pdb_by_loopid(isp, 0, tmd->cd_nphdl, &lp)) {
tmd->cd_portid = lp->portid;
CALL_PARENT_TMD(isp, tmd, QOUT_TMD_START);
} else {
tmd->cd_portid = PORT_NONE;
isp_add_wwn_entry(isp, 0, tmd->cd_iid, tmd->cd_nphdl, PORT_ANY);
(void) isp_thread_event(isp, ISP_THREAD_FINDPORTID, tmd, 0, __func__, __LINE__);
}
}
}
static void
isp_handle_platform_atio7(ispsoftc_t *isp, at7_entry_t *aep)
{
int tattr, iulen, cdbxlen;
uint16_t lun, chan, nphdl = NIL_HANDLE;
uint32_t did, sid;
uint64_t iid = INI_NONE;
fcportdb_t *lp;
fcparam *fcp;
tmd_cmd_t *tmd;
isp->isp_osinfo.cmds_started++;
tattr = aep->at_ta_len >> 12;
iulen = aep->at_ta_len & 0xffffff;
did = (aep->at_hdr.d_id[0] << 16) | (aep->at_hdr.d_id[1] << 8) | aep->at_hdr.d_id[2];
sid = (aep->at_hdr.s_id[0] << 16) | (aep->at_hdr.s_id[1] << 8) | aep->at_hdr.s_id[2];
lun = (aep->at_cmnd.fcp_cmnd_lun[0] << 8) | aep->at_cmnd.fcp_cmnd_lun[1];
/*
* Find the N-port handle, and Virtual Port Index for this command.
*
* If we can't, we're somewhat in trouble because we can't actually respond w/o that information.
* We also, as a matter of course, need to know the WWN of the initiator too.
*/
/*
* Find the right channel based upon D_ID
*/
for (chan = 0; chan < isp->isp_nchan; chan++) {
fcp = FCPARAM(isp, chan);
if ((fcp->role & ISP_ROLE_TARGET) == 0 || fcp->isp_fwstate != FW_READY || fcp->isp_loopstate < LOOP_PDB_RCVD) {
continue;
}
if (fcp->isp_portid == did) {
break;
}
}
if (chan == isp->isp_nchan) {
/*
* If this happens, it could be because one of our channels is busy starting up.
*
* It's Hackaroni time...
* It's Hackaroni time...
* It's Hackaroni time...
* It's Hackaroni time...
*/
if ((tmd = isp->isp_osinfo.tfreelist) == NULL || ++aep->at_count == 250) {
isp_prt(isp, ISP_LOGWARN, "%s: [RX_ID 0x%x] D_ID %x not found on any channel- dropping", __func__, aep->at_rxid, did);
return;
}
if ((isp->isp_osinfo.tfreelist = tmd->cd_next) == NULL) {
isp->isp_osinfo.bfreelist = NULL;
}
memcpy(tmd, aep, sizeof (at7_entry_t));
/* we should be safe here ... */
tmd->cd_lflags = CDFL_BUSY;
tmd->cd_next = isp->isp_osinfo.waiting_t;
isp->isp_osinfo.waiting_t = tmd;
tmd->cd_lastoff = 1;
return;
}
isp_prt(isp, ISP_LOGTDEBUG0, "%s: [RX_ID 0x%x] D_ID 0x%06x found on Chan %d for S_ID 0x%06x", __func__, aep->at_rxid, did, chan, sid);
if (isp_find_pdb_by_sid(isp, chan, sid, &lp)) {
nphdl = lp->handle;
iid = lp->port_wwn;
} else {
/*
* If we're not in the port database, do a tentative entry.
*/
isp_prt(isp, ISP_LOGTINFO, "%s: [RX_ID 0x%x] D_ID 0x%06x found on Chan %d for S_ID 0x%06x wasn't in PDB already", __func__, aep->at_rxid, did, chan, sid);
isp_add_wwn_entry(isp, chan, INI_NONE, NIL_HANDLE, sid);
}
/*
* If the f/w is out of resources, just send a BUSY status back.
*/
if (aep->at_rxid == AT7_NORESRC_RXID) {
isp_endcmd(isp, aep, nphdl, chan, SCSI_BUSY, 0);
return;
}
/*
* If we're out of resources, just send a BUSY status back.
*/
if ((tmd = isp->isp_osinfo.tfreelist) == NULL) {
if (isp->isp_osinfo.out_of_tmds == 0) {
isp_prt(isp, ISP_LOGWARN, "out of TMDs");
isp->isp_osinfo.out_of_tmds = jiffies;
}
isp_endcmd(isp, aep, chan, SCSI_BUSY, 0);
if (jiffies - isp->isp_osinfo.out_of_tmds > 30 * HZ) {
isp_prt(isp, ISP_LOGERR, "out of TMDs too long: disabling port");
isp_thread_event(isp, ISP_THREAD_REINIT, NULL, 0, __func__, __LINE__);
}
return;
}
isp->isp_osinfo.out_of_tmds = 0;
if ((isp->isp_osinfo.tfreelist = tmd->cd_next) == NULL) {
isp->isp_osinfo.bfreelist = NULL;
}
memset(tmd, 0, TMD_SIZE);
/*
* Set the local flags to BUSY.
*/
tmd->cd_lflags = CDFL_BUSY;
cdbxlen = aep->at_cmnd.fcp_cmnd_alen_datadir >> FCP_CMND_ADDTL_CDBLEN_SHIFT;
if (cdbxlen) {
isp_prt(isp, ISP_LOGWARN, "additional CDBLEN ignored");
}
cdbxlen = sizeof (aep->at_cmnd.cdb_dl.sf.fcp_cmnd_cdb);
memcpy(tmd->cd_cdb, aep->at_cmnd.cdb_dl.sf.fcp_cmnd_cdb, cdbxlen);
tmd->cd_totlen = aep->at_cmnd.cdb_dl.sf.fcp_cmnd_dl;
switch (aep->at_cmnd.fcp_cmnd_task_attribute & FCP_CMND_TASK_ATTR_MASK) {
case FCP_CMND_TASK_ATTR_SIMPLE:
tmd->cd_tagtype = CD_SIMPLE_TAG;
break;
case FCP_CMND_TASK_ATTR_HEAD:
tmd->cd_tagtype = CD_HEAD_TAG;
break;
case FCP_CMND_TASK_ATTR_ORDERED:
tmd->cd_tagtype = CD_ORDERED_TAG;
break;
case FCP_CMND_TASK_ATTR_ACA:
tmd->cd_tagtype = CD_ACA_TAG;
break;
case FCP_CMND_TASK_ATTR_UNTAGGED:
tmd->cd_tagtype = CD_UNTAGGED;
break;
default:
isp_prt(isp, ISP_LOGWARN, "unknown task attribute %x", aep->at_cmnd.fcp_cmnd_task_attribute & FCP_CMND_TASK_ATTR_MASK);
tmd->cd_tagtype = CD_UNTAGGED;
break;
}
switch (aep->at_cmnd.fcp_cmnd_alen_datadir & FCP_CMND_DATA_DIR_MASK) {
case FCP_CMND_DATA_WRITE:
tmd->cd_flags |= CDF_DATA_OUT;
break;
case FCP_CMND_DATA_READ:
tmd->cd_flags |= CDF_DATA_IN;
break;
case FCP_CMND_DATA_READ|FCP_CMND_DATA_WRITE:
tmd->cd_flags |= CDF_BIDIR;
isp_prt(isp, ISP_LOGINFO, "FCP_CMND_IU with both read/write set");
break;
default:
break;
}
tmd->cd_tgt = FCPARAM(isp, chan)->isp_wwpn; /* channel is valid at this point */
tmd->cd_nphdl = nphdl; /* nphdl is either known or NIL- in either case, we can set it if needed */
tmd->cd_tagval = aep->at_rxid; /* we can construct a tag value at this point */
tmd->cd_iid = iid; /* iid is either INI_NONE or known */
memcpy(tmd->cd_lun, aep->at_cmnd.fcp_cmnd_lun, sizeof (tmd->cd_lun));
tmd->cd_portid = sid;
tmd->cd_channel = chan;
tmd->cd_hba = isp;
tmd->cd_oxid = aep->at_hdr.ox_id;
if ((isp->isp_dblev & ISP_LOGTDEBUG0) || isp->isp_osinfo.hcb == 0) {
const char *sstr;
switch (tmd->cd_flags & CDF_BIDIR) {
default:
sstr = "nodatadir";
break;
case CDF_DATA_OUT:
sstr = "DATA OUT";
break;
case CDF_DATA_IN:
sstr = "DATA IN";
break;
case CDF_DATA_OUT|CDF_DATA_IN:
sstr = "BIDIR";
break;
}
isp_prt(isp, ISP_LOGALL, "ATIO7[%llx] cdb0=0x%x from 0x%016llx/0x%06x ox_id 0x%x N-Port Handle 0x%02x for lun %u dlen %d %s", (ull) tmd->cd_tagval,
tmd->cd_cdb[0] & 0xff, (ull) tmd->cd_iid, tmd->cd_portid, tmd->cd_oxid, tmd->cd_nphdl, lun, tmd->cd_totlen, sstr);
}
if (isp->isp_osinfo.hcb == 0) {
isp_lcl_respond(isp, aep, tmd);
return;
}
if (VALID_INI(tmd->cd_iid)) {
CALL_PARENT_TMD(isp, tmd, QOUT_TMD_START);
} else {
isp_prt(isp, ISP_LOGTDEBUG0, "[0x%llx] asking taskthread to find iid of initiator", (ull) tmd->cd_tagval);
if (isp_thread_event(isp, ISP_THREAD_FINDIID, tmd, 0, __func__, __LINE__)) {
isp_endcmd(isp, aep, nphdl, chan, SCSI_BUSY, 0);
memset(tmd, 0, TMD_SIZE);
if (isp->isp_osinfo.tfreelist) {
isp->isp_osinfo.bfreelist->cd_next = tmd;
} else {
isp->isp_osinfo.tfreelist = tmd;
}
isp->isp_osinfo.bfreelist = tmd;
}
}
}
/*
* Terminate a command
*/
static int
isp_terminate_cmd(ispsoftc_t *isp, tmd_cmd_t *tmd)
{
ct7_entry_t local, *cto = &local;
if (IS_24XX(isp)) {
isp_prt(isp, ISP_LOGTINFO, "isp_terminate_cmd: [%llx] is being terminated", (ull) tmd->cd_tagval);
memset(&local, 0, sizeof (local));
cto->ct_header.rqs_entry_type = RQSTYPE_CTIO7;
cto->ct_header.rqs_entry_count = 1;
cto->ct_nphdl = tmd->cd_nphdl;
cto->ct_rxid = tmd->cd_tagval;
cto->ct_vpidx = tmd->cd_channel;
cto->ct_iid_lo = tmd->cd_portid;
cto->ct_iid_hi = tmd->cd_portid >> 16;
cto->ct_oxid = tmd->cd_oxid;
cto->ct_flags = CT7_TERMINATE;
cto->ct_syshandle = 0;
return (isp_target_put_entry(isp, &local));
} else {
return (-1);
}
}
static void
isp_handle_platform_ctio(ispsoftc_t *isp, void *arg)
{
tmd_xact_t *xact;
tmd_cmd_t *tmd;
char *ctstr;
int sentstatus = 0, ok, resid = 0, id;
int status, flags;
/*
* CTIO, CTIO2, and CTIO7 are close enough....
*/
if (IS_24XX(isp)) {
ct7_entry_t *ct = arg;
xact = (tmd_xact_t *) isp_find_xs_tgt(isp, ct->ct_syshandle);
if (xact == NULL) {
isp_prt(isp, ISP_LOGERR, "isp_handle_platform_ctio: null xact");
return;
}
tmd = xact->td_cmd;
isp_destroy_tgt_handle(isp, ct->ct_syshandle);
status = ct->ct_nphdl;
flags = ct->ct_flags;
sentstatus = (flags & CT7_SENDSTATUS) != 0;
ok = status == CT7_OK;
ctstr = "CTIO7";
if ((ct->ct_flags & CT7_DATAMASK) != CT7_NO_DATA) {
resid = ct->ct_resid;
}
id = ct->ct_iid_lo | (ct->ct_iid_hi << 16);
} else if (IS_FC(isp)) {
ct2_entry_t *ct = arg;
xact = (tmd_xact_t *) isp_find_xs_tgt(isp, ct->ct_syshandle);
if (xact == NULL) {
isp_prt(isp, ISP_LOGERR, "isp_handle_platform_ctio: null xact");
return;
}
tmd = xact->td_cmd;
isp_destroy_tgt_handle(isp, ct->ct_syshandle);
status = ct->ct_status;
flags = ct->ct_flags;
sentstatus = (flags & CT2_SENDSTATUS) != 0;
ok = (status & ~QLTM_SVALID) == CT_OK;
ctstr = "CTIO2";
if ((ct->ct_flags & CT2_DATAMASK) != CT2_NO_DATA) {
resid = ct->ct_resid;
}
id = ct->ct_iid;
} else {
ct_entry_t *ct = arg;
xact = (tmd_xact_t *) isp_find_xs_tgt(isp, ct->ct_syshandle);
if (xact == NULL) {
isp_prt(isp, ISP_LOGERR, "isp_handle_platform_ctio: null xact");
return;
}
tmd = xact->td_cmd;
isp_destroy_tgt_handle(isp, ct->ct_syshandle);
status = ct->ct_status;
flags = ct->ct_flags;
sentstatus = (flags & CT_SENDSTATUS) != 0;
ok = (status & ~QLTM_SVALID) == CT_OK;
ctstr = "CTIO";
if (ct->ct_status & QLTM_SVALID) {
char *sp = (char *)ct;
sp += CTIO_SENSE_OFFSET;
memcpy(tmd->cd_sense, sp, QLTM_SENSELEN);
tmd->cd_flags |= CDF_SNSVALID;
}
if ((ct->ct_flags & CT_DATAMASK) != CT_NO_DATA) {
resid = ct->ct_resid;
}
id = ct->ct_iid;
}
if (sentstatus) {
xact->td_lflags |= TDFL_SENTSTATUS;
}
if (ok && sentstatus && (xact->td_hflags & TDFH_SNSVALID)) {
xact->td_lflags |= TDFL_SENTSENSE;
}
tmd->cd_req_cnt -= 1;
tmd->cd_moved -= resid;
isp_prt(isp, ISP_LOGTDEBUG0, "%s[%llx] status 0x%x flg 0x%x resid %d %s", ctstr, (ull) tmd->cd_tagval, status, flags, resid, sentstatus? "FIN" : "MID");
/*
* We're here either because intermediate data transfers are done
* and/or the final status CTIO (which may have joined with a
* Data Transfer) is done.
*
* In any case, for this platform, the upper layers figure out
* what to do next, so all we do here is collect status and
* pass information along.
*/
isp_prt(isp, ISP_LOGTDEBUG0, "%s CTIO done (moved %u)", (sentstatus)? " FINAL " : "MIDTERM ", tmd->cd_moved);
if (!ok) {
const char *cx;
if (IS_24XX(isp)) {
cx = "O7";
} else if (IS_FC(isp)) {
cx = "O2";
} else {
cx = "O";
}
if ((status & ~QLTM_SVALID) == CT_ABORTED) {
isp_prt(isp, ISP_LOGINFO, "[%llx] CTI%s aborted", (ull) tmd->cd_tagval, cx);
tmd->cd_lflags |= CDFL_ABORTED;
} else if ((status & QLTM_SVALID) == CT_LOGOUT) {
isp_prt(isp, ISP_LOGINFO, "[%llx] CTI%s killed by Port Logout", (ull) tmd->cd_tagval, cx);
} else {
isp_prt(isp, ISP_LOGINFO, "[%llx] CTI%s ended with badstate (0x%x)", (ull) tmd->cd_tagval, cx, status);
}
xact->td_error = -EIO;
xact->td_lflags |= TDFL_ERROR;
if (isp_target_putback_atio(isp, tmd)) {
tmd->cd_lflags |= CDFL_RESRC_FILL;
}
if ((status & ~QLTM_SVALID) == CT_LOGOUT) {
int i;
for (i = 0; i < MAX_FC_TARG; i++) {
if (FCPARAM(isp, tmd->cd_channel)->portdb[i].target_mode == 0) {
continue;
}
if (IS_24XX(isp)) {
if (id != FCPARAM(isp, tmd->cd_channel)->portdb[i].portid) {
continue;
}
} else {
if (id != FCPARAM(isp, tmd->cd_channel)->portdb[i].handle) {
continue;
}
}
isp_thread_event(isp, ISP_THREAD_LOGOUT, &FCPARAM(isp, tmd->cd_channel)->portdb[i], 0, __func__, __LINE__);
break;
}
}
}
isp_complete_ctio(isp, xact);
}
static int
isp_target_putback_atio(ispsoftc_t *isp, tmd_cmd_t *tmd)
{
uint8_t local[QENTRY_LEN];
void *qe;
tmd->cd_lflags &= ~CDFL_RESRC_FILL;
if (IS_24XX(isp)) {
return (0);
}
qe = isp_getrqentry(isp);
if (qe == NULL) {
isp_prt(isp, ISP_LOGWARN, "%s: Request Queue Overflow", __func__);
return (-ENOMEM);
}
isp_prt(isp, ISP_LOGTDEBUG0, "[%llx] resource putback being sent", (ull) tmd->cd_tagval);
memset(local, 0, sizeof (local));
if (IS_FC(isp)) {
at2_entry_t *at = (at2_entry_t *) local;
at->at_header.rqs_entry_type = RQSTYPE_ATIO2;
at->at_header.rqs_entry_count = 1;
at->at_status = CT_OK;
at->at_rxid = tmd->cd_tagval;
if (ISP_CAP_2KLOGIN(isp)) {
at2e_entry_t *ate = (at2e_entry_t *) local;
FLATLUN_TO_L0LUN(tmd->cd_lun, ate->at_scclun);
} else {
if (ISP_CAP_SCCFW(isp)) {
FLATLUN_TO_L0LUN(tmd->cd_lun, at->at_scclun);
} else {
FLATLUN_TO_L0LUN(tmd->cd_lun, at->at_lun);
}
}
isp_put_atio2(isp, at, qe);
} else {
at_entry_t *at = (at_entry_t *)local;
at->at_header.rqs_entry_type = RQSTYPE_ATIO;
at->at_header.rqs_entry_count = 1;
at->at_iid = tmd->cd_iid;
at->at_iid |= tmd->cd_channel << 7;
at->at_tgt = tmd->cd_tgt;
FLATLUN_TO_L0LUN(tmd->cd_lun, at->at_lun);
at->at_status = CT_OK;
at->at_tag_val = AT_GET_TAG(tmd->cd_tagval);
at->at_handle = AT_GET_HANDLE(tmd->cd_tagval);
isp_put_atio(isp, at, qe);
}
ISP_TDQE(isp, "isp_target_putback_atio", isp->isp_reqidx, qe);
ISP_SYNC_REQUEST(isp);
return (0);
}
static void
isp_complete_ctio(ispsoftc_t *isp, tmd_xact_t *xact)
{
tmd_cmd_t *tmd = xact->td_cmd;
isp->isp_osinfo.cmds_completed++;
if (isp->isp_osinfo.hcb || (tmd->cd_lflags & CDFL_LCL)) {
if (isp->isp_osinfo.hcb == 0) {
isp_prt(isp, ISP_LOGWARN, "nobody to tell about CTIO complete, leaking xact structure");
memset(tmd, 0, TMD_SIZE);
if (isp->isp_osinfo.tfreelist) {
isp->isp_osinfo.bfreelist->cd_next = tmd;
} else {
isp->isp_osinfo.tfreelist = tmd;
}
isp->isp_osinfo.bfreelist = tmd;
} else {
CALL_PARENT_XFR(isp, xact);
}
}
}
int
isp_enable_lun(ispsoftc_t *isp, uint16_t bus, uint16_t lun)
{
struct semaphore rsem;
tgt_enalun_t *axl;
uint16_t rstat;
int cmd, r;
unsigned long flags;
/*
* Validity check the bus argument.
*/
if (bus >= isp->isp_nchan) {
return (-ENODEV);
}
/*
* Allocate a sparse lun descriptor
*/
axl = isp_kzalloc(sizeof (tgt_enalun_t), GFP_KERNEL);
if (axl == NULL) {
return (-ENOMEM);
}
/*
* Snag the semaphore on the return state value on enables/disables.
*/
if (down_interruptible(&isp->isp_osinfo.tgt_inisem)) {
isp_kfree(axl, sizeof (tgt_enalun_t));
return (-EINTR);
}
/*
* If this lun is enabled on this bus already, that's an error.
*/
if (lunenabled(isp, bus, lun)) {
up(&isp->isp_osinfo.tgt_inisem);
isp_kfree(axl, sizeof (tgt_enalun_t));
return (-EEXIST);
}
/*
* Validity check with our enable ruleset.
* We can't enable busses > 1 without enabling bus 0
* first in some kind of role.
*/
if (IS_FC(isp) && bus != 0 && (((FCPARAM(isp, 0)->role & ISP_ROLE_TARGET) && nolunsenabled(isp, 0)) || (FCPARAM(isp, 0)->role == ISP_ROLE_NONE))) {
isp_prt(isp, ISP_LOGWARN, "%s: must enable Chan 0 before Chan %u", __func__, bus);
up(&isp->isp_osinfo.tgt_inisem);
isp_kfree(axl, sizeof (tgt_enalun_t));
return (-EINVAL);
}
/*
* If this bus is wildcarded, we don't allow any further actions.
*/
if (lun != LUN_ANY) {
if (lunenabled(isp, bus, LUN_ANY)) {
up(&isp->isp_osinfo.tgt_inisem);
isp_kfree(axl, sizeof (tgt_enalun_t));
return (-EEXIST);
}
}
/*
* If this is the first lun being enabled for this bus make sure we're set up for
* being in target mode.
*/
if (nolunsenabled(isp, bus)) {
if (IS_SCSI(isp)) {
uint16_t tb;
for (tb = 0; tb < isp->isp_nchan; tb++) {
if (tb == bus)
continue;
if (nolunsenabled(isp, tb) == 0) {
break;
}
}
/*
* This is the very first bus being enabled.
*/
if (tb < isp->isp_nchan) {
mbreg_t mbs;
memset(&mbs, 0, sizeof (mbs));
mbs.param[0] = MBOX_ENABLE_TARGET_MODE;
mbs.param[1] = ENABLE_TARGET_FLAG|ENABLE_TQING_FLAG;
mbs.param[2] = bus << 7;
mbs.logval = MBLOGALL;
ISP_LOCK_SOFTC(isp);
r = isp_control(isp, ISPCTL_RUN_MBOXCMD, &mbs);
ISP_UNLK_SOFTC(isp);
if (r < 0 || mbs.param[0] != MBOX_COMMAND_COMPLETE) {
up(&isp->isp_osinfo.tgt_inisem);
isp_kfree(axl, sizeof (tgt_enalun_t));
return (-EIO);
}
}
} else {
fcparam *fcp = FCPARAM(isp, bus);
if ((fcp->role & ISP_ROLE_TARGET) == 0) {
ISP_LOCK_SOFTC(isp);
r = isp_fc_change_role(isp, bus, fcp->role | ISP_ROLE_TARGET);
if (r) {
ISP_UNLK_SOFTC(isp);
up(&isp->isp_osinfo.tgt_inisem);
isp_kfree(axl, sizeof (tgt_enalun_t));
return (r);
}
ISP_UNLK_SOFTC(isp);
}
}
}
ISP_LOCK_SOFTC(isp);
isp->isp_osinfo.rsemap = &rsem;
sema_init(&rsem, 0);
if (IS_24XX(isp)) {
rstat = LUN_OK;
} else {
int n, ulun = lun;
cmd = RQSTYPE_ENABLE_LUN;
n = DFLT_INOT_CNT;
if (IS_FC(isp)) {
if (lun != 0 && lun != LUN_ANY) {
cmd = RQSTYPE_MODIFY_LUN;
n = 0;
} else if (lun == LUN_ANY) {
/*
* For SCC firmware, we only deal with setting
* (enabling or modifying) lun 0.
*/
ulun = 0;
}
}
rstat = LUN_ERR;
if (isp_lun_cmd(isp, cmd, bus, ulun, DFLT_CMND_CNT, n)) {
isp_prt(isp, ISP_LOGERR, "isp_lun_cmd failed");
goto out;
}
ISP_UNLK_SOFTC(isp);
down(isp->isp_osinfo.rsemap);
ISP_LOCK_SOFTC(isp);
isp->isp_osinfo.rsemap = NULL;
rstat = isp->isp_osinfo.rstatus;
if (rstat != LUN_OK) {
isp_prt(isp, ISP_LOGERR, "MODIFY/ENABLE LUN returned 0x%x", rstat);
goto out;
}
}
out:
if (rstat != LUN_OK) {
isp_prt(isp, ISP_LOGERR, "lun %u enable failed", lun);
ISP_UNLK_SOFTC(isp);
up(&isp->isp_osinfo.tgt_inisem);
isp_kfree(axl, sizeof (tgt_enalun_t));
return (-EIO);
}
addlun(isp, axl, bus, lun);
ISP_UNLK_SOFTC(isp);
if (lun == LUN_ANY) {
isp_prt(isp, ISP_LOGINFO, "All luns now enabled for target mode on channel %d", bus);
} else {
isp_prt(isp, ISP_LOGINFO, "lun %u now disabled for target mode on channel %d", lun, bus);
}
/*
* Make sure we stay resident while we have a lun enabled
*/
if (try_module_get(isp->isp_osinfo.host->hostt->module)) {
isp->isp_osinfo.isget++;
}
up(&isp->isp_osinfo.tgt_inisem);
return (0);
}
int
isp_disable_lun(ispsoftc_t *isp, uint16_t bus, uint16_t lun)
{
struct semaphore rsem;
uint16_t rstat;
tgt_enalun_t *axl;
int cmd;
unsigned long flags;
/*
* Snag the semaphore on the return state value on enables/disables.
*/
if (down_interruptible(&isp->isp_osinfo.tgt_inisem)) {
return (-EINTR);
}
if (lunenabled(isp, bus, lun) == 0) {
up(&isp->isp_osinfo.tgt_inisem);
return (-ENODEV);
}
ISP_LOCK_SOFTC(isp);
/*
* Validate this disable based upon our rulesets.
*/
if (IS_FC(isp)) {
if (bus == 0 && (FCPARAM(isp, 0)->role & ISP_ROLE_TARGET) != 0) {
for (rstat = isp->isp_nchan - 1; rstat > 0; rstat--) {
if (nolunsenabled(isp, rstat) == 0) {
break;
}
}
if (rstat > 0) {
isp_prt(isp, ISP_LOGERR, "%s: must disable Chan %u before Chan 0\n", __func__, rstat);
ISP_UNLK_SOFTC(isp);
up(&isp->isp_osinfo.tgt_inisem);
return (-EINVAL);
}
}
}
isp->isp_osinfo.rsemap = &rsem;
sema_init(&rsem, 0);
if (IS_24XX(isp)) {
rstat = LUN_OK;
} else {
int n, ulun = lun;
rstat = LUN_ERR;
cmd = -RQSTYPE_MODIFY_LUN;
n = DFLT_INOT_CNT;
if (IS_FC(isp) && lun != 0) {
n = 0;
/*
* For SCC firmware, we only deal with setting
* (enabling or modifying) lun 0.
*/
ulun = 0;
}
if (isp_lun_cmd(isp, cmd, bus, ulun, DFLT_CMND_CNT, n)) {
isp_prt(isp, ISP_LOGINFO, "isp_lun_cmd failed");
/* but proceed anyway */
rstat = LUN_OK;
}
ISP_UNLK_SOFTC(isp);
down(isp->isp_osinfo.rsemap);
ISP_LOCK_SOFTC(isp);
isp->isp_osinfo.rsemap = NULL;
rstat = isp->isp_osinfo.rstatus;
if (rstat != LUN_OK) {
/* but proceed anyway */
isp_prt(isp, ISP_LOGINFO, "MODIFY LUN returned 0x%x", rstat);
/* but proceed anyway */
rstat = LUN_OK;
}
if (IS_FC(isp) && lun) {
goto out;
}
isp->isp_osinfo.rsemap = &rsem;
rstat = LUN_ERR;
cmd = -RQSTYPE_ENABLE_LUN;
if (isp_lun_cmd(isp, cmd, bus, lun, 0, 0)) {
isp_prt(isp, ISP_LOGERR, "isp_lun_cmd failed");
goto out;
}
ISP_UNLK_SOFTC(isp);
down(isp->isp_osinfo.rsemap);
ISP_LOCK_SOFTC(isp);
isp->isp_osinfo.rsemap = NULL;
rstat = isp->isp_osinfo.rstatus;
if (rstat != LUN_OK) {
isp_prt(isp, ISP_LOGINFO, "DISABLE LUN returned 0x%x", rstat);
/* but proceed anyway */
rstat = LUN_OK;
}
}
out:
axl = remlun(isp, bus, lun);
ISP_UNLK_SOFTC(isp);
if (axl) {
isp_kfree(axl, sizeof (tgt_enalun_t));
} else {
isp_prt(isp, ISP_LOGWARN, "%s: Chan %d lun %u unable to find axl to delete", __func__, bus, lun);
}
if (rstat != LUN_OK) {
isp_prt(isp, ISP_LOGERR, "lun %u disable failed", lun);
/* but continue anyway */
}
if (lun == LUN_ANY) {
isp_prt(isp, ISP_LOGINFO, "All luns now disabled for target mode on channel %d", bus);
} else {
isp_prt(isp, ISP_LOGINFO, "lun %u now disabled for target mode on channel %d", lun, bus);
}
if (isp->isp_osinfo.isget) {
module_put(isp->isp_osinfo.host->hostt->module);
isp->isp_osinfo.isget--;
}
if (IS_SCSI(isp)) {
for (bus = 0; bus < isp->isp_nchan; bus++) {
if (!nolunsenabled(isp, bus)) {
break;
}
}
if (bus == isp->isp_nchan) {
int r;
mbreg_t mbs;
memset(&mbs, 0, sizeof (mbs));
mbs.param[0] = MBOX_ENABLE_TARGET_MODE;
mbs.param[2] = bus << 7;
mbs.logval = MBLOGNONE;
ISP_LOCK_SOFTC(isp);
r = isp_control(isp, ISPCTL_RUN_MBOXCMD, &mbs);
ISP_UNLK_SOFTC(isp);
if (r < 0 || mbs.param[0] != MBOX_COMMAND_COMPLETE) {
isp_prt(isp, ISP_LOGERR, "Chan %d unable to disable target mode", bus);
}
}
} else {
if (nolunsenabled(isp, bus)) {
fcparam *fcp = FCPARAM(isp, bus);
ISP_LOCK_SOFTC(isp);
if (isp_fc_change_role(isp, bus, fcp->role & ~ISP_ROLE_TARGET)) {
isp_prt(isp, ISP_LOGERR, "Chan %d unable to disable target mode", bus);
}
ISP_UNLK_SOFTC(isp);
}
}
up(&isp->isp_osinfo.tgt_inisem);
return (0);
}
#endif
void
isp_async(ispsoftc_t *isp, ispasync_t cmd, ...)
{
static const char prom0[] = "Chan %d PortID 0x%06x handle 0x%x role %s %s WWNN 0x%016llx WWPN 0x%016llx";
static const char prom2[] = "Chan %d PortID 0x%06x handle 0x%x role %s %s tgt %u WWNN 0x%016llx WWPN 0x%016llx";
fcportdb_t *lp;
fcparam *fcp;
va_list ap;
int bus, tgt;
switch (cmd) {
case ISPASYNC_NEW_TGT_PARAMS:
if (IS_SCSI(isp)) {
sdparam *sdp;
char *wt;
int mhz, flags, period;
va_start(ap, cmd);
bus = va_arg(ap, int);
tgt = va_arg(ap, int);
va_end(ap);
sdp = SDPARAM(isp, bus);
flags = sdp->isp_devparam[tgt].actv_flags;
period = sdp->isp_devparam[tgt].actv_period;
if ((flags & DPARM_SYNC) && period && (sdp->isp_devparam[tgt].actv_offset) != 0) {
if (sdp->isp_lvdmode || period < 0xc) {
switch (period) {
case 0x9:
mhz = 80;
break;
case 0xa:
mhz = 40;
break;
case 0xb:
mhz = 33;
break;
case 0xc:
mhz = 25;
break;
default:
mhz = 1000 / (period * 4);
break;
}
} else {
mhz = 1000 / (period * 4);
}
} else {
mhz = 0;
}
switch (flags & (DPARM_WIDE|DPARM_TQING)) {
case DPARM_WIDE:
wt = ", 16 bit wide";
break;
case DPARM_TQING:
wt = ", Tagged Queueing Enabled";
break;
case DPARM_WIDE|DPARM_TQING:
wt = ", 16 bit wide, Tagged Queueing Enabled";
break;
default:
wt = " ";
break;
}
if (mhz) {
isp_prt(isp, ISP_LOGINFO, "Chan %d Target %d at %dMHz Max Offset %d%s", bus, tgt, mhz, sdp->isp_devparam[tgt].actv_offset, wt);
} else {
isp_prt(isp, ISP_LOGINFO, "Chan %d Target %d Async Mode%s", bus, tgt, wt);
}
}
break;
case ISPASYNC_LIP:
isp_prt(isp, ISP_LOGINFO, "LIP Received");
break;
case ISPASYNC_LOOP_RESET:
va_start(ap, cmd);
bus = va_arg(ap, int);
va_end(ap);
isp_prt(isp, ISP_LOGINFO, "Chan %d Loop Reset Received", bus);
break;
case ISPASYNC_BUS_RESET:
va_start(ap, cmd);
bus = va_arg(ap, int);
va_end(ap);
isp_prt(isp, ISP_LOGINFO, "SCSI bus %d reset detected", bus);
break;
case ISPASYNC_LOOP_DOWN:
va_start(ap, cmd);
bus = va_arg(ap, int);
va_end(ap);
isp_prt(isp, ISP_LOGINFO, "Chan %d Loop DOWN", bus);
break;
case ISPASYNC_LOOP_UP:
va_start(ap, cmd);
bus = va_arg(ap, int);
va_end(ap);
isp_prt(isp, ISP_LOGINFO, "Chan %d Loop UP", bus);
break;
case ISPASYNC_DEV_ARRIVED:
va_start(ap, cmd);
bus = va_arg(ap, int);
lp = va_arg(ap, fcportdb_t *);
va_end(ap);
fcp = FCPARAM(isp, bus);
if ((fcp->role & ISP_ROLE_INITIATOR) != 0 && (lp->roles & (SVC3_TGT_ROLE >> SVC3_ROLE_SHIFT))) {
int dbidx = lp - fcp->portdb;
int i;
for (i = 0; i < MAX_FC_TARG; i++) {
if (i >= FL_ID && i <= SNS_ID) {
continue;
}
if (fcp->isp_dev_map[i] == 0) {
break;
}
}
if (i < MAX_FC_TARG) {
fcp->isp_dev_map[i] = dbidx + 1;
lp->dev_map_idx = i + 1;
} else {
isp_prt(isp, ISP_LOGWARN, "out of target ids");
isp_dump_portdb(isp, bus);
}
}
isp_prt(isp, ISP_LOGCONFIG, prom0, bus, lp->portid, lp->handle, isp_class3_roles[lp->roles], "arrived", (ull) lp->node_wwn, (ull) lp->port_wwn);
if (lp->dev_map_idx) {
lp->dirty = 0;
if (isp->isp_osinfo.scan_timeout == 0) {
isp->isp_osinfo.scan_timeout = ISP_SCAN_TIMEOUT;
}
}
break;
case ISPASYNC_DEV_CHANGED:
va_start(ap, cmd);
bus = va_arg(ap, int);
lp = va_arg(ap, fcportdb_t *);
va_end(ap);
fcp = FCPARAM(isp, bus);
lp->portid = lp->new_portid;
lp->roles = lp->new_roles;
if (lp->dev_map_idx) {
int t = lp->dev_map_idx - 1;
fcp->isp_dev_map[t] = (lp - fcp->portdb) + 1;
tgt = lp->dev_map_idx - 1;
isp_prt(isp, ISP_LOGCONFIG, prom2, bus, lp->portid, lp->handle, isp_class3_roles[lp->roles], "changed at", tgt, (ull) lp->node_wwn, (ull) lp->port_wwn);
} else {
isp_prt(isp, ISP_LOGCONFIG, prom0, bus, lp->portid, lp->handle, isp_class3_roles[lp->roles], "changed", (ull) lp->node_wwn, (ull) lp->port_wwn);
}
break;
case ISPASYNC_DEV_STAYED:
va_start(ap, cmd);
bus = va_arg(ap, int);
lp = va_arg(ap, fcportdb_t *);
va_end(ap);
if (lp->dev_map_idx) {
tgt = lp->dev_map_idx - 1;
isp_prt(isp, ISP_LOGCONFIG, prom2, bus, lp->portid, lp->handle, isp_class3_roles[lp->roles], "stayed at", tgt, (ull) lp->node_wwn, (ull) lp->port_wwn);
} else {
isp_prt(isp, ISP_LOGCONFIG, prom0, bus, lp->portid, lp->handle, isp_class3_roles[lp->roles], "stayed", (ull) lp->node_wwn, (ull) lp->port_wwn);
}
break;
case ISPASYNC_DEV_GONE:
va_start(ap, cmd);
bus = va_arg(ap, int);
lp = va_arg(ap, fcportdb_t *);
va_end(ap);
fcp = FCPARAM(isp, bus);
if (lp->dev_map_idx) {
lp->reserved = 2;
if (isp->isp_osinfo.rescan_timeout == 0) {
isp->isp_osinfo.rescan_timeout = ISP_RESCAN_TIMEOUT;
}
isp_prt(isp, ISP_LOGCONFIG, prom0, bus, lp->portid, lp->handle, isp_class3_roles[lp->roles], "zombie", (ull) lp->node_wwn, (ull) lp->port_wwn);
lp->state = FC_PORTDB_STATE_ZOMBIE;
} else {
isp_prt(isp, ISP_LOGCONFIG, prom0, bus, lp->portid, lp->handle, isp_class3_roles[lp->roles], "departed", (ull) lp->node_wwn, (ull) lp->port_wwn);
}
break;
case ISPASYNC_CHANGE_NOTIFY:
{
int chg, nphdl, nlstate, reason;
va_start(ap, cmd);
bus = va_arg(ap, int);
chg = va_arg(ap, int);
if (chg == ISPASYNC_CHANGE_PDB) {
nphdl = va_arg(ap, int);
nlstate = va_arg(ap, int);
reason = va_arg(ap, int);
} else {
nphdl = NIL_HANDLE;
nlstate = reason = 0;
}
va_end(ap);
fcp = FCPARAM(isp, bus);
if (chg == ISPASYNC_CHANGE_PDB) {
if (IS_24XX(isp)) {
isp_prt(isp, ISP_LOGINFO, "Chan %d Port Database Changed, N-Port Handle 0x%04x nlstate %x reason 0x%02x", bus, nphdl, nlstate, reason);
} else {
isp_prt(isp, ISP_LOGINFO, "Chan %d Port Database Changed", bus);
}
} else if (chg == ISPASYNC_CHANGE_SNS) {
isp_prt(isp, ISP_LOGINFO, "Chan %d Name Server Database Changed", bus);
} else {
isp_prt(isp, ISP_LOGINFO, "Chan %d Other Change Notify occurred", bus);
}
if (isp->isp_state >= ISP_INITSTATE) {
isp_thread_event(isp, ISP_THREAD_FC_RESCAN, fcp, 0, __func__, __LINE__);
}
break;
}
#ifdef ISP_TARGET_MODE
case ISPASYNC_TARGET_NOTIFY:
{
notify_t *ins;
isp_notify_t *mp;
va_start(ap, cmd);
mp = va_arg(ap, isp_notify_t *);
va_end(ap);
if (mp == NULL) {
break;
}
if (FCPARAM(isp, mp->nt_channel) == NULL) {
break;
}
if (isp->isp_osinfo.hcb == 0) {
isp_prt(isp, ISP_LOGWARN, "ISPASYNC_TARGET_NOTIFY with target mode not enabled");
if (mp->nt_need_ack && mp->nt_lreserved) {
isp_notify_ack(isp, mp->nt_lreserved);
}
break;
}
ins = isp->isp_osinfo.nfreelist;
if (ins == NULL) {
isp_prt(isp, ISP_LOGERR, "out of TMD NOTIFY structs");
if (mp->nt_need_ack && mp->nt_lreserved) {
isp_notify_ack(isp, mp->nt_lreserved);
}
break;
}
isp->isp_osinfo.nfreelist = ins->notify.nt_lreserved;
memcpy(&ins->notify, mp, sizeof (isp_notify_t));
if (mp->nt_lreserved) {
memcpy(ins->qentry, mp->nt_lreserved, QENTRY_LEN);
ins->qevalid = 1;
} else {
ins->qevalid = 0;
}
mp = &ins->notify;
if (IS_24XX(isp)) {
at7_entry_t *aep = mp->nt_lreserved;
uint32_t sid;
int i;
if (aep) {
sid = (aep->at_hdr.s_id[0] << 16) | (aep->at_hdr.s_id[1] << 8) | aep->at_hdr.s_id[2];
} else {
sid = 0xffffff;
}
switch (mp->nt_ncode) {
case NT_HBA_RESET:
case NT_LINK_UP:
case NT_LINK_DOWN:
break;
case NT_DEPARTED:
case NT_ARRIVED:
break;
case NT_LUN_RESET:
case NT_TARGET_RESET:
/*
* Mark all pertinent commands as dead and needing cleanup.
*/
for (i = 0; i < NTGT_CMDS; i++) {
tmd_cmd_t *tmd = &isp->isp_osinfo.pool[i];
if (tmd->cd_lflags & CDFL_BUSY) {
if (mp->nt_channel == tmd->cd_channel && (mp->nt_lun == LUN_ANY || mp->nt_lun == L0LUN_TO_FLATLUN(tmd->cd_lun))) {
tmd->cd_lflags |= CDFL_ABORTED|CDFL_NEED_CLNUP;
}
}
}
/* FALLTHROUGH */
default:
if (isp_find_pdb_by_sid(isp, mp->nt_channel, sid, &lp)) {
mp->nt_wwn = lp->port_wwn;
}
break;
}
/*
* Replace target with our port WWN.
*/
mp->nt_tgt = FCPARAM(isp, mp->nt_channel)->isp_wwpn;
} else if (IS_FC(isp)) {
uint16_t loopid;
/*
* The outer layer just set the loopid into nt_wwn. We try and find the WWPN.
*/
loopid = ins->notify.nt_wwn;
switch (mp->nt_ncode) {
case NT_HBA_RESET:
case NT_LINK_UP:
case NT_LINK_DOWN:
ins->notify.nt_wwn = INI_NONE;
break;
case NT_DEPARTED:
case NT_ARRIVED:
break;
default:
if (isp_find_pdb_by_loopid(isp, mp->nt_channel, loopid, &lp) == 0) {
isp_prt(isp, ISP_LOGTINFO, "cannot find WWN for loopid 0x%x for notify action 0x%x", loopid, mp->nt_ncode);
ins->notify.nt_wwn = INI_NONE;
} else {
ins->notify.nt_wwn = lp->port_wwn;
}
break;
}
/*
* Replace target with our port WWN.
*/
mp->nt_tgt = FCPARAM(isp, mp->nt_channel)->isp_wwpn;
}
isp_prt(isp, ISP_LOGTINFO, "Notify Code 0x%x iid 0x%016llx tgt 0x%016llx lun %u tag %llx",
mp->nt_ncode, (ull) mp->nt_wwn, (ull) mp->nt_tgt, mp->nt_lun, mp->nt_tagval);
CALL_PARENT_NOTIFY(isp, ins);
break;
}
case ISPASYNC_TARGET_ACTION:
{
void *qe;
va_start(ap, cmd);
qe = va_arg(ap, void *);
va_end(ap);
switch (((isphdr_t *)qe)->rqs_entry_type) {
case RQSTYPE_ATIO:
if (IS_24XX(isp)) {
isp_handle_platform_atio7(isp, (at7_entry_t *) qe);
} else {
isp_handle_platform_atio(isp, (at_entry_t *) qe);
}
break;
case RQSTYPE_ATIO2:
isp_handle_platform_atio2(isp, (at2_entry_t *)qe);
break;
case RQSTYPE_CTIO7:
case RQSTYPE_CTIO3:
case RQSTYPE_CTIO2:
case RQSTYPE_CTIO:
isp_handle_platform_ctio(isp, qe);
break;
case RQSTYPE_ENABLE_LUN:
case RQSTYPE_MODIFY_LUN:
isp->isp_osinfo.rstatus = ((lun_entry_t *)qe)->le_status;
if (isp->isp_osinfo.rsemap) {
up(isp->isp_osinfo.rsemap);
}
break;
case RQSTYPE_ABTS_RCVD:
{
notify_t *ins = NULL;
tmd_cmd_t *tmd;
abts_t *abts = qe;
int i;
uint16_t chan, lun;
uint32_t sid, did;
if (isp->isp_osinfo.hcb == 0) {
isp_prt(isp, ISP_LOGTINFO, "RQSTYPE_ABTS_RCVD: with no upstream listener");
WARN_ON(isp_acknak_abts(isp, qe, 0));
break;
}
did = (abts->abts_did_hi << 16) | abts->abts_did_lo;
sid = (abts->abts_sid_hi << 16) | abts->abts_sid_lo;
/*
* Try to find the original tmd so we can get the lun.
*/
for (i = 0; i < NTGT_CMDS; i++) {
tmd = &isp->isp_osinfo.pool[i];
if (tmd->cd_lflags & CDFL_BUSY) {
if (tmd->cd_tagval == abts->abts_rx_id && tmd->cd_oxid == abts->abts_ox_id) {
break;
}
}
tmd = NULL;
}
if (tmd == NULL) {
lun = LUN_ANY;
} else {
lun = L0LUN_TO_FLATLUN(tmd->cd_lun);
}
if (abts->abts_rxid_task == ISP24XX_NO_TASK) {
isp_prt(isp, ISP_LOGTINFO, "ABTS from N-Port handle 0x%x Port 0x%06x has no task id (rx_id 0x%04x ox_id 0x%04x)",
abts->abts_nphdl, sid, abts->abts_rx_id, abts->abts_ox_id);
if (tmd) {
isp_prt(isp, ISP_LOGTINFO, "... but found tmd to to mark as aborted (active xact count %d)", tmd->cd_req_cnt);
tmd->cd_lflags |= CDFL_ABORTED|CDFL_NEED_CLNUP;
}
WARN_ON(isp_acknak_abts(isp, qe, 0));
break;
}
ins = isp->isp_osinfo.nfreelist;
if (ins == NULL) {
isp_prt(isp, ISP_LOGWARN, "out of TMD NOTIFY structs for RQSTYPE_ABTS_RCVD");
WARN_ON(isp_acknak_abts(isp, qe, ENOMEM));
break;
}
isp->isp_osinfo.nfreelist = ins->notify.nt_lreserved;
memset(&ins->notify, 0, sizeof (isp_notify_t));
lp = NULL;
for (chan = 0; chan < isp->isp_nchan; chan++) {
if (isp_find_pdb_by_loopid(isp, chan, abts->abts_nphdl, &lp)) {
break;
}
}
if (lp == NULL) {
if (tmd) {
ins->notify.nt_wwn = tmd->cd_iid;
chan = tmd->cd_channel;
} else {
isp_prt(isp, ISP_LOGTINFO, "cannot find WWN for N-port handle 0x%x for ABTS", abts->abts_nphdl);
ins->notify.nt_wwn = INI_ANY;
chan = ISP_GET_VPIDX(isp, ISP_NOCHAN);
}
} else {
ins->notify.nt_wwn = lp->port_wwn;
}
memcpy(ins->qentry, qe, QENTRY_LEN);
ins->qevalid = 1;
ins->notify.nt_hba = isp;
ins->notify.nt_tgt = FCPARAM(isp, chan)->isp_wwpn;
ins->notify.nt_sid = sid;
ins->notify.nt_did = did;
ins->notify.nt_nphdl = abts->abts_nphdl;
ins->notify.nt_lun = lun;
ins->notify.nt_tagval = abts->abts_rxid_task;
ins->notify.nt_ncode = NT_ABORT_TASK;
ins->notify.nt_need_ack = 1;
ins->notify.nt_channel = chan;
ins->notify.nt_tmd = NULL;
/*
* If we have the command mark it aborted and needing cleanup
*/
if (tmd) {
isp_prt(isp, ISP_LOGTINFO, "[0x%llx] marked as aborted (active xact count %d)", (ull) tmd->cd_tagval, tmd->cd_req_cnt);
tmd->cd_lflags |= CDFL_ABORTED|CDFL_NEED_CLNUP;
ins->notify.nt_tmd = tmd;
}
isp_prt(isp, ISP_LOGTINFO, "ABTS [%llx] from 0x%016llx", (ull) ins->notify.nt_tagval, (ull) ins->notify.nt_wwn);
CALL_PARENT_NOTIFY(isp, ins);
break;
}
case RQSTYPE_NOTIFY:
{
notify_t *ins = NULL;
uint16_t status, nphdl;
uint32_t lun, seqid, portid;
uint8_t *ptr = NULL;
if (isp->isp_osinfo.hcb == 0) {
isp_prt(isp, ISP_LOGWARN, "TARGET_NOTIFY with no upstream listener");
isp_notify_ack(isp, qe);
break;
}
if (qe == NULL) {
isp_prt(isp, ISP_LOGERR, "null argument for RQSTYPE_NOTIFY");
break;
}
if (IS_SCSI(isp)) {
in_entry_t *inot = qe;
status = inot->in_status;
if (inot->in_status == IN_ABORT_TASK) {
ins = isp->isp_osinfo.nfreelist;
if (ins == NULL) {
isp_prt(isp, ISP_LOGERR, "out of TMD NOTIFY structs for RQSTYPE_NOTIFY!");
isp_notify_ack(isp, qe);
break;
}
isp->isp_osinfo.nfreelist = ins->notify.nt_lreserved;
memset(&ins->notify, 0, sizeof (isp_notify_t));
memcpy(ins->qentry, qe, QENTRY_LEN);
ins->qevalid = 1;
ins->notify.nt_hba = isp;
ins->notify.nt_wwn = GET_IID_VAL(inot->in_iid);
ins->notify.nt_tgt = inot->in_tgt;
ins->notify.nt_lun = inot->in_lun;
IN_MAKE_TAGID(ins->notify.nt_tagval, inot);
ins->notify.nt_ncode = NT_ABORT_TASK;
ins->notify.nt_need_ack = 1;
isp_prt(isp, ISP_LOGTINFO, "ABORT TASK [%llx] from iid %u to lun %u", (ull) ins->notify.nt_tagval,
(uint32_t) ins->notify.nt_wwn, inot->in_lun);
CALL_PARENT_NOTIFY(isp, ins);
break;
} else {
isp_notify_ack(isp, qe);
}
break;
} else if (IS_24XX(isp)) {
in_fcentry_24xx_t *inot = qe;
uint64_t wwn;
nphdl = inot->in_nphdl;
if (nphdl != NIL_HANDLE) {
portid = inot->in_portid_hi << 16 | inot->in_portid_lo;
} else {
portid = PORT_ANY;
}
status = inot->in_status;
seqid = inot->in_rxid;
lun = 0;
switch (status) {
case IN24XX_ELS_RCVD:
{
char *msg = NULL;
switch (inot->in_status_subcode) {
case LOGO:
msg = "LOGO";
if (ISP_FW_NEWER_THAN(isp, 4, 0, 25)) {
ptr = qe; /* point to unswizzled entry! */
wwn =
(((uint64_t) ptr[IN24XX_LOGO_WWPN_OFF]) << 56) |
(((uint64_t) ptr[IN24XX_LOGO_WWPN_OFF+1]) << 48) |
(((uint64_t) ptr[IN24XX_LOGO_WWPN_OFF+2]) << 40) |
(((uint64_t) ptr[IN24XX_LOGO_WWPN_OFF+3]) << 32) |
(((uint64_t) ptr[IN24XX_LOGO_WWPN_OFF+4]) << 24) |
(((uint64_t) ptr[IN24XX_LOGO_WWPN_OFF+5]) << 16) |
(((uint64_t) ptr[IN24XX_LOGO_WWPN_OFF+6]) << 8) |
(((uint64_t) ptr[IN24XX_LOGO_WWPN_OFF+7]));
} else {
wwn = INI_ANY;
}
isp_del_wwn_entry(isp, ISP_GET_VPIDX(isp, inot->in_vpidx), wwn, nphdl, portid);
break;
case PRLO:
msg = "PRLO";
break;
case PLOGI:
msg = "PLOGI";
if (ISP_FW_NEWER_THAN(isp, 4, 0, 25)) {
uint8_t *ptr = qe; /* point to unswizzled entry! */
wwn =
(((uint64_t) ptr[IN24XX_PLOGI_WWPN_OFF]) << 56) |
(((uint64_t) ptr[IN24XX_PLOGI_WWPN_OFF+1]) << 48) |
(((uint64_t) ptr[IN24XX_PLOGI_WWPN_OFF+2]) << 40) |
(((uint64_t) ptr[IN24XX_PLOGI_WWPN_OFF+3]) << 32) |
(((uint64_t) ptr[IN24XX_PLOGI_WWPN_OFF+4]) << 24) |
(((uint64_t) ptr[IN24XX_PLOGI_WWPN_OFF+5]) << 16) |
(((uint64_t) ptr[IN24XX_PLOGI_WWPN_OFF+6]) << 8) |
(((uint64_t) ptr[IN24XX_PLOGI_WWPN_OFF+7]));
} else {
wwn = INI_NONE;
}
isp_add_wwn_entry(isp, ISP_GET_VPIDX(isp, inot->in_vpidx), wwn, nphdl, portid);
break;
case PRLI:
msg = "PRLI";
break;
case PDISC:
isp_prt(isp, ISP_LOGTINFO, "%s: Chan %d IID N-Port Handle 0x%x Port ID 0x%06x PDISC",
__func__, ISP_GET_VPIDX(isp, inot->in_vpidx), nphdl, portid);
break;
default:
isp_prt(isp, ISP_LOGTINFO, "ELS CODE 0x%x Received from 0x%06x", inot->in_status_subcode, portid);
break;
}
if (msg) {
isp_prt(isp, ISP_LOGTINFO, "%s Chan %d ELS N-port handle %x PortID 0x%06x", msg, ISP_GET_VPIDX(isp, inot->in_vpidx), nphdl, portid);
}
if ((inot->in_flags & IN24XX_FLAG_PUREX_IOCB) == 0) {
isp_notify_ack(isp, qe);
}
break;
}
case IN24XX_PORT_LOGOUT:
ptr = "PORT LOGOUT";
/* FALLTHROUGH */
case IN24XX_PORT_CHANGED:
if (ptr == NULL) {
ptr = "PORT CHANGED";
}
/* FALLTHROUGH */
case IN24XX_LIP_RESET:
if (ptr == NULL) {
ptr = "LIP RESET";
}
isp_prt(isp, ISP_LOGINFO, "Chan %d %s (sub-status 0x%x) for N-port handle 0x%x", ISP_GET_VPIDX(isp, inot->in_vpidx), ptr, inot->in_status_subcode, nphdl);
/*
* All subcodes here are irrelevant. What is relevant
* is that we need to terminate all active commands from
* this initiator (known by N-port handle).
*/
/* XXX IMPLEMENT XXX */
isp_notify_ack(isp, inot);
break;
case IN24XX_LINK_RESET:
case IN24XX_LINK_FAILED:
case IN24XX_SRR_RCVD:
default:
isp_notify_ack(isp, qe);
break;
}
} else if (IS_FC(isp)) {
if (ISP_CAP_2KLOGIN(isp)) {
in_fcentry_e_t *inot = qe;
nphdl = inot->in_iid;
status = inot->in_status;
seqid = inot->in_seqid;
lun = inot->in_scclun;
} else {
in_fcentry_t *inot = qe;
nphdl = inot->in_iid;
status = inot->in_status;
seqid = inot->in_seqid;
if (ISP_CAP_SCCFW(isp)) {
lun = inot->in_scclun;
} else {
lun = inot->in_lun;
}
}
if (status == IN_ABORT_TASK || status == IN_PORT_LOGOUT || status == IN_GLOBAL_LOGO) {
ins = isp->isp_osinfo.nfreelist;
if (ins == NULL) {
isp_prt(isp, ISP_LOGWARN, "out of TMD NOTIFY structs for RQSTYPE_NOTIFY!");
isp_notify_ack(isp, qe);
break;
}
isp->isp_osinfo.nfreelist = ins->notify.nt_lreserved;
memset(&ins->notify, 0, sizeof (isp_notify_t));
memcpy(ins->qentry, qe, QENTRY_LEN);
ins->qevalid = 1;
ins->notify.nt_hba = isp;
} else {
isp_prt(isp, ISP_LOGINFO, "skipping handling of Notify Status 0x%x", status);
isp_notify_ack(isp, qe);
break;
}
if (status == IN_ABORT_TASK) {
if (isp_find_pdb_by_loopid(isp, 0, nphdl, &lp) == 0) {
isp_prt(isp, ISP_LOGINFO, "cannot find WWN for loopid 0x%x for ABORT TASK", nphdl);
ins->notify.nt_wwn = INI_NONE;
} else {
ins->notify.nt_wwn = lp->port_wwn;
}
ins->notify.nt_channel = 0;
ins->notify.nt_tgt = FCPARAM(isp, 0)->isp_wwpn;
ins->notify.nt_lun = lun;
ins->notify.nt_need_ack = 1;
ins->notify.nt_tagval = seqid;
ins->notify.nt_ncode = NT_ABORT_TASK;
isp_prt(isp, ISP_LOGTINFO, "ABORT TASK [%llx] from 0x%016llx to lun %u", (ull) ins->notify.nt_tagval,
(ull) ins->notify.nt_wwn, lun);
CALL_PARENT_NOTIFY(isp, ins);
break;
} else if (status == IN_PORT_LOGOUT) {
/*
* The port specified by the loop id listed in nphdl has logged out. We need to tell our upstream listener about it.
*/
if (isp_find_pdb_by_loopid(isp, 0, nphdl, &lp)) {
ins->notify.nt_wwn = lp->port_wwn;
ins->notify.nt_ncode = NT_LOGOUT;
isp_prt(isp, ISP_LOGTINFO, "%s: isp_del_wwn called for 0x%016llx due to PORT_LOGOUT", __func__, (ull) ins->notify.nt_wwn);
isp_del_wwn_entry(isp, 0, ins->notify.nt_wwn, nphdl, PORT_ANY);
ins->notify.nt_tagval = seqid;
isp_prt(isp, ISP_LOGTINFO, "PORT LOGOUT [%llx] from 0x%016llx", (ull) ins->notify.nt_tagval, (ull) ins->notify.nt_wwn);
ins->notify.nt_need_ack = 1;
CALL_PARENT_NOTIFY(isp, ins);
break;
}
/*
* We don't know the WWPN for this loop ID. This likely
* is because we've not as yet received a command from
* this initiator (and we're not maintaining an active
* port database ourselves).
*
* We could turn this into a (synthesized) global
* logout, but it's just as well that we just ack
* it and move on. After all, if we've not yet received
* a command for this initiator, we don't have to
* note that it left as it hasn't really arrived yet
* (at least to any upstream command interpreter),
* now has it?
*/
ins->notify.nt_lreserved = isp->isp_osinfo.nfreelist;
isp->isp_osinfo.nfreelist = ins;
isp_prt(isp, ISP_LOGINFO, "Port Logout at handle 0x%x (seqid 0x%x) but have no WWPN for it- just ACKing", nphdl, seqid);
isp_notify_ack(isp, qe);
} else if (status == IN_GLOBAL_LOGO) {
/*
* Everyone Logged Out
*/
isp_prt(isp, ISP_LOGTINFO, "%s: isp_del_wwn called for everyone due to GLOBAL PORT_LOGOUT", __func__);
isp_del_wwn_entry(isp, 0, INI_ANY, NIL_HANDLE, PORT_ANY);
ins->notify.nt_wwn = INI_ANY;
ins->notify.nt_ncode = NT_LOGOUT;
ins->notify.nt_need_ack = 1;
CALL_PARENT_NOTIFY(isp, ins);
} else {
isp_prt(isp, ISP_LOGINFO, "%s: ACKing unknown status 0x%x", __func__, status);
isp_notify_ack(isp, qe);
}
}
break;
}
default:
isp_prt(isp, ISP_LOGWARN, "event 0x%x for unhandled target action", ((isphdr_t *)qe)->rqs_entry_type);
break;
}
break;
}
#endif
case ISPASYNC_FW_CRASH:
{
uint16_t mbox1, mbox6;
mbox1 = ISP_READ(isp, OUTMAILBOX1);
if (IS_DUALBUS(isp)) {
mbox6 = ISP_READ(isp, OUTMAILBOX6);
} else {
mbox6 = 0;
}
isp_prt(isp, ISP_LOGERR, "Internal F/W Error on bus %d @ RISC Address 0x%x", mbox6, mbox1);
ISP_DATA(isp, mbox6)->blocked = 1;
ISP_RESET0(isp);
isp_shutdown(isp);
isp_thread_event(isp, ISP_THREAD_REINIT, NULL, 0, __func__, __LINE__);
break;
}
case ISPASYNC_FW_RESTARTED:
{
if (IS_FC(isp)) {
int i;
for (i = 0; i < isp->isp_nchan; i++) {
isp_thread_event(isp, ISP_THREAD_FC_RESCAN, FCPARAM(isp, i), 0, __func__, __LINE__);
}
}
break;
}
default:
break;
}
}
int
isplinux_biosparam(struct scsi_device *sdev, struct block_device *n, sector_t capacity, int ip[])
{
int size = capacity;
ip[0] = 64;
ip[1] = 32;
ip[2] = size >> 11;
if (ip[2] > 1024) {
ip[0] = 255;
ip[1] = 63;
ip[2] = size / (ip[0] * ip[1]);
}
return (0);
}
static int
isplinux_slave_configure(struct scsi_device * device)
{
if (device->tagged_supported) {
/*
* XXX: FIX LATER
*/
scsi_adjust_queue_depth(device, MSG_ORDERED_TAG, 63);
}
return 0;
}
int
isplinux_get_default_id(ispsoftc_t *isp, int chan)
{
if (IS_FC(isp))
return (isp_fc_id);
else
return (isp_spi_id);
}
int
isplinux_get_default_role(ispsoftc_t *isp, int chan)
{
return (ISP_DATA(isp, chan)->role);
}
void
isplinux_set_default_role(ispsoftc_t *isp, int chan, int role)
{
ISP_DATA(isp, chan)->role = role;
}
/*
* When we want to get the 'default' WWNs (when lacking NVRAM), we pick them up
* from our platform default (defww{p|n}n) and morph them based upon channel.
*
* When we want to get the 'active' WWNs, we get NVRAM WWNs and then morph
* them based upon channel.
*/
uint64_t
isplinux_default_wwn(ispsoftc_t *isp, int chan, int isactive, int iswwnn)
{
uint64_t seed;
isp_data *fc = ISP_DATA(isp, chan);
/*
* If we're asking for a active WWN, the default overrides get returned,
* otherwise the NVRAM value is picked.
*
* If we're asking for a default WWN, we just pick the default override.
*/
if (isactive) {
seed = iswwnn? fc->def_wwnn : fc->def_wwpn;
if (seed) {
return (seed);
}
seed = iswwnn? FCPARAM(isp, chan)->isp_wwnn_nvram : FCPARAM(isp, chan)->isp_wwpn_nvram;
if (seed) {
return (seed);
}
return (0x400000007F00000aull);
} else {
seed = iswwnn? fc->def_wwnn : fc->def_wwpn;
}
/*
* For channel zero just return what we have. For either ACIIVE or DEFAULT cases,
* we depend on default override of NVRAM values for channel zero.
*/
if (chan == 0) {
return (seed);
}
/*
* For other channels, we are doing one of three things:
*
* 1. If what we have now is non-zero, return it. Otherwise
* we morph values from channel 0.
* 2. If we're here for a WWPN we synthesize it if
* Channel 0's wwpn has a type 2 NAA.
* 3. If we're here for a WWNN we synthesize it if
* Channel 0's wwnn has a type 2 NAA.
*/
if (seed) {
return (seed);
}
if (isactive) {
seed = iswwnn? FCPARAM(isp, 0)->isp_wwnn_nvram : FCPARAM(isp, 0)->isp_wwpn_nvram;
} else {
seed = iswwnn? ISP_DATA(isp, 0)->def_wwnn : ISP_DATA(isp, 0)->def_wwpn;
}
if (((seed >> 60) & 0xf) == 2) {
/*
* The type 2 NAA fields for QLogic cards appear be laid out thusly:
*
* bits 63..60 NAA == 2
* bits 59..57 unused/zero
* bit 56 port (1) or node (0) WWN distinguishor
* bit 48 physical port on dual-port chips (23XX/24XX)
*
* This is somewhat nutty, particularly since bit 48 is irrelevant
* as they assign seperate serial numbers to different physical ports
* anyway.
*
* We'll stick our channel number plus one first into bits 57..59 and
* thence into bits 52..55 which allows for 8 bits of channel which is
* comfortably more than our maximum (126) now.
*/
seed &= ~0x0FF0000000000000ULL;
if (iswwnn == 0) {
seed |= ((uint64_t) (chan+1) & 0xf) << 56;
seed |= ((uint64_t) ((chan+1) >> 4) & 0xf) << 52;
}
} else {
seed = 0;
}
return (seed);
}
/*
* Periodic watchdog timer.. the main purpose here is to restart
* commands that were pegged on resources, etc...
*/
void
isplinux_timer(unsigned long arg)
{
Scsi_Cmnd *Cmnd;
ispsoftc_t *isp = (ispsoftc_t *) arg;
unsigned long flags;
int i;
ISP_ILOCK_SOFTC(isp);
isp->isp_osinfo.dogcnt++;
for (i = 0; i < isp->isp_nchan; i++) {
if (ISP_DATA(isp, i)->qfdelay) {
ISP_DATA(isp, i)->qfdelay--;
}
}
/*
* Check to see whether we need to check for loop state
*/
if (IS_FC(isp) && isp->isp_state == ISP_RUNSTATE) {
for (i = 0 ; i < isp->isp_nchan; i++) {
fcparam *fcp = FCPARAM(isp, i);
if (fcp->role == ISP_ROLE_NONE || ISP_DATA(isp, i)->deadloop || ISP_DATA(isp, i)->nextscan == 0) {
continue;
}
if (jiffies > ISP_DATA(isp, i)->nextscan) {
ISP_DATA(isp, i)->nextscan = jiffies;
isp_thread_event(isp, ISP_THREAD_FC_RESCAN, fcp, 0, __func__, __LINE__);
}
}
}
/*
* Run any commands that were waitinbg.
*/
isplinux_runwaitq(isp);
/*
* Pick up any commands needing completion...
*/
if ((Cmnd = isp->isp_osinfo.dqnext) != NULL) {
isp->isp_osinfo.dqnext = isp->isp_osinfo.dqtail = NULL;
}
/*
* Check for any rescan activity that needs running
*/
if (isp->isp_osinfo.scan_timeout && --isp->isp_osinfo.scan_timeout == 0) {
isp_thread_event(isp, ISP_THREAD_SCSI_SCAN, (void *)1, 0, __func__, __LINE__);
}
if (isp->isp_osinfo.rescan_timeout && --isp->isp_osinfo.rescan_timeout == 0) {
isp_thread_event(isp, ISP_THREAD_SCSI_SCAN, (void *)0, 0, __func__, __LINE__);
}
/*
* Set up the timer again
*/
if (isp->dogactive) {
isp->isp_osinfo.timer.expires = jiffies + ISP_WATCH_TIME;
add_timer(&isp->isp_osinfo.timer);
}
#ifdef ISP_TARGET_MODE
/*
* Fire up any delayed target mode actions based
* upon whether the dog counter has wrapped to
* zero.
*/
if (isp->isp_osinfo.dogcnt == 0) {
tmd_cmd_t *wt;
while ((wt = isp->isp_osinfo.waiting_t) != NULL) {
isp->isp_osinfo.waiting_t = wt->cd_next;
wt->cd_next = NULL;
if (wt->cd_lastoff == 0) {
isp_thread_event(isp, ISP_THREAD_FINDIID, wt, 0, __func__, __LINE__);
} else {
isp_thread_event(isp, ISP_THREAD_RESTART_AT7, wt, 0, __func__, __LINE__);
}
}
}
#endif
ISP_IUNLK_SOFTC(isp);
/*
* Complete any commands that had been on the done queue
*/
if (Cmnd) {
ISP_LOCK_SCSI_DONE(isp);
while (Cmnd) {
Scsi_Cmnd *f = (Scsi_Cmnd *) Cmnd->host_scribble;
Cmnd->host_scribble = NULL;
(*Cmnd->scsi_done)(Cmnd);
Cmnd = f;
}
ISP_UNLK_SCSI_DONE(isp);
}
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19)
#define PTARG , struct pt_regs *pt
#else
#define PTARG
#endif
irqreturn_t
isplinux_intr(int irq, void *arg PTARG)
{
ispsoftc_t *isp = arg;
uint32_t isr;
uint16_t sema, mbox;
Scsi_Cmnd *Cmnd;
unsigned long flags;
ISP_ILOCK_SOFTC(isp);
isp->isp_intcnt++;
if (ISP_READ_ISR(isp, &isr, &sema, &mbox) == 0) {
isp->isp_intbogus++;
if (isp->intsok) {
ISP_ENABLE_INTS(isp);
}
ISP_IUNLK_SOFTC(isp);
return IRQ_NONE;
}
isp_intr(isp, isr, sema, mbox);
isplinux_runwaitq(isp);
if ((Cmnd = isp->isp_osinfo.dqnext) != NULL) {
isp->isp_osinfo.dqnext = isp->isp_osinfo.dqtail = NULL;
}
if (isp->intsok) {
ISP_ENABLE_INTS(isp);
}
ISP_IUNLK_SOFTC(isp);
if (Cmnd) {
ISP_LOCK_SCSI_DONE(isp);
while (Cmnd) {
Scsi_Cmnd *f = (Scsi_Cmnd *) Cmnd->host_scribble;
Cmnd->host_scribble = NULL;
(*Cmnd->scsi_done)(Cmnd);
Cmnd = f;
}
ISP_UNLK_SCSI_DONE(isp);
}
#ifdef ISP_TARGET_MODE
isp_tgt_tq(isp);
#endif
return IRQ_HANDLED;
}
/*
* roles=DEVID=role[,...]
*/
static int
isp_parse_rolearg(ispsoftc_t *isp, int chan, char *roles)
{
char *role = roles;
while (role && *role) {
unsigned int id;
char *eqtok, *commatok, *p, *q;
eqtok = role;
eqtok = strchr(role, '=');
if (eqtok == NULL) {
break;
}
*eqtok = 0;
commatok = strchr(eqtok+1, ',');
if (commatok) {
*commatok = 0;
}
if (strncmp(role, "0x", 2) == 0) {
q = role + 2;
} else {
q = role;
}
if (*q == '*') {
id = isp->isp_osinfo.device_id;
p = NULL;
} else {
id = simple_strtoul(q, &p, 16);
}
*eqtok = '=';
if (p != q && id == isp->isp_osinfo.device_id) {
p = eqtok + 1;
if (strcmp(p, "none") == 0) {
if (commatok) {
*commatok = ',';
}
return (ISP_ROLE_NONE);
}
if (strcmp(p, "target") == 0) {
if (commatok) {
*commatok = ',';
}
return (ISP_ROLE_TARGET);
}
if (strcmp(p, "initiator") == 0) {
if (commatok) {
*commatok = ',';
}
return (ISP_ROLE_INITIATOR);
}
if (strcmp(p, "both") == 0) {
if (commatok) {
*commatok = ',';
}
return (ISP_ROLE_BOTH);
}
break;
}
if (commatok) {
role = commatok+1;
*commatok = ',';
} else {
break;
}
}
return (ISP_DEFAULT_ROLES);
}
/*
* isp_wwnns=DEVID=[chan:]wwn[,...]
* isp_wwpns=DEVID=[chan:]wwn[,...]
*/
static uint64_t
isp_parse_wwnarg(ispsoftc_t *isp, int chan, char *wwns)
{
char *wwnt = wwns;
uint64_t wwn = 0;
while (wwn == 0 && wwnt && *wwnt) {
unsigned int id;
int thischan;
char *eqtok, *commatok, *colontok, *wwnstart, *p, *q;
eqtok = wwnt;
eqtok = strchr(wwnt, '=');
if (eqtok == NULL) {
break;
}
*eqtok = 0;
commatok = strchr(eqtok+1, ',');
if (commatok) {
*commatok = 0;
}
q = wwnt;
colontok = strchr(eqtok+1, ':');
thischan = 0;
if (colontok) {
*colontok = 0;
q = eqtok + 1;
thischan = simple_strtoul(q, &p, 0);
*colontok = ':';
if (p == q) {
thischan = 0;
}
q = wwnt;
wwnstart = colontok + 1;
} else {
wwnstart = eqtok + 1;
}
if (strncmp(q, "0x", 2) == 0) {
q += 2;
}
id = simple_strtoul(q, &p, 16);
if (p != q && id == isp->isp_osinfo.device_id && thischan == chan) {
unsigned long t, t2;
p = wwnstart;
while (*p) {
p++;
}
p -= 8;
if (p > wwnstart) {
char *q;
char c;
q = p;
t = simple_strtoul(p, &q, 16);
c = *p;
*p = 0;
t2 = simple_strtoul(wwnstart, NULL, 16);
*p = c;
} else {
t = simple_strtoul(wwnstart, NULL, 16);
t2 = 0;
}
wwn = (((uint64_t) t2) << 32) | (uint64_t) t;
}
*eqtok = '=';
if (commatok) {
wwnt = commatok+1;
*commatok = ',';
} else {
break;
}
}
return (wwn);
}
int
isplinux_common_init(ispsoftc_t *isp)
{
int retval, chan, i;
unsigned long flags;
if (isp_nofwreload & (1 << isp->isp_unit)) {
isp->isp_confopts |= ISP_CFG_NORELOAD;
}
if (isp_nonvram & (1 << isp->isp_unit)) {
isp->isp_confopts |= ISP_CFG_NONVRAM;
}
if (IS_FC(isp)) {
if (isp_nport_only & (1 << isp->isp_unit)) {
isp->isp_confopts |= ISP_CFG_NPORT_ONLY;
} else if (isp_loop_only & (1 << isp->isp_unit)) {
isp->isp_confopts |= ISP_CFG_LPORT_ONLY;
} else {
isp->isp_confopts |= ISP_CFG_NPORT;
}
isp->isp_osinfo.host->this_id = MAX_FC_TARG+1;
if (isp_default_frame_size) {
if (isp_default_frame_size != 512 && isp_default_frame_size != 1024 && isp_default_frame_size != 2048) {
isp_prt(isp, ISP_LOGERR, "bad frame size (%d), defaulting to (%d)", isp_default_frame_size, ICB_DFLT_FRMLEN);
isp_default_frame_size = 0;
}
}
if (isp_default_exec_throttle) {
if (isp_default_exec_throttle < 1 || isp_default_exec_throttle > 255) {
isp_prt(isp, ISP_LOGERR, "bad execution throttle size (%d), defaulting to (%d)", isp_default_exec_throttle, ICB_DFLT_THROTTLE);
isp_default_exec_throttle = 0;
}
}
if (isp_fcduplex & (1 << isp->isp_unit)) {
isp->isp_confopts |= ISP_CFG_FULL_DUPLEX;
}
isp->isp_osinfo.host->max_id = MAX_FC_TARG;
isp->isp_osinfo.host->max_cmd_len = 16;
for (chan = 0; chan < isp->isp_nchan; chan++) {
isp_data *fc = ISP_DATA(isp, chan);
fc->def_wwnn = isp_parse_wwnarg(isp, chan, isp_wwnns);
fc->def_wwpn = isp_parse_wwnarg(isp, chan, isp_wwpns);
if (isp_default_frame_size) {
isp->isp_confopts |= ISP_CFG_OWNFSZ;
DEFAULT_FRAMESIZE(isp) = isp_default_frame_size;
} else {
DEFAULT_FRAMESIZE(isp) = ICB_DFLT_FRMLEN;
}
if (isp_default_exec_throttle) {
isp->isp_confopts |= ISP_CFG_OWNEXCTHROTTLE;
DEFAULT_EXEC_THROTTLE(isp) = isp_default_exec_throttle;
} else {
DEFAULT_EXEC_THROTTLE(isp) = ICB_DFLT_THROTTLE;
}
fc->role = isp_parse_rolearg(isp, chan, isp_roles);
SET_DEFAULT_ROLE(isp, chan, fc->role);
}
} else {
isp->isp_osinfo.host->max_id = MAX_TARGETS;
isp->isp_osinfo.host->max_cmd_len = 12;
isp->isp_osinfo.host->this_id = 7; /* temp default */
for (chan = 0; chan < isp->isp_nchan; chan++) {
SDPARAM(isp, chan)->role = isp_parse_rolearg(isp, chan, isp_roles);
SET_DEFAULT_ROLE(isp, chan, SDPARAM(isp, chan)->role);
}
}
if (isp_own_id) {
isp->isp_confopts |= ISP_CFG_OWNLOOPID;
}
/*
* Initialize locks
*/
ISP_LOCK_INIT(isp);
ISP_TLOCK_INIT(isp);
sema_init(&isp->isp_osinfo.mbox_sem, 1);
init_waitqueue_head(&isp->isp_osinfo.mboxwq);
init_waitqueue_head(&isp->isp_osinfo.trq);
for (i = 0; i < MAX_THREAD_ACTION; i++) {
init_waitqueue_head(&isp->isp_osinfo.t_actions[i].thread_waiter);
if (i < MAX_THREAD_ACTION - 1) {
isp->isp_osinfo.t_actions[i].next = &isp->isp_osinfo.t_actions[i+1];
}
}
isp->isp_osinfo.t_busy = NULL;
isp->isp_osinfo.t_busy_t = NULL;
isp->isp_osinfo.t_free = isp->isp_osinfo.t_actions;
#ifdef ISP_TARGET_MODE
/*
* Initialize target stuff here
*/
if (isp_init_target(isp)) {
return (-1);
}
#endif
/*
* Start watchdog timer, create FC handler thread and reinit hardware.
*/
if (IS_FC(isp)) {
isp->isp_osinfo.thread_task = kthread_run(isp_task_thread, isp, "isp%d_fc", isp->isp_unit);
if (IS_ERR(isp->isp_osinfo.thread_task)) {
isp_prt(isp, ISP_LOGERR, "unable to start FC task thread");
#ifdef ISP_TARGET_MODE
isp_deinit_target(isp);
#endif
isp->isp_osinfo.thread_task = NULL;
}
}
ISP_LOCK_SOFTC(isp);
init_timer(&isp->isp_osinfo.timer);
isp->isp_osinfo.timer.data = (unsigned long) isp;
isp->isp_osinfo.timer.function = isplinux_timer;
isp->isp_osinfo.timer.expires = jiffies + ISP_WATCH_TIME;
add_timer(&isp->isp_osinfo.timer);
isp->dogactive = 1;
retval = isplinux_reinit(isp, 1);
if (retval) {
isp_prt(isp, ISP_LOGERR, "failed to init HBA port- skipping it");
del_timer(&isp->isp_osinfo.timer);
isp->dogactive = 0;
ISP_UNLK_SOFTC(isp);
#ifdef ISP_TARGET_MODE
isp_deinit_target(isp);
#endif
if (isp->isp_osinfo.thread_task) {
kthread_stop(isp->isp_osinfo.thread_task);
}
return (-1);
}
ISP_UNLK_SOFTC(isp);
#ifdef ISP_TARGET_MODE
isp_attach_target(isp);
#endif
return (0);
}
int
isplinux_reinit(ispsoftc_t *isp, int doset_defaults)
{
int maxluns = isp_maxluns;
isp_reset(isp, doset_defaults);
if (isp->isp_state != ISP_RESETSTATE) {
isp_prt(isp, ISP_LOGERR, "failed to enter RESET state");
return (-1);
}
/*
* Until the midlayer starts using REPORT LUNS to dertermine how many
* luns there are for SCSI-3 devices and sets a reasonable limit for
* SCSI-2 devices, we'll follow this ruleset:
*
* If our isp_maxluns parameter is unchanged from its default, we
* limit ourselves to 8 luns for parallel SCSI, 256 for FC-SCSI.
*
* If somebody has set isp_maxluns away from the default, we follow that.
*
* We filter any value through the HBA maximum
*/
if (isp_maxluns == 8) {
if (IS_FC(isp)) {
maxluns = 256;
}
}
isp->isp_osinfo.host->max_lun = min(maxluns, ISP_MAX_LUNS(isp));
isp->isp_osinfo.host->can_queue = 1;
isp->isp_osinfo.host->cmd_per_lun = 1;
isp->isp_osinfo.host->this_id = IS_FC(isp)? MAX_FC_TARG : 7;
isp_init(isp);
if (isp->isp_state != ISP_INITSTATE) {
isp_prt(isp, ISP_LOGERR, "failed to enter INIT state");
return (-1);
}
isp->isp_osinfo.host->can_queue = isp->isp_maxcmds;
if (IS_FC(isp)) {
isp->isp_osinfo.host->this_id = MAX_FC_TARG;
/*
* This is *not* the same as execution throttle- that is set
* in isplinux_sqd and is per-device.
*
* What we try and do here is take how much we can queue at
* a given time and spread it, reasonably, over all the luns
* we expect to run at a time.
*/
if (isp_cmd_per_lun) {
isp->isp_osinfo.host->cmd_per_lun = isp_cmd_per_lun;
} else {
/*
* JAWAG.
*/
isp->isp_osinfo.host->cmd_per_lun = isp->isp_maxcmds >> 3;
}
/*
* We seem to need a bit of settle time.
*/
ISP_SLEEP(isp, 1 * 1000000);
} else {
int chan;
if (isp_cmd_per_lun) {
isp->isp_osinfo.host->cmd_per_lun = isp_cmd_per_lun;
} else {
/*
* Maximum total commands spread over either 8 targets,
* or 4 targets, 2 luns, etc.
*/
isp->isp_osinfo.host->cmd_per_lun = isp->isp_maxcmds >> 3;
}
/*
* No way to give different ID's for the second bus.
*/
isp->isp_osinfo.host->this_id = SDPARAM(isp, 0)->isp_initiator_id;
for (chan = 0; chan < isp->isp_nchan; chan++) {
(void) isp_control(isp, ISPCTL_RESET_BUS, chan);
}
/*
* Bus Reset delay handled by firmware.
*/
}
isp->mbintsok = 1;
isp->isp_state = ISP_RUNSTATE;
return (0);
}
int
isp_thread_event(ispsoftc_t *isp, int action, void *a, int dowait, const char *file, const int line)
{
isp_thread_action_t *tap;
unsigned long flags;
spin_lock_irqsave(&isp->isp_osinfo.tlock, flags);
/*
* Check for duplicates
*/
for (tap = isp->isp_osinfo.t_busy; tap != NULL; tap = tap->next) {
if (tap->thread_action == action && tap->arg == a && dowait == 0) {
tap->count++;
spin_unlock_irqrestore(&isp->isp_osinfo.tlock, flags);
wake_up(&isp->isp_osinfo.trq);
isp_prt(isp, ISP_LOGDEBUG1, "async thread event %d from %s:%d now has count %d", action, file, line, tap->count);
return (0);
}
}
if ((tap = isp->isp_osinfo.t_free) == NULL) {
spin_unlock_irqrestore(&isp->isp_osinfo.tlock, flags);
isp_prt(isp, ISP_LOGERR, "thread event %d from %s:%d sent with thread overflow", action, file, line);
return (-1);
}
isp->isp_osinfo.t_free = tap->next;
tap->next = NULL;
tap->count = 1;
tap->thread_action = action;
tap->arg = a;
tap->done = 0;
if (dowait) {
tap->waiting = 1;
isp_prt(isp, ISP_LOGDEBUG0, "action %d sending from %s:%d and now waiting", action, file, line);
} else {
tap->waiting = 0;
isp_prt(isp, ISP_LOGDEBUG0, "action %d from %s:%d sending", action, file, line);
}
if (isp->isp_osinfo.t_busy) {
isp->isp_osinfo.t_busy_t->next = tap;
} else {
isp->isp_osinfo.t_busy = tap;
}
isp->isp_osinfo.t_busy_t = tap;
spin_unlock_irqrestore(&isp->isp_osinfo.tlock, flags);
wake_up(&isp->isp_osinfo.trq);
if (dowait) {
while (wait_event_interruptible_timeout(tap->thread_waiter, (tap->done == 1), 100)) {
if (kthread_should_stop()) {
break;
}
}
if (kthread_should_stop()) {
tap->waiting = 0;
return (-1);
}
spin_lock_irqsave(&isp->isp_osinfo.tlock, flags);
tap->waiting = 0;
tap->next = isp->isp_osinfo.t_free;
isp->isp_osinfo.t_free = tap;
tap->next = NULL;
spin_unlock_irqrestore(&isp->isp_osinfo.tlock, flags);
isp_prt(isp, ISP_LOGDEBUG0, "action %d from %s:%d done", action, file, line);
}
return (0);
}
static void
isp_scsi_scan(ispsoftc_t *isp)
{
unsigned long flags;
int chan, tgt, i;
if (!IS_FC(isp)) {
return;
}
ISP_LOCKU_SOFTC(isp);
for (chan = 0; chan < isp->isp_nchan; chan++) {
fcparam *fcp = FCPARAM(isp, chan);
for (i = 0; i < MAX_FC_TARG; i++) {
fcportdb_t *lp = &fcp->portdb[i];
if (lp->dev_map_idx == 0) {
continue;
}
tgt = lp->dev_map_idx - 1;
if (lp->state == FC_PORTDB_STATE_VALID && lp->dirty == 0) {
ISP_UNLKU_SOFTC(isp);
scsi_scan_target(&isp->isp_osinfo.host->shost_gendev, chan, tgt, 0, 0);
ISP_LOCKU_SOFTC(isp);
} else if (lp->state == FC_PORTDB_STATE_ZOMBIE) {
struct scsi_device *sdev;
fcp->isp_dev_map[tgt] = 0;
lp->state = FC_PORTDB_STATE_NIL;
lp->dev_map_idx = 0;
lp->dirty = 0;
ISP_UNLKU_SOFTC(isp);
sdev = scsi_device_lookup(isp->isp_osinfo.host, 0, tgt, 0);
if (sdev) {
scsi_remove_device(sdev);
scsi_device_put(sdev);
}
ISP_LOCKU_SOFTC(isp);
}
}
}
ISP_UNLKU_SOFTC(isp);
}
static int
isp_task_thread(void *arg)
{
ispsoftc_t *isp = arg;
isp_thread_action_t *tap;
unsigned long flags;
int i;
isp_prt(isp, ISP_LOGDEBUG0, "isp_task_thread starting");
while (!kthread_should_stop()) {
isp_prt(isp, ISP_LOGDEBUG0, "isp_task_thread sleeping");
if (wait_event_interruptible(isp->isp_osinfo.trq, (isp->isp_osinfo.t_busy || kthread_should_stop()))) {
continue;
}
isp_prt(isp, ISP_LOGDEBUG0, "isp_task_thread running");
if (kthread_should_stop()) {
break;
}
spin_lock_irqsave(&isp->isp_osinfo.tlock, flags);
while ((tap = isp->isp_osinfo.t_busy) != NULL) {
if ((isp->isp_osinfo.t_busy = tap->next) == NULL) {
isp->isp_osinfo.t_busy_t = NULL;
}
spin_unlock_irqrestore(&isp->isp_osinfo.tlock, flags);
if (tap == NULL) {
break;
}
isp_prt(isp, ISP_LOGDEBUG0, "isp_task_thread: action %d", tap->thread_action);
switch (tap->thread_action) {
case ISP_THREAD_NIL:
break;
case ISP_THREAD_SCSI_SCAN:
isp_scsi_scan(isp);
break;
case ISP_THREAD_REINIT:
ISP_LOCKU_SOFTC(isp);
if (isp->isp_dead) {
isp_prt(isp, ISP_LOGERR, "chip marked dead- not restarting");
isp_shutdown(isp);
ISP_DISABLE_INTS(isp);
ISP_UNLKU_SOFTC(isp);
break;
}
isp_reinit(isp, 0);
if (isp->isp_state == ISP_RUNSTATE) {
for (i = 0; i < isp->isp_nchan; i++) {
ISP_DATA(isp, i)->blocked = 0;
}
isp_async(isp, ISPASYNC_FW_RESTARTED);
} else {
isp_prt(isp, ISP_LOGERR, "unable to restart chip");
}
ISP_UNLKU_SOFTC(isp);
break;
case ISP_THREAD_FC_RESCAN:
{
fcparam *fcp = tap->arg;
int chan = fcp - FCPARAM(isp, 0);
fcp = FCPARAM(isp, chan);
ISP_LOCKU_SOFTC(isp);
ISP_DATA(isp, chan)->nextscan = 0;
if (isp_fc_runstate(isp, chan, 250000) == 0) {
ISP_DATA(isp, chan)->deadloop = 0;
ISP_DATA(isp, chan)->downcount = 0;
ISP_DATA(isp, chan)->blocked = 0;
isplinux_runwaitq(isp);
} else {
if (ISP_DATA(isp, chan)->downcount == 0) {
ISP_DATA(isp, chan)->downcount = jiffies;
}
/*
* Try again in a little while.
*/
if ((jiffies - ISP_DATA(isp, chan)->downcount) > (isp_deadloop_time * HZ)) {
fcp->loop_seen_once = 0;
ISP_DATA(isp, chan)->deadloop = 1;
ISP_DATA(isp, chan)->downcount = 0;
ISP_DATA(isp, chan)->blocked = 0;
isp_prt(isp, ISP_LOGWARN, "Chan %d assuming loop is dead", chan);
isplinux_flushwaitq(isp);
ISP_UNLKU_SOFTC(isp);
break;
}
ISP_DATA(isp, chan)->nextscan = jiffies + HZ;
}
ISP_UNLKU_SOFTC(isp);
break;
}
#ifdef ISP_TARGET_MODE
case ISP_THREAD_LOGOUT:
{
mbreg_t mbs;
union {
isp_pdb_t pdb;
int id;
} u;
fcportdb_t *lp = tap->arg;
ISP_LOCKU_SOFTC(isp);
if (lp->state != FC_PORTDB_STATE_VALID) {
isp_prt(isp, ISP_LOGTINFO, "target mode entry no longer valid");
ISP_UNLKU_SOFTC(isp);
break;
}
memset(&u, 0, sizeof (u));
u.id = lp->handle;
isp_prt(isp, ISP_LOGTINFO, "Doing Port Logout repair for 0x%016llx@0x%x (loop id) %u", (ull) lp->port_wwn, lp->portid, lp->handle);
memset(&mbs, 0, sizeof (mbs));
mbs.param[0] = MBOX_FABRIC_LOGOUT;
if (ISP_CAP_2KLOGIN(isp)) {
mbs.param[1] = lp->handle;
mbs.obits |= (1 << 10);
} else {
mbs.param[1] = lp->handle << 8;
}
mbs.logval = MBLOGNONE;
(void) isp_control(isp, ISPCTL_RUN_MBOXCMD, &mbs);
if (mbs.param[0] != MBOX_COMMAND_COMPLETE) {
isp_prt(isp, ISP_LOGERR, "failed to get logout loop id %u", lp->handle);
lp->state = FC_PORTDB_STATE_PROBATIONAL;
ISP_UNLKU_SOFTC(isp);
break;
}
memset(&mbs, 0, sizeof (mbs));
mbs.param[0] = MBOX_FABRIC_LOGIN;
if (ISP_CAP_2KLOGIN(isp)) {
mbs.param[1] = lp->handle;
mbs.obits |= (1 << 10);
} else {
mbs.param[1] = lp->handle << 8;
}
mbs.param[2] = lp->portid >> 16;
mbs.param[3] = lp->portid & 0xffff;
(void) isp_control(isp, ISPCTL_RUN_MBOXCMD, &mbs);
if (mbs.param[0] != MBOX_COMMAND_COMPLETE) {
isp_prt(isp, ISP_LOGERR, "failed to get login port id %x at loop id %u", lp->portid, lp->handle);
lp->state = FC_PORTDB_STATE_PROBATIONAL;
ISP_UNLKU_SOFTC(isp);
break;
}
lp->state = FC_PORTDB_STATE_VALID;
ISP_UNLKU_SOFTC(isp);
break;
}
case ISP_THREAD_FINDIID:
{
tmd_cmd_t *tmd = tap->arg;
fcportdb_t *lp = NULL;
uint64_t iid = INI_NONE;
uint16_t nphdl = NIL_HANDLE;
if (tmd->cd_lflags & CDFL_ABORTED) {
isp_prt(isp, ISP_LOGTINFO, "[%llx] asking thread to terminate because it was marked aborted", (ull) tmd->cd_tagval);
isp_thread_event(isp, ISP_THREAD_TERMINATE, tmd, 0, __func__, __LINE__);
break;
}
ISP_LOCKU_SOFTC(isp);
if (isp_find_pdb_by_sid(isp, tmd->cd_channel, tmd->cd_portid, &lp)) {
if (!VALID_INI(lp->port_wwn)) {
if (lp->handle == NIL_HANDLE) {
/*
* Ooops- all we have is the port id.
*/
uint16_t nphdl, max;
isp_pdb_t pdb;
if (IS_24XX(isp)) {
max = NPH_MAX_2K;
} else {
max = NPH_MAX;
}
for (nphdl = 0; nphdl != max; nphdl++) {
if (isp_control(isp, ISPCTL_GET_PDB, tmd->cd_channel, nphdl, &pdb)) {
continue;
}
isp_prt(isp, ISP_LOGTINFO, "%s: nphdl 0x%04x has portid 0x%06x", __func__, nphdl, pdb.portid);
if (pdb.portid == tmd->cd_portid) {
lp->handle = nphdl;
break;
}
}
if (nphdl == max) {
ISP_UNLKU_SOFTC(isp);
isp_prt(isp, ISP_LOGTINFO, "[0x%llx] asking thread to terminate cmd [0x%02x] because because we can't find the N-Port handle", (ull) tmd->cd_tagval, tmd->cd_cdb[0] & 0xff);
isp_tgt_dump_pdb(isp, tmd->cd_channel);
isp_thread_event(isp, ISP_THREAD_TERMINATE, tmd, 0, __func__, __LINE__);
break;
}
}
if (isp_control(isp, ISPCTL_GET_NAMES, tmd->cd_channel, lp->handle, NULL, &lp->port_wwn) == 0) {
nphdl = lp->handle;
iid = lp->port_wwn;
} else {
isp_prt(isp, ISP_LOGALL, "%s: Chan %d [0x%llx] failed to get name for handle 0x%02x for portid 0x%06x", __func__, tmd->cd_channel, (ull) tmd->cd_tagval, lp->handle, tmd->cd_portid);
}
} else {
nphdl = lp->handle;
iid = lp->port_wwn;
}
} else {
/*
* If it's no longer in the port database, then some event between the receipt of the command and now
* has cleared it out. The command is probably already dead due to initiator port logout.
*/
ISP_UNLKU_SOFTC(isp);
isp_prt(isp, ISP_LOGTINFO, "[0x%llx] asking thread to terminate cmd [0x%02x] because PortID 0x%06x no longer in port database", (ull) tmd->cd_tagval, tmd->cd_cdb[0] & 0xff, tmd->cd_portid);
isp_tgt_dump_pdb(isp, tmd->cd_channel);
isp_thread_event(isp, ISP_THREAD_TERMINATE, tmd, 0, __func__, __LINE__);
break;
}
if (iid == INI_NONE) {
isp_prt(isp, ISP_LOGTDEBUG0, "%s: [0x%llx] trying to find IID again...", __func__, (ull) tmd->cd_tagval);
tmd->cd_next = isp->isp_osinfo.waiting_t;
isp->isp_osinfo.waiting_t = tmd;
tmd->cd_lastoff = 0;
ISP_UNLKU_SOFTC(isp);
break;
}
tmd->cd_tgt = FCPARAM(isp, tmd->cd_channel)->isp_wwpn;
tmd->cd_nphdl = nphdl;
tmd->cd_iid = iid;
isp_prt(isp, ISP_LOGTINFO, "%s: [0x%llx] Chan %d found initiator @ IID 0x%016llx N-Port Handle 0x%02x Port ID 0x%06x", __func__,
(ull) tmd->cd_tagval, tmd->cd_channel, (ull)tmd->cd_iid, tmd->cd_nphdl, tmd->cd_portid);
CALL_PARENT_TMD(isp, tmd, QOUT_TMD_START);
ISP_UNLKU_SOFTC(isp);
isp_tgt_tq(isp);
break;
}
case ISP_THREAD_FINDPORTID:
{
tmd_cmd_t *tmd = tap->arg;
fcportdb_t *lp;
ISP_LOCKU_SOFTC(isp);
if (isp_find_pdb_by_loopid(isp, tmd->cd_channel, tmd->cd_nphdl, &lp)) {
if (lp->portid == PORT_NONE) {
isp_pdb_t pdb;
if (isp_control(isp, ISPCTL_GET_PDB, tmd->cd_channel, tmd->cd_nphdl, &pdb) == 0) {
tmd->cd_portid = lp->portid = pdb.portid;
}
} else {
tmd->cd_portid = lp->portid;
}
} else {
isp_prt(isp, ISP_LOGTINFO, "[0x%llx] not in port database at all any more", (ull) tmd->cd_tagval);
}
if (tmd->cd_portid != PORT_NONE) {
isp_prt(isp, ISP_LOGTINFO, "%s: [0x%llx] Chan %d found initiator @ IID 0x%016llx N-Port Handle 0x%02x Port ID 0x%06x", __func__,
(ull) tmd->cd_tagval, tmd->cd_channel, (ull)tmd->cd_iid, tmd->cd_nphdl, tmd->cd_portid);
}
CALL_PARENT_TMD(isp, tmd, QOUT_TMD_START);
ISP_UNLKU_SOFTC(isp);
isp_tgt_tq(isp);
break;
}
case ISP_THREAD_TERMINATE:
{
fcportdb_t *lp;
tmd_cmd_t *tmd = tap->arg;
ISP_LOCKU_SOFTC(isp);
if (isp_find_pdb_by_sid(isp, tmd->cd_channel, tmd->cd_portid, &lp)) {
tmd->cd_iid = lp->port_wwn;
tmd->cd_nphdl = lp->handle;
CALL_PARENT_TMD(isp, tmd, QOUT_TMD_START);
ISP_UNLKU_SOFTC(isp);
isp_tgt_tq(isp);
isp_prt(isp, ISP_LOGINFO, "Chan %d [%llx] reprieved", tmd->cd_channel, (ull) tmd->cd_tagval);
break;
}
isp_prt(isp, ISP_LOGTINFO, "%s now terminating [%llx] from 0x%06x", __func__, (ull) tmd->cd_tagval, tmd->cd_portid);
if (isp_terminate_cmd(isp, tmd)) {
ISP_UNLKU_SOFTC(isp);
isp_thread_event(isp, ISP_THREAD_TERMINATE, tmd, 0, __func__, __LINE__);
break;
}
tmd->cd_next = NULL;
if (isp->isp_osinfo.tfreelist) {
isp->isp_osinfo.bfreelist->cd_next = tmd;
} else {
isp->isp_osinfo.tfreelist = tmd;
}
isp->isp_osinfo.bfreelist = tmd;
ISP_UNLKU_SOFTC(isp);
break;
}
case ISP_THREAD_RESTART_AT7:
{
at7_entry_t at;
tmd_cmd_t *tmd = tap->arg;
memcpy(&at, tmd, sizeof (at7_entry_t));
ISP_LOCKU_SOFTC(isp);
memset(tmd, 0, sizeof (tmd_cmd_t));
if (isp->isp_osinfo.tfreelist) {
isp->isp_osinfo.bfreelist->cd_next = tmd;
} else {
isp->isp_osinfo.tfreelist = tmd;
}
isp->isp_osinfo.bfreelist = tmd; /* remember to move the list tail pointer */
isp_handle_platform_atio7(isp, &at);
ISP_UNLKU_SOFTC(isp);
break;
}
case ISP_THREAD_FC_PUTBACK:
{
tmd_cmd_t *tmd = tap->arg;
ISP_LOCKU_SOFTC(isp);
isp_prt(isp, ISP_LOGTINFO, "%s: [%llx] calling putback", __func__, (ull) tmd->cd_tagval);
if (isp_target_putback_atio(isp, tmd)) {
ISP_UNLKU_SOFTC(isp);
isp_thread_event(isp, ISP_THREAD_FC_PUTBACK, tmd, 0, __func__, __LINE__);
break;
}
if (tmd->cd_lflags & CDFL_NEED_CLNUP) {
tmd->cd_lflags ^= CDFL_NEED_CLNUP;
isp_prt(isp, ISP_LOGTINFO, "Terminating %llx too", (ull) tmd->cd_tagval);
(void) isp_terminate_cmd(isp, tmd);
}
memset(tmd, 0, sizeof (tmd_cmd_t));
if (isp->isp_osinfo.tfreelist) {
isp->isp_osinfo.bfreelist->cd_next = tmd;
} else {
isp->isp_osinfo.tfreelist = tmd;
}
isp->isp_osinfo.bfreelist = tmd; /* remember to move the list tail pointer */
isp_prt(isp, ISP_LOGTDEBUG0, "DONE freeing tmd %p [%llx] after retry", tmd, (ull) tmd->cd_tagval);
ISP_UNLKU_SOFTC(isp);
break;
}
#endif
default:
break;
}
tap->done = 0;
if (tap->waiting) {
isp_prt(isp, ISP_LOGDEBUG0, "isp_task_thread signalling");
tap->waiting = 0;
wake_up(&tap->thread_waiter);
spin_lock_irqsave(&isp->isp_osinfo.tlock, flags);
} else {
spin_lock_irqsave(&isp->isp_osinfo.tlock, flags);
tap->next = isp->isp_osinfo.t_free;
isp->isp_osinfo.t_free = tap;
}
}
spin_unlock_irqrestore(&isp->isp_osinfo.tlock, flags);
}
isp_prt(isp, ISP_LOGDEBUG0, "isp_task_thread exiting");
return (0);
}
void
isp_prt(ispsoftc_t *isp, int level, const char *fmt, ...)
{
char buf[256];
char *prefl;
va_list ap;
if (level != ISP_LOGALL && (level & isp->isp_dblev) == 0) {
return;
}
if (level & (ISP_LOGTINFO|ISP_LOGINFO|ISP_LOGCONFIG|ISP_LOGSANCFG)) {
prefl = KERN_INFO "%s: ";
} else if (level & ISP_LOGWARN) {
prefl = KERN_WARNING "%s: ";
} else if (level & ISP_LOGERR) {
prefl = KERN_ERR "%s: ";
} else if (level & (ISP_LOGTDEBUG0|ISP_LOGTDEBUG1|ISP_LOGTDEBUG2)) {
prefl = KERN_DEBUG "%s: ";
} else if (level & (ISP_LOGDEBUG0|ISP_LOGDEBUG1|ISP_LOGDEBUG2|ISP_LOGDEBUG3)) {
prefl = KERN_DEBUG "%s: ";
} else {
prefl = "%s: ";
}
printk(prefl, isp->isp_name);
va_start(ap, fmt);
vsnprintf(buf, sizeof (buf), fmt, ap);
va_end(ap);
printk("%s\n", buf);
}
#ifndef ISP_LICENSE
#define ISP_LICENSE "GPL"
#endif
#ifdef MODULE
#ifdef MODULE_LICENSE
MODULE_LICENSE( ISP_LICENSE );
#endif
module_param(isp_debug, int, 0);
module_param(isp_disable, int, 0);
module_param(isp_nonvram, int, 0);
module_param(isp_nofwreload, int, 0);
module_param(isp_maxluns, int, 0);
module_param(isp_throttle, int, 0);
module_param(isp_cmd_per_lun, int, 0);
module_param(isp_maxsectors, int, 0);
module_param(isp_fcduplex, int, 0);
module_param(isp_nport_only, int, 0);
module_param(isp_loop_only, int, 0);
module_param(isp_deadloop_time, int, 0);
module_param(isp_fc_id, int, 0);
module_param(isp_spi_id, int, 0);
module_param(isp_own_id, int, 0);
module_param(isp_default_frame_size, int, 0);
module_param(isp_default_exec_throttle, int, 0);
module_param(isp_roles, charp, 0);
module_param(isp_wwpns, charp, 0);
module_param(isp_wwnns, charp, 0);
module_param(isp_vports, int, 0);
#else
static int __init isp_roleinit(char *str)
{
isp_roles = str;
return 0;
}
__setup("isp_roles=", isp_roleinit);
#endif
static struct scsi_host_template driver_template = {
.name = ISP_NAME,
.module = THIS_MODULE,
.info = isplinux_info,
.queuecommand = isplinux_queuecommand,
.eh_abort_handler = isplinux_abort,
.eh_device_reset_handler = isplinux_bdr,
.eh_bus_reset_handler = isplinux_sreset,
.eh_host_reset_handler = isplinux_hreset,
.slave_configure = isplinux_slave_configure,
.bios_param = isplinux_biosparam,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,28)
.eh_timed_out = isplinux_eh_timed_out,
#endif
#if defined(CONFIG_PROC_FS)
.proc_info = isplinux_proc_info,
.proc_name = ISP_NAME,
#endif
.can_queue = 1,
.sg_tablesize = SG_ALL,
.use_clustering = ENABLE_CLUSTERING
};
struct scsi_host_template *isp_template = &driver_template;
/*
* vim:ts=4:sw=4:expandtab
*/