mirror of
https://github.com/SCST-project/scst.git
synced 2026-05-14 09:11:27 +00:00
and to allow some debug and enhancements that will be easier soon. git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@679 d57e44dd-8a1f-0410-8b47-8ef2f437770f
4978 lines
166 KiB
C
4978 lines
166 KiB
C
/* $Id: isp_linux.c,v 1.241 2009/02/13 23:58:38 mjacob Exp $ */
|
|
/*
|
|
* Copyright (c) 1997-2008 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 inline tmd_cmd_t *isp_find_tmd(ispsoftc_t *, uint64_t);
|
|
static void isp_add_wwn_entry(ispsoftc_t *, int, uint64_t, uint16_t, uint32_t);
|
|
static void isp_del_wwn_entry(ispsoftc_t *, int, uint64_t, uint16_t, uint32_t);
|
|
static inline int isp_find_pdb_by_loopid(ispsoftc_t *, int, uint32_t, fcportdb_t **);
|
|
static inline int isp_find_pdb_by_sid(ispsoftc_t *, int, uint32_t, fcportdb_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';
|
|
}
|
|
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");
|
|
}
|
|
}
|
|
|
|
static 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.
|
|
*/
|
|
if (Cmnd->eh_timeout.function) {
|
|
mod_timer(&Cmnd->eh_timeout, jiffies + Cmnd->timeout_per_command);
|
|
}
|
|
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.
|
|
*/
|
|
if (Cmnd->eh_timeout.function) {
|
|
del_timer(&Cmnd->eh_timeout);
|
|
}
|
|
}
|
|
|
|
static 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 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 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) {
|
|
if (f->eh_timeout.function) {
|
|
mod_timer(&f->eh_timeout, jiffies + f->timeout_per_command);
|
|
}
|
|
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 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.
|
|
*/
|
|
if (Cmnd->eh_timeout.function) {
|
|
mod_timer(&Cmnd->eh_timeout, jiffies + Cmnd->timeout_per_command);
|
|
}
|
|
ISP_LOCK_SCSI_DONE(isp);
|
|
(*Cmnd->scsi_done)(Cmnd);
|
|
ISP_UNLK_SCSI_DONE(isp);
|
|
} while ((Cmnd = Ncmnd) != NULL);
|
|
ISP_IGET_LK_SOFTC(isp);
|
|
}
|
|
|
|
static 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 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);
|
|
|
|
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 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_add_wwn_entry(ispsoftc_t *isp, int chan, uint64_t ini, uint16_t nphdl, uint32_t s_id)
|
|
{
|
|
fcparam *fcp;
|
|
fcportdb_t *lp;
|
|
int i;
|
|
|
|
/*
|
|
* Make sure the addition of a new target mode entry doesn't duplicate entries
|
|
* with the same N-Port handles, the same portids or the same Port WWN.
|
|
*/
|
|
fcp = FCPARAM(isp, chan);
|
|
for (i = 0; i < MAX_FC_TARG; i++) {
|
|
lp = &fcp->portdb[i];
|
|
|
|
if (lp->target_mode == 0) {
|
|
continue;
|
|
}
|
|
if (nphdl != NIL_HANDLE && lp->handle == nphdl) {
|
|
break;
|
|
}
|
|
if (s_id != PORT_NONE && lp->portid == s_id) {
|
|
break;
|
|
}
|
|
if (VALID_INI(ini) && lp->port_wwn == ini) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i < MAX_FC_TARG) {
|
|
if (lp->portid != s_id || (VALID_INI(lp->port_wwn) && lp->port_wwn != ini)) {
|
|
i = ISP_LOGWARN;
|
|
} else {
|
|
i = ISP_LOGTINFO;
|
|
}
|
|
if (lp->portid == s_id && VALID_INI(lp->port_wwn) && (lp->port_wwn == ini || ini == INI_NONE) && lp->handle == nphdl) {
|
|
isp_prt(isp, i, "%s: Chan %d IID 0x%016llx N-Port Handle 0x%02x Port ID 0x%06x reentered", __func__, chan,
|
|
(ull) lp->port_wwn, lp->handle, lp->portid);
|
|
} else {
|
|
isp_prt(isp, i, "%s: Chan %d IID 0x%016llx N-Port Handle 0x%02x Port ID 0x%06x overwrites IID 0x%016llx N-Port Handle 0x%02x Port Id 0x%06x",
|
|
__func__, chan, (ull) ini, nphdl, s_id, (ull) lp->port_wwn, lp->handle, lp->portid);
|
|
}
|
|
lp->portid = s_id;
|
|
if (VALID_INI(ini)) {
|
|
lp->port_wwn = ini;
|
|
}
|
|
lp->handle = nphdl;
|
|
return;
|
|
}
|
|
while (--i >= 0) {
|
|
if (i >= FL_ID && i <= SNS_ID) {
|
|
continue;
|
|
}
|
|
if (fcp->portdb[i].target_mode == 1) {
|
|
continue;
|
|
}
|
|
if (fcp->portdb[i].state == FC_PORTDB_STATE_NIL) {
|
|
break;
|
|
}
|
|
}
|
|
if (i < 0) {
|
|
isp_prt(isp, ISP_LOGWARN, "%s: Chan %d IID 0x%016llx N-Port Handle 0x%02x Port ID 0x%06x- no room in port database",
|
|
__func__, chan, (ull) ini, nphdl, s_id);
|
|
return;
|
|
}
|
|
lp = &fcp->portdb[i];
|
|
memset(lp, 0, sizeof (fcportdb_t));
|
|
lp->target_mode = 1;
|
|
lp->handle = nphdl;
|
|
lp->portid = s_id;
|
|
lp->port_wwn = ini;
|
|
isp_prt(isp, ISP_LOGTINFO, "%s: Chan %d IID 0x%016llx N-Port Handle 0x%02x Port ID 0x%06x added",
|
|
__func__, chan, (ull) ini, nphdl, s_id);
|
|
}
|
|
|
|
static void
|
|
isp_del_wwn_entry(ispsoftc_t *isp, int chan, uint64_t ini, uint16_t nphdl, uint32_t s_id)
|
|
{
|
|
fcparam *fcp;
|
|
fcportdb_t *lp;
|
|
int i;
|
|
fcp = FCPARAM(isp, chan);
|
|
for (i = 0; i < MAX_FC_TARG; i++) {
|
|
lp = &fcp->portdb[i];
|
|
if (lp->target_mode == 0) {
|
|
continue;
|
|
}
|
|
if (ini == INI_ANY && nphdl == NIL_HANDLE && s_id == PORT_ANY) {
|
|
isp_prt(isp, ISP_LOGTINFO, "%s: Chan %d IID 0x%016llx N-Port Handle 0x%x Port ID 0x%06x cleared due to wildcard call",
|
|
__func__, chan, (ull) lp->port_wwn, lp->handle, lp->portid);
|
|
memset(&fcp->portdb[i], 0, sizeof (fcportdb_t));
|
|
continue;
|
|
}
|
|
if ((s_id == PORT_ANY || lp->portid == s_id) && lp->handle == nphdl) {
|
|
break;
|
|
}
|
|
}
|
|
if (ini == INI_ANY && nphdl == NIL_HANDLE && s_id == PORT_ANY) {
|
|
return;
|
|
}
|
|
if (i == MAX_FC_TARG) {
|
|
isp_prt(isp, ISP_LOGTINFO, "%s: Chan %d IID 0x%016llx N-Port Handle 0x%x Port ID 0x%06x cannot be found to be cleared",
|
|
__func__, chan, (ull) lp->port_wwn, nphdl, lp->portid);
|
|
} else {
|
|
isp_prt(isp, ISP_LOGTINFO, "%s: Chan %d IID 0x%016llx N-Port Handle 0x%x Port ID 0x%06x cleared",
|
|
__func__, chan, (ull) lp->port_wwn, nphdl, lp->portid);
|
|
memset(&fcp->portdb[i], 0, sizeof (fcportdb_t));
|
|
}
|
|
}
|
|
|
|
static inline int
|
|
isp_find_pdb_by_loopid(ispsoftc_t *isp, int chan, uint32_t loopid, fcportdb_t **lptr)
|
|
{
|
|
fcparam *fcp;
|
|
int i;
|
|
|
|
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;
|
|
}
|
|
if (lp->handle == loopid) {
|
|
*lptr = lp;
|
|
return (1);
|
|
}
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
static inline int
|
|
isp_find_pdb_by_sid(ispsoftc_t *isp, int chan, uint32_t sid, fcportdb_t **lptr)
|
|
{
|
|
fcparam *fcp;
|
|
int i;
|
|
|
|
if (chan >= isp->isp_nchan) {
|
|
return (0);
|
|
}
|
|
|
|
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;
|
|
}
|
|
if (lp->portid == sid) {
|
|
*lptr = lp;
|
|
return (1);
|
|
}
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
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;
|
|
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 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 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, "[%llx] already ABORTED- not sending a CTIO", (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, "CTIO, no data, and no status is wrong");
|
|
dump_stack();
|
|
xact->td_error = -EINVAL;
|
|
goto out;
|
|
}
|
|
} else {
|
|
if ((xact->td_hflags & TDFH_DATA_MASK) == 0) {
|
|
isp_prt(isp, ISP_LOGERR, "data CTIO with no direction is wrong");
|
|
dump_stack();
|
|
xact->td_error = -EINVAL;
|
|
goto out;
|
|
}
|
|
if ((xact->td_hflags & TDFH_DATA_MASK) == TDFH_DATA_MASK) {
|
|
isp_prt(isp, ISP_LOGERR, "data CTIO with both directions is wrong (for now)");
|
|
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, "[%llx] cdb0 0x%02x CHECK CONDITION but bogus sense 0x%x/0x%x/0x%x", (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, "CTIO7[%llx] scsi sts %x flags %x resid %d offset %u", (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;
|
|
}
|
|
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, "CTIO2[%llx] scsi sts %x flags %x resid %d", (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, "CTIO[%llx] scsi sts %x resid %d cd_lflags %x", (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, "isp_target_start_ctio: No XFLIST pointers");
|
|
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_NONE);
|
|
(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 %s", ctstr, (ull) tmd->cd_tagval, status, flags, 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
isp_fc_change_role(ispsoftc_t *isp, int chan, int new_role)
|
|
{
|
|
fcparam *fcp;
|
|
if (IS_SCSI(isp)) {
|
|
return (0);
|
|
}
|
|
if (chan >= isp->isp_nchan) {
|
|
isp_prt(isp, ISP_LOGWARN, "%s: bad channel %d", __func__, chan);
|
|
return (-ENXIO);
|
|
}
|
|
fcp = FCPARAM(isp, chan);
|
|
ISP_DATA(isp, chan)->blocked = 1;
|
|
if (chan == 0 || new_role == ISP_ROLE_NONE) {
|
|
SET_DEFAULT_ROLE(isp, chan, new_role);
|
|
isp_reinit(isp);
|
|
} else {
|
|
mbreg_t mbs;
|
|
vp_modify_t *vp;
|
|
uint8_t qe[QENTRY_LEN], *scp;
|
|
|
|
memset(qe, 0, QENTRY_LEN);
|
|
/* Acquire Scratch */
|
|
|
|
if (FC_SCRATCH_ACQUIRE(isp, chan)) {
|
|
ISP_DATA(isp, chan)->blocked = 0;
|
|
return (-EBUSY);
|
|
}
|
|
scp = fcp->isp_scratch;
|
|
|
|
/*
|
|
* Build a VP MODIFY command in memory
|
|
*/
|
|
vp = (vp_modify_t *) qe;
|
|
vp->vp_mod_hdr.rqs_entry_type = RQSTYPE_VP_MODIFY;
|
|
vp->vp_mod_hdr.rqs_entry_count = 1;
|
|
vp->vp_mod_cnt = 1;
|
|
vp->vp_mod_idx0 = chan;
|
|
vp->vp_mod_cmd = VP_MODIFY_ENA;
|
|
vp->vp_mod_ports[0].options = ICB2400_VPOPT_ENABLED;
|
|
if (new_role & ISP_ROLE_INITIATOR) {
|
|
vp->vp_mod_ports[0].options |= ICB2400_VPOPT_INI_ENABLE;
|
|
}
|
|
if ((new_role & ISP_ROLE_TARGET) == 0) {
|
|
vp->vp_mod_ports[0].options |= ICB2400_VPOPT_TGT_DISABLE;
|
|
}
|
|
MAKE_NODE_NAME_FROM_WWN(vp->vp_mod_ports[0].wwpn, fcp->isp_wwpn);
|
|
MAKE_NODE_NAME_FROM_WWN(vp->vp_mod_ports[0].wwnn, fcp->isp_wwnn);
|
|
isp_put_vp_modify(isp, vp, (vp_modify_t *) scp);
|
|
|
|
/*
|
|
* Build a EXEC IOCB A64 command that points to the VP MODIFY command
|
|
*/
|
|
memset(&mbs, 0, sizeof (mbs));
|
|
mbs.param[0] = MBOX_EXEC_COMMAND_IOCB_A64;
|
|
mbs.param[1] = QENTRY_LEN;
|
|
mbs.param[2] = DMA_WD1(fcp->isp_scdma);
|
|
mbs.param[3] = DMA_WD0(fcp->isp_scdma);
|
|
mbs.param[6] = DMA_WD3(fcp->isp_scdma);
|
|
mbs.param[7] = DMA_WD2(fcp->isp_scdma);
|
|
mbs.logval = MBLOGALL;
|
|
MEMORYBARRIER(isp, SYNC_SFORDEV, 0, 2 * QENTRY_LEN);
|
|
isp_control(isp, ISPCTL_RUN_MBOXCMD, &mbs);
|
|
if (mbs.param[0] != MBOX_COMMAND_COMPLETE) {
|
|
FC_SCRATCH_RELEASE(isp, chan);
|
|
ISP_DATA(isp, chan)->blocked = 0;
|
|
return (-EIO);
|
|
}
|
|
MEMORYBARRIER(isp, SYNC_SFORCPU, QENTRY_LEN, QENTRY_LEN);
|
|
isp_get_vp_modify(isp, (vp_modify_t *)&scp[QENTRY_LEN], vp);
|
|
|
|
/*
|
|
* Release Scratch
|
|
*/
|
|
FC_SCRATCH_RELEASE(isp, chan);
|
|
|
|
if (vp->vp_mod_status != VP_STS_OK) {
|
|
isp_prt(isp, ISP_LOGERR, "%s: VP_MODIFY of Chan %d failed with status %d", __func__, chan, vp->vp_mod_status);
|
|
ISP_DATA(isp, chan)->blocked = 0;
|
|
return (-EIO);
|
|
}
|
|
SET_DEFAULT_ROLE(isp, chan, new_role);
|
|
fcp->role = new_role;
|
|
}
|
|
ISP_DATA(isp, chan)->blocked = 0;
|
|
return (0);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
if (lp->dev_map_idx) {
|
|
unsigned long arg;
|
|
tgt = lp->dev_map_idx - 1;
|
|
isp_prt(isp, ISP_LOGCONFIG, prom2, bus, lp->portid, lp->handle, isp_class3_roles[lp->roles], "arrived at", tgt, (ull) lp->node_wwn, (ull) lp->port_wwn);
|
|
arg = tgt | (bus << 16);
|
|
isp_thread_event(isp, ISP_THREAD_SCSI_SCAN, (void *)arg, 0, __func__, __LINE__);
|
|
} else {
|
|
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);
|
|
}
|
|
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) {
|
|
unsigned long arg;
|
|
tgt = lp->dev_map_idx - 1;
|
|
fcp->isp_dev_map[tgt] = 0;
|
|
lp->state = FC_PORTDB_STATE_NIL;
|
|
lp->dev_map_idx = 0;
|
|
isp_prt(isp, ISP_LOGCONFIG, prom2, bus, lp->portid, lp->handle, isp_class3_roles[lp->roles], "departed", tgt, (ull) lp->node_wwn, (ull) lp->port_wwn);
|
|
arg = tgt | (bus << 16) | (1 << 31);
|
|
isp_thread_event(isp, ISP_THREAD_SCSI_SCAN, (void *)arg, 0, __func__, __LINE__);
|
|
} else if (lp->reserved == 0) {
|
|
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 (isp->isp_osinfo.hcb == 0) {
|
|
isp_prt(isp, ISP_LOGWARN, "ISPASYNC_TARGET_NOTIFY with target mode not enabled");
|
|
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");
|
|
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_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;
|
|
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;
|
|
} 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;
|
|
}
|
|
|
|
/*
|
|
* 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);
|
|
|
|
if (retval) {
|
|
isp_prt(isp, ISP_LOGERR, "failed to init HBA port (%d): skipping it", retval);
|
|
del_timer(&isp->isp_osinfo.timer);
|
|
isp->dogactive = 0;
|
|
ISP_UNLK_SOFTC(isp);
|
|
#ifdef ISP_TARGET_MODE
|
|
isp_deinit_target(isp);
|
|
#endif
|
|
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 maxluns = isp_maxluns;
|
|
|
|
isp_reset(isp);
|
|
|
|
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->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 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:
|
|
{
|
|
unsigned long arg = (unsigned long) tap->arg;
|
|
int tgt, chan, rescan;
|
|
tgt = arg & 0xffff;
|
|
chan = (arg >> 16) & 0xff;
|
|
rescan = (arg >> 31) & 1;
|
|
if (rescan == 0) {
|
|
scsi_scan_target(&isp->isp_osinfo.host->shost_gendev, chan, tgt, 0, rescan);
|
|
} else {
|
|
struct scsi_device *sdev;
|
|
sdev = scsi_device_lookup(isp->isp_osinfo.host, 0, tgt, 0);
|
|
if (sdev) {
|
|
scsi_remove_device(sdev);
|
|
scsi_device_put(sdev);
|
|
}
|
|
}
|
|
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);
|
|
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 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
|
|
*/
|