kernel-ark/drivers/scsi/cpqfcTSworker.c
be7db055dd [PATCH] remove old scsi data direction macros
these have been wrappers for the generic dma direction bits since 2.5.x.
This patch converts the few remaining drivers and removes the macros.

Arjan noticed there's some hunk in here that shouldn't.  Updated patch
below:

Signed-off-by: James Bottomley <James.Bottomley@SteelEye.com>
2005-04-18 13:49:58 -05:00

6517 lines
207 KiB
C

/* Copyright(c) 2000, Compaq Computer Corporation
* Fibre Channel Host Bus Adapter
* 64-bit, 66MHz PCI
* Originally developed and tested on:
* (front): [chip] Tachyon TS HPFC-5166A/1.2 L2C1090 ...
* SP# P225CXCBFIEL6T, Rev XC
* SP# 161290-001, Rev XD
* (back): Board No. 010008-001 A/W Rev X5, FAB REV X5
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2, or (at your option) any
* later version.
*
* 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.
* Written by Don Zimmerman
*/
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/ioport.h>
#include <linux/kernel.h>
#include <linux/stat.h>
#include <linux/blkdev.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/smp_lock.h>
#include <linux/pci.h>
#define SHUTDOWN_SIGS (sigmask(SIGKILL)|sigmask(SIGINT)|sigmask(SIGTERM))
#include <asm/system.h>
#include <asm/irq.h>
#include <asm/dma.h>
#include "scsi.h"
#include <scsi/scsi_host.h> // struct Scsi_Host definition for T handler
#include "cpqfcTSchip.h"
#include "cpqfcTSstructs.h"
#include "cpqfcTStrigger.h"
//#define LOGIN_DBG 1
// REMARKS:
// Since Tachyon chips may be permitted to wait from 500ms up to 2 sec
// to empty an outgoing frame from its FIFO to the Fibre Channel stream,
// we cannot do everything we need to in the interrupt handler. Specifically,
// every time a link re-init (e.g. LIP) takes place, all SCSI I/O has to be
// suspended until the login sequences have been completed. Login commands
// are frames just like SCSI commands are frames; they are subject to the same
// timeout issues and delays. Also, various specs provide up to 2 seconds for
// devices to log back in (i.e. respond with ACC to a login frame), so I/O to
// that device has to be suspended.
// A serious problem here occurs on highly loaded FC-AL systems. If our FC port
// has a low priority (e.g. high arbitrated loop physical address, alpa), and
// some other device is hogging bandwidth (permissible under FC-AL), we might
// time out thinking the link is hung, when it's simply busy. Many such
// considerations complicate the design. Although Tachyon assumes control
// (in silicon) for many link-specific issues, the Linux driver is left with the
// rest, which turns out to be a difficult, time critical chore.
// These "worker" functions will handle things like FC Logins; all
// processes with I/O to our device must wait for the Login to complete
// and (if successful) I/O to resume. In the event of a malfunctioning or
// very busy loop, it may take hundreds of millisecs or even seconds to complete
// a frame send. We don't want to hang up the entire server (and all
// processes which don't depend on Fibre) during this wait.
// The Tachyon chip can have around 30,000 I/O operations ("exchanges")
// open at one time. However, each exchange must be initiated
// synchronously (i.e. each of the 30k I/O had to be started one at a
// time by sending a starting frame via Tachyon's outbound que).
// To accommodate kernel "module" build, this driver limits the exchanges
// to 256, because of the contiguous physical memory limitation of 128M.
// Typical FC Exchanges are opened presuming the FC frames start without errors,
// while Exchange completion is handled in the interrupt handler. This
// optimizes performance for the "everything's working" case.
// However, when we have FC related errors or hot plugging of FC ports, we pause
// I/O and handle FC-specific tasks in the worker thread. These FC-specific
// functions will handle things like FC Logins and Aborts. As the Login sequence
// completes to each and every target, I/O can resume to that target.
// Our kernel "worker thread" must share the HBA with threads calling
// "queuecommand". We define a "BoardLock" semaphore which indicates
// to "queuecommand" that the HBA is unavailable, and Cmnds are added to a
// board lock Q. When the worker thread finishes with the board, the board
// lock Q commands are completed with status causing immediate retry.
// Typically, the board is locked while Logins are in progress after an
// FC Link Down condition. When Cmnds are re-queued after board lock, the
// particular Scsi channel/target may or may not have logged back in. When
// the device is waiting for login, the "prli" flag is clear, in which case
// commands are passed to a Link Down Q. Whenever the login finally completes,
// the LinkDown Q is completed, again with status causing immediate retry.
// When FC devices are logged in, we build and start FC commands to the
// devices.
// NOTE!! As of May 2000, kernel 2.2.14, the error recovery logic for devices
// that never log back in (e.g. physically removed) is NOT completely
// understood. I've still seen instances of system hangs on failed Write
// commands (possibly from the ext2 layer?) on device removal. Such special
// cases need to be evaluated from a system/application view - e.g., how
// exactly does the system want me to complete commands when the device is
// physically removed??
// local functions
static void SetLoginFields(
PFC_LOGGEDIN_PORT pLoggedInPort,
TachFCHDR_GCMND* fchs,
BOOLEAN PDisc,
BOOLEAN Originator);
static void AnalyzeIncomingFrame(
CPQFCHBA *cpqfcHBAdata,
ULONG QNdx );
static void SendLogins( CPQFCHBA *cpqfcHBAdata, __u32 *FabricPortIds );
static int verify_PLOGI( PTACHYON fcChip,
TachFCHDR_GCMND* fchs, ULONG* reject_explain);
static int verify_PRLI( TachFCHDR_GCMND* fchs, ULONG* reject_explain);
static void LoadWWN( PTACHYON fcChip, UCHAR* dest, UCHAR type);
static void BuildLinkServicePayload(
PTACHYON fcChip, ULONG type, void* payload);
static void UnblockScsiDevice( struct Scsi_Host *HostAdapter,
PFC_LOGGEDIN_PORT pLoggedInPort);
static void cpqfcTSCheckandSnoopFCP( PTACHYON fcChip, ULONG x_ID);
static void CompleteBoardLockCmnd( CPQFCHBA *cpqfcHBAdata);
static void RevalidateSEST( struct Scsi_Host *HostAdapter,
PFC_LOGGEDIN_PORT pLoggedInPort);
static void IssueReportLunsCommand(
CPQFCHBA* cpqfcHBAdata,
TachFCHDR_GCMND* fchs);
// (see scsi_error.c comments on kernel task creation)
void cpqfcTSWorkerThread( void *host)
{
struct Scsi_Host *HostAdapter = (struct Scsi_Host*)host;
CPQFCHBA *cpqfcHBAdata = (CPQFCHBA *)HostAdapter->hostdata;
#ifdef PCI_KERNEL_TRACE
PTACHYON fcChip = &cpqfcHBAdata->fcChip;
#endif
DECLARE_MUTEX_LOCKED(fcQueReady);
DECLARE_MUTEX_LOCKED(fcTYOBcomplete);
DECLARE_MUTEX_LOCKED(TachFrozen);
DECLARE_MUTEX_LOCKED(BoardLock);
ENTER("WorkerThread");
lock_kernel();
daemonize("cpqfcTS_wt_%d", HostAdapter->host_no);
siginitsetinv(&current->blocked, SHUTDOWN_SIGS);
cpqfcHBAdata->fcQueReady = &fcQueReady; // primary wait point
cpqfcHBAdata->TYOBcomplete = &fcTYOBcomplete;
cpqfcHBAdata->TachFrozen = &TachFrozen;
cpqfcHBAdata->worker_thread = current;
unlock_kernel();
if( cpqfcHBAdata->notify_wt != NULL )
up( cpqfcHBAdata->notify_wt); // OK to continue
while(1)
{
unsigned long flags;
down_interruptible( &fcQueReady); // wait for something to do
if (signal_pending(current) )
break;
PCI_TRACE( 0x90)
// first, take the IO lock so the SCSI upper layers can't call
// into our _quecommand function (this also disables INTs)
spin_lock_irqsave( HostAdapter->host_lock, flags); // STOP _que function
PCI_TRACE( 0x90)
CPQ_SPINLOCK_HBA( cpqfcHBAdata)
// next, set this pointer to indicate to the _quecommand function
// that the board is in use, so it should que the command and
// immediately return (we don't actually require the semaphore function
// in this driver rev)
cpqfcHBAdata->BoardLock = &BoardLock;
PCI_TRACE( 0x90)
// release the IO lock (and re-enable interrupts)
spin_unlock_irqrestore( HostAdapter->host_lock, flags);
// disable OUR HBA interrupt (keep them off as much as possible
// during error recovery)
disable_irq( cpqfcHBAdata->HostAdapter->irq);
// OK, let's process the Fibre Channel Link Q and do the work
cpqfcTS_WorkTask( HostAdapter);
// hopefully, no more "work" to do;
// re-enable our INTs for "normal" completion processing
enable_irq( cpqfcHBAdata->HostAdapter->irq);
cpqfcHBAdata->BoardLock = NULL; // allow commands to be queued
CPQ_SPINUNLOCK_HBA( cpqfcHBAdata)
// Now, complete any Cmnd we Q'd up while BoardLock was held
CompleteBoardLockCmnd( cpqfcHBAdata);
}
// hopefully, the signal was for our module exit...
if( cpqfcHBAdata->notify_wt != NULL )
up( cpqfcHBAdata->notify_wt); // yep, we're outta here
}
// Freeze Tachyon routine.
// If Tachyon is already frozen, return FALSE
// If Tachyon is not frozen, call freeze function, return TRUE
//
static BOOLEAN FreezeTach( CPQFCHBA *cpqfcHBAdata)
{
PTACHYON fcChip = &cpqfcHBAdata->fcChip;
BOOLEAN FrozeTach = FALSE;
// It's possible that the chip is already frozen; if so,
// "Freezing" again will NOT! generate another Freeze
// Completion Message.
if( (fcChip->Registers.TYstatus.value & 0x70000) != 0x70000)
{ // (need to freeze...)
fcChip->FreezeTachyon( fcChip, 2); // both ERQ and FCP assists
// 2. Get Tach freeze confirmation
// (synchronize SEST manipulation with Freeze Completion Message)
// we need INTs on so semaphore can be set.
enable_irq( cpqfcHBAdata->HostAdapter->irq); // only way to get Semaphore
down_interruptible( cpqfcHBAdata->TachFrozen); // wait for INT handler sem.
// can we TIMEOUT semaphore wait?? TBD
disable_irq( cpqfcHBAdata->HostAdapter->irq);
FrozeTach = TRUE;
} // (else, already frozen)
return FrozeTach;
}
// This is the kernel worker thread task, which processes FC
// tasks which were queued by the Interrupt handler or by
// other WorkTask functions.
#define DBG 1
//#undef DBG
void cpqfcTS_WorkTask( struct Scsi_Host *HostAdapter)
{
CPQFCHBA *cpqfcHBAdata = (CPQFCHBA *)HostAdapter->hostdata;
PTACHYON fcChip = &cpqfcHBAdata->fcChip;
FC_EXCHANGES *Exchanges = fcChip->Exchanges;
ULONG QconsumerNdx;
LONG ExchangeID;
ULONG ulStatus=0;
TachFCHDR_GCMND fchs;
PFC_LINK_QUE fcLQ = cpqfcHBAdata->fcLQ;
ENTER("WorkTask");
// copy current index to work on
QconsumerNdx = fcLQ->consumer;
PCI_TRACEO( fcLQ->Qitem[QconsumerNdx].Type, 0x90)
// NOTE: when this switch completes, we will "consume" the Que item
// printk("Que type %Xh\n", fcLQ->Qitem[QconsumerNdx].Type);
switch( fcLQ->Qitem[QconsumerNdx].Type )
{
// incoming frame - link service (ACC, UNSOL REQ, etc.)
// or FCP-SCSI command
case SFQ_UNKNOWN:
AnalyzeIncomingFrame( cpqfcHBAdata, QconsumerNdx );
break;
case EXCHANGE_QUEUED: // an Exchange (i.e. FCP-SCSI) was previously
// Queued because the link was down. The
// heartbeat timer detected it and Queued it here.
// We attempt to start it again, and if
// successful we clear the EXCHANGE_Q flag.
// If the link doesn't come up, the Exchange
// will eventually time-out.
ExchangeID = (LONG) // x_ID copied from DPC timeout function
fcLQ->Qitem[QconsumerNdx].ulBuff[0];
// It's possible that a Q'd exchange could have already
// been started by other logic (e.g. ABTS process)
// Don't start if already started (Q'd flag clear)
if( Exchanges->fcExchange[ExchangeID].status & EXCHANGE_QUEUED )
{
// printk(" *Start Q'd x_ID %Xh: type %Xh ",
// ExchangeID, Exchanges->fcExchange[ExchangeID].type);
ulStatus = cpqfcTSStartExchange( cpqfcHBAdata, ExchangeID);
if( !ulStatus )
{
// printk("success* ");
}
else
{
#ifdef DBG
if( ulStatus == EXCHANGE_QUEUED)
printk("Queued* ");
else
printk("failed* ");
#endif
}
}
break;
case LINKDOWN:
// (lots of things already done in INT handler) future here?
break;
case LINKACTIVE: // Tachyon set the Lup bit in FM status
// NOTE: some misbehaving FC ports (like Tach2.1)
// can re-LIP immediately after a LIP completes.
// if "initiator", need to verify LOGs with ports
// printk("\n*LNKUP* ");
if( fcChip->Options.initiator )
SendLogins( cpqfcHBAdata, NULL ); // PLOGI or PDISC, based on fcPort data
// if SendLogins successfully completes, PortDiscDone
// will be set.
// If SendLogins was successful, then we expect to get incoming
// ACCepts or REJECTs, which are handled below.
break;
// LinkService and Fabric request/reply processing
case ELS_FDISC: // need to send Fabric Discovery (Login)
case ELS_FLOGI: // need to send Fabric Login
case ELS_SCR: // need to send State Change Registration
case FCS_NSR: // need to send Name Service Request
case ELS_PLOGI: // need to send PLOGI
case ELS_ACC: // send generic ACCept
case ELS_PLOGI_ACC: // need to send ELS ACCept frame to recv'd PLOGI
case ELS_PRLI_ACC: // need to send ELS ACCept frame to recv'd PRLI
case ELS_LOGO: // need to send ELS LOGO (logout)
case ELS_LOGO_ACC: // need to send ELS ACCept frame to recv'd PLOGI
case ELS_RJT: // ReJecT reply
case ELS_PRLI: // need to send ELS PRLI
// printk(" *ELS %Xh* ", fcLQ->Qitem[QconsumerNdx].Type);
// if PortDiscDone is not set, it means the SendLogins routine
// failed to complete -- assume that LDn occurred, so login frames
// are invalid
if( !cpqfcHBAdata->PortDiscDone) // cleared by LDn
{
printk("Discard Q'd ELS login frame\n");
break;
}
ulStatus = cpqfcTSBuildExchange(
cpqfcHBAdata,
fcLQ->Qitem[QconsumerNdx].Type, // e.g. PLOGI
(TachFCHDR_GCMND*)
fcLQ->Qitem[QconsumerNdx].ulBuff, // incoming fchs
NULL, // no data (no scatter/gather list)
&ExchangeID );// fcController->fcExchanges index, -1 if failed
if( !ulStatus ) // Exchange setup?
{
ulStatus = cpqfcTSStartExchange( cpqfcHBAdata, ExchangeID );
if( !ulStatus )
{
// submitted to Tach's Outbound Que (ERQ PI incremented)
// waited for completion for ELS type (Login frames issued
// synchronously)
}
else
// check reason for Exchange not being started - we might
// want to Queue and start later, or fail with error
{
}
}
else // Xchange setup failed...
printk(" cpqfcTSBuildExchange failed: %Xh\n", ulStatus );
break;
case SCSI_REPORT_LUNS:
// pass the incoming frame (actually, it's a PRLI frame)
// so we can send REPORT_LUNS, in order to determine VSA/PDU
// FCP-SCSI Lun address mode
IssueReportLunsCommand( cpqfcHBAdata, (TachFCHDR_GCMND*)
fcLQ->Qitem[QconsumerNdx].ulBuff);
break;
case BLS_ABTS: // need to ABORT one or more exchanges
{
LONG x_ID = fcLQ->Qitem[QconsumerNdx].ulBuff[0];
BOOLEAN FrozeTach = FALSE;
if ( x_ID >= TACH_SEST_LEN ) // (in)sanity check
{
// printk( " cpqfcTS ERROR! BOGUS x_ID %Xh", x_ID);
break;
}
if( Exchanges->fcExchange[ x_ID].Cmnd == NULL ) // should be RARE
{
// printk(" ABTS %Xh Scsi Cmnd null! ", x_ID);
break; // nothing to abort!
}
//#define ABTS_DBG
#ifdef ABTS_DBG
printk("INV SEST[%X] ", x_ID);
if( Exchanges->fcExchange[x_ID].status & FC2_TIMEOUT)
{
printk("FC2TO");
}
if( Exchanges->fcExchange[x_ID].status & INITIATOR_ABORT)
{
printk("IA");
}
if( Exchanges->fcExchange[x_ID].status & PORTID_CHANGED)
{
printk("PORTID");
}
if( Exchanges->fcExchange[x_ID].status & DEVICE_REMOVED)
{
printk("DEVRM");
}
if( Exchanges->fcExchange[x_ID].status & LINKFAIL_TX)
{
printk("LKF");
}
if( Exchanges->fcExchange[x_ID].status & FRAME_TO)
{
printk("FRMTO");
}
if( Exchanges->fcExchange[x_ID].status & ABORTSEQ_NOTIFY)
{
printk("ABSQ");
}
if( Exchanges->fcExchange[x_ID].status & SFQ_FRAME)
{
printk("SFQFR");
}
if( Exchanges->fcExchange[ x_ID].type == 0x2000)
printk(" WR");
else if( Exchanges->fcExchange[ x_ID].type == 0x3000)
printk(" RD");
else if( Exchanges->fcExchange[ x_ID].type == 0x10)
printk(" ABTS");
else
printk(" %Xh", Exchanges->fcExchange[ x_ID].type);
if( !(Exchanges->fcExchange[x_ID].status & INITIATOR_ABORT))
{
printk(" Cmd %p, ",
Exchanges->fcExchange[ x_ID].Cmnd);
printk(" brd/chn/trg/lun %d/%d/%d/%d port_id %06X\n",
cpqfcHBAdata->HBAnum,
Exchanges->fcExchange[ x_ID].Cmnd->channel,
Exchanges->fcExchange[ x_ID].Cmnd->target,
Exchanges->fcExchange[ x_ID].Cmnd->lun,
Exchanges->fcExchange[ x_ID].fchs.d_id & 0xFFFFFF);
}
else // assume that Cmnd ptr is invalid on _abort()
{
printk(" Cmd ptr invalid\n");
}
#endif
// Steps to ABORT a SEST exchange:
// 1. Freeze TL SCSI assists & ERQ (everything)
// 2. Receive FROZEN inbound CM (must succeed!)
// 3. Invalidate x_ID SEST entry
// 4. Resume TL SCSI assists & ERQ (everything)
// 5. Build/start on exchange - change "type" to BLS_ABTS,
// timeout to X sec (RA_TOV from PLDA is actually 0)
// 6. Set Exchange Q'd status if ABTS cannot be started,
// or simply complete Exchange in "Terminate" condition
PCI_TRACEO( x_ID, 0xB4)
// 1 & 2 . Freeze Tach & get confirmation of freeze
FrozeTach = FreezeTach( cpqfcHBAdata);
// 3. OK, Tachyon is frozen, so we can invalidate SEST exchange.
// FC2_TIMEOUT means we are originating the abort, while
// TARGET_ABORT means we are ACCepting an abort.
// LINKFAIL_TX, ABORTSEQ_NOFITY, INV_ENTRY or FRAME_TO are
// all from Tachyon:
// Exchange was corrupted by LDn or other FC physical failure
// INITIATOR_ABORT means the upper layer driver/application
// requested the abort.
// clear bit 31 (VALid), to invalidate & take control from TL
fcChip->SEST->u[ x_ID].IWE.Hdr_Len &= 0x7FFFFFFF;
// examine and Tach's "Linked List" for IWEs that
// received (nearly) simultaneous transfer ready (XRDY)
// repair linked list if necessary (TBD!)
// (If we ignore the "Linked List", we will time out
// WRITE commands where we received the FCP-SCSI XFRDY
// frame (because Tachyon didn't processes it). Linked List
// management should be done as an optimization.
// readl( fcChip->Registers.ReMapMemBase+TL_MEM_SEST_LINKED_LIST ));
// 4. Resume all Tachlite functions (for other open Exchanges)
// as quickly as possible to allow other exchanges to other ports
// to resume. Freezing Tachyon may cause cascading errors, because
// any received SEST frame cannot be processed by the SEST.
// Don't "unfreeze" unless Link is operational
if( FrozeTach ) // did we just freeze it (above)?
fcChip->UnFreezeTachyon( fcChip, 2); // both ERQ and FCP assists
PCI_TRACEO( x_ID, 0xB4)
// Note there is no confirmation that the chip is "unfrozen". Also,
// if the Link is down when unfreeze is called, it has no effect.
// Chip will unfreeze when the Link is back up.
// 5. Now send out Abort commands if possible
// Some Aborts can't be "sent" (Port_id changed or gone);
// if the device is gone, there is no port_id to send the ABTS to.
if( !(Exchanges->fcExchange[ x_ID].status & PORTID_CHANGED)
&&
!(Exchanges->fcExchange[ x_ID].status & DEVICE_REMOVED) )
{
Exchanges->fcExchange[ x_ID].type = BLS_ABTS;
fchs.s_id = Exchanges->fcExchange[ x_ID].fchs.d_id;
ulStatus = cpqfcTSBuildExchange(
cpqfcHBAdata,
BLS_ABTS,
&fchs, // (uses only s_id)
NULL, // (no scatter/gather list for ABTS)
&x_ID );// ABTS on this Exchange ID
if( !ulStatus ) // Exchange setup build OK?
{
// ABTS may be needed because an Exchange was corrupted
// by a Link disruption. If the Link is UP, we can
// presume that this ABTS can start immediately; otherwise,
// set Que'd status so the Login functions
// can restart it when the FC physical Link is restored
if( ((fcChip->Registers.FMstatus.value &0xF0) &0x80)) // loop init?
{
// printk(" *set Q status x_ID %Xh on LDn* ", x_ID);
Exchanges->fcExchange[ x_ID].status |= EXCHANGE_QUEUED;
}
else // what FC device (port_id) does the Cmd belong to?
{
PFC_LOGGEDIN_PORT pLoggedInPort =
Exchanges->fcExchange[ x_ID].pLoggedInPort;
// if Port is logged in, we might start the abort.
if( (pLoggedInPort != NULL)
&&
(pLoggedInPort->prli == TRUE) )
{
// it's possible that an Exchange has already been Queued
// to start after Login completes. Check and don't
// start it (again) here if Q'd status set
// printk(" ABTS xchg %Xh ", x_ID);
if( Exchanges->fcExchange[x_ID].status & EXCHANGE_QUEUED)
{
// printk("already Q'd ");
}
else
{
// printk("starting ");
fcChip->fcStats.FC2aborted++;
ulStatus = cpqfcTSStartExchange( cpqfcHBAdata, x_ID );
if( !ulStatus )
{
// OK
// submitted to Tach's Outbound Que (ERQ PI incremented)
}
else
{
/* printk("ABTS exchange start failed -status %Xh, x_ID %Xh ",
ulStatus, x_ID);
*/
}
}
}
else
{
/* printk(" ABTS NOT starting xchg %Xh, %p ",
x_ID, pLoggedInPort);
if( pLoggedInPort )
printk("prli %d ", pLoggedInPort->prli);
*/
}
}
}
else // what the #@!
{ // how do we fail to build an Exchange for ABTS??
printk("ABTS exchange build failed -status %Xh, x_ID %Xh\n",
ulStatus, x_ID);
}
}
else // abort without ABTS -- just complete exchange/Cmnd to Linux
{
// printk(" *Terminating x_ID %Xh on %Xh* ",
// x_ID, Exchanges->fcExchange[x_ID].status);
cpqfcTSCompleteExchange( cpqfcHBAdata->PciDev, fcChip, x_ID);
}
} // end of ABTS case
break;
case BLS_ABTS_ACC: // need to ACCept one ABTS
// (NOTE! this code not updated for Linux yet..)
printk(" *ABTS_ACC* ");
// 1. Freeze TL
fcChip->FreezeTachyon( fcChip, 2); // both ERQ and FCP assists
memcpy( // copy the incoming ABTS frame
&fchs,
fcLQ->Qitem[QconsumerNdx].ulBuff, // incoming fchs
sizeof( fchs));
// 3. OK, Tachyon is frozen so we can invalidate SEST entry
// (if necessary)
// Status FC2_TIMEOUT means we are originating the abort, while
// TARGET_ABORT means we are ACCepting an abort
ExchangeID = fchs.ox_rx_id & 0x7FFF; // RX_ID for exchange
// printk("ABTS ACC for Target ExchangeID %Xh\n", ExchangeID);
// sanity check on received ExchangeID
if( Exchanges->fcExchange[ ExchangeID].status == TARGET_ABORT )
{
// clear bit 31 (VALid), to invalidate & take control from TL
// printk("Invalidating SEST exchange %Xh\n", ExchangeID);
fcChip->SEST->u[ ExchangeID].IWE.Hdr_Len &= 0x7FFFFFFF;
}
// 4. Resume all Tachlite functions (for other open Exchanges)
// as quickly as possible to allow other exchanges to other ports
// to resume. Freezing Tachyon for too long may royally screw
// up everything!
fcChip->UnFreezeTachyon( fcChip, 2); // both ERQ and FCP assists
// Note there is no confirmation that the chip is "unfrozen". Also,
// if the Link is down when unfreeze is called, it has no effect.
// Chip will unfreeze when the Link is back up.
// 5. Now send out Abort ACC reply for this exchange
Exchanges->fcExchange[ ExchangeID].type = BLS_ABTS_ACC;
fchs.s_id = Exchanges->fcExchange[ ExchangeID].fchs.d_id;
ulStatus = cpqfcTSBuildExchange(
cpqfcHBAdata,
BLS_ABTS_ACC,
&fchs,
NULL, // no data (no scatter/gather list)
&ExchangeID );// fcController->fcExchanges index, -1 if failed
if( !ulStatus ) // Exchange setup?
{
ulStatus = cpqfcTSStartExchange( cpqfcHBAdata, ExchangeID );
if( !ulStatus )
{
// submitted to Tach's Outbound Que (ERQ PI incremented)
// waited for completion for ELS type (Login frames issued
// synchronously)
}
else
// check reason for Exchange not being started - we might
// want to Queue and start later, or fail with error
{
}
}
break;
case BLS_ABTS_RJT: // need to ReJecT one ABTS; reject implies the
// exchange doesn't exist in the TARGET context.
// ExchangeID has to come from LinkService space.
printk(" *ABTS_RJT* ");
ulStatus = cpqfcTSBuildExchange(
cpqfcHBAdata,
BLS_ABTS_RJT,
(TachFCHDR_GCMND*)
fcLQ->Qitem[QconsumerNdx].ulBuff, // incoming fchs
NULL, // no data (no scatter/gather list)
&ExchangeID );// fcController->fcExchanges index, -1 if failed
if( !ulStatus ) // Exchange setup OK?
{
ulStatus = cpqfcTSStartExchange( cpqfcHBAdata, ExchangeID );
// If it fails, we aren't required to retry.
}
if( ulStatus )
{
printk("Failed to send BLS_RJT for ABTS, X_ID %Xh\n", ExchangeID);
}
else
{
printk("Sent BLS_RJT for ABTS, X_ID %Xh\n", ExchangeID);
}
break;
default:
break;
} // end switch
//doNothing:
// done with this item - now set the NEXT index
if( QconsumerNdx+1 >= FC_LINKQ_DEPTH ) // rollover test
{
fcLQ->consumer = 0;
}
else
{
fcLQ->consumer++;
}
PCI_TRACEO( fcLQ->Qitem[QconsumerNdx].Type, 0x94)
LEAVE("WorkTask");
return;
}
// When Tachyon reports link down, bad al_pa, or Link Service (e.g. Login)
// commands come in, post to the LinkQ so that action can be taken outside the
// interrupt handler.
// This circular Q works like Tachyon's que - the producer points to the next
// (unused) entry. Called by Interrupt handler, WorkerThread, Timer
// sputlinkq
void cpqfcTSPutLinkQue( CPQFCHBA *cpqfcHBAdata,
int Type,
void *QueContent)
{
PTACHYON fcChip = &cpqfcHBAdata->fcChip;
// FC_EXCHANGES *Exchanges = fcChip->Exchanges;
PFC_LINK_QUE fcLQ = cpqfcHBAdata->fcLQ;
ULONG ndx;
ENTER("cpqfcTSPutLinkQ");
ndx = fcLQ->producer;
ndx += 1; // test for Que full
if( ndx >= FC_LINKQ_DEPTH ) // rollover test
ndx = 0;
if( ndx == fcLQ->consumer ) // QUE full test
{
// QUE was full! lost LK command (fatal to logic)
fcChip->fcStats.lnkQueFull++;
printk("*LinkQ Full!*");
TriggerHBA( fcChip->Registers.ReMapMemBase, 1);
/*
{
int i;
printk("LinkQ PI %d, CI %d\n", fcLQ->producer,
fcLQ->consumer);
for( i=0; i< FC_LINKQ_DEPTH; )
{
printk(" [%d]%Xh ", i, fcLQ->Qitem[i].Type);
if( (++i %8) == 0) printk("\n");
}
}
*/
printk( "cpqfcTS: WARNING!! PutLinkQue - FULL!\n"); // we're hung
}
else // QUE next element
{
// Prevent certain multiple (back-to-back) requests.
// This is important in that we don't want to issue multiple
// ABTS for the same Exchange, or do multiple FM inits, etc.
// We can never be sure of the timing of events reported to
// us by Tach's IMQ, which can depend on system/bus speeds,
// FC physical link circumstances, etc.
if( (fcLQ->producer != fcLQ->consumer)
&&
(Type == FMINIT) )
{
LONG lastNdx; // compute previous producer index
if( fcLQ->producer)
lastNdx = fcLQ->producer- 1;
else
lastNdx = FC_LINKQ_DEPTH-1;
if( fcLQ->Qitem[lastNdx].Type == FMINIT)
{
// printk(" *skip FMINIT Q post* ");
// goto DoneWithPutQ;
}
}
// OK, add the Q'd item...
fcLQ->Qitem[fcLQ->producer].Type = Type;
memcpy(
fcLQ->Qitem[fcLQ->producer].ulBuff,
QueContent,
sizeof(fcLQ->Qitem[fcLQ->producer].ulBuff));
fcLQ->producer = ndx; // increment Que producer
// set semaphore to wake up Kernel (worker) thread
//
up( cpqfcHBAdata->fcQueReady );
}
//DoneWithPutQ:
LEAVE("cpqfcTSPutLinkQ");
}
// reset device ext FC link Q
void cpqfcTSLinkQReset( CPQFCHBA *cpqfcHBAdata)
{
PFC_LINK_QUE fcLQ = cpqfcHBAdata->fcLQ;
fcLQ->producer = 0;
fcLQ->consumer = 0;
}
// When Tachyon gets an unassisted FCP-SCSI frame, post here so
// an arbitrary context thread (e.g. IOCTL loopback test function)
// can process it.
// (NOTE: Not revised for Linux)
// This Q works like Tachyon's que - the producer points to the next
// (unused) entry.
void cpqfcTSPutScsiQue( CPQFCHBA *cpqfcHBAdata,
int Type,
void *QueContent)
{
// CPQFCHBA *cpqfcHBAdata = (CPQFCHBA *)HostAdapter->hostdata;
// PTACHYON fcChip = &cpqfcHBAdata->fcChip;
// ULONG ndx;
// ULONG *pExchangeID;
// LONG ExchangeID;
/*
KeAcquireSpinLockAtDpcLevel( &pDevExt->fcScsiQueLock);
ndx = pDevExt->fcScsiQue.producer + 1; // test for Que full
if( ndx >= FC_SCSIQ_DEPTH ) // rollover test
ndx = 0;
if( ndx == pDevExt->fcScsiQue.consumer ) // QUE full test
{
// QUE was full! lost LK command (fatal to logic)
fcChip->fcStats.ScsiQueFull++;
#ifdef DBG
printk( "fcPutScsiQue - FULL!\n");
#endif
}
else // QUE next element
{
pDevExt->fcScsiQue.Qitem[pDevExt->fcScsiQue.producer].Type = Type;
if( Type == FCP_RSP )
{
// this TL inbound message type means that a TL SEST exchange has
// copied an FCP response frame into a buffer pointed to by the SEST
// entry. That buffer is allocated in the SEST structure at ->RspHDR.
// Copy the RspHDR for use by the Que handler.
pExchangeID = (ULONG *)QueContent;
memcpy(
pDevExt->fcScsiQue.Qitem[pDevExt->fcScsiQue.producer].ulBuff,
&fcChip->SEST->RspHDR[ *pExchangeID ],
sizeof(pDevExt->fcScsiQue.Qitem[0].ulBuff)); // (any element for size)
}
else
{
memcpy(
pDevExt->fcScsiQue.Qitem[pDevExt->fcScsiQue.producer].ulBuff,
QueContent,
sizeof(pDevExt->fcScsiQue.Qitem[pDevExt->fcScsiQue.producer].ulBuff));
}
pDevExt->fcScsiQue.producer = ndx; // increment Que
KeSetEvent( &pDevExt->TYIBscsi, // signal any waiting thread
0, // no priority boost
FALSE ); // no waiting later for this event
}
KeReleaseSpinLockFromDpcLevel( &pDevExt->fcScsiQueLock);
*/
}
static void ProcessELS_Request( CPQFCHBA*,TachFCHDR_GCMND*);
static void ProcessELS_Reply( CPQFCHBA*,TachFCHDR_GCMND*);
static void ProcessFCS_Reply( CPQFCHBA*,TachFCHDR_GCMND*);
void cpqfcTSImplicitLogout( CPQFCHBA* cpqfcHBAdata,
PFC_LOGGEDIN_PORT pFcPort)
{
PTACHYON fcChip = &cpqfcHBAdata->fcChip;
if( pFcPort->port_id != 0xFFFC01 ) // don't care about Fabric
{
fcChip->fcStats.logouts++;
printk("cpqfcTS: Implicit logout of WWN %08X%08X, port_id %06X\n",
(ULONG)pFcPort->u.liWWN,
(ULONG)(pFcPort->u.liWWN >>32),
pFcPort->port_id);
// Terminate I/O with this (Linux) Scsi target
cpqfcTSTerminateExchange( cpqfcHBAdata,
&pFcPort->ScsiNexus,
DEVICE_REMOVED);
}
// Do an "implicit logout" - we can't really Logout the device
// (i.e. with LOGOut Request) because of port_id confusion
// (i.e. the Other port has no port_id).
// A new login for that WWN will have to re-write port_id (0 invalid)
pFcPort->port_id = 0; // invalid!
pFcPort->pdisc = FALSE;
pFcPort->prli = FALSE;
pFcPort->plogi = FALSE;
pFcPort->flogi = FALSE;
pFcPort->LOGO_timer = 0;
pFcPort->device_blocked = TRUE; // block Scsi Requests
pFcPort->ScsiNexus.VolumeSetAddressing=0;
}
// On FC-AL, there is a chance that a previously known device can
// be quietly removed (e.g. with non-managed hub),
// while a NEW device (with different WWN) took the same alpa or
// even 24-bit port_id. This chance is unlikely but we must always
// check for it.
static void TestDuplicatePortId( CPQFCHBA* cpqfcHBAdata,
PFC_LOGGEDIN_PORT pLoggedInPort)
{
PTACHYON fcChip = &cpqfcHBAdata->fcChip;
// set "other port" at beginning of fcPorts list
PFC_LOGGEDIN_PORT pOtherPortWithPortId = fcChip->fcPorts.pNextPort;
while( pOtherPortWithPortId )
{
if( (pOtherPortWithPortId->port_id ==
pLoggedInPort->port_id)
&&
(pOtherPortWithPortId != pLoggedInPort) )
{
// trouble! (Implicitly) Log the other guy out
printk(" *port_id %Xh is duplicated!* ",
pOtherPortWithPortId->port_id);
cpqfcTSImplicitLogout( cpqfcHBAdata, pOtherPortWithPortId);
}
pOtherPortWithPortId = pOtherPortWithPortId->pNextPort;
}
}
// Dynamic Memory Allocation for newly discovered FC Ports.
// For simplicity, maintain fcPorts structs for ALL
// for discovered devices, including those we never do I/O with
// (e.g. Fabric addresses)
static PFC_LOGGEDIN_PORT CreateFcPort(
CPQFCHBA* cpqfcHBAdata,
PFC_LOGGEDIN_PORT pLastLoggedInPort,
TachFCHDR_GCMND* fchs,
LOGIN_PAYLOAD* plogi)
{
PTACHYON fcChip = &cpqfcHBAdata->fcChip;
PFC_LOGGEDIN_PORT pNextLoggedInPort = NULL;
int i;
printk("cpqfcTS: New FC port %06Xh WWN: ", fchs->s_id);
for( i=3; i>=0; i--) // copy the LOGIN port's WWN
printk("%02X", plogi->port_name[i]);
for( i=7; i>3; i--) // copy the LOGIN port's WWN
printk("%02X", plogi->port_name[i]);
// allocate mem for new port
// (these are small and rare allocations...)
pNextLoggedInPort = kmalloc( sizeof( FC_LOGGEDIN_PORT), GFP_ATOMIC );
// allocation succeeded? Fill out NEW PORT
if( pNextLoggedInPort )
{
// clear out any garbage (sometimes exists)
memset( pNextLoggedInPort, 0, sizeof( FC_LOGGEDIN_PORT));
// If we login to a Fabric, we don't want to treat it
// as a SCSI device...
if( (fchs->s_id & 0xFFF000) != 0xFFF000)
{
int i;
// create a unique "virtual" SCSI Nexus (for now, just a
// new target ID) -- we will update channel/target on REPORT_LUNS
// special case for very first SCSI target...
if( cpqfcHBAdata->HostAdapter->max_id == 0)
{
pNextLoggedInPort->ScsiNexus.target = 0;
fcChip->fcPorts.ScsiNexus.target = -1; // don't use "stub"
}
else
{
pNextLoggedInPort->ScsiNexus.target =
cpqfcHBAdata->HostAdapter->max_id;
}
// initialize the lun[] Nexus struct for lun masking
for( i=0; i< CPQFCTS_MAX_LUN; i++)
pNextLoggedInPort->ScsiNexus.lun[i] = 0xFF; // init to NOT USED
pNextLoggedInPort->ScsiNexus.channel = 0; // cpqfcTS has 1 FC port
printk(" SCSI Chan/Trgt %d/%d",
pNextLoggedInPort->ScsiNexus.channel,
pNextLoggedInPort->ScsiNexus.target);
// tell Scsi layers about the new target...
cpqfcHBAdata->HostAdapter->max_id++;
// printk("HostAdapter->max_id = %d\n",
// cpqfcHBAdata->HostAdapter->max_id);
}
else
{
// device is NOT SCSI (in case of Fabric)
pNextLoggedInPort->ScsiNexus.target = -1; // invalid
}
// create forward link to new port
pLastLoggedInPort->pNextPort = pNextLoggedInPort;
printk("\n");
}
return pNextLoggedInPort; // NULL on allocation failure
} // end NEW PORT (WWN) logic
// For certain cases, we want to terminate exchanges without
// sending ABTS to the device. Examples include when an FC
// device changed it's port_id after Loop re-init, or when
// the device sent us a logout. In the case of changed port_id,
// we want to complete the command and return SOFT_ERROR to
// force a re-try. In the case of LOGOut, we might return
// BAD_TARGET if the device is really gone.
// Since we must ensure that Tachyon is not operating on the
// exchange, we have to freeze the chip
// sterminateex
void cpqfcTSTerminateExchange(
CPQFCHBA* cpqfcHBAdata, SCSI_NEXUS *ScsiNexus, int TerminateStatus)
{
PTACHYON fcChip = &cpqfcHBAdata->fcChip;
FC_EXCHANGES *Exchanges = fcChip->Exchanges;
ULONG x_ID;
if( ScsiNexus )
{
// printk("TerminateExchange: ScsiNexus chan/target %d/%d\n",
// ScsiNexus->channel, ScsiNexus->target);
}
for( x_ID = 0; x_ID < TACH_SEST_LEN; x_ID++)
{
if( Exchanges->fcExchange[x_ID].type ) // in use?
{
if( ScsiNexus == NULL ) // our HBA changed - term. all
{
Exchanges->fcExchange[x_ID].status = TerminateStatus;
cpqfcTSPutLinkQue( cpqfcHBAdata, BLS_ABTS, &x_ID );
}
else
{
// If a device, according to WWN, has been removed, it's
// port_id may be used by another working device, so we
// have to terminate by SCSI target, NOT port_id.
if( Exchanges->fcExchange[x_ID].Cmnd) // Cmnd in progress?
{
if( (Exchanges->fcExchange[x_ID].Cmnd->device->id == ScsiNexus->target)
&&
(Exchanges->fcExchange[x_ID].Cmnd->device->channel == ScsiNexus->channel))
{
Exchanges->fcExchange[x_ID].status = TerminateStatus;
cpqfcTSPutLinkQue( cpqfcHBAdata, BLS_ABTS, &x_ID ); // timed-out
}
}
// (in case we ever need it...)
// all SEST structures have a remote node ID at SEST DWORD 2
// if( (fcChip->SEST->u[ x_ID ].TWE.Remote_Node_ID >> 8)
// == port_id)
}
}
}
}
static void ProcessELS_Request(
CPQFCHBA* cpqfcHBAdata, TachFCHDR_GCMND* fchs)
{
PTACHYON fcChip = &cpqfcHBAdata->fcChip;
// FC_EXCHANGES *Exchanges = fcChip->Exchanges;
// ULONG ox_id = (fchs->ox_rx_id >>16);
PFC_LOGGEDIN_PORT pLoggedInPort=NULL, pLastLoggedInPort;
BOOLEAN NeedReject = FALSE;
ULONG ls_reject_code = 0; // default don'n know??
// Check the incoming frame for a supported ELS type
switch( fchs->pl[0] & 0xFFFF)
{
case 0x0050: // PDISC?
// Payload for PLOGI and PDISC is identical (request & reply)
if( !verify_PLOGI( fcChip, fchs, &ls_reject_code) ) // valid payload?
{
LOGIN_PAYLOAD logi; // FC-PH Port Login
// PDISC payload OK. If critical login fields
// (e.g. WWN) matches last login for this port_id,
// we may resume any prior exchanges
// with the other port
BigEndianSwap( (UCHAR*)&fchs->pl[0], (UCHAR*)&logi, sizeof(logi));
pLoggedInPort = fcFindLoggedInPort(
fcChip,
NULL, // don't search Scsi Nexus
0, // don't search linked list for port_id
&logi.port_name[0], // search linked list for WWN
&pLastLoggedInPort); // must return non-NULL; when a port_id
// is not found, this pointer marks the
// end of the singly linked list
if( pLoggedInPort != NULL) // WWN found (prior login OK)
{
if( (fchs->s_id & 0xFFFFFF) == pLoggedInPort->port_id)
{
// Yes. We were expecting PDISC?
if( pLoggedInPort->pdisc )
{
// Yes; set fields accordingly. (PDISC, not Originator)
SetLoginFields( pLoggedInPort, fchs, TRUE, FALSE);
// send 'ACC' reply
cpqfcTSPutLinkQue( cpqfcHBAdata,
ELS_PLOGI_ACC, // (PDISC same as PLOGI ACC)
fchs );
// OK to resume I/O...
}
else
{
printk("Not expecting PDISC (pdisc=FALSE)\n");
NeedReject = TRUE;
// set reject reason code
ls_reject_code =
LS_RJT_REASON( PROTOCOL_ERROR, INITIATOR_CTL_ERROR);
}
}
else
{
if( pLoggedInPort->port_id != 0)
{
printk("PDISC PortID change: old %Xh, new %Xh\n",
pLoggedInPort->port_id, fchs->s_id &0xFFFFFF);
}
NeedReject = TRUE;
// set reject reason code
ls_reject_code =
LS_RJT_REASON( PROTOCOL_ERROR, INITIATOR_CTL_ERROR);
}
}
else
{
printk("PDISC Request from unknown WWN\n");
NeedReject = TRUE;
// set reject reason code
ls_reject_code =
LS_RJT_REASON( LOGICAL_ERROR, INVALID_PORT_NAME);
}
}
else // Payload unacceptable
{
printk("payload unacceptable\n");
NeedReject = TRUE; // reject code already set
}
if( NeedReject)
{
ULONG port_id;
// The PDISC failed. Set login struct flags accordingly,
// terminate any I/O to this port, and Q a PLOGI
if( pLoggedInPort )
{
pLoggedInPort->pdisc = FALSE;
pLoggedInPort->prli = FALSE;
pLoggedInPort->plogi = FALSE;
cpqfcTSTerminateExchange( cpqfcHBAdata,
&pLoggedInPort->ScsiNexus, PORTID_CHANGED);
port_id = pLoggedInPort->port_id;
}
else
{
port_id = fchs->s_id &0xFFFFFF;
}
fchs->reserved = ls_reject_code; // borrow this (unused) field
cpqfcTSPutLinkQue( cpqfcHBAdata, ELS_RJT, fchs );
}
break;
case 0x0003: // PLOGI?
// Payload for PLOGI and PDISC is identical (request & reply)
if( !verify_PLOGI( fcChip, fchs, &ls_reject_code) ) // valid payload?
{
LOGIN_PAYLOAD logi; // FC-PH Port Login
BOOLEAN NeedReject = FALSE;
// PDISC payload OK. If critical login fields
// (e.g. WWN) matches last login for this port_id,
// we may resume any prior exchanges
// with the other port
BigEndianSwap( (UCHAR*)&fchs->pl[0], (UCHAR*)&logi, sizeof(logi));
pLoggedInPort = fcFindLoggedInPort(
fcChip,
NULL, // don't search Scsi Nexus
0, // don't search linked list for port_id
&logi.port_name[0], // search linked list for WWN
&pLastLoggedInPort); // must return non-NULL; when a port_id
// is not found, this pointer marks the
// end of the singly linked list
if( pLoggedInPort == NULL) // WWN not found -New Port
{
pLoggedInPort = CreateFcPort(
cpqfcHBAdata,
pLastLoggedInPort,
fchs,
&logi);
if( pLoggedInPort == NULL )
{
printk(" cpqfcTS: New port allocation failed - lost FC device!\n");
// Now Q a LOGOut Request, since we won't be talking to that device
NeedReject = TRUE;
// set reject reason code
ls_reject_code =
LS_RJT_REASON( LOGICAL_ERROR, NO_LOGIN_RESOURCES);
}
}
if( !NeedReject )
{
// OK - we have valid fcPort ptr; set fields accordingly.
// (not PDISC, not Originator)
SetLoginFields( pLoggedInPort, fchs, FALSE, FALSE);
// send 'ACC' reply
cpqfcTSPutLinkQue( cpqfcHBAdata,
ELS_PLOGI_ACC, // (PDISC same as PLOGI ACC)
fchs );
}
}
else // Payload unacceptable
{
printk("payload unacceptable\n");
NeedReject = TRUE; // reject code already set
}
if( NeedReject)
{
// The PDISC failed. Set login struct flags accordingly,
// terminate any I/O to this port, and Q a PLOGI
pLoggedInPort->pdisc = FALSE;
pLoggedInPort->prli = FALSE;
pLoggedInPort->plogi = FALSE;
fchs->reserved = ls_reject_code; // borrow this (unused) field
// send 'RJT' reply
cpqfcTSPutLinkQue( cpqfcHBAdata, ELS_RJT, fchs );
}
// terminate any exchanges with this device...
if( pLoggedInPort )
{
cpqfcTSTerminateExchange( cpqfcHBAdata,
&pLoggedInPort->ScsiNexus, PORTID_CHANGED);
}
break;
case 0x1020: // PRLI?
{
BOOLEAN NeedReject = TRUE;
pLoggedInPort = fcFindLoggedInPort(
fcChip,
NULL, // don't search Scsi Nexus
(fchs->s_id & 0xFFFFFF), // search linked list for port_id
NULL, // DON'T search linked list for WWN
NULL); // don't care
if( pLoggedInPort == NULL )
{
// huh?
printk(" Unexpected PRLI Request -not logged in!\n");
// set reject reason code
ls_reject_code = LS_RJT_REASON( PROTOCOL_ERROR, INITIATOR_CTL_ERROR);
// Q a LOGOut here?
}
else
{
// verify the PRLI ACC payload
if( !verify_PRLI( fchs, &ls_reject_code) )
{
// PRLI Reply is acceptable; were we expecting it?
if( pLoggedInPort->plogi )
{
// yes, we expected the PRLI ACC (not PDISC; not Originator)
SetLoginFields( pLoggedInPort, fchs, FALSE, FALSE);
// Q an ACCept Reply
cpqfcTSPutLinkQue( cpqfcHBAdata,
ELS_PRLI_ACC,
fchs );
NeedReject = FALSE;
}
else
{
// huh?
printk(" (unexpected) PRLI REQEST with plogi FALSE\n");
// set reject reason code
ls_reject_code = LS_RJT_REASON( PROTOCOL_ERROR, INITIATOR_CTL_ERROR);
// Q a LOGOut here?
}
}
else
{
printk(" PRLI REQUEST payload failed verify\n");
// (reject code set by "verify")
// Q a LOGOut here?
}
}
if( NeedReject )
{
// Q a ReJecT Reply with reason code
fchs->reserved = ls_reject_code;
cpqfcTSPutLinkQue( cpqfcHBAdata,
ELS_RJT, // Q Type
fchs );
}
}
break;
case 0x0005: // LOGOut?
{
// was this LOGOUT because we sent a ELS_PDISC to an FC device
// with changed (or new) port_id, or does the port refuse
// to communicate to us?
// We maintain a logout counter - if we get 3 consecutive LOGOuts,
// give up!
LOGOUT_PAYLOAD logo;
BOOLEAN GiveUpOnDevice = FALSE;
ULONG ls_reject_code = 0;
BigEndianSwap( (UCHAR*)&fchs->pl[0], (UCHAR*)&logo, sizeof(logo));
pLoggedInPort = fcFindLoggedInPort(
fcChip,
NULL, // don't search Scsi Nexus
0, // don't search linked list for port_id
&logo.port_name[0], // search linked list for WWN
NULL); // don't care about end of list
if( pLoggedInPort ) // found the device?
{
// Q an ACC reply
cpqfcTSPutLinkQue( cpqfcHBAdata,
ELS_LOGO_ACC, // Q Type
fchs ); // device to respond to
// set login struct fields (LOGO_counter increment)
SetLoginFields( pLoggedInPort, fchs, FALSE, FALSE);
// are we an Initiator?
if( fcChip->Options.initiator)
{
// we're an Initiator, so check if we should
// try (another?) login
// Fabrics routinely log out from us after
// getting device info - don't try to log them
// back in.
if( (fchs->s_id & 0xFFF000) == 0xFFF000 )
{
; // do nothing
}
else if( pLoggedInPort->LOGO_counter <= 3)
{
// try (another) login (PLOGI request)
cpqfcTSPutLinkQue( cpqfcHBAdata,
ELS_PLOGI, // Q Type
fchs );
// Terminate I/O with "retry" potential
cpqfcTSTerminateExchange( cpqfcHBAdata,
&pLoggedInPort->ScsiNexus,
PORTID_CHANGED);
}
else
{
printk(" Got 3 LOGOuts - terminating comm. with port_id %Xh\n",
fchs->s_id &&0xFFFFFF);
GiveUpOnDevice = TRUE;
}
}
else
{
GiveUpOnDevice = TRUE;
}
if( GiveUpOnDevice == TRUE )
{
cpqfcTSTerminateExchange( cpqfcHBAdata,
&pLoggedInPort->ScsiNexus,
DEVICE_REMOVED);
}
}
else // we don't know this WWN!
{
// Q a ReJecT Reply with reason code
fchs->reserved = ls_reject_code;
cpqfcTSPutLinkQue( cpqfcHBAdata,
ELS_RJT, // Q Type
fchs );
}
}
break;
// FABRIC only case
case 0x0461: // ELS RSCN (Registered State Change Notification)?
{
int Ports;
int i;
__u32 Buff;
// Typically, one or more devices have been added to or dropped
// from the Fabric.
// The format of this frame is defined in FC-FLA (Rev 2.7, Aug 1997)
// The first 32-bit word has a 2-byte Payload Length, which
// includes the 4 bytes of the first word. Consequently,
// this PL len must never be less than 4, must be a multiple of 4,
// and has a specified max value 256.
// (Endianess!)
Ports = ((fchs->pl[0] >>24) - 4) / 4;
Ports = Ports > 63 ? 63 : Ports;
printk(" RSCN ports: %d\n", Ports);
if( Ports <= 0 ) // huh?
{
// ReJecT the command
fchs->reserved = LS_RJT_REASON( UNABLE_TO_PERFORM, 0);
cpqfcTSPutLinkQue( cpqfcHBAdata,
ELS_RJT, // Q Type
fchs );
break;
}
else // Accept the command
{
cpqfcTSPutLinkQue( cpqfcHBAdata,
ELS_ACC, // Q Type
fchs );
}
// Check the "address format" to determine action.
// We have 3 cases:
// 0 = Port Address; 24-bit address of affected device
// 1 = Area Address; MS 16 bits valid
// 2 = Domain Address; MS 8 bits valid
for( i=0; i<Ports; i++)
{
BigEndianSwap( (UCHAR*)&fchs->pl[i+1],(UCHAR*)&Buff, 4);
switch( Buff & 0xFF000000)
{
case 0: // Port Address?
case 0x01000000: // Area Domain?
case 0x02000000: // Domain Address
// For example, "port_id" 0x201300
// OK, let's try a Name Service Request (Query)
fchs->s_id = 0xFFFFFC; // Name Server Address
cpqfcTSPutLinkQue( cpqfcHBAdata, FCS_NSR, fchs);
break;
default: // huh? new value on version change?
break;
}
}
}
break;
default: // don't support this request (yet)
// set reject reason code
fchs->reserved = LS_RJT_REASON( UNABLE_TO_PERFORM,
REQUEST_NOT_SUPPORTED);
cpqfcTSPutLinkQue( cpqfcHBAdata,
ELS_RJT, // Q Type
fchs );
break;
}
}
static void ProcessELS_Reply(
CPQFCHBA* cpqfcHBAdata, TachFCHDR_GCMND* fchs)
{
PTACHYON fcChip = &cpqfcHBAdata->fcChip;
FC_EXCHANGES *Exchanges = fcChip->Exchanges;
ULONG ox_id = (fchs->ox_rx_id >>16);
ULONG ls_reject_code;
PFC_LOGGEDIN_PORT pLoggedInPort, pLastLoggedInPort;
// If this is a valid reply, then we MUST have sent a request.
// Verify that we can find a valid request OX_ID corresponding to
// this reply
if( Exchanges->fcExchange[(fchs->ox_rx_id >>16)].type == 0)
{
printk(" *Discarding ACC/RJT frame, xID %04X/%04X* ",
ox_id, fchs->ox_rx_id & 0xffff);
goto Quit; // exit this routine
}
// Is the reply a RJT (reject)?
if( (fchs->pl[0] & 0xFFFFL) == 0x01) // Reject reply?
{
// ****** REJECT REPLY ********
switch( Exchanges->fcExchange[ox_id].type )
{
case ELS_FDISC: // we sent out Fabric Discovery
case ELS_FLOGI: // we sent out FLOGI
printk("RJT received on Fabric Login from %Xh, reason %Xh\n",
fchs->s_id, fchs->pl[1]);
break;
default:
break;
}
goto Done;
}
// OK, we have an ACCept...
// What's the ACC type? (according to what we sent)
switch( Exchanges->fcExchange[ox_id].type )
{
case ELS_PLOGI: // we sent out PLOGI
if( !verify_PLOGI( fcChip, fchs, &ls_reject_code) )
{
LOGIN_PAYLOAD logi; // FC-PH Port Login
// login ACC payload acceptable; search for WWN in our list
// of fcPorts
BigEndianSwap( (UCHAR*)&fchs->pl[0], (UCHAR*)&logi, sizeof(logi));
pLoggedInPort = fcFindLoggedInPort(
fcChip,
NULL, // don't search Scsi Nexus
0, // don't search linked list for port_id
&logi.port_name[0], // search linked list for WWN
&pLastLoggedInPort); // must return non-NULL; when a port_id
// is not found, this pointer marks the
// end of the singly linked list
if( pLoggedInPort == NULL) // WWN not found - new port
{
pLoggedInPort = CreateFcPort(
cpqfcHBAdata,
pLastLoggedInPort,
fchs,
&logi);
if( pLoggedInPort == NULL )
{
printk(" cpqfcTS: New port allocation failed - lost FC device!\n");
// Now Q a LOGOut Request, since we won't be talking to that device
goto Done; // exit with error! dropped login frame
}
}
else // WWN was already known. Ensure that any open
// exchanges for this WWN are terminated.
// NOTE: It's possible that a device can change its
// 24-bit port_id after a Link init or Fabric change
// (e.g. LIP or Fabric RSCN). In that case, the old
// 24-bit port_id may be duplicated, or no longer exist.
{
cpqfcTSTerminateExchange( cpqfcHBAdata,
&pLoggedInPort->ScsiNexus, PORTID_CHANGED);
}
// We have an fcPort struct - set fields accordingly
// not PDISC, originator
SetLoginFields( pLoggedInPort, fchs, FALSE, TRUE);
// We just set a "port_id"; is it duplicated?
TestDuplicatePortId( cpqfcHBAdata, pLoggedInPort);
// For Fabric operation, we issued PLOGI to 0xFFFFFC
// so we can send SCR (State Change Registration)
// Check for this special case...
if( fchs->s_id == 0xFFFFFC )
{
// PLOGI ACC was a Fabric response... issue SCR
fchs->s_id = 0xFFFFFD; // address for SCR
cpqfcTSPutLinkQue( cpqfcHBAdata, ELS_SCR, fchs);
}
else
{
// Now we need a PRLI to enable FCP-SCSI operation
// set flags and Q up a ELS_PRLI
cpqfcTSPutLinkQue( cpqfcHBAdata, ELS_PRLI, fchs);
}
}
else
{
// login payload unacceptable - reason in ls_reject_code
// Q up a Logout Request
printk("Login Payload unacceptable\n");
}
break;
// PDISC logic very similar to PLOGI, except we never want
// to allocate mem for "new" port, and we set flags differently
// (might combine later with PLOGI logic for efficiency)
case ELS_PDISC: // we sent out PDISC
if( !verify_PLOGI( fcChip, fchs, &ls_reject_code) )
{
LOGIN_PAYLOAD logi; // FC-PH Port Login
BOOLEAN NeedLogin = FALSE;
// login payload acceptable; search for WWN in our list
// of (previously seen) fcPorts
BigEndianSwap( (UCHAR*)&fchs->pl[0], (UCHAR*)&logi, sizeof(logi));
pLoggedInPort = fcFindLoggedInPort(
fcChip,
NULL, // don't search Scsi Nexus
0, // don't search linked list for port_id
&logi.port_name[0], // search linked list for WWN
&pLastLoggedInPort); // must return non-NULL; when a port_id
// is not found, this pointer marks the
// end of the singly linked list
if( pLoggedInPort != NULL) // WWN found?
{
// WWN has same port_id as last login? (Of course, a properly
// working FC device should NEVER ACCept a PDISC if it's
// port_id changed, but check just in case...)
if( (fchs->s_id & 0xFFFFFF) == pLoggedInPort->port_id)
{
// Yes. We were expecting PDISC?
if( pLoggedInPort->pdisc )
{
int i;
// PDISC expected -- set fields. (PDISC, Originator)
SetLoginFields( pLoggedInPort, fchs, TRUE, TRUE);
// We are ready to resume FCP-SCSI to this device...
// Do we need to start anything that was Queued?
for( i=0; i< TACH_SEST_LEN; i++)
{
// see if any exchange for this PDISC'd port was queued
if( ((fchs->s_id &0xFFFFFF) ==
(Exchanges->fcExchange[i].fchs.d_id & 0xFFFFFF))
&&
(Exchanges->fcExchange[i].status & EXCHANGE_QUEUED))
{
fchs->reserved = i; // copy ExchangeID
// printk(" *Q x_ID %Xh after PDISC* ",i);
cpqfcTSPutLinkQue( cpqfcHBAdata, EXCHANGE_QUEUED, fchs );
}
}
// Complete commands Q'd while we were waiting for Login
UnblockScsiDevice( cpqfcHBAdata->HostAdapter, pLoggedInPort);
}
else
{
printk("Not expecting PDISC (pdisc=FALSE)\n");
NeedLogin = TRUE;
}
}
else
{
printk("PDISC PortID change: old %Xh, new %Xh\n",
pLoggedInPort->port_id, fchs->s_id &0xFFFFFF);
NeedLogin = TRUE;
}
}
else
{
printk("PDISC ACC from unknown WWN\n");
NeedLogin = TRUE;
}
if( NeedLogin)
{
// The PDISC failed. Set login struct flags accordingly,
// terminate any I/O to this port, and Q a PLOGI
if( pLoggedInPort ) // FC device previously known?
{
cpqfcTSPutLinkQue( cpqfcHBAdata,
ELS_LOGO, // Q Type
fchs ); // has port_id to send to
// There are a variety of error scenarios which can result
// in PDISC failure, so as a catchall, add the check for
// duplicate port_id.
TestDuplicatePortId( cpqfcHBAdata, pLoggedInPort);
// TriggerHBA( fcChip->Registers.ReMapMemBase, 0);
pLoggedInPort->pdisc = FALSE;
pLoggedInPort->prli = FALSE;
pLoggedInPort->plogi = FALSE;
cpqfcTSTerminateExchange( cpqfcHBAdata,
&pLoggedInPort->ScsiNexus, PORTID_CHANGED);
}
cpqfcTSPutLinkQue( cpqfcHBAdata, ELS_PLOGI, fchs );
}
}
else
{
// login payload unacceptable - reason in ls_reject_code
// Q up a Logout Request
printk("ERROR: Login Payload unacceptable!\n");
}
break;
case ELS_PRLI: // we sent out PRLI
pLoggedInPort = fcFindLoggedInPort(
fcChip,
NULL, // don't search Scsi Nexus
(fchs->s_id & 0xFFFFFF), // search linked list for port_id
NULL, // DON'T search linked list for WWN
NULL); // don't care
if( pLoggedInPort == NULL )
{
// huh?
printk(" Unexpected PRLI ACCept frame!\n");
// Q a LOGOut here?
goto Done;
}
// verify the PRLI ACC payload
if( !verify_PRLI( fchs, &ls_reject_code) )
{
// PRLI Reply is acceptable; were we expecting it?
if( pLoggedInPort->plogi )
{
// yes, we expected the PRLI ACC (not PDISC; Originator)
SetLoginFields( pLoggedInPort, fchs, FALSE, TRUE);
// OK, let's send a REPORT_LUNS command to determine
// whether VSA or PDA FCP-LUN addressing is used.
cpqfcTSPutLinkQue( cpqfcHBAdata, SCSI_REPORT_LUNS, fchs );
// It's possible that a device we were talking to changed
// port_id, and has logged back in. This function ensures
// that I/O will resume.
UnblockScsiDevice( cpqfcHBAdata->HostAdapter, pLoggedInPort);
}
else
{
// huh?
printk(" (unexpected) PRLI ACCept with plogi FALSE\n");
// Q a LOGOut here?
goto Done;
}
}
else
{
printk(" PRLI ACCept payload failed verify\n");
// Q a LOGOut here?
}
break;
case ELS_FLOGI: // we sent out FLOGI (Fabric Login)
// update the upper 16 bits of our port_id in Tachyon
// the switch adds those upper 16 bits when responding
// to us (i.e. we are the destination_id)
fcChip->Registers.my_al_pa = (fchs->d_id & 0xFFFFFF);
writel( fcChip->Registers.my_al_pa,
fcChip->Registers.ReMapMemBase + TL_MEM_TACH_My_ID);
// now send out a PLOGI to the well known port_id 0xFFFFFC
fchs->s_id = 0xFFFFFC;
cpqfcTSPutLinkQue( cpqfcHBAdata, ELS_PLOGI, fchs);
break;
case ELS_FDISC: // we sent out FDISC (Fabric Discovery (Login))
printk( " ELS_FDISC success ");
break;
case ELS_SCR: // we sent out State Change Registration
// now we can issue Name Service Request to find any
// Fabric-connected devices we might want to login to.
fchs->s_id = 0xFFFFFC; // Name Server Address
cpqfcTSPutLinkQue( cpqfcHBAdata, FCS_NSR, fchs);
break;
default:
printk(" *Discarding unknown ACC frame, xID %04X/%04X* ",
ox_id, fchs->ox_rx_id & 0xffff);
break;
}
Done:
// Regardless of whether the Reply is valid or not, the
// the exchange is done - complete
cpqfcTSCompleteExchange(cpqfcHBAdata->PciDev, fcChip, (fchs->ox_rx_id >>16));
Quit:
return;
}
// **************** Fibre Channel Services **************
// This is where we process the Directory (Name) Service Reply
// to know which devices are on the Fabric
static void ProcessFCS_Reply(
CPQFCHBA* cpqfcHBAdata, TachFCHDR_GCMND* fchs)
{
PTACHYON fcChip = &cpqfcHBAdata->fcChip;
FC_EXCHANGES *Exchanges = fcChip->Exchanges;
ULONG ox_id = (fchs->ox_rx_id >>16);
// ULONG ls_reject_code;
// PFC_LOGGEDIN_PORT pLoggedInPort, pLastLoggedInPort;
// If this is a valid reply, then we MUST have sent a request.
// Verify that we can find a valid request OX_ID corresponding to
// this reply
if( Exchanges->fcExchange[(fchs->ox_rx_id >>16)].type == 0)
{
printk(" *Discarding Reply frame, xID %04X/%04X* ",
ox_id, fchs->ox_rx_id & 0xffff);
goto Quit; // exit this routine
}
// OK, we were expecting it. Now check to see if it's a
// "Name Service" Reply, and if so force a re-validation of
// Fabric device logins (i.e. Start the login timeout and
// send PDISC or PLOGI)
// (Endianess Byte Swap?)
if( fchs->pl[1] == 0x02FC ) // Name Service
{
// got a new (or NULL) list of Fabric attach devices...
// Invalidate current logins
PFC_LOGGEDIN_PORT pLoggedInPort = &fcChip->fcPorts;
while( pLoggedInPort ) // for all ports which are expecting
// PDISC after the next LIP, set the
// logoutTimer
{
if( (pLoggedInPort->port_id & 0xFFFF00) // Fabric device?
&&
(pLoggedInPort->port_id != 0xFFFFFC) ) // NOT the F_Port
{
pLoggedInPort->LOGO_timer = 6; // what's the Fabric timeout??
// suspend any I/O in progress until
// PDISC received...
pLoggedInPort->prli = FALSE; // block FCP-SCSI commands
}
pLoggedInPort = pLoggedInPort->pNextPort;
}
if( fchs->pl[2] == 0x0280) // ACCept?
{
// Send PLOGI or PDISC to these Fabric devices
SendLogins( cpqfcHBAdata, &fchs->pl[4] );
}
// As of this writing, the only reason to reject is because NO
// devices are left on the Fabric. We already started
// "logged out" timers; if the device(s) don't come
// back, we'll do the implicit logout in the heart beat
// timer routine
else // ReJecT
{
// this just means no Fabric device is visible at this instant
}
}
// Regardless of whether the Reply is valid or not, the
// the exchange is done - complete
cpqfcTSCompleteExchange(cpqfcHBAdata->PciDev, fcChip, (fchs->ox_rx_id >>16));
Quit:
return;
}
static void AnalyzeIncomingFrame(
CPQFCHBA *cpqfcHBAdata,
ULONG QNdx )
{
PTACHYON fcChip = &cpqfcHBAdata->fcChip;
FC_EXCHANGES *Exchanges = fcChip->Exchanges;
PFC_LINK_QUE fcLQ = cpqfcHBAdata->fcLQ;
TachFCHDR_GCMND* fchs =
(TachFCHDR_GCMND*)fcLQ->Qitem[QNdx].ulBuff;
// ULONG ls_reject_code; // reason for rejecting login
LONG ExchangeID;
// FC_LOGGEDIN_PORT *pLoggedInPort;
BOOLEAN AbortAccept;
ENTER("AnalyzeIncomingFrame");
switch( fcLQ->Qitem[QNdx].Type) // FCP or Unknown
{
case SFQ_UNKNOWN: // unknown frame (e.g. LIP position frame, NOP, etc.)
// ********* FC-4 Device Data/ Fibre Channel Service *************
if( ((fchs->d_id &0xF0000000) == 0) // R_CTL (upper nibble) 0x0?
&&
(fchs->f_ctl & 0x20000000) ) // TYPE 20h is Fibre Channel Service
{
// ************** FCS Reply **********************
if( (fchs->d_id & 0xff000000L) == 0x03000000L) // (31:23 R_CTL)
{
ProcessFCS_Reply( cpqfcHBAdata, fchs );
} // end of FCS logic
}
// *********** Extended Link Service **************
else if( fchs->d_id & 0x20000000 // R_CTL 0x2?
&&
(fchs->f_ctl & 0x01000000) ) // TYPE = 1
{
// these frames are either a response to
// something we sent (0x23) or "unsolicited"
// frames (0x22).
// **************Extended Link REPLY **********************
// R_CTL Solicited Control Reply
if( (fchs->d_id & 0xff000000L) == 0x23000000L) // (31:23 R_CTL)
{
ProcessELS_Reply( cpqfcHBAdata, fchs );
} // end of "R_CTL Solicited Control Reply"
// **************Extended Link REQUEST **********************
// (unsolicited commands from another port or task...)
// R_CTL Ext Link REQUEST
else if( (fchs->d_id & 0xff000000L) == 0x22000000L &&
(fchs->ox_rx_id != 0xFFFFFFFFL) ) // (ignore LIP frame)
{
ProcessELS_Request( cpqfcHBAdata, fchs );
}
// ************** LILP **********************
else if( (fchs->d_id & 0xff000000L) == 0x22000000L &&
(fchs->ox_rx_id == 0xFFFFFFFFL)) // (e.g., LIP frames)
{
// SANMark specifies that when available, we must use
// the LILP frame to determine which ALPAs to send Port Discovery
// to...
if( fchs->pl[0] == 0x0711L) // ELS_PLOGI?
{
// UCHAR *ptr = (UCHAR*)&fchs->pl[1];
// printk(" %d ALPAs found\n", *ptr);
memcpy( fcChip->LILPmap, &fchs->pl[1], 32*4); // 32 DWORDs
fcChip->Options.LILPin = 1; // our LILPmap is valid!
// now post to make Port Discovery happen...
cpqfcTSPutLinkQue( cpqfcHBAdata, LINKACTIVE, fchs);
}
}
}
// ***************** BASIC LINK SERVICE *****************
else if( fchs->d_id & 0x80000000 // R_CTL:
&& // Basic Link Service Request
!(fchs->f_ctl & 0xFF000000) ) // type=0 for BLS
{
// Check for ABTS (Abort Sequence)
if( (fchs->d_id & 0x8F000000) == 0x81000000)
{
// look for OX_ID, S_ID pair that matches in our
// fcExchanges table; if found, reply with ACCept and complete
// the exchange
// Per PLDA, an ABTS is sent by an initiator; therefore
// assume that if we have an exhange open to the port who
// sent ABTS, it will be the d_id of what we sent.
for( ExchangeID = 0, AbortAccept=FALSE;
ExchangeID < TACH_SEST_LEN; ExchangeID++)
{
// Valid "target" exchange 24-bit port_id matches?
// NOTE: For the case of handling Intiator AND Target
// functions on the same chip, we can have TWO Exchanges
// with the same OX_ID -- OX_ID/FFFF for the CMND, and
// OX_ID/RX_ID for the XRDY or DATA frame(s). Ideally,
// we would like to support ABTS from Initiators or Targets,
// but it's not clear that can be supported on Tachyon for
// all cases (requires more investigation).
if( (Exchanges->fcExchange[ ExchangeID].type == SCSI_TWE ||
Exchanges->fcExchange[ ExchangeID].type == SCSI_TRE)
&&
((Exchanges->fcExchange[ ExchangeID].fchs.d_id & 0xFFFFFF) ==
(fchs->s_id & 0xFFFFFF)) )
{
// target xchnge port_id matches -- how about OX_ID?
if( (Exchanges->fcExchange[ ExchangeID].fchs.ox_rx_id &0xFFFF0000)
== (fchs->ox_rx_id & 0xFFFF0000) )
// yes! post ACCept response; will be completed by fcStart
{
Exchanges->fcExchange[ ExchangeID].status = TARGET_ABORT;
// copy (add) rx_id field for simplified ACCept reply
fchs->ox_rx_id =
Exchanges->fcExchange[ ExchangeID].fchs.ox_rx_id;
cpqfcTSPutLinkQue( cpqfcHBAdata,
BLS_ABTS_ACC, // Q Type
fchs ); // void QueContent
AbortAccept = TRUE;
printk("ACCepting ABTS for x_ID %8.8Xh, SEST pair %8.8Xh\n",
fchs->ox_rx_id, Exchanges->fcExchange[ ExchangeID].fchs.ox_rx_id);
break; // ABTS can affect only ONE exchange -exit loop
}
}
} // end of FOR loop
if( !AbortAccept ) // can't ACCept ABTS - send Reject
{
printk("ReJecTing: can't find ExchangeID %8.8Xh for ABTS command\n",
fchs->ox_rx_id);
if( Exchanges->fcExchange[ ExchangeID].type
&&
!(fcChip->SEST->u[ ExchangeID].IWE.Hdr_Len
& 0x80000000))
{
cpqfcTSCompleteExchange( cpqfcHBAdata->PciDev, fcChip, ExchangeID);
}
else
{
printk("Unexpected ABTS ReJecT! SEST[%X] Dword 0: %Xh\n",
ExchangeID, fcChip->SEST->u[ ExchangeID].IWE.Hdr_Len);
}
}
}
// Check for BLS {ABTS? (Abort Sequence)} ACCept
else if( (fchs->d_id & 0x8F000000) == 0x84000000)
{
// target has responded with ACC for our ABTS;
// complete the indicated exchange with ABORTED status
// Make no checks for correct RX_ID, since
// all we need to conform ABTS ACC is the OX_ID.
// Verify that the d_id matches!
ExchangeID = (fchs->ox_rx_id >> 16) & 0x7FFF; // x_id from ACC
// printk("ABTS ACC x_ID 0x%04X 0x%04X, status %Xh\n",
// fchs->ox_rx_id >> 16, fchs->ox_rx_id & 0xffff,
// Exchanges->fcExchange[ExchangeID].status);
if( ExchangeID < TACH_SEST_LEN ) // x_ID makes sense
{
// Does "target" exchange 24-bit port_id match?
// (See "NOTE" above for handling Intiator AND Target in
// the same device driver)
// First, if this is a target response, then we originated
// (initiated) it with BLS_ABTS:
if( (Exchanges->fcExchange[ ExchangeID].type == BLS_ABTS)
&&
// Second, does the source of this ACC match the destination
// of who we originally sent it to?
((Exchanges->fcExchange[ ExchangeID].fchs.d_id & 0xFFFFFF) ==
(fchs->s_id & 0xFFFFFF)) )
{
cpqfcTSCompleteExchange( cpqfcHBAdata->PciDev, fcChip, ExchangeID );
}
}
}
// Check for BLS {ABTS? (Abort Sequence)} ReJecT
else if( (fchs->d_id & 0x8F000000) == 0x85000000)
{
// target has responded with RJT for our ABTS;
// complete the indicated exchange with ABORTED status
// Make no checks for correct RX_ID, since
// all we need to conform ABTS ACC is the OX_ID.
// Verify that the d_id matches!
ExchangeID = (fchs->ox_rx_id >> 16) & 0x7FFF; // x_id from ACC
// printk("BLS_ABTS RJT on Exchange 0x%04X 0x%04X\n",
// fchs->ox_rx_id >> 16, fchs->ox_rx_id & 0xffff);
if( ExchangeID < TACH_SEST_LEN ) // x_ID makes sense
{
// Does "target" exchange 24-bit port_id match?
// (See "NOTE" above for handling Intiator AND Target in
// the same device driver)
// First, if this is a target response, then we originated
// (initiated) it with BLS_ABTS:
if( (Exchanges->fcExchange[ ExchangeID].type == BLS_ABTS)
&&
// Second, does the source of this ACC match the destination
// of who we originally sent it to?
((Exchanges->fcExchange[ ExchangeID].fchs.d_id & 0xFFFFFF) ==
(fchs->s_id & 0xFFFFFF)) )
{
// YES! NOTE: There is a bug in CPQ's RA-4000 box
// where the "reason code" isn't returned in the payload
// For now, simply presume the reject is because the target
// already completed the exchange...
// printk("complete x_ID %Xh on ABTS RJT\n", ExchangeID);
cpqfcTSCompleteExchange( cpqfcHBAdata->PciDev, fcChip, ExchangeID );
}
}
} // end of ABTS check
} // end of Basic Link Service Request
break;
default:
printk("AnalyzeIncomingFrame: unknown type: %Xh(%d)\n",
fcLQ->Qitem[QNdx].Type,
fcLQ->Qitem[QNdx].Type);
break;
}
}
// Function for Port Discovery necessary after every FC
// initialization (e.g. LIP).
// Also may be called if from Fabric Name Service logic.
static void SendLogins( CPQFCHBA *cpqfcHBAdata, __u32 *FabricPortIds )
{
PTACHYON fcChip = &cpqfcHBAdata->fcChip;
FC_EXCHANGES *Exchanges = fcChip->Exchanges;
ULONG ulStatus=0;
TachFCHDR_GCMND fchs; // copy fields for transmission
int i;
ULONG loginType;
LONG ExchangeID;
PFC_LOGGEDIN_PORT pLoggedInPort;
__u32 PortIds[ number_of_al_pa];
int NumberOfPorts=0;
// We're going to presume (for now) that our limit of Fabric devices
// is the same as the number of alpa on a private loop (126 devices).
// (Of course this could be changed to support however many we have
// memory for).
memset( &PortIds[0], 0, sizeof(PortIds));
// First, check if this login is for our own Link Initialization
// (e.g. LIP on FC-AL), or if we have knowledge of Fabric devices
// from a switch. If we are logging into Fabric devices, we'll
// have a non-NULL FabricPortId pointer
if( FabricPortIds != NULL) // may need logins
{
int LastPort=FALSE;
i = 0;
while( !LastPort)
{
// port IDs From NSR payload; byte swap needed?
BigEndianSwap( (UCHAR*)FabricPortIds, (UCHAR*)&PortIds[i], 4);
// printk("FPortId[%d] %Xh ", i, PortIds[i]);
if( PortIds[i] & 0x80000000)
LastPort = TRUE;
PortIds[i] &= 0xFFFFFF; // get 24-bit port_id
// some non-Fabric devices (like the Crossroads Fibre/Scsi bridge)
// erroneously use ALPA 0.
if( PortIds[i] ) // need non-zero port_id...
i++;
if( i >= number_of_al_pa ) // (in)sanity check
break;
FabricPortIds++; // next...
}
NumberOfPorts = i;
// printk("NumberOf Fabric ports %d", NumberOfPorts);
}
else // need to send logins on our "local" link
{
// are we a loop port? If so, check for reception of LILP frame,
// and if received use it (SANMark requirement)
if( fcChip->Options.LILPin )
{
int j=0;
// sanity check on number of ALPAs from LILP frame...
// For format of LILP frame, see FC-AL specs or
// "Fibre Channel Bench Reference", J. Stai, 1995 (ISBN 1-879936-17-8)
// First byte is number of ALPAs
i = fcChip->LILPmap[0] >= (32*4) ? 32*4 : fcChip->LILPmap[0];
NumberOfPorts = i;
// printk(" LILP alpa count %d ", i);
while( i > 0)
{
PortIds[j] = fcChip->LILPmap[1+ j];
j++; i--;
}
}
else // have to send login to everybody
{
int j=0;
i = number_of_al_pa;
NumberOfPorts = i;
while( i > 0)
{
PortIds[j] = valid_al_pa[j]; // all legal ALPAs
j++; i--;
}
}
}
// Now we have a copy of the port_ids (and how many)...
for( i = 0; i < NumberOfPorts; i++)
{
// 24-bit FC Port ID
fchs.s_id = PortIds[i]; // note: only 8-bits used for ALPA
// don't log into ourselves (Linux Scsi disk scan will stop on
// no TARGET support error on us, and quit trying for rest of devices)
if( (fchs.s_id & 0xFF ) == (fcChip->Registers.my_al_pa & 0xFF) )
continue;
// fabric login needed?
if( (fchs.s_id == 0) ||
(fcChip->Options.fabric == 1) )
{
fcChip->Options.flogi = 1; // fabric needs longer for login
// Do we need FLOGI or FDISC?
pLoggedInPort = fcFindLoggedInPort(
fcChip,
NULL, // don't search SCSI Nexus
0xFFFFFC, // search linked list for Fabric port_id
NULL, // don't search WWN
NULL); // (don't care about end of list)
if( pLoggedInPort ) // If found, we have prior experience with
// this port -- check whether PDISC is needed
{
if( pLoggedInPort->flogi )
{
// does the switch support FDISC?? (FLOGI for now...)
loginType = ELS_FLOGI; // prior FLOGI still valid
}
else
loginType = ELS_FLOGI; // expired FLOGI
}
else // first FLOGI?
loginType = ELS_FLOGI;
fchs.s_id = 0xFFFFFE; // well known F_Port address
// Fabrics are not required to support FDISC, and
// it's not clear if that helps us anyway, since
// we'll want a Name Service Request to re-verify
// visible devices...
// Consequently, we always want our upper 16 bit
// port_id to be zero (we'll be rejected if we
// use our prior port_id if we've been plugged into
// a different switch port).
// Trick Tachyon to send to ALPA 0 (see TL/TS UG, pg 87)
// If our ALPA is 55h for instance, we want the FC frame
// s_id to be 0x000055, while Tach's my_al_pa register
// must be 0x000155, to force an OPN at ALPA 0
// (the Fabric port)
fcChip->Registers.my_al_pa &= 0xFF; // only use ALPA for FLOGI
writel( fcChip->Registers.my_al_pa | 0x0100,
fcChip->Registers.ReMapMemBase + TL_MEM_TACH_My_ID);
}
else // not FLOGI...
{
// should we send PLOGI or PDISC? Check if any prior port_id
// (e.g. alpa) completed a PLOGI/PRLI exchange by checking
// the pdisc flag.
pLoggedInPort = fcFindLoggedInPort(
fcChip,
NULL, // don't search SCSI Nexus
fchs.s_id, // search linked list for al_pa
NULL, // don't search WWN
NULL); // (don't care about end of list)
if( pLoggedInPort ) // If found, we have prior experience with
// this port -- check whether PDISC is needed
{
if( pLoggedInPort->pdisc )
{
loginType = ELS_PDISC; // prior PLOGI and PRLI maybe still valid
}
else
loginType = ELS_PLOGI; // prior knowledge, but can't use PDISC
}
else // never talked to this port_id before
loginType = ELS_PLOGI; // prior knowledge, but can't use PDISC
}
ulStatus = cpqfcTSBuildExchange(
cpqfcHBAdata,
loginType, // e.g. PLOGI
&fchs, // no incoming frame (we are originator)
NULL, // no data (no scatter/gather list)
&ExchangeID );// fcController->fcExchanges index, -1 if failed
if( !ulStatus ) // Exchange setup OK?
{
ulStatus = cpqfcTSStartExchange( cpqfcHBAdata, ExchangeID );
if( !ulStatus )
{
// submitted to Tach's Outbound Que (ERQ PI incremented)
// waited for completion for ELS type (Login frames issued
// synchronously)
if( loginType == ELS_PDISC )
{
// now, we really shouldn't Revalidate SEST exchanges until
// we get an ACC reply from our target and verify that
// the target address/WWN is unchanged. However, when a fast
// target gets the PDISC, they can send SEST Exchange data
// before we even get around to processing the PDISC ACC.
// Consequently, we lose the I/O.
// To avoid this, go ahead and Revalidate when the PDISC goes
// out, anticipating that the ACC will be truly acceptable
// (this happens 99.9999....% of the time).
// If we revalidate a SEST write, and write data goes to a
// target that is NOT the one we originated the WRITE to,
// that target is required (FCP-SCSI specs, etc) to discard
// our WRITE data.
// Re-validate SEST entries (Tachyon hardware assists)
RevalidateSEST( cpqfcHBAdata->HostAdapter, pLoggedInPort);
//TriggerHBA( fcChip->Registers.ReMapMemBase, 1);
}
}
else // give up immediately on error
{
#ifdef LOGIN_DBG
printk("SendLogins: fcStartExchange failed: %Xh\n", ulStatus );
#endif
break;
}
if( fcChip->Registers.FMstatus.value & 0x080 ) // LDn during Port Disc.
{
ulStatus = LNKDWN_OSLS;
#ifdef LOGIN_DBG
printk("SendLogins: PortDisc aborted (LDn) @alpa %Xh\n", fchs.s_id);
#endif
break;
}
// Check the exchange for bad status (i.e. FrameTimeOut),
// and complete on bad status (most likely due to BAD_ALPA)
// on LDn, DPC function may already complete (ABORT) a started
// exchange, so check type first (type = 0 on complete).
if( Exchanges->fcExchange[ExchangeID].status )
{
#ifdef LOGIN_DBG
printk("completing x_ID %X on status %Xh\n",
ExchangeID, Exchanges->fcExchange[ExchangeID].status);
#endif
cpqfcTSCompleteExchange( cpqfcHBAdata->PciDev, fcChip, ExchangeID);
}
}
else // Xchange setup failed...
{
#ifdef LOGIN_DBG
printk("FC: cpqfcTSBuildExchange failed: %Xh\n", ulStatus );
#endif
break;
}
}
if( !ulStatus )
{
// set the event signifying that all ALPAs were sent out.
#ifdef LOGIN_DBG
printk("SendLogins: PortDiscDone\n");
#endif
cpqfcHBAdata->PortDiscDone = 1;
// TL/TS UG, pg. 184
// 0x0065 = 100ms for RT_TOV
// 0x01f5 = 500ms for ED_TOV
fcChip->Registers.ed_tov.value = 0x006501f5L;
writel( fcChip->Registers.ed_tov.value,
(fcChip->Registers.ed_tov.address));
// set the LP_TOV back to ED_TOV (i.e. 500 ms)
writel( 0x00000010, fcChip->Registers.ReMapMemBase +TL_MEM_FM_TIMEOUT2);
}
else
{
printk("SendLogins: failed at xchng %Xh, alpa %Xh, status %Xh\n",
ExchangeID, fchs.s_id, ulStatus);
}
LEAVE("SendLogins");
}
// for REPORT_LUNS documentation, see "In-Depth Exploration of Scsi",
// D. Deming, 1994, pg 7-19 (ISBN 1-879936-08-9)
static void ScsiReportLunsDone(Scsi_Cmnd *Cmnd)
{
struct Scsi_Host *HostAdapter = Cmnd->device->host;
CPQFCHBA *cpqfcHBAdata = (CPQFCHBA *)HostAdapter->hostdata;
PTACHYON fcChip = &cpqfcHBAdata->fcChip;
FC_EXCHANGES *Exchanges = fcChip->Exchanges;
PFC_LOGGEDIN_PORT pLoggedInPort;
int LunListLen=0;
int i;
ULONG x_ID = 0xFFFFFFFF;
UCHAR *ucBuff = Cmnd->request_buffer;
// printk("cpqfcTS: ReportLunsDone \n");
// first, we need to find the Exchange for this command,
// so we can find the fcPort struct to make the indicated
// changes.
for( i=0; i< TACH_SEST_LEN; i++)
{
if( Exchanges->fcExchange[i].type // exchange defined?
&&
(Exchanges->fcExchange[i].Cmnd == Cmnd) ) // matches?
{
x_ID = i; // found exchange!
break;
}
}
if( x_ID == 0xFFFFFFFF)
{
// printk("cpqfcTS: ReportLuns failed - no FC Exchange\n");
goto Done; // Report Luns FC Exchange gone;
// exchange probably Terminated by Implicit logout
}
// search linked list for the port_id we sent INQUIRY to
pLoggedInPort = fcFindLoggedInPort( fcChip,
NULL, // DON'T search Scsi Nexus (we will set it)
Exchanges->fcExchange[ x_ID].fchs.d_id & 0xFFFFFF,
NULL, // DON'T search linked list for FC WWN
NULL); // DON'T care about end of list
if( !pLoggedInPort )
{
// printk("cpqfcTS: ReportLuns failed - device gone\n");
goto Done; // error! can't find logged in Port
}
LunListLen = ucBuff[3];
LunListLen += ucBuff[2]>>8;
if( !LunListLen ) // failed
{
// generically speaking, a soft error means we should retry...
if( (Cmnd->result >> 16) == DID_SOFT_ERROR )
{
if( ((Cmnd->sense_buffer[2] & 0xF) == 0x6) &&
(Cmnd->sense_buffer[12] == 0x29) ) // Sense Code "reset"
{
TachFCHDR_GCMND *fchs = &Exchanges->fcExchange[ x_ID].fchs;
// did we fail because of "check condition, device reset?"
// e.g. the device was reset (i.e., at every power up)
// retry the Report Luns
// who are we sending it to?
// we know this because we have a copy of the command
// frame from the original Report Lun command -
// switch the d_id/s_id fields, because the Exchange Build
// context is "reply to source".
fchs->s_id = fchs->d_id; // (temporarily re-use the struct)
cpqfcTSPutLinkQue( cpqfcHBAdata, SCSI_REPORT_LUNS, fchs );
}
}
else // probably, the device doesn't support Report Luns
pLoggedInPort->ScsiNexus.VolumeSetAddressing = 0;
}
else // we have LUN info - check VSA mode
{
// for now, assume all LUNs will have same addr mode
// for VSA, payload byte 8 will be 0x40; otherwise, 0
pLoggedInPort->ScsiNexus.VolumeSetAddressing = ucBuff[8];
// Since we got a Report Luns answer, set lun masking flag
pLoggedInPort->ScsiNexus.LunMasking = 1;
if( LunListLen > 8*CPQFCTS_MAX_LUN) // We expect CPQFCTS_MAX_LUN max
LunListLen = 8*CPQFCTS_MAX_LUN;
/*
printk("Device WWN %08X%08X Reports Luns @: ",
(ULONG)(pLoggedInPort->u.liWWN &0xFFFFFFFF),
(ULONG)(pLoggedInPort->u.liWWN>>32));
for( i=8; i<LunListLen+8; i+=8)
{
printk("%02X%02X ", ucBuff[i], ucBuff[i+1] );
}
printk("\n");
*/
// Since the device was kind enough to tell us where the
// LUNs are, lets ensure they are contiguous for Linux's
// SCSI driver scan, which expects them to start at 0.
// Since Linux only supports 8 LUNs, only copy the first
// eight from the report luns command
// e.g., the Compaq RA4x00 f/w Rev 2.54 and above may report
// LUNs 4001, 4004, etc., because other LUNs are masked from
// this HBA (owned by someone else). We'll make those appear as
// LUN 0, 1... to Linux
{
int j;
int AppendLunList = 0;
// Walk through the LUN list. The 'j' array number is
// Linux's lun #, while the value of .lun[j] is the target's
// lun #.
// Once we build a LUN list, it's possible for a known device
// to go offline while volumes (LUNs) are added. Later,
// the device will do another PLOGI ... Report Luns command,
// and we must not alter the existing Linux Lun map.
// (This will be very rare).
for( j=0; j < CPQFCTS_MAX_LUN; j++)
{
if( pLoggedInPort->ScsiNexus.lun[j] != 0xFF )
{
AppendLunList = 1;
break;
}
}
if( AppendLunList )
{
int k;
int FreeLunIndex;
// printk("cpqfcTS: AppendLunList\n");
// If we get a new Report Luns, we cannot change
// any existing LUN mapping! (Only additive entry)
// For all LUNs in ReportLun list
// if RL lun != ScsiNexus lun
// if RL lun present in ScsiNexus lun[], continue
// else find ScsiNexus lun[]==FF and add, continue
for( i=8, j=0; i<LunListLen+8 && j< CPQFCTS_MAX_LUN; i+=8, j++)
{
if( pLoggedInPort->ScsiNexus.lun[j] != ucBuff[i+1] )
{
// something changed from the last Report Luns
printk(" cpqfcTS: Report Lun change!\n");
for( k=0, FreeLunIndex=CPQFCTS_MAX_LUN;
k < CPQFCTS_MAX_LUN; k++)
{
if( pLoggedInPort->ScsiNexus.lun[k] == 0xFF)
{
FreeLunIndex = k;
break;
}
if( pLoggedInPort->ScsiNexus.lun[k] == ucBuff[i+1] )
break; // we already masked this lun
}
if( k >= CPQFCTS_MAX_LUN )
{
printk(" no room for new LUN %d\n", ucBuff[i+1]);
}
else if( k == FreeLunIndex ) // need to add LUN
{
pLoggedInPort->ScsiNexus.lun[k] = ucBuff[i+1];
// printk("add [%d]->%02d\n", k, pLoggedInPort->ScsiNexus.lun[k]);
}
else
{
// lun already known
}
break;
}
}
// print out the new list...
for( j=0; j< CPQFCTS_MAX_LUN; j++)
{
if( pLoggedInPort->ScsiNexus.lun[j] == 0xFF)
break; // done
// printk("[%d]->%02d ", j, pLoggedInPort->ScsiNexus.lun[j]);
}
}
else
{
// printk("Linux SCSI LUNs[] -> Device LUNs: ");
// first time - this is easy
for( i=8, j=0; i<LunListLen+8 && j< CPQFCTS_MAX_LUN; i+=8, j++)
{
pLoggedInPort->ScsiNexus.lun[j] = ucBuff[i+1];
// printk("[%d]->%02d ", j, pLoggedInPort->ScsiNexus.lun[j]);
}
// printk("\n");
}
}
}
Done: ;
}
extern int is_private_data_of_cpqfc(CPQFCHBA *hba, void * pointer);
extern void cpqfc_free_private_data(CPQFCHBA *hba, cpqfc_passthru_private_t *data);
static void
call_scsi_done(Scsi_Cmnd *Cmnd)
{
CPQFCHBA *hba;
hba = (CPQFCHBA *) Cmnd->device->host->hostdata;
// Was this command a cpqfc passthru ioctl ?
if (Cmnd->sc_request != NULL && Cmnd->device->host != NULL &&
Cmnd->device->host->hostdata != NULL &&
is_private_data_of_cpqfc((CPQFCHBA *) Cmnd->device->host->hostdata,
Cmnd->sc_request->upper_private_data)) {
cpqfc_free_private_data(hba,
Cmnd->sc_request->upper_private_data);
Cmnd->sc_request->upper_private_data = NULL;
Cmnd->result &= 0xff00ffff;
Cmnd->result |= (DID_PASSTHROUGH << 16); // prevents retry
}
if (Cmnd->scsi_done != NULL)
(*Cmnd->scsi_done)(Cmnd);
}
// After successfully getting a "Process Login" (PRLI) from an
// FC port, we want to Discover the LUNs so that we know the
// addressing type (e.g., FCP-SCSI Volume Set Address, Peripheral
// Unit Device), and whether SSP (Selective Storage Presentation or
// Lun Masking) has made the LUN numbers non-zero based or
// non-contiguous. To remain backward compatible with the SCSI-2
// driver model, which expects a contiguous LUNs starting at 0,
// will use the ReportLuns info to map from "device" to "Linux"
// LUNs.
static void IssueReportLunsCommand(
CPQFCHBA* cpqfcHBAdata,
TachFCHDR_GCMND* fchs)
{
PTACHYON fcChip = &cpqfcHBAdata->fcChip;
PFC_LOGGEDIN_PORT pLoggedInPort;
struct scsi_cmnd *Cmnd = NULL;
struct scsi_device *ScsiDev = NULL;
LONG x_ID;
ULONG ulStatus;
UCHAR *ucBuff;
if( !cpqfcHBAdata->PortDiscDone) // cleared by LDn
{
printk("Discard Q'd ReportLun command\n");
goto Done;
}
// find the device (from port_id) we're talking to
pLoggedInPort = fcFindLoggedInPort( fcChip,
NULL, // DON'T search Scsi Nexus
fchs->s_id & 0xFFFFFF,
NULL, // DON'T search linked list for FC WWN
NULL); // DON'T care about end of list
if( pLoggedInPort ) // we'd BETTER find it!
{
if( !(pLoggedInPort->fcp_info & TARGET_FUNCTION) )
goto Done; // forget it - FC device not a "target"
ScsiDev = scsi_get_host_dev (cpqfcHBAdata->HostAdapter);
if (!ScsiDev)
goto Done;
Cmnd = scsi_get_command (ScsiDev, GFP_KERNEL);
if (!Cmnd)
goto Done;
ucBuff = pLoggedInPort->ReportLunsPayload;
memset( ucBuff, 0, REPORT_LUNS_PL);
Cmnd->scsi_done = ScsiReportLunsDone;
Cmnd->request_buffer = pLoggedInPort->ReportLunsPayload;
Cmnd->request_bufflen = REPORT_LUNS_PL;
Cmnd->cmnd[0] = 0xA0;
Cmnd->cmnd[8] = REPORT_LUNS_PL >> 8;
Cmnd->cmnd[9] = (UCHAR)REPORT_LUNS_PL;
Cmnd->cmd_len = 12;
Cmnd->device->channel = pLoggedInPort->ScsiNexus.channel;
Cmnd->device->id = pLoggedInPort->ScsiNexus.target;
ulStatus = cpqfcTSBuildExchange(
cpqfcHBAdata,
SCSI_IRE,
fchs,
Cmnd, // buffer for Report Lun data
&x_ID );// fcController->fcExchanges index, -1 if failed
if( !ulStatus ) // Exchange setup?
{
ulStatus = cpqfcTSStartExchange( cpqfcHBAdata, x_ID );
if( !ulStatus )
{
// submitted to Tach's Outbound Que (ERQ PI incremented)
// waited for completion for ELS type (Login frames issued
// synchronously)
}
else
// check reason for Exchange not being started - we might
// want to Queue and start later, or fail with error
{
}
}
else // Xchange setup failed...
printk(" cpqfcTSBuildExchange failed: %Xh\n", ulStatus );
}
else // like, we just got a PRLI ACC, and now the port is gone?
{
printk(" can't send ReportLuns - no login for port_id %Xh\n",
fchs->s_id & 0xFFFFFF);
}
Done:
if (Cmnd)
scsi_put_command (Cmnd);
if (ScsiDev)
scsi_free_host_dev (ScsiDev);
}
static void CompleteBoardLockCmnd( CPQFCHBA *cpqfcHBAdata)
{
int i;
for( i = CPQFCTS_REQ_QUEUE_LEN-1; i>= 0; i--)
{
if( cpqfcHBAdata->BoardLockCmnd[i] != NULL )
{
Scsi_Cmnd *Cmnd = cpqfcHBAdata->BoardLockCmnd[i];
cpqfcHBAdata->BoardLockCmnd[i] = NULL;
Cmnd->result = (DID_SOFT_ERROR << 16); // ask for retry
// printk(" BoardLockCmnd[%d] %p Complete, chnl/target/lun %d/%d/%d\n",
// i,Cmnd, Cmnd->channel, Cmnd->target, Cmnd->lun);
call_scsi_done(Cmnd);
}
}
}
// runs every 1 second for FC exchange timeouts and implicit FC device logouts
void cpqfcTSheartbeat( unsigned long ptr )
{
CPQFCHBA *cpqfcHBAdata = (CPQFCHBA *)ptr;
PTACHYON fcChip = &cpqfcHBAdata->fcChip;
FC_EXCHANGES *Exchanges = fcChip->Exchanges;
PFC_LOGGEDIN_PORT pLoggedInPort = &fcChip->fcPorts;
ULONG i;
unsigned long flags;
DECLARE_MUTEX_LOCKED(BoardLock);
PCI_TRACE( 0xA8)
if( cpqfcHBAdata->BoardLock) // Worker Task Running?
goto Skip;
// STOP _que function
spin_lock_irqsave( cpqfcHBAdata->HostAdapter->host_lock, flags);
PCI_TRACE( 0xA8)
cpqfcHBAdata->BoardLock = &BoardLock; // stop Linux SCSI command queuing
// release the IO lock (and re-enable interrupts)
spin_unlock_irqrestore( cpqfcHBAdata->HostAdapter->host_lock, flags);
// Ensure no contention from _quecommand or Worker process
CPQ_SPINLOCK_HBA( cpqfcHBAdata)
PCI_TRACE( 0xA8)
disable_irq( cpqfcHBAdata->HostAdapter->irq); // our IRQ
// Complete the "bad target" commands (normally only used during
// initialization, since we aren't supposed to call "scsi_done"
// inside the queuecommand() function). (this is overly contorted,
// scsi_done can be safely called from queuecommand for
// this bad target case. May want to simplify this later)
for( i=0; i< CPQFCTS_MAX_TARGET_ID; i++)
{
if( cpqfcHBAdata->BadTargetCmnd[i] )
{
Scsi_Cmnd *Cmnd = cpqfcHBAdata->BadTargetCmnd[i];
cpqfcHBAdata->BadTargetCmnd[i] = NULL;
Cmnd->result = (DID_BAD_TARGET << 16);
call_scsi_done(Cmnd);
}
else
break;
}
// logged in ports -- re-login check (ports required to verify login with
// PDISC after LIP within 2 secs)
// prevent contention
while( pLoggedInPort ) // for all ports which are expecting
// PDISC after the next LIP, check to see if
// time is up!
{
// Important: we only detect "timeout" condition on TRANSITION
// from non-zero to zero
if( pLoggedInPort->LOGO_timer ) // time-out "armed"?
{
if( !(--pLoggedInPort->LOGO_timer) ) // DEC from 1 to 0?
{
// LOGOUT time! Per PLDA, PDISC hasn't complete in 2 secs, so
// issue LOGO request and destroy all I/O with other FC port(s).
/*
printk(" ~cpqfcTS heartbeat: LOGOut!~ ");
printk("Linux SCSI Chanl/Target %d/%d (port_id %06Xh) WWN %08X%08X\n",
pLoggedInPort->ScsiNexus.channel,
pLoggedInPort->ScsiNexus.target,
pLoggedInPort->port_id,
(ULONG)(pLoggedInPort->u.liWWN &0xFFFFFFFF),
(ULONG)(pLoggedInPort->u.liWWN>>32));
*/
cpqfcTSImplicitLogout( cpqfcHBAdata, pLoggedInPort);
}
// else simply decremented - maybe next time...
}
pLoggedInPort = pLoggedInPort->pNextPort;
}
// ************ FC EXCHANGE TIMEOUT CHECK **************
for( i=0; i< TACH_MAX_XID; i++)
{
if( Exchanges->fcExchange[i].type ) // exchange defined?
{
if( !Exchanges->fcExchange[i].timeOut ) // time expired
{
// Set Exchange timeout status
Exchanges->fcExchange[i].status |= FC2_TIMEOUT;
if( i >= TACH_SEST_LEN ) // Link Service Exchange
{
cpqfcTSCompleteExchange( cpqfcHBAdata->PciDev, fcChip, i); // Don't "abort" LinkService
}
else // SEST Exchange TO -- may post ABTS to Worker Thread Que
{
// (Make sure we don't keep timing it out; let other functions
// complete it or set the timeOut as needed)
Exchanges->fcExchange[i].timeOut = 30000; // seconds default
if( Exchanges->fcExchange[i].type
&
(BLS_ABTS | BLS_ABTS_ACC ) )
{
// For BLS_ABTS*, an upper level might still have
// an outstanding command waiting for low-level completion.
// Also, in the case of a WRITE, we MUST get confirmation
// of either ABTS ACC or RJT before re-using the Exchange.
// It's possible that the RAID cache algorithm can hang
// if we fail to complete a WRITE to a LBA, when a READ
// comes later to that same LBA. Therefore, we must
// ensure that the target verifies receipt of ABTS for
// the exchange
printk("~TO Q'd ABTS (x_ID %Xh)~ ", i);
// TriggerHBA( fcChip->Registers.ReMapMemBase);
// On timeout of a ABTS exchange, check to
// see if the FC device has a current valid login.
// If so, restart it.
pLoggedInPort = fcFindLoggedInPort( fcChip,
Exchanges->fcExchange[i].Cmnd, // find Scsi Nexus
0, // DON'T search linked list for FC port id
NULL, // DON'T search linked list for FC WWN
NULL); // DON'T care about end of list
// device exists?
if( pLoggedInPort ) // device exists?
{
if( pLoggedInPort->prli ) // logged in for FCP-SCSI?
{
// attempt to restart the ABTS
printk(" ~restarting ABTS~ ");
cpqfcTSStartExchange( cpqfcHBAdata, i );
}
}
}
else // not an ABTS
{
// We expect the WorkerThread to change the xchng type to
// abort and set appropriate timeout.
cpqfcTSPutLinkQue( cpqfcHBAdata, BLS_ABTS, &i ); // timed-out
}
}
}
else // time not expired...
{
// decrement timeout: 1 or more seconds left
--Exchanges->fcExchange[i].timeOut;
}
}
}
enable_irq( cpqfcHBAdata->HostAdapter->irq);
CPQ_SPINUNLOCK_HBA( cpqfcHBAdata)
cpqfcHBAdata->BoardLock = NULL; // Linux SCSI commands may be queued
// Now, complete any Cmnd we Q'd up while BoardLock was held
CompleteBoardLockCmnd( cpqfcHBAdata);
// restart the timer to run again (1 sec later)
Skip:
mod_timer( &cpqfcHBAdata->cpqfcTStimer, jiffies + HZ);
PCI_TRACEO( i, 0xA8)
return;
}
// put valid FC-AL physical address in spec order
static const UCHAR valid_al_pa[]={
0xef, 0xe8, 0xe4, 0xe2,
0xe1, 0xE0, 0xDC, 0xDA,
0xD9, 0xD6, 0xD5, 0xD4,
0xD3, 0xD2, 0xD1, 0xCe,
0xCd, 0xCc, 0xCb, 0xCa,
0xC9, 0xC7, 0xC6, 0xC5,
0xC3, 0xBc, 0xBa, 0xB9,
0xB6, 0xB5, 0xB4, 0xB3,
0xB2, 0xB1, 0xae, 0xad,
0xAc, 0xAb, 0xAa, 0xA9,
0xA7, 0xA6, 0xA5, 0xA3,
0x9f, 0x9e, 0x9d, 0x9b,
0x98, 0x97, 0x90, 0x8f,
0x88, 0x84, 0x82, 0x81,
0x80, 0x7c, 0x7a, 0x79,
0x76, 0x75, 0x74, 0x73,
0x72, 0x71, 0x6e, 0x6d,
0x6c, 0x6b, 0x6a, 0x69,
0x67, 0x66, 0x65, 0x63,
0x5c, 0x5a, 0x59, 0x56,
0x55, 0x54, 0x53, 0x52,
0x51, 0x4e, 0x4d, 0x4c,
0x4b, 0x4a, 0x49, 0x47,
0x46, 0x45, 0x43, 0x3c,
0x3a, 0x39, 0x36, 0x35,
0x34, 0x33, 0x32, 0x31,
0x2e, 0x2d, 0x2c, 0x2b,
0x2a, 0x29, 0x27, 0x26,
0x25, 0x23, 0x1f, 0x1E,
0x1d, 0x1b, 0x18, 0x17,
0x10, 0x0f, 8, 4, 2, 1 }; // ALPA 0 (Fabric) is special case
const int number_of_al_pa = (sizeof(valid_al_pa) );
// this function looks up an al_pa from the table of valid al_pa's
// we decrement from the last decimal loop ID, because soft al_pa
// (our typical case) are assigned with highest priority (and high al_pa)
// first. See "In-Depth FC-AL", R. Kembel pg. 38
// INPUTS:
// al_pa - 24 bit port identifier (8 bit al_pa on private loop)
// RETURN:
// Loop ID - serves are index to array of logged in ports
// -1 - invalid al_pa (not all 8 bit values are legal)
#if (0)
static int GetLoopID( ULONG al_pa )
{
int i;
for( i = number_of_al_pa -1; i >= 0; i--) // dec.
{
if( valid_al_pa[i] == (UCHAR)al_pa ) // take lowest 8 bits
return i; // success - found valid al_pa; return decimal LoopID
}
return -1; // failed - not found
}
#endif
extern cpqfc_passthru_private_t *cpqfc_private(Scsi_Request *sr);
// Search the singly (forward) linked list "fcPorts" looking for
// either the SCSI target (if != -1), port_id (if not NULL),
// or WWN (if not null), in that specific order.
// If we find a SCSI nexus (from Cmnd arg), set the SCp.phase
// field according to VSA or PDU
// RETURNS:
// Ptr to logged in port struct if found
// (NULL if not found)
// pLastLoggedInPort - ptr to last struct (for adding new ones)
//
PFC_LOGGEDIN_PORT fcFindLoggedInPort(
PTACHYON fcChip,
Scsi_Cmnd *Cmnd, // search linked list for Scsi Nexus (channel/target/lun)
ULONG port_id, // search linked list for al_pa, or
UCHAR wwn[8], // search linked list for WWN, or...
PFC_LOGGEDIN_PORT *pLastLoggedInPort )
{
PFC_LOGGEDIN_PORT pLoggedInPort = &fcChip->fcPorts;
BOOLEAN target_id_valid=FALSE;
BOOLEAN port_id_valid=FALSE;
BOOLEAN wwn_valid=FALSE;
int i;
if( Cmnd != NULL )
target_id_valid = TRUE;
else if( port_id ) // note! 24-bit NULL address is illegal
port_id_valid = TRUE;
else
{
if( wwn ) // non-null arg? (OK to pass NULL when not searching WWN)
{
for( i=0; i<8; i++) // valid WWN passed? NULL WWN invalid
{
if( wwn[i] != 0 )
wwn_valid = TRUE; // any non-zero byte makes (presumably) valid
}
}
}
// check other options ...
// In case multiple search options are given, we use a priority
// scheme:
// While valid pLoggedIn Ptr
// If port_id is valid
// if port_id matches, return Ptr
// If wwn is valid
// if wwn matches, return Ptr
// Next Ptr in list
//
// Return NULL (not found)
while( pLoggedInPort ) // NULL marks end of list (1st ptr always valid)
{
if( pLastLoggedInPort ) // caller's pointer valid?
*pLastLoggedInPort = pLoggedInPort; // end of linked list
if( target_id_valid )
{
// check Linux Scsi Cmnd for channel/target Nexus match
// (all luns are accessed through matching "pLoggedInPort")
if( (pLoggedInPort->ScsiNexus.target == Cmnd->device->id)
&&
(pLoggedInPort->ScsiNexus.channel == Cmnd->device->channel))
{
// For "passthru" modes, the IOCTL caller is responsible
// for setting the FCP-LUN addressing
if (Cmnd->sc_request != NULL && Cmnd->device->host != NULL &&
Cmnd->device->host->hostdata != NULL &&
is_private_data_of_cpqfc((CPQFCHBA *) Cmnd->device->host->hostdata,
Cmnd->sc_request->upper_private_data)) {
/* This is a passthru... */
cpqfc_passthru_private_t *pd;
pd = Cmnd->sc_request->upper_private_data;
Cmnd->SCp.phase = pd->bus;
// Cmnd->SCp.have_data_in = pd->pdrive;
Cmnd->SCp.have_data_in = Cmnd->device->lun;
} else {
/* This is not a passthru... */
// set the FCP-LUN addressing type
Cmnd->SCp.phase = pLoggedInPort->ScsiNexus.VolumeSetAddressing;
// set the Device Type we got from the snooped INQUIRY string
Cmnd->SCp.Message = pLoggedInPort->ScsiNexus.InqDeviceType;
// handle LUN masking; if not "default" (illegal) lun value,
// the use it. These lun values are set by a successful
// Report Luns command
if( pLoggedInPort->ScsiNexus.LunMasking == 1)
{
if (Cmnd->device->lun > sizeof(pLoggedInPort->ScsiNexus.lun))
return NULL;
// we KNOW all the valid LUNs... 0xFF is invalid!
Cmnd->SCp.have_data_in = pLoggedInPort->ScsiNexus.lun[Cmnd->device->lun];
if (pLoggedInPort->ScsiNexus.lun[Cmnd->device->lun] == 0xFF)
return NULL;
// printk("xlating lun %d to 0x%02x\n", Cmnd->lun,
// pLoggedInPort->ScsiNexus.lun[Cmnd->lun]);
}
else
Cmnd->SCp.have_data_in = Cmnd->device->lun; // Linux & target luns match
}
break; // found it!
}
}
if( port_id_valid ) // look for alpa first
{
if( pLoggedInPort->port_id == port_id )
break; // found it!
}
if( wwn_valid ) // look for wwn second
{
if( !memcmp( &pLoggedInPort->u.ucWWN[0], &wwn[0], 8))
{
// all 8 bytes of WWN match
break; // found it!
}
}
pLoggedInPort = pLoggedInPort->pNextPort; // try next port
}
return pLoggedInPort;
}
//
// We need to examine the SEST table and re-validate
// any open Exchanges for this LoggedInPort
// To make Tachyon pay attention, Freeze FCP assists,
// set VAL bits, Unfreeze FCP assists
static void RevalidateSEST( struct Scsi_Host *HostAdapter,
PFC_LOGGEDIN_PORT pLoggedInPort)
{
CPQFCHBA *cpqfcHBAdata = (CPQFCHBA *)HostAdapter->hostdata;
PTACHYON fcChip = &cpqfcHBAdata->fcChip;
FC_EXCHANGES *Exchanges = fcChip->Exchanges;
ULONG x_ID;
BOOLEAN TachFroze = FALSE;
// re-validate any SEST exchanges that are permitted
// to survive the link down (e.g., good PDISC performed)
for( x_ID = 0; x_ID < TACH_SEST_LEN; x_ID++)
{
// If the SEST entry port_id matches the pLoggedInPort,
// we need to re-validate
if( (Exchanges->fcExchange[ x_ID].type == SCSI_IRE)
||
(Exchanges->fcExchange[ x_ID].type == SCSI_IWE))
{
if( (Exchanges->fcExchange[ x_ID].fchs.d_id & 0xFFFFFF) // (24-bit port ID)
== pLoggedInPort->port_id)
{
// printk(" re-val xID %Xh ", x_ID);
if( !TachFroze ) // freeze if not already frozen
TachFroze |= FreezeTach( cpqfcHBAdata);
fcChip->SEST->u[ x_ID].IWE.Hdr_Len |= 0x80000000; // set VAL bit
}
}
}
if( TachFroze)
{
fcChip->UnFreezeTachyon( fcChip, 2); // both ERQ and FCP assists
}
}
// Complete an Linux Cmnds that we Queued because
// our FC link was down (cause immediate retry)
static void UnblockScsiDevice( struct Scsi_Host *HostAdapter,
PFC_LOGGEDIN_PORT pLoggedInPort)
{
CPQFCHBA *cpqfcHBAdata = (CPQFCHBA *)HostAdapter->hostdata;
Scsi_Cmnd* *SCptr = &cpqfcHBAdata->LinkDnCmnd[0];
Scsi_Cmnd *Cmnd;
int indx;
// if the device was previously "blocked", make sure
// we unblock it so Linux SCSI will resume
pLoggedInPort->device_blocked = FALSE; // clear our flag
// check the Link Down command ptr buffer;
// we can complete now causing immediate retry
for( indx=0; indx < CPQFCTS_REQ_QUEUE_LEN; indx++, SCptr++)
{
if( *SCptr != NULL ) // scsi command to complete?
{
#ifdef DUMMYCMND_DBG
printk("complete Cmnd %p in LinkDnCmnd[%d]\n", *SCptr,indx);
#endif
Cmnd = *SCptr;
// Are there any Q'd commands for this target?
if( (Cmnd->device->id == pLoggedInPort->ScsiNexus.target)
&&
(Cmnd->device->channel == pLoggedInPort->ScsiNexus.channel) )
{
Cmnd->result = (DID_SOFT_ERROR <<16); // force retry
if( Cmnd->scsi_done == NULL)
{
printk("LinkDnCmnd scsi_done ptr null, port_id %Xh\n",
pLoggedInPort->port_id);
}
else
call_scsi_done(Cmnd);
*SCptr = NULL; // free this slot for next use
}
}
}
}
//#define WWN_DBG 1
static void SetLoginFields(
PFC_LOGGEDIN_PORT pLoggedInPort,
TachFCHDR_GCMND* fchs,
BOOLEAN PDisc,
BOOLEAN Originator)
{
LOGIN_PAYLOAD logi; // FC-PH Port Login
PRLI_REQUEST prli; // copy for BIG ENDIAN switch
int i;
#ifdef WWN_DBG
ULONG ulBuff;
#endif
BigEndianSwap( (UCHAR*)&fchs->pl[0], (UCHAR*)&logi, sizeof(logi));
pLoggedInPort->Originator = Originator;
pLoggedInPort->port_id = fchs->s_id & 0xFFFFFF;
switch( fchs->pl[0] & 0xffff )
{
case 0x00000002: // PLOGI or PDISC ACCept?
if( PDisc ) // PDISC accept
goto PDISC_case;
case 0x00000003: // ELS_PLOGI or ELS_PLOGI_ACC
// Login BB_credit typically 0 for Tachyons
pLoggedInPort->BB_credit = logi.cmn_services.bb_credit;
// e.g. 128, 256, 1024, 2048 per FC-PH spec
// We have to use this when setting up SEST Writes,
// since that determines frame size we send.
pLoggedInPort->rx_data_size = logi.class3.rx_data_size;
pLoggedInPort->plogi = TRUE;
pLoggedInPort->pdisc = FALSE;
pLoggedInPort->prli = FALSE; // ELS_PLOGI resets
pLoggedInPort->flogi = FALSE; // ELS_PLOGI resets
pLoggedInPort->logo = FALSE; // ELS_PLOGI resets
pLoggedInPort->LOGO_counter = 0;// ELS_PLOGI resets
pLoggedInPort->LOGO_timer = 0;// ELS_PLOGI resets
// was this PLOGI to a Fabric?
if( pLoggedInPort->port_id == 0xFFFFFC ) // well know address
pLoggedInPort->flogi = TRUE;
for( i=0; i<8; i++) // copy the LOGIN port's WWN
pLoggedInPort->u.ucWWN[i] = logi.port_name[i];
#ifdef WWN_DBG
ulBuff = (ULONG)pLoggedInPort->u.liWWN;
if( pLoggedInPort->Originator)
printk("o");
else
printk("r");
printk("PLOGI port_id %Xh, WWN %08X",
pLoggedInPort->port_id, ulBuff);
ulBuff = (ULONG)(pLoggedInPort->u.liWWN >> 32);
printk("%08Xh fcPort %p\n", ulBuff, pLoggedInPort);
#endif
break;
case 0x00000005: // ELS_LOGO (logout)
pLoggedInPort->plogi = FALSE;
pLoggedInPort->pdisc = FALSE;
pLoggedInPort->prli = FALSE; // ELS_PLOGI resets
pLoggedInPort->flogi = FALSE; // ELS_PLOGI resets
pLoggedInPort->logo = TRUE; // ELS_PLOGI resets
pLoggedInPort->LOGO_counter++; // ELS_PLOGI resets
pLoggedInPort->LOGO_timer = 0;
#ifdef WWN_DBG
ulBuff = (ULONG)pLoggedInPort->u.liWWN;
if( pLoggedInPort->Originator)
printk("o");
else
printk("r");
printk("LOGO port_id %Xh, WWN %08X",
pLoggedInPort->port_id, ulBuff);
ulBuff = (ULONG)(pLoggedInPort->u.liWWN >> 32);
printk("%08Xh\n", ulBuff);
#endif
break;
PDISC_case:
case 0x00000050: // ELS_PDISC or ELS_PDISC_ACC
pLoggedInPort->LOGO_timer = 0; // stop the time-out
pLoggedInPort->prli = TRUE; // ready to accept FCP-SCSI I/O
#ifdef WWN_DBG
ulBuff = (ULONG)pLoggedInPort->u.liWWN;
if( pLoggedInPort->Originator)
printk("o");
else
printk("r");
printk("PDISC port_id %Xh, WWN %08X",
pLoggedInPort->port_id, ulBuff);
ulBuff = (ULONG)(pLoggedInPort->u.liWWN >> 32);
printk("%08Xh\n", ulBuff);
#endif
break;
case 0x1020L: // PRLI?
case 0x1002L: // PRLI ACCept?
BigEndianSwap( (UCHAR*)&fchs->pl[0], (UCHAR*)&prli, sizeof(prli));
pLoggedInPort->fcp_info = prli.fcp_info; // target/initiator flags
pLoggedInPort->prli = TRUE; // PLOGI resets, PDISC doesn't
pLoggedInPort->pdisc = TRUE; // expect to send (or receive) PDISC
// next time
pLoggedInPort->LOGO_timer = 0; // will be set next LinkDown
#ifdef WWN_DBG
ulBuff = (ULONG)pLoggedInPort->u.liWWN;
if( pLoggedInPort->Originator)
printk("o");
else
printk("r");
printk("PRLI port_id %Xh, WWN %08X",
pLoggedInPort->port_id, ulBuff);
ulBuff = (ULONG)(pLoggedInPort->u.liWWN >> 32);
printk("%08Xh\n", ulBuff);
#endif
break;
}
return;
}
static void BuildLinkServicePayload( PTACHYON fcChip, ULONG type, void* payload)
{
LOGIN_PAYLOAD *plogi; // FC-PH Port Login
LOGIN_PAYLOAD PlogiPayload; // copy for BIG ENDIAN switch
PRLI_REQUEST *prli; // FCP-SCSI Process Login
PRLI_REQUEST PrliPayload; // copy for BIG ENDIAN switch
LOGOUT_PAYLOAD *logo;
LOGOUT_PAYLOAD LogoutPayload;
// PRLO_REQUEST *prlo;
// PRLO_REQUEST PrloPayload;
REJECT_MESSAGE rjt, *prjt;
memset( &PlogiPayload, 0, sizeof( PlogiPayload));
plogi = &PlogiPayload; // load into stack buffer,
// then BIG-ENDIAN switch a copy to caller
switch( type ) // payload type can be ELS_PLOGI, ELS_PRLI, ADISC, ...
{
case ELS_FDISC:
case ELS_FLOGI:
case ELS_PLOGI_ACC: // FC-PH PORT Login Accept
case ELS_PLOGI: // FC-PH PORT Login
case ELS_PDISC: // FC-PH2 Port Discovery - same payload as ELS_PLOGI
plogi->login_cmd = LS_PLOGI;
if( type == ELS_PDISC)
plogi->login_cmd = LS_PDISC;
else if( type == ELS_PLOGI_ACC )
plogi->login_cmd = LS_ACC;
plogi->cmn_services.bb_credit = 0x00;
plogi->cmn_services.lowest_ver = fcChip->lowest_FCPH_ver;
plogi->cmn_services.highest_ver = fcChip->highest_FCPH_ver;
plogi->cmn_services.bb_rx_size = TACHLITE_TS_RX_SIZE;
plogi->cmn_services.common_features = CONTINUOSLY_INCREASING |
RANDOM_RELATIVE_OFFSET;
// fill in with World Wide Name based Port Name - 8 UCHARs
// get from Tach registers WWN hi & lo
LoadWWN( fcChip, plogi->port_name, 0);
// fill in with World Wide Name based Node/Fabric Name - 8 UCHARs
// get from Tach registers WWN hi & lo
LoadWWN( fcChip, plogi->node_name, 1);
// For Seagate Drives.
//
plogi->cmn_services.common_features |= 0x800;
plogi->cmn_services.rel_offset = 0xFE;
plogi->cmn_services.concurrent_seq = 1;
plogi->class1.service_options = 0x00;
plogi->class2.service_options = 0x00;
plogi->class3.service_options = CLASS_VALID;
plogi->class3.initiator_control = 0x00;
plogi->class3.rx_data_size = MAX_RX_PAYLOAD;
plogi->class3.recipient_control =
ERROR_DISCARD | ONE_CATEGORY_SEQUENCE;
plogi->class3.concurrent_sequences = 1;
plogi->class3.open_sequences = 1;
plogi->vendor_id[0] = 'C'; plogi->vendor_id[1] = 'Q';
plogi->vendor_version[0] = 'C'; plogi->vendor_version[1] = 'Q';
plogi->vendor_version[2] = ' '; plogi->vendor_version[3] = '0';
plogi->vendor_version[4] = '0'; plogi->vendor_version[5] = '0';
// FLOGI specific fields... (see FC-FLA, Rev 2.7, Aug 1999, sec 5.1)
if( (type == ELS_FLOGI) || (type == ELS_FDISC) )
{
if( type == ELS_FLOGI )
plogi->login_cmd = LS_FLOGI;
else
plogi->login_cmd = LS_FDISC;
plogi->cmn_services.lowest_ver = 0x20;
plogi->cmn_services.common_features = 0x0800;
plogi->cmn_services.rel_offset = 0;
plogi->cmn_services.concurrent_seq = 0;
plogi->class3.service_options = 0x8800;
plogi->class3.rx_data_size = 0;
plogi->class3.recipient_control = 0;
plogi->class3.concurrent_sequences = 0;
plogi->class3.open_sequences = 0;
}
// copy back to caller's buff, w/ BIG ENDIAN swap
BigEndianSwap( (UCHAR*)&PlogiPayload, payload, sizeof(PlogiPayload));
break;
case ELS_ACC: // generic Extended Link Service ACCept
plogi->login_cmd = LS_ACC;
// copy back to caller's buff, w/ BIG ENDIAN swap
BigEndianSwap( (UCHAR*)&PlogiPayload, payload, 4);
break;
case ELS_SCR: // Fabric State Change Registration
{
SCR_PL scr; // state change registration
memset( &scr, 0, sizeof(scr));
scr.command = LS_SCR; // 0x62000000
// see FC-FLA, Rev 2.7, Table A.22 (pg 82)
scr.function = 3; // 1 = Events detected by Fabric
// 2 = N_Port detected registration
// 3 = Full registration
// copy back to caller's buff, w/ BIG ENDIAN swap
BigEndianSwap( (UCHAR*)&scr, payload, sizeof(SCR_PL));
}
break;
case FCS_NSR: // Fabric Name Service Request
{
NSR_PL nsr; // Name Server Req. payload
memset( &nsr, 0, sizeof(NSR_PL));
// see Brocade Fabric Programming Guide,
// Rev 1.3, pg 4-44
nsr.CT_Rev = 0x01000000;
nsr.FCS_Type = 0xFC020000;
nsr.Command_code = 0x01710000;
nsr.FCP = 8;
// copy back to caller's buff, w/ BIG ENDIAN swap
BigEndianSwap( (UCHAR*)&nsr, payload, sizeof(NSR_PL));
}
break;
case ELS_LOGO: // FC-PH PORT LogOut
logo = &LogoutPayload; // load into stack buffer,
// then BIG-ENDIAN switch a copy to caller
logo->cmd = LS_LOGO;
// load the 3 UCHARs of the node name
// (if private loop, upper two UCHARs 0)
logo->reserved = 0;
logo->n_port_identifier[0] = (UCHAR)(fcChip->Registers.my_al_pa);
logo->n_port_identifier[1] =
(UCHAR)(fcChip->Registers.my_al_pa>>8);
logo->n_port_identifier[2] =
(UCHAR)(fcChip->Registers.my_al_pa>>16);
// fill in with World Wide Name based Port Name - 8 UCHARs
// get from Tach registers WWN hi & lo
LoadWWN( fcChip, logo->port_name, 0);
BigEndianSwap( (UCHAR*)&LogoutPayload,
payload, sizeof(LogoutPayload) ); // 16 UCHAR struct
break;
case ELS_LOGO_ACC: // Logout Accept (FH-PH pg 149, table 74)
logo = &LogoutPayload; // load into stack buffer,
// then BIG-ENDIAN switch a copy to caller
logo->cmd = LS_ACC;
BigEndianSwap( (UCHAR*)&LogoutPayload, payload, 4 ); // 4 UCHAR cmnd
break;
case ELS_RJT: // ELS_RJT link service reject (FH-PH pg 155)
prjt = (REJECT_MESSAGE*)payload; // pick up passed data
rjt.command_code = ELS_RJT;
// reverse fields, because of Swap that follows...
rjt.vendor = prjt->reserved; // vendor specific
rjt.explain = prjt->reason; //
rjt.reason = prjt->explain; //
rjt.reserved = prjt->vendor; //
// BIG-ENDIAN switch a copy to caller
BigEndianSwap( (UCHAR*)&rjt, payload, 8 ); // 8 UCHAR cmnd
break;
case ELS_PRLI_ACC: // Process Login ACCept
case ELS_PRLI: // Process Login
case ELS_PRLO: // Process Logout
memset( &PrliPayload, 0, sizeof( PrliPayload));
prli = &PrliPayload; // load into stack buffer,
if( type == ELS_PRLI )
prli->cmd = 0x20; // Login
else if( type == ELS_PRLO )
prli->cmd = 0x21; // Logout
else if( type == ELS_PRLI_ACC )
{
prli->cmd = 0x02; // Login ACCept
prli->valid = REQUEST_EXECUTED;
}
prli->valid |= SCSI_FCP | ESTABLISH_PAIR;
prli->fcp_info = READ_XFER_RDY;
prli->page_length = 0x10;
prli->payload_length = 20;
// Can be initiator AND target
if( fcChip->Options.initiator )
prli->fcp_info |= INITIATOR_FUNCTION;
if( fcChip->Options.target )
prli->fcp_info |= TARGET_FUNCTION;
BigEndianSwap( (UCHAR*)&PrliPayload, payload, prli->payload_length);
break;
default: // no can do - programming error
printk(" BuildLinkServicePayload unknown!\n");
break;
}
}
// loads 8 UCHARs for PORT name or NODE name base on
// controller's WWN.
void LoadWWN( PTACHYON fcChip, UCHAR* dest, UCHAR type)
{
UCHAR* bPtr, i;
switch( type )
{
case 0: // Port_Name
bPtr = (UCHAR*)&fcChip->Registers.wwn_hi;
for( i =0; i<4; i++)
dest[i] = *bPtr++;
bPtr = (UCHAR*)&fcChip->Registers.wwn_lo;
for( i =4; i<8; i++)
dest[i] = *bPtr++;
break;
case 1: // Node/Fabric _Name
bPtr = (UCHAR*)&fcChip->Registers.wwn_hi;
for( i =0; i<4; i++)
dest[i] = *bPtr++;
bPtr = (UCHAR*)&fcChip->Registers.wwn_lo;
for( i =4; i<8; i++)
dest[i] = *bPtr++;
break;
}
}
// We check the Port Login payload for required values. Note that
// ELS_PLOGI and ELS_PDISC (Port DISCover) use the same payload.
int verify_PLOGI( PTACHYON fcChip,
TachFCHDR_GCMND* fchs,
ULONG* reject_explain)
{
LOGIN_PAYLOAD login;
// source, dest, len (should be mult. of 4)
BigEndianSwap( (UCHAR*)&fchs->pl[0], (UCHAR*)&login, sizeof(login));
// check FC version
// if other port's highest supported version
// is less than our lowest, and
// if other port's lowest
if( login.cmn_services.highest_ver < fcChip->lowest_FCPH_ver ||
login.cmn_services.lowest_ver > fcChip->highest_FCPH_ver )
{
*reject_explain = LS_RJT_REASON( LOGICAL_ERROR, OPTIONS_ERROR);
return LOGICAL_ERROR;
}
// Receive Data Field Size must be >=128
// per FC-PH
if (login.cmn_services.bb_rx_size < 128)
{
*reject_explain = LS_RJT_REASON( LOGICAL_ERROR, DATA_FIELD_SIZE_ERROR);
return LOGICAL_ERROR;
}
// Only check Class 3 params
if( login.class3.service_options & CLASS_VALID)
{
if (login.class3.rx_data_size < 128)
{
*reject_explain = LS_RJT_REASON( LOGICAL_ERROR, INVALID_CSP);
return LOGICAL_ERROR;
}
if( login.class3.initiator_control & XID_REQUIRED)
{
*reject_explain = LS_RJT_REASON( LOGICAL_ERROR, INITIATOR_CTL_ERROR);
return LOGICAL_ERROR;
}
}
return 0; // success
}
int verify_PRLI( TachFCHDR_GCMND* fchs, ULONG* reject_explain)
{
PRLI_REQUEST prli; // buffer for BIG ENDIAN
// source, dest, len (should be mult. of 4)
BigEndianSwap( (UCHAR*)&fchs->pl[0], (UCHAR*)&prli, sizeof(prli));
if( prli.fcp_info == 0 ) // i.e., not target or initiator?
{
*reject_explain = LS_RJT_REASON( LOGICAL_ERROR, OPTIONS_ERROR);
return LOGICAL_ERROR;
}
return 0; // success
}
// SWAP UCHARs as required by Fibre Channel (i.e. BIG ENDIAN)
// INPUTS:
// source - ptr to LITTLE ENDIAN ULONGS
// cnt - number of UCHARs to switch (should be mult. of ULONG)
// OUTPUTS:
// dest - ptr to BIG ENDIAN copy
// RETURN:
// none
//
void BigEndianSwap( UCHAR *source, UCHAR *dest, USHORT cnt)
{
int i,j;
source+=3; // start at MSB of 1st ULONG
for( j=0; j < cnt; j+=4, source+=4, dest+=4) // every ULONG
{
for( i=0; i<4; i++) // every UCHAR in ULONG
*(dest+i) = *(source-i);
}
}
// Build FC Exchanges............
static void buildFCPstatus(
PTACHYON fcChip,
ULONG ExchangeID);
static LONG FindFreeExchange( PTACHYON fcChip, ULONG type );
static ULONG build_SEST_sgList(
struct pci_dev *pcidev,
ULONG *SESTalPairStart,
Scsi_Cmnd *Cmnd,
ULONG *sgPairs,
PSGPAGES *sgPages_head // link list of TL Ext. S/G pages from O/S Pool
);
static int build_FCP_payload( Scsi_Cmnd *Cmnd,
UCHAR* payload, ULONG type, ULONG fcp_dl );
/*
IRB
ERQ __________________
| | / | Req_A_SFS_Len | ____________________
|----------| / | Req_A_SFS_Addr |------->| Reserved |
| IRB | / | Req_A_D_ID | | SOF EOF TimeStamp |
|-----------/ | Req_A_SEST_Index |-+ | R_CTL | D_ID |
| IRB | | Req_B... | | | CS_CTL| S_ID |
|-----------\ | | | | TYPE | F_CTL |
| IRB | \ | | | | SEQ_ID | SEQ_CNT |
|----------- \ | | +-->+--| OX_ID | RX_ID |
| | \ |__________________| | | RO |
| | pl (payload/cmnd) |
| | ..... |
| |___________________|
|
|
+-------------------------------------------+
|
|
| e.g. IWE
| SEST __________________ for FCP_DATA
| | | / | | Hdr_Len | ____________________
| |----------| / | Hdr_Addr_Addr |------->| Reserved |
| | [0] | / |Remote_ID| RSP_Len| | SOF EOF TimeStamp |
| |-----------/ | RSP_Addr |---+ | R_CTL | D_ID |
+-> [1] | | | Buff_Off | | | CS_CTL| S_ID |
|-----------\ |BuffIndex| Link | | | TYPE | F_CTL |
| [2] | \ | Rsvd | RX_ID | | | SEQ_ID | SEQ_CNT |
|----------- \ | Data_Len | | | OX_ID | RX_ID |
| ... | \ | Exp_RO | | | RO |
|----------| | Exp_Byte_Cnt | | |___________________|
| SEST_LEN | +--| Len | |
|__________| | | Address | |
| | ... | | for FCP_RSP
| |__________________| | ____________________
| +----| Reserved |
| | SOF EOF TimeStamp |
| | R_CTL | D_ID |
| | CS_CTL| S_ID |
+--- local or extended | .... |
scatter/gather lists
defining upper-layer
data (e.g. from user's App)
*/
// All TachLite commands must start with a SFS (Single Frame Sequence)
// command. In the simplest case (a NOP Basic Link command),
// only one frame header and ERQ entry is required. The most complex
// case is the SCSI assisted command, which requires an ERQ entry,
// SEST entry, and several frame headers and data buffers all
// logically linked together.
// Inputs:
// cpqfcHBAdata - controller struct
// type - PLOGI, SCSI_IWE, etc.
// InFCHS - Incoming Tachlite FCHS which prompted this exchange
// (only s_id set if we are originating)
// Data - PVOID to data struct consistent with "type"
// fcExchangeIndex - pointer to OX/RD ID value of built exchange
// Return:
// fcExchangeIndex - OX/RD ID value if successful
// 0 - success
// INVALID_ARGS - NULL/ invalid passed args
// BAD_ALPA - Bad source al_pa address
// LNKDWN_OSLS - Link Down (according to this controller)
// OUTQUE_FULL - Outbound Que full
// DRIVERQ_FULL - controller's Exchange array full
// SEST_FULL - SEST table full
//
// Remarks:
// Psuedo code:
// Check for NULL pointers / bad args
// Build outgoing FCHS - the header/payload struct
// Build IRB (for ERQ entry)
// if SCSI command, build SEST entry (e.g. IWE, TRE,...)
// return success
//sbuildex
ULONG cpqfcTSBuildExchange(
CPQFCHBA *cpqfcHBAdata,
ULONG type, // e.g. PLOGI
TachFCHDR_GCMND* InFCHS, // incoming FCHS
void *Data, // the CDB, scatter/gather, etc.
LONG *fcExchangeIndex ) // points to allocated exchange,
{
PTACHYON fcChip = &cpqfcHBAdata->fcChip;
FC_EXCHANGES *Exchanges = fcChip->Exchanges;
ULONG ulStatus = 0; // assume OK
USHORT ox_ID, rx_ID=0xFFFF;
ULONG SfsLen=0L;
TachLiteIRB* pIRB;
IRBflags IRB_flags;
UCHAR *pIRB_flags = (UCHAR*)&IRB_flags;
TachFCHDR_GCMND* CMDfchs;
TachFCHDR* dataHDR; // 32 byte HEADER ONLY FCP-DATA buffer
TachFCHDR_RSP* rspHDR; // 32 byte header + RSP payload
Scsi_Cmnd *Cmnd = (Scsi_Cmnd*)Data; // Linux Scsi CDB, S/G, ...
TachLiteIWE* pIWE;
TachLiteIRE* pIRE;
TachLiteTWE* pTWE;
TachLiteTRE* pTRE;
ULONG fcp_dl; // total byte length of DATA transferred
ULONG fl; // frame length (FC frame size, 128, 256, 512, 1024)
ULONG sgPairs; // number of valid scatter/gather pairs
int FCP_SCSI_command;
BA_ACC_PAYLOAD *ba_acc;
BA_RJT_PAYLOAD *ba_rjt;
// check passed ARGS
if( !fcChip->ERQ ) // NULL ptr means uninitialized Tachlite chip
return INVALID_ARGS;
if( type == SCSI_IRE ||
type == SCSI_TRE ||
type == SCSI_IWE ||
type == SCSI_TWE)
FCP_SCSI_command = 1;
else
FCP_SCSI_command = 0;
// for commands that pass payload data (e.g. SCSI write)
// examine command struct - verify that the
// length of s/g buffers is adequate for total payload
// length (end of list is NULL address)
if( FCP_SCSI_command )
{
if( Data ) // must have data descriptor (S/G list -- at least
// one address with at least 1 byte of data)
{
// something to do (later)?
}
else
return INVALID_ARGS; // invalid DATA ptr
}
// we can build an Exchange for later Queuing (on the TL chip)
// if an empty slot is available in the DevExt for this controller
// look for available Exchange slot...
if( type != FCP_RESPONSE &&
type != BLS_ABTS &&
type != BLS_ABTS_ACC ) // already have Exchange slot!
*fcExchangeIndex = FindFreeExchange( fcChip, type );
if( *fcExchangeIndex != -1 ) // Exchange is available?
{
// assign tmp ptr (shorthand)
CMDfchs = &Exchanges->fcExchange[ *fcExchangeIndex].fchs;
if( Cmnd != NULL ) // (necessary for ABTS cases)
{
Exchanges->fcExchange[ *fcExchangeIndex].Cmnd = Cmnd; // Linux Scsi
Exchanges->fcExchange[ *fcExchangeIndex].pLoggedInPort =
fcFindLoggedInPort( fcChip,
Exchanges->fcExchange[ *fcExchangeIndex].Cmnd, // find Scsi Nexus
0, // DON'T search linked list for FC port id
NULL, // DON'T search linked list for FC WWN
NULL); // DON'T care about end of list
}
// Build the command frame header (& data) according
// to command type
// fields common for all SFS frame types
CMDfchs->reserved = 0L; // must clear
CMDfchs->sof_eof = 0x75000000L; // SOFi3:EOFn no UAM; LCr=0, no TS
// get the destination port_id from incoming FCHS
// (initialized before calling if we're Originator)
// Frame goes to port it was from - the source_id
CMDfchs->d_id = InFCHS->s_id &0xFFFFFF; // destination (add R_CTL later)
CMDfchs->s_id = fcChip->Registers.my_al_pa; // CS_CTL = 0
// now enter command-specific fields
switch( type )
{
case BLS_NOP: // FC defined basic link service command NO-OP
// ensure unique X_IDs! (use tracking function)
*pIRB_flags = 0; // clear IRB flags
IRB_flags.SFA = 1; // send SFS (not SEST index)
SfsLen = *pIRB_flags;
SfsLen <<= 24; // shift flags to MSB
SfsLen += 32L; // add len to LSB (header only - no payload)
// TYPE[31-24] 00 Basic Link Service
// f_ctl[23:0] exchg originator, 1st seq, xfer S.I.
CMDfchs->d_id |= 0x80000000L; // R_CTL = 80 for NOP (Basic Link Ser.)
CMDfchs->f_ctl = 0x00310000L; // xchng originator, 1st seq,....
CMDfchs->seq_cnt = 0x0L;
CMDfchs->ox_rx_id = 0xFFFF; // RX_ID for now; OX_ID on start
CMDfchs->ro = 0x0L; // relative offset (n/a)
CMDfchs->pl[0] = 0xaabbccddL; // words 8-15 frame data payload (n/a)
Exchanges->fcExchange[ *fcExchangeIndex].timeOut = 1; // seconds
// (NOP should complete ~instantly)
break;
case BLS_ABTS_ACC: // Abort Sequence ACCept
*pIRB_flags = 0; // clear IRB flags
IRB_flags.SFA = 1; // send SFS (not SEST index)
SfsLen = *pIRB_flags;
SfsLen <<= 24; // shift flags to MSB
SfsLen += 32 + 12; // add len to LSB (header + 3 DWORD payload)
CMDfchs->d_id |= 0x84000000L; // R_CTL = 84 for BASIC ACCept
// TYPE[31-24] 00 Basic Link Service
// f_ctl[23:0] exchg originator, not 1st seq, xfer S.I.
CMDfchs->f_ctl = 0x00910000L; // xchnge responder, last seq, xfer SI
// CMDfchs->seq_id & count might be set from DataHdr?
CMDfchs->ro = 0x0L; // relative offset (n/a)
Exchanges->fcExchange[ *fcExchangeIndex].timeOut = 5; // seconds
// (Timeout in case of weird error)
// now set the ACCept payload...
ba_acc = (BA_ACC_PAYLOAD*)&CMDfchs->pl[0];
memset( ba_acc, 0, sizeof( BA_ACC_PAYLOAD));
// Since PLDA requires (only) entire Exchange aborts, we don't need
// to worry about what the last sequence was.
// We expect that a "target" task is accepting the abort, so we
// can use the OX/RX ID pair
ba_acc->ox_rx_id = CMDfchs->ox_rx_id;
// source, dest, #bytes
BigEndianSwap((UCHAR *)&CMDfchs->ox_rx_id, (UCHAR *)&ba_acc->ox_rx_id, 4);
ba_acc->low_seq_cnt = 0;
ba_acc->high_seq_cnt = 0xFFFF;
break;
case BLS_ABTS_RJT: // Abort Sequence ACCept
*pIRB_flags = 0; // clear IRB flags
IRB_flags.SFA = 1; // send SFS (not SEST index)
SfsLen = *pIRB_flags;
SfsLen <<= 24; // shift flags to MSB
SfsLen += 32 + 12; // add len to LSB (header + 3 DWORD payload)
CMDfchs->d_id |= 0x85000000L; // R_CTL = 85 for BASIC ReJecT
// f_ctl[23:0] exchg originator, not 1st seq, xfer S.I.
// TYPE[31-24] 00 Basic Link Service
CMDfchs->f_ctl = 0x00910000L; // xchnge responder, last seq, xfer SI
// CMDfchs->seq_id & count might be set from DataHdr?
CMDfchs->ro = 0x0L; // relative offset (n/a)
Exchanges->fcExchange[ *fcExchangeIndex].timeOut = 5; // seconds
// (Timeout in case of weird error)
CMDfchs->ox_rx_id = InFCHS->ox_rx_id; // copy from sender!
// now set the ReJecT payload...
ba_rjt = (BA_RJT_PAYLOAD*)&CMDfchs->pl[0];
memset( ba_rjt, 0, sizeof( BA_RJT_PAYLOAD));
// We expect that a "target" task couldn't find the Exhange in the
// array of active exchanges, so we use a new LinkService X_ID.
// See Reject payload description in FC-PH (Rev 4.3), pg. 140
ba_rjt->reason_code = 0x09; // "unable to perform command request"
ba_rjt->reason_explain = 0x03; // invalid OX/RX ID pair
break;
case BLS_ABTS: // FC defined basic link service command ABTS
// Abort Sequence
*pIRB_flags = 0; // clear IRB flags
IRB_flags.SFA = 1; // send SFS (not SEST index)
SfsLen = *pIRB_flags;
SfsLen <<= 24; // shift flags to MSB
SfsLen += 32L; // add len to LSB (header only - no payload)
// TYPE[31-24] 00 Basic Link Service
// f_ctl[23:0] exchg originator, not 1st seq, xfer S.I.
CMDfchs->d_id |= 0x81000000L; // R_CTL = 81 for ABTS
CMDfchs->f_ctl = 0x00110000L; // xchnge originator, last seq, xfer SI
// CMDfchs->seq_id & count might be set from DataHdr?
CMDfchs->ro = 0x0L; // relative offset (n/a)
Exchanges->fcExchange[ *fcExchangeIndex].timeOut = 2; // seconds
// (ABTS must timeout when responder is gone)
break;
case FCS_NSR: // Fabric Name Service Request
Exchanges->fcExchange[ *fcExchangeIndex].reTries = 2;
Exchanges->fcExchange[ *fcExchangeIndex].timeOut = 2; // seconds
// OX_ID, linked to Driver Transaction ID
// (fix-up at Queing time)
CMDfchs->ox_rx_id = 0xFFFF; // RX_ID - Responder (target) to modify
// OX_ID set at ERQueing time
*pIRB_flags = 0; // clear IRB flags
IRB_flags.SFA = 1; // send SFS (not SEST index)
SfsLen = *pIRB_flags;
SfsLen <<= 24; // shift flags to MSB
SfsLen += (32L + sizeof(NSR_PL)); // add len (header & NSR payload)
CMDfchs->d_id |= 0x02000000L; // R_CTL = 02 for -
// Name Service Request: Unsolicited
// TYPE[31-24] 01 Extended Link Service
// f_ctl[23:0] exchg originator, 1st seq, xfer S.I.
CMDfchs->f_ctl = 0x20210000L;
// OX_ID will be fixed-up at Tachyon enqueing time
CMDfchs->seq_cnt = 0; // seq ID, DF_ctl, seq cnt
CMDfchs->ro = 0x0L; // relative offset (n/a)
BuildLinkServicePayload( fcChip, type, &CMDfchs->pl[0]);
break;
case ELS_PLOGI: // FC-PH extended link service command Port Login
// (May, 2000)
// NOTE! This special case facilitates SANMark testing. The SANMark
// test script for initialization-timeout.fcal.SANMark-1.fc
// "eats" the OPN() primitive without issuing an R_RDY, causing
// Tachyon to report LST (loop state timeout), which causes a
// LIP. To avoid this, simply send out the frame (i.e. assuming a
// buffer credit of 1) without waiting for R_RDY. Many FC devices
// (other than Tachyon) have been doing this for years. We don't
// ever want to do this for non-Link Service frames unless the
// other device really did report non-zero login BB credit (i.e.
// in the PLOGI ACCept frame).
// CMDfchs->sof_eof |= 0x00000400L; // LCr=1
case ELS_FDISC: // Fabric Discovery (Login)
case ELS_FLOGI: // Fabric Login
case ELS_SCR: // Fabric State Change Registration
case ELS_LOGO: // FC-PH extended link service command Port Logout
case ELS_PDISC: // FC-PH extended link service cmnd Port Discovery
case ELS_PRLI: // FC-PH extended link service cmnd Process Login
Exchanges->fcExchange[ *fcExchangeIndex].reTries = 2;
Exchanges->fcExchange[ *fcExchangeIndex].timeOut = 2; // seconds
// OX_ID, linked to Driver Transaction ID
// (fix-up at Queing time)
CMDfchs->ox_rx_id = 0xFFFF; // RX_ID - Responder (target) to modify
// OX_ID set at ERQueing time
*pIRB_flags = 0; // clear IRB flags
IRB_flags.SFA = 1; // send SFS (not SEST index)
SfsLen = *pIRB_flags;
SfsLen <<= 24; // shift flags to MSB
if( type == ELS_LOGO )
SfsLen += (32L + 16L); // add len (header & PLOGI payload)
else if( type == ELS_PRLI )
SfsLen += (32L + 20L); // add len (header & PRLI payload)
else if( type == ELS_SCR )
SfsLen += (32L + sizeof(SCR_PL)); // add len (header & SCR payload)
else
SfsLen += (32L + 116L); // add len (header & PLOGI payload)
CMDfchs->d_id |= 0x22000000L; // R_CTL = 22 for -
// Extended Link_Data: Unsolicited Control
// TYPE[31-24] 01 Extended Link Service
// f_ctl[23:0] exchg originator, 1st seq, xfer S.I.
CMDfchs->f_ctl = 0x01210000L;
// OX_ID will be fixed-up at Tachyon enqueing time
CMDfchs->seq_cnt = 0; // seq ID, DF_ctl, seq cnt
CMDfchs->ro = 0x0L; // relative offset (n/a)
BuildLinkServicePayload( fcChip, type, &CMDfchs->pl[0]);
break;
case ELS_LOGO_ACC: // FC-PH extended link service logout accept
case ELS_RJT: // extended link service reject (add reason)
case ELS_ACC: // ext. link service generic accept
case ELS_PLOGI_ACC:// ext. link service login accept (PLOGI or PDISC)
case ELS_PRLI_ACC: // ext. link service process login accept
Exchanges->fcExchange[ *fcExchangeIndex].timeOut = 1; // assume done
// ensure unique X_IDs! (use tracking function)
// OX_ID from initiator cmd
ox_ID = (USHORT)(InFCHS->ox_rx_id >> 16);
rx_ID = 0xFFFF; // RX_ID, linked to Driver Exchange ID
*pIRB_flags = 0; // clear IRB flags
IRB_flags.SFA = 1; // send SFS (not SEST index)
SfsLen = *pIRB_flags;
SfsLen <<= 24; // shift flags to MSB
if( type == ELS_RJT )
{
SfsLen += (32L + 8L); // add len (header + payload)
// ELS_RJT reason codes (utilize unused "reserved" field)
CMDfchs->pl[0] = 1;
CMDfchs->pl[1] = InFCHS->reserved;
}
else if( (type == ELS_LOGO_ACC) || (type == ELS_ACC) )
SfsLen += (32L + 4L); // add len (header + payload)
else if( type == ELS_PLOGI_ACC )
SfsLen += (32L + 116L); // add len (header + payload)
else if( type == ELS_PRLI_ACC )
SfsLen += (32L + 20L); // add len (header + payload)
CMDfchs->d_id |= 0x23000000L; // R_CTL = 23 for -
// Extended Link_Data: Control Reply
// TYPE[31-24] 01 Extended Link Service
// f_ctl[23:0] exchg responder, last seq, e_s, tsi
CMDfchs->f_ctl = 0x01990000L;
CMDfchs->seq_cnt = 0x0L;
CMDfchs->ox_rx_id = 0L; // clear
CMDfchs->ox_rx_id = ox_ID; // load upper 16 bits
CMDfchs->ox_rx_id <<= 16; // shift them
CMDfchs->ro = 0x0L; // relative offset (n/a)
BuildLinkServicePayload( fcChip, type, &CMDfchs->pl[0]);
break;
// Fibre Channel SCSI 'originator' sequences...
// (originator means 'initiator' in FCP-SCSI)
case SCSI_IWE: // TachLite Initiator Write Entry
{
PFC_LOGGEDIN_PORT pLoggedInPort =
Exchanges->fcExchange[ *fcExchangeIndex].pLoggedInPort;
Exchanges->fcExchange[ *fcExchangeIndex].reTries = 1;
Exchanges->fcExchange[ *fcExchangeIndex].timeOut = 7; // FC2 timeout
// first, build FCP_CMND
// unique X_ID fix-ups in StartExchange
*pIRB_flags = 0; // clear IRB flags
IRB_flags.SFA = 1; // send SFS FCP-CMND (not SEST index)
// NOTE: unlike FC LinkService login frames, normal
// SCSI commands are sent without outgoing verification
IRB_flags.DCM = 1; // Disable completion message for Cmnd frame
SfsLen = *pIRB_flags;
SfsLen <<= 24; // shift flags to MSB
SfsLen += 64L; // add len to LSB (header & CMND payload)
CMDfchs->d_id |= (0x06000000L); // R_CTL = 6 for command
// TYPE[31-24] 8 for FCP SCSI
// f_ctl[23:0] exchg originator, 1st seq, xfer S.I.
// valid RO
CMDfchs->f_ctl = 0x08210008L;
CMDfchs->seq_cnt = 0x0L;
CMDfchs->ox_rx_id = 0L; // clear for now (-or- in later)
CMDfchs->ro = 0x0L; // relative offset (n/a)
// now, fill out FCP-DATA header
// (use buffer inside SEST object)
dataHDR = &fcChip->SEST->DataHDR[ *fcExchangeIndex ];
dataHDR->reserved = 0L; // must clear
dataHDR->sof_eof = 0x75002000L; // SOFi3:EOFn no UAM; no CLS, noLCr, no TS
dataHDR->d_id = (InFCHS->s_id | 0x01000000L); // R_CTL= FCP_DATA
dataHDR->s_id = fcChip->Registers.my_al_pa; // CS_CTL = 0
// TYPE[31-24] 8 for FCP SCSI
// f_ctl[23:0] xfer S.I.| valid RO
dataHDR->f_ctl = 0x08010008L;
dataHDR->seq_cnt = 0x02000000L; // sequence ID: df_ctl : seqence count
dataHDR->ox_rx_id = 0L; // clear; fix-up dataHDR fields later
dataHDR->ro = 0x0L; // relative offset (n/a)
// Now setup the SEST entry
pIWE = &fcChip->SEST->u[ *fcExchangeIndex ].IWE;
// fill out the IWE:
// VALid entry:Dir outbound:DCM:enable CM:enal INT: FC frame len
pIWE->Hdr_Len = 0x8e000020L; // data frame Len always 32 bytes
// from login parameters with other port, what's the largest frame
// we can send?
if( pLoggedInPort == NULL)
{
ulStatus = INVALID_ARGS; // failed! give up
break;
}
if( pLoggedInPort->rx_data_size >= 2048)
fl = 0x00020000; // 2048 code (only support 1024!)
else if( pLoggedInPort->rx_data_size >= 1024)
fl = 0x00020000; // 1024 code
else if( pLoggedInPort->rx_data_size >= 512)
fl = 0x00010000; // 512 code
else
fl = 0; // 128 bytes -- should never happen
pIWE->Hdr_Len |= fl; // add xmit FC frame len for data phase
pIWE->Hdr_Addr = fcChip->SEST->base +
((unsigned long)&fcChip->SEST->DataHDR[*fcExchangeIndex] -
(unsigned long)fcChip->SEST);
pIWE->RSP_Len = sizeof(TachFCHDR_RSP) ; // hdr+data (recv'd RSP frame)
pIWE->RSP_Len |= (InFCHS->s_id << 8); // MS 24 bits Remote_ID
memset( &fcChip->SEST->RspHDR[ *fcExchangeIndex].pl, 0,
sizeof( FCP_STATUS_RESPONSE) ); // clear out previous status
pIWE->RSP_Addr = fcChip->SEST->base +
((unsigned long)&fcChip->SEST->RspHDR[*fcExchangeIndex] -
(unsigned long)fcChip->SEST);
// Do we need local or extended gather list?
// depends on size - we can handle 3 len/addr pairs
// locally.
fcp_dl = build_SEST_sgList(
cpqfcHBAdata->PciDev,
&pIWE->GLen1,
Cmnd, // S/G list
&sgPairs, // return # of pairs in S/G list (from "Data" descriptor)
&fcChip->SEST->sgPages[ *fcExchangeIndex ]);// (for Freeing later)
if( !fcp_dl ) // error building S/G list?
{
ulStatus = MEMPOOL_FAIL;
break; // give up
}
// Now that we know total data length in
// the passed S/G buffer, set FCP CMND frame
build_FCP_payload( Cmnd, (UCHAR*)&CMDfchs->pl[0], type, fcp_dl );
if( sgPairs > 3 ) // need extended s/g list
pIWE->Buff_Off = 0x78000000L; // extended data | (no offset)
else // local data pointers (in SEST)
pIWE->Buff_Off = 0xf8000000L; // local data | (no offset)
// ULONG 5
pIWE->Link = 0x0000ffffL; // Buff_Index | Link
pIWE->RX_ID = 0x0L; // DWord 6: RX_ID set by target XFER_RDY
// DWord 7
pIWE->Data_Len = 0L; // TL enters rcv'd XFER_RDY BURST_LEN
pIWE->Exp_RO = 0L; // DWord 8
// DWord 9
pIWE->Exp_Byte_Cnt = fcp_dl; // sum of gather buffers
}
break;
case SCSI_IRE: // TachLite Initiator Read Entry
if( Cmnd->timeout != 0)
{
// printk("Cmnd->timeout %d\n", Cmnd->timeout);
// per Linux Scsi
Exchanges->fcExchange[ *fcExchangeIndex].timeOut = Cmnd->timeout;
}
else // use our best guess, based on FC & device
{
if( Cmnd->SCp.Message == 1 ) // Tape device? (from INQUIRY)
{
// turn off our timeouts (for now...)
Exchanges->fcExchange[ *fcExchangeIndex].timeOut = 0xFFFFFFFF;
}
else
{
Exchanges->fcExchange[ *fcExchangeIndex].reTries = 1;
Exchanges->fcExchange[ *fcExchangeIndex].timeOut = 7; // per SCSI req.
}
}
// first, build FCP_CMND
*pIRB_flags = 0; // clear IRB flags
IRB_flags.SFA = 1; // send SFS FCP-CMND (not SEST index)
// NOTE: unlike FC LinkService login frames,
// normal SCSI commands are sent "open loop"
IRB_flags.DCM = 1; // Disable completion message for Cmnd frame
SfsLen = *pIRB_flags;
SfsLen <<= 24; // shift flags to MSB
SfsLen += 64L; // add len to LSB (header & CMND payload)
CMDfchs->d_id |= (0x06000000L); // R_CTL = 6 for command
// TYPE[31-24] 8 for FCP SCSI
// f_ctl[23:0] exchg originator, 1st seq, xfer S.I.
// valid RO
CMDfchs->f_ctl = 0x08210008L;
CMDfchs->seq_cnt = 0x0L;
// x_ID & data direction bit set later
CMDfchs->ox_rx_id = 0xFFFF; // clear
CMDfchs->ro = 0x0L; // relative offset (n/a)
// Now setup the SEST entry
pIRE = &fcChip->SEST->u[ *fcExchangeIndex ].IRE;
// fill out the IRE:
// VALid entry:Dir outbound:enable CM:enal INT:
pIRE->Seq_Accum = 0xCE000000L; // VAL,DIR inbound,DCM| INI,DAT,RSP
pIRE->reserved = 0L;
pIRE->RSP_Len = sizeof(TachFCHDR_RSP) ; // hdr+data (recv'd RSP frame)
pIRE->RSP_Len |= (InFCHS->s_id << 8); // MS 24 bits Remote_ID
pIRE->RSP_Addr = fcChip->SEST->base +
((unsigned long)&fcChip->SEST->RspHDR[*fcExchangeIndex] -
(unsigned long)fcChip->SEST);
// Do we need local or extended gather list?
// depends on size - we can handle 3 len/addr pairs
// locally.
fcp_dl = build_SEST_sgList(
cpqfcHBAdata->PciDev,
&pIRE->SLen1,
Cmnd, // SCSI command Data desc. with S/G list
&sgPairs, // return # of pairs in S/G list (from "Data" descriptor)
&fcChip->SEST->sgPages[ *fcExchangeIndex ]);// (for Freeing later)
if( !fcp_dl ) // error building S/G list?
{
// It is permissible to have a ZERO LENGTH Read command.
// If there is the case, simply set fcp_dl (and Exp_Byte_Cnt)
// to 0 and continue.
if( Cmnd->request_bufflen == 0 )
{
fcp_dl = 0; // no FC DATA frames expected
}
else
{
ulStatus = MEMPOOL_FAIL;
break; // give up
}
}
// now that we know the S/G length, build CMND payload
build_FCP_payload( Cmnd, (UCHAR*)&CMDfchs->pl[0], type, fcp_dl );
if( sgPairs > 3 ) // need extended s/g list
pIRE->Buff_Off = 0x00000000; // DWord 4: extended s/g list, no offset
else
pIRE->Buff_Off = 0x80000000; // local data, no offset
pIRE->Buff_Index = 0x0L; // DWord 5: Buff_Index | Reserved
pIRE->Exp_RO = 0x0L; // DWord 6: Expected Rel. Offset
pIRE->Byte_Count = 0; // DWord 7: filled in by TL on err
pIRE->reserved_ = 0; // DWord 8: reserved
// NOTE: 0 length READ is OK.
pIRE->Exp_Byte_Cnt = fcp_dl;// DWord 9: sum of scatter buffers
break;
// Fibre Channel SCSI 'responder' sequences...
// (originator means 'target' in FCP-SCSI)
case SCSI_TWE: // TachLite Target Write Entry
Exchanges->fcExchange[ *fcExchangeIndex].timeOut = 10; // per SCSI req.
// first, build FCP_CMND
*pIRB_flags = 0; // clear IRB flags
IRB_flags.SFA = 1; // send SFS (XFER_RDY)
SfsLen = *pIRB_flags;
SfsLen <<= 24; // shift flags to MSB
SfsLen += (32L + 12L);// add SFS len (header & XFER_RDY payload)
CMDfchs->d_id |= (0x05000000L); // R_CTL = 5 for XFER_RDY
// TYPE[31-24] 8 for FCP SCSI
// f_ctl[23:0] exchg responder, 1st seq, xfer S.I.
// valid RO
CMDfchs->f_ctl = 0x08810008L;
CMDfchs->seq_cnt = 0x01000000; // sequence ID: df_ctl: sequence count
// use originator (other port's) OX_ID
CMDfchs->ox_rx_id = InFCHS->ox_rx_id; // we want upper 16 bits
CMDfchs->ro = 0x0L; // relative offset (n/a)
// now, fill out FCP-RSP header
// (use buffer inside SEST object)
rspHDR = &fcChip->SEST->RspHDR[ *fcExchangeIndex ];
rspHDR->reserved = 0L; // must clear
rspHDR->sof_eof = 0x75000000L; // SOFi3:EOFn no UAM; no CLS, noLCr, no TS
rspHDR->d_id = (InFCHS->s_id | 0x07000000L); // R_CTL= FCP_RSP
rspHDR->s_id = fcChip->Registers.my_al_pa; // CS_CTL = 0
// TYPE[31-24] 8 for FCP SCSI
// f_ctl[23:0] responder|last seq| xfer S.I.
rspHDR->f_ctl = 0x08910000L;
rspHDR->seq_cnt = 0x03000000; // sequence ID
rspHDR->ox_rx_id = InFCHS->ox_rx_id; // gives us OX_ID
rspHDR->ro = 0x0L; // relative offset (n/a)
// Now setup the SEST entry
pTWE = &fcChip->SEST->u[ *fcExchangeIndex ].TWE;
// fill out the TWE:
// VALid entry:Dir outbound:enable CM:enal INT:
pTWE->Seq_Accum = 0xC4000000L; // upper word flags
pTWE->reserved = 0L;
pTWE->Remote_Node_ID = 0L; // no more auto RSP frame! (TL/TS change)
pTWE->Remote_Node_ID |= (InFCHS->s_id << 8); // MS 24 bits Remote_ID
// Do we need local or extended gather list?
// depends on size - we can handle 3 len/addr pairs
// locally.
fcp_dl = build_SEST_sgList(
cpqfcHBAdata->PciDev,
&pTWE->SLen1,
Cmnd, // S/G list
&sgPairs, // return # of pairs in S/G list (from "Data" descriptor)
&fcChip->SEST->sgPages[ *fcExchangeIndex ]);// (for Freeing later)
if( !fcp_dl ) // error building S/G list?
{
ulStatus = MEMPOOL_FAIL;
break; // give up
}
// now that we know the S/G length, build CMND payload
build_FCP_payload( Cmnd, (UCHAR*)&CMDfchs->pl[0], type, fcp_dl );
if( sgPairs > 3 ) // need extended s/g list
pTWE->Buff_Off = 0x00000000; // extended s/g list, no offset
else
pTWE->Buff_Off = 0x80000000; // local data, no offset
pTWE->Buff_Index = 0; // Buff_Index | Link
pTWE->Exp_RO = 0;
pTWE->Byte_Count = 0; // filled in by TL on err
pTWE->reserved_ = 0;
pTWE->Exp_Byte_Cnt = fcp_dl;// sum of scatter buffers
break;
case SCSI_TRE: // TachLite Target Read Entry
// It doesn't make much sense for us to "time-out" a READ,
// but we'll use it for design consistency and internal error recovery.
Exchanges->fcExchange[ *fcExchangeIndex].timeOut = 10; // per SCSI req.
// I/O request block settings...
*pIRB_flags = 0; // clear IRB flags
// check PRLI (process login) info
// to see if Initiator Requires XFER_RDY
// if not, don't send one!
// { PRLI check...}
IRB_flags.SFA = 0; // don't send XFER_RDY - start data
SfsLen = *pIRB_flags;
SfsLen <<= 24; // shift flags to MSB
SfsLen += (32L + 12L);// add SFS len (header & XFER_RDY payload)
// now, fill out FCP-DATA header
// (use buffer inside SEST object)
dataHDR = &fcChip->SEST->DataHDR[ *fcExchangeIndex ];
dataHDR->reserved = 0L; // must clear
dataHDR->sof_eof = 0x75000000L; // SOFi3:EOFn no UAM; no CLS,noLCr,no TS
dataHDR->d_id = (InFCHS->s_id | 0x01000000L); // R_CTL= FCP_DATA
dataHDR->s_id = fcChip->Registers.my_al_pa; // CS_CTL = 0
// TYPE[31-24] 8 for FCP SCSI
// f_ctl[23:0] exchg responder, not 1st seq, xfer S.I.
// valid RO
dataHDR->f_ctl = 0x08810008L;
dataHDR->seq_cnt = 0x01000000; // sequence ID (no XRDY)
dataHDR->ox_rx_id = InFCHS->ox_rx_id & 0xFFFF0000; // we want upper 16 bits
dataHDR->ro = 0x0L; // relative offset (n/a)
// now, fill out FCP-RSP header
// (use buffer inside SEST object)
rspHDR = &fcChip->SEST->RspHDR[ *fcExchangeIndex ];
rspHDR->reserved = 0L; // must clear
rspHDR->sof_eof = 0x75000000L; // SOFi3:EOFn no UAM; no CLS, noLCr, no TS
rspHDR->d_id = (InFCHS->s_id | 0x07000000L); // R_CTL= FCP_RSP
rspHDR->s_id = fcChip->Registers.my_al_pa; // CS_CTL = 0
// TYPE[31-24] 8 for FCP SCSI
// f_ctl[23:0] responder|last seq| xfer S.I.
rspHDR->f_ctl = 0x08910000L;
rspHDR->seq_cnt = 0x02000000; // sequence ID: df_ctl: sequence count
rspHDR->ro = 0x0L; // relative offset (n/a)
// Now setup the SEST entry
pTRE = &fcChip->SEST->u[ *fcExchangeIndex ].TRE;
// VALid entry:Dir outbound:enable CM:enal INT:
pTRE->Hdr_Len = 0x86010020L; // data frame Len always 32 bytes
pTRE->Hdr_Addr = // bus address of dataHDR;
fcChip->SEST->base +
((unsigned long)&fcChip->SEST->DataHDR[ *fcExchangeIndex ] -
(unsigned long)fcChip->SEST);
pTRE->RSP_Len = 64L; // hdr+data (TL assisted RSP frame)
pTRE->RSP_Len |= (InFCHS->s_id << 8); // MS 24 bits Remote_ID
pTRE->RSP_Addr = // bus address of rspHDR
fcChip->SEST->base +
((unsigned long)&fcChip->SEST->RspHDR[ *fcExchangeIndex ] -
(unsigned long)fcChip->SEST);
// Do we need local or extended gather list?
// depends on size - we can handle 3 len/addr pairs
// locally.
fcp_dl = build_SEST_sgList(
cpqfcHBAdata->PciDev,
&pTRE->GLen1,
Cmnd, // S/G list
&sgPairs, // return # of pairs in S/G list (from "Data" descriptor)
&fcChip->SEST->sgPages[ *fcExchangeIndex ]);// (for Freeing later)
if( !fcp_dl ) // error building S/G list?
{
ulStatus = MEMPOOL_FAIL;
break; // give up
}
// no payload or command to build -- READ doesn't need XRDY
if( sgPairs > 3 ) // need extended s/g list
pTRE->Buff_Off = 0x78000000L; // extended data | (no offset)
else // local data pointers (in SEST)
pTRE->Buff_Off = 0xf8000000L; // local data | (no offset)
// ULONG 5
pTRE->Buff_Index = 0L; // Buff_Index | reserved
pTRE->reserved = 0x0L; // DWord 6
// DWord 7: NOTE: zero length will
// hang TachLite!
pTRE->Data_Len = fcp_dl; // e.g. sum of scatter buffers
pTRE->reserved_ = 0L; // DWord 8
pTRE->reserved__ = 0L; // DWord 9
break;
case FCP_RESPONSE:
// Target response frame: this sequence uses an OX/RX ID
// pair from a completed SEST exchange. We built most
// of the response frame when we created the TWE/TRE.
*pIRB_flags = 0; // clear IRB flags
IRB_flags.SFA = 1; // send SFS (RSP)
SfsLen = *pIRB_flags;
SfsLen <<= 24; // shift flags to MSB
SfsLen += sizeof(TachFCHDR_RSP);// add SFS len (header & RSP payload)
Exchanges->fcExchange[ *fcExchangeIndex].type =
FCP_RESPONSE; // change Exchange type to "response" phase
// take advantage of prior knowledge of OX/RX_ID pair from
// previous XFER outbound frame (still in fchs of exchange)
fcChip->SEST->RspHDR[ *fcExchangeIndex ].ox_rx_id =
CMDfchs->ox_rx_id;
// Check the status of the DATA phase of the exchange so we can report
// status to the initiator
buildFCPstatus( fcChip, *fcExchangeIndex); // set RSP payload fields
memcpy(
CMDfchs, // re-use same XFER fchs for Response frame
&fcChip->SEST->RspHDR[ *fcExchangeIndex ],
sizeof( TachFCHDR_RSP ));
break;
default:
printk("cpqfcTS: don't know how to build FC type: %Xh(%d)\n", type,type);
break;
}
if( !ulStatus) // no errors above?
{
// FCHS is built; now build IRB
// link the just built FCHS (the "command") to the IRB entry
// for this Exchange.
pIRB = &Exchanges->fcExchange[ *fcExchangeIndex].IRB;
// len & flags according to command type above
pIRB->Req_A_SFS_Len = SfsLen; // includes IRB flags & len
pIRB->Req_A_SFS_Addr = // TL needs physical addr of frame to send
fcChip->exch_dma_handle + (unsigned long)CMDfchs -
(unsigned long)Exchanges;
pIRB->Req_A_SFS_D_ID = CMDfchs->d_id << 8; // Dest_ID must be consistent!
// Exchange is complete except for "fix-up" fields to be set
// at Tachyon Queuing time:
// IRB->Req_A_Trans_ID (OX_ID/ RX_ID):
// for SEST entry, lower bits correspond to actual FC Exchange ID
// fchs->OX_ID or RX_ID
}
else
{
#ifdef DBG
printk( "FC Error: SEST build Pool Allocation failed\n");
#endif
// return resources...
cpqfcTSCompleteExchange( cpqfcHBAdata->PciDev, fcChip, *fcExchangeIndex); // SEST build failed
}
}
else // no Exchanges available
{
ulStatus = SEST_FULL;
printk( "FC Error: no fcExchanges available\n");
}
return ulStatus;
}
// set RSP payload fields
static void buildFCPstatus( PTACHYON fcChip, ULONG ExchangeID)
{
FC_EXCHANGES *Exchanges = fcChip->Exchanges;
FC_EXCHANGE *pExchange = &Exchanges->fcExchange[ExchangeID]; // shorthand
PFCP_STATUS_RESPONSE pFcpStatus;
memset( &fcChip->SEST->RspHDR[ ExchangeID ].pl, 0,
sizeof( FCP_STATUS_RESPONSE) );
if( pExchange->status ) // something wrong?
{
pFcpStatus = (PFCP_STATUS_RESPONSE) // cast RSP buffer for this xchng
&fcChip->SEST->RspHDR[ ExchangeID ].pl;
if( pExchange->status & COUNT_ERROR )
{
// set FCP response len valid (so we can report count error)
pFcpStatus->fcp_status |= FCP_RSP_LEN_VALID;
pFcpStatus->fcp_rsp_len = 0x04000000; // 4 byte len (BIG Endian)
pFcpStatus->fcp_rsp_info = FCP_DATA_LEN_NOT_BURST_LEN; // RSP_CODE
}
}
}
static dma_addr_t
cpqfc_pci_map_sg_page(
struct pci_dev *pcidev,
ULONG *hw_paddr, // where to put phys addr for HW use
void *sgp_vaddr, // the virtual address of the sg page
dma_addr_t *umap_paddr, // where to put phys addr for unmap
unsigned int *maplen, // where to store sg entry length
int PairCount) // number of sg pairs used in the page.
{
unsigned long aligned_addr = (unsigned long) sgp_vaddr;
*maplen = PairCount * 8;
aligned_addr += TL_EXT_SG_PAGE_BYTELEN;
aligned_addr &= ~(TL_EXT_SG_PAGE_BYTELEN -1);
*umap_paddr = pci_map_single(pcidev, (void *) aligned_addr,
*maplen, PCI_DMA_TODEVICE);
*hw_paddr = (ULONG) *umap_paddr;
# if BITS_PER_LONG > 32
if( *umap_paddr >>32 ) {
printk("cqpfcTS:Tach SG DMA addr %p>32 bits\n",
(void*)umap_paddr);
return 0;
}
# endif
return *umap_paddr;
}
static void
cpqfc_undo_SEST_mappings(struct pci_dev *pcidev,
unsigned long contigaddr, int len, int dir,
struct scatterlist *sgl, int use_sg,
PSGPAGES *sgPages_head,
int allocated_pages)
{
PSGPAGES i, next;
if (contigaddr != (unsigned long) NULL)
pci_unmap_single(pcidev, contigaddr, len, dir);
if (sgl != NULL)
pci_unmap_sg(pcidev, sgl, use_sg, dir);
for (i=*sgPages_head; i != NULL ;i = next)
{
pci_unmap_single(pcidev, i->busaddr, i->maplen,
PCI_DMA_TODEVICE);
i->busaddr = (dma_addr_t) NULL;
i->maplen = 0L;
next = i->next;
kfree(i);
}
*sgPages_head = NULL;
}
// This routine builds scatter/gather lists into SEST entries
// INPUTS:
// SESTalPair - SEST address @DWordA "Local Buffer Length"
// sgList - Scatter/Gather linked list of Len/Address data buffers
// OUTPUT:
// sgPairs - number of valid address/length pairs
// Remarks:
// The SEST data buffer pointers only depend on number of
// length/ address pairs, NOT on the type (IWE, TRE,...)
// Up to 3 pairs can be referenced in the SEST - more than 3
// require this Extended S/G list page. The page holds 4, 8, 16...
// len/addr pairs, per Scatter/Gather List Page Length Reg.
// TachLite allows pages to be linked to any depth.
//#define DBG_SEST_SGLIST 1 // for printing out S/G pairs with Ext. pages
static int ap_hi_water = TL_DANGER_SGPAGES;
static ULONG build_SEST_sgList(
struct pci_dev *pcidev,
ULONG *SESTalPairStart, // the 3 len/address buffers in SEST
Scsi_Cmnd *Cmnd,
ULONG *sgPairs,
PSGPAGES *sgPages_head) // link list of TL Ext. S/G pages from O/S Pool
{
ULONG i, AllocatedPages=0; // Tach Ext. S/G page allocations
ULONG* alPair = SESTalPairStart;
ULONG* ext_sg_page_phys_addr_place = NULL;
int PairCount;
unsigned long ulBuff, contigaddr;
ULONG total_data_len=0; // (in bytes)
ULONG bytes_to_go = Cmnd->request_bufflen; // total xfer (S/G sum)
ULONG thisMappingLen;
struct scatterlist *sgl = NULL; // S/G list (Linux format)
int sg_count, totalsgs;
dma_addr_t busaddr;
unsigned long thislen, offset;
PSGPAGES *sgpage = sgPages_head;
PSGPAGES prev_page = NULL;
# define WE_HAVE_SG_LIST (sgl != (unsigned long) NULL)
contigaddr = (unsigned long) NULL;
if( !Cmnd->use_sg ) // no S/G list?
{
if (bytes_to_go <= TL_MAX_SG_ELEM_LEN)
{
*sgPairs = 1; // use "local" S/G pair in SEST entry
// (for now, ignore address bits above #31)
*alPair++ = bytes_to_go; // bits 18-0, length
if (bytes_to_go != 0) {
contigaddr = ulBuff = pci_map_single(pcidev,
Cmnd->request_buffer,
Cmnd->request_bufflen,
Cmnd->sc_data_direction);
// printk("ms %p ", ulBuff);
}
else {
// No data transfer, (e.g.: Test Unit Ready)
// printk("btg=0 ");
*sgPairs = 0;
memset(alPair, 0, sizeof(*alPair));
return 0;
}
# if BITS_PER_LONG > 32
if( ulBuff >>32 ) {
printk("FATAL! Tachyon DMA address %p "
"exceeds 32 bits\n", (void*)ulBuff );
return 0;
}
# endif
*alPair = (ULONG)ulBuff;
return bytes_to_go;
}
else // We have a single large (too big) contiguous buffer.
{ // We will have to break it up. We'll use the scatter
// gather code way below, but use contigaddr instead
// of sg_dma_addr(). (this is a very rare case).
unsigned long btg;
contigaddr = pci_map_single(pcidev, Cmnd->request_buffer,
Cmnd->request_bufflen,
Cmnd->sc_data_direction);
// printk("contigaddr = %p, len = %d\n",
// (void *) contigaddr, bytes_to_go);
totalsgs = 0;
for (btg = bytes_to_go; btg > 0; ) {
btg -= ( btg > TL_MAX_SG_ELEM_LEN ?
TL_MAX_SG_ELEM_LEN : btg );
totalsgs++;
}
sgl = NULL;
*sgPairs = totalsgs;
}
}
else // we do have a scatter gather list
{
// [TBD - update for Linux to support > 32 bits addressing]
// since the format for local & extended S/G lists is different,
// check if S/G pairs exceeds 3.
// *sgPairs = Cmnd->use_sg; Nope, that's wrong.
sgl = (struct scatterlist*)Cmnd->request_buffer;
sg_count = pci_map_sg(pcidev, sgl, Cmnd->use_sg,
Cmnd->sc_data_direction);
if( sg_count <= 3 ) {
// we need to be careful here that no individual mapping
// is too large, and if any is, that breaking it up
// doesn't push us over 3 sgs, or, if it does, that we
// handle that case. Tachyon can take 0x7FFFF bits for length,
// but sg structure uses "unsigned int", on the face of it,
// up to 0xFFFFFFFF or even more.
int i;
unsigned long thislen;
totalsgs = 0;
for (i=0;i<sg_count;i++) {
thislen = sg_dma_len(&sgl[i]);
while (thislen >= TL_MAX_SG_ELEM_LEN) {
totalsgs++;
thislen -= TL_MAX_SG_ELEM_LEN;
}
if (thislen > 0) totalsgs++;
}
*sgPairs = totalsgs;
} else totalsgs = 999; // as a first estimate, definitely >3,
// if (totalsgs != sg_count)
// printk("totalsgs = %d, sgcount=%d\n",totalsgs,sg_count);
}
if( totalsgs <= 3 ) // can (must) use "local" SEST list
{
while( bytes_to_go)
{
offset = 0L;
if ( WE_HAVE_SG_LIST )
thisMappingLen = sg_dma_len(sgl);
else // or contiguous buffer?
thisMappingLen = bytes_to_go;
while (thisMappingLen > 0)
{
thislen = thisMappingLen > TL_MAX_SG_ELEM_LEN ?
TL_MAX_SG_ELEM_LEN : thisMappingLen;
bytes_to_go = bytes_to_go - thislen;
// we have L/A pair; L = thislen, A = physicalAddress
// load into SEST...
total_data_len += thislen;
*alPair = thislen; // bits 18-0, length
alPair++;
if ( WE_HAVE_SG_LIST )
ulBuff = sg_dma_address(sgl) + offset;
else
ulBuff = contigaddr + offset;
offset += thislen;
# if BITS_PER_LONG > 32
if( ulBuff >>32 ) {
printk("cqpfcTS: 2Tach DMA address %p > 32 bits\n",
(void*)ulBuff );
printk("%s = %p, offset = %ld\n",
WE_HAVE_SG_LIST ? "ulBuff" : "contigaddr",
WE_HAVE_SG_LIST ? (void *) ulBuff : (void *) contigaddr,
offset);
return 0;
}
# endif
*alPair++ = (ULONG)ulBuff; // lower 32 bits (31-0)
thisMappingLen -= thislen;
}
if ( WE_HAVE_SG_LIST ) ++sgl; // next S/G pair
else if (bytes_to_go != 0) printk("BTG not zero!\n");
# ifdef DBG_SEST_SGLIST
printk("L=%d ", thisMappingLen);
printk("btg=%d ", bytes_to_go);
# endif
}
// printk("i:%d\n", *sgPairs);
}
else // more than 3 pairs requires Extended S/G page (Pool Allocation)
{
// clear out SEST DWORDs (local S/G addr) C-F (A-B set in following logic)
for( i=2; i<6; i++)
alPair[i] = 0;
PairCount = TL_EXT_SG_PAGE_COUNT; // forces initial page allocation
totalsgs = 0;
while( bytes_to_go )
{
// Per SEST format, we can support 524287 byte lengths per
// S/G pair. Typical user buffers are 4k, and very rarely
// exceed 12k due to fragmentation of physical memory pages.
// However, on certain O/S system (not "user") buffers (on platforms
// with huge memories), it's possible to exceed this
// length in a single S/G address/len mapping, so we have to handle
// that.
offset = 0L;
if ( WE_HAVE_SG_LIST )
thisMappingLen = sg_dma_len(sgl);
else
thisMappingLen = bytes_to_go;
while (thisMappingLen > 0)
{
thislen = thisMappingLen > TL_MAX_SG_ELEM_LEN ?
TL_MAX_SG_ELEM_LEN : thisMappingLen;
// printk("%d/%d/%d\n", thislen, thisMappingLen, bytes_to_go);
// should we load into "this" extended S/G page, or allocate
// new page?
if( PairCount >= TL_EXT_SG_PAGE_COUNT )
{
// Now, we have to map the previous page, (triggering buffer bounce)
// The first time thru the loop, there won't be a previous page.
if (prev_page != NULL) // is there a prev page?
{
// this code is normally kind of hard to trigger,
// you have to use up more than 256 scatter gather
// elements to get here. Cranking down TL_MAX_SG_ELEM_LEN
// to an absurdly low value (128 bytes or so) to artificially
// break i/o's into a zillion pieces is how I tested it.
busaddr = cpqfc_pci_map_sg_page(pcidev,
ext_sg_page_phys_addr_place,
prev_page->page,
&prev_page->busaddr,
&prev_page->maplen,
PairCount);
}
// Allocate the TL Extended S/G list page. We have
// to allocate twice what we want to ensure required TL alignment
// (Tachlite TL/TS User Man. Rev 6.0, p 168)
// We store the original allocated PVOID so we can free later
*sgpage = kmalloc( sizeof(SGPAGES), GFP_ATOMIC);
if ( ! *sgpage )
{
printk("cpqfc: Allocation failed @ %d S/G page allocations\n",
AllocatedPages);
total_data_len = 0; // failure!! Ext. S/G is All-or-none affair
// unmap the previous mappings, if any.
cpqfc_undo_SEST_mappings(pcidev, contigaddr,
Cmnd->request_bufflen,
Cmnd->sc_data_direction,
sgl, Cmnd->use_sg, sgPages_head, AllocatedPages+1);
// FIXME: testing shows that if we get here,
// it's bad news. (this has been this way for a long
// time though, AFAIK. Not that that excuses it.)
return 0; // give up (and probably hang the system)
}
// clear out memory we just allocated
memset( (*sgpage)->page,0,TL_EXT_SG_PAGE_BYTELEN*2);
(*sgpage)->next = NULL;
(*sgpage)->busaddr = (dma_addr_t) NULL;
(*sgpage)->maplen = 0L;
// align the memory - TL requires sizeof() Ext. S/G page alignment.
// We doubled the actual required size so we could mask off LSBs
// to get desired offset
ulBuff = (unsigned long) (*sgpage)->page;
ulBuff += TL_EXT_SG_PAGE_BYTELEN;
ulBuff &= ~(TL_EXT_SG_PAGE_BYTELEN -1);
// set pointer, in SEST if first Ext. S/G page, or in last pair
// of linked Ext. S/G pages... (Only 32-bit PVOIDs, so just
// load lower 32 bits)
// NOTE: the Len field must be '0' if this is the first Ext. S/G
// pointer in SEST, and not 0 otherwise (we know thislen != 0).
*alPair = (alPair != SESTalPairStart) ? thislen : 0;
# ifdef DBG_SEST_SGLIST
printk("PairCount %d @%p even %Xh, ",
PairCount, alPair, *alPair);
# endif
// Save the place where we need to store the physical
// address of this scatter gather page which we get when we map it
// (and mapping we can do only after we fill it in.)
alPair++; // next DWORD, will contain phys addr of the ext page
ext_sg_page_phys_addr_place = alPair;
// Now, set alPair = the virtual addr of the (Extended) S/G page
// which will accept the Len/ PhysicalAddress pairs
alPair = (ULONG *) ulBuff;
AllocatedPages++;
if (AllocatedPages >= ap_hi_water)
{
// This message should rarely, if ever, come out.
// Previously (cpqfc version <= 2.0.5) the driver would
// just puke if more than 4 SG pages were used, and nobody
// ever complained about that. This only comes out if
// more than 8 pages are used.
printk(KERN_WARNING
"cpqfc: Possible danger. %d scatter gather pages used.\n"
"cpqfc: detected seemingly extreme memory "
"fragmentation or huge data transfers.\n",
AllocatedPages);
ap_hi_water = AllocatedPages+1;
}
PairCount = 1; // starting new Ext. S/G page
prev_page = (*sgpage); // remember this page, for next time thru
sgpage = &((*sgpage)->next);
} // end of new TL Ext. S/G page allocation
*alPair = thislen; // bits 18-0, length (range check above)
# ifdef DBG_SEST_SGLIST
printk("PairCount %d @%p, even %Xh, ", PairCount, alPair, *alPair);
# endif
alPair++; // next DWORD, physical address
if ( WE_HAVE_SG_LIST )
ulBuff = sg_dma_address(sgl) + offset;
else
ulBuff = contigaddr + offset;
offset += thislen;
# if BITS_PER_LONG > 32
if( ulBuff >>32 )
{
printk("cqpfcTS: 1Tach DMA address %p > 32 bits\n", (void*)ulBuff );
printk("%s = %p, offset = %ld\n",
WE_HAVE_SG_LIST ? "ulBuff" : "contigaddr",
WE_HAVE_SG_LIST ? (void *) ulBuff : (void *) contigaddr,
offset);
return 0;
}
# endif
*alPair = (ULONG) ulBuff; // lower 32 bits (31-0)
# ifdef DBG_SEST_SGLIST
printk("odd %Xh\n", *alPair);
# endif
alPair++; // next DWORD, next address/length pair
PairCount++; // next Length/Address pair
// if (PairCount > pc_hi_water)
// {
// printk("pc hi = %d ", PairCount);
// pc_hi_water = PairCount;
// }
bytes_to_go -= thislen;
total_data_len += thislen;
thisMappingLen -= thislen;
totalsgs++;
} // while (thisMappingLen > 0)
if ( WE_HAVE_SG_LIST ) sgl++; // next S/G pair
} // while (bytes_to_go)
// printk("Totalsgs=%d\n", totalsgs);
*sgPairs = totalsgs;
// PCI map (and bounce) the last (and usually only) extended SG page
busaddr = cpqfc_pci_map_sg_page(pcidev,
ext_sg_page_phys_addr_place,
prev_page->page,
&prev_page->busaddr,
&prev_page->maplen,
PairCount);
}
return total_data_len;
}
// The Tachlite SEST table is referenced to OX_ID (or RX_ID). To optimize
// performance and debuggability, we index the Exchange structure to FC X_ID
// This enables us to build exchanges for later en-queing to Tachyon,
// provided we have an open X_ID slot. At Tachyon queing time, we only
// need an ERQ slot; then "fix-up" references in the
// IRB, FCHS, etc. as needed.
// RETURNS:
// 0 if successful
// non-zero on error
//sstartex
ULONG cpqfcTSStartExchange(
CPQFCHBA *cpqfcHBAdata,
LONG ExchangeID )
{
PTACHYON fcChip = &cpqfcHBAdata->fcChip;
FC_EXCHANGES *Exchanges = fcChip->Exchanges;
FC_EXCHANGE *pExchange = &Exchanges->fcExchange[ ExchangeID ]; // shorthand
USHORT producer, consumer;
ULONG ulStatus=0;
short int ErqIndex;
BOOLEAN CompleteExchange = FALSE; // e.g. ACC replies are complete
BOOLEAN SestType=FALSE;
ULONG InboundData=0;
// We will manipulate Tachlite chip registers here to successfully
// start exchanges.
// Check that link is not down -- we can't start an exchange on a
// down link!
if( fcChip->Registers.FMstatus.value & 0x80) // LPSM offline?
{
printk("fcStartExchange: PSM offline (%Xh), x_ID %Xh, type %Xh, port_id %Xh\n",
fcChip->Registers.FMstatus.value & 0xFF,
ExchangeID,
pExchange->type,
pExchange->fchs.d_id);
if( ExchangeID >= TACH_SEST_LEN ) // Link Service Outbound frame?
{
// Our most popular LinkService commands are port discovery types
// (PLOGI/ PDISC...), which are implicitly nullified by Link Down
// events, so it makes no sense to Que them. However, ABTS should
// be queued, since exchange sequences are likely destroyed by
// Link Down events, and we want to notify other ports of broken
// sequences by aborting the corresponding exchanges.
if( pExchange->type != BLS_ABTS )
{
ulStatus = LNKDWN_OSLS;
goto Done;
// don't Que most LinkServ exchanges on LINK DOWN
}
}
printk("fcStartExchange: Que x_ID %Xh, type %Xh\n",
ExchangeID, pExchange->type);
pExchange->status |= EXCHANGE_QUEUED;
ulStatus = EXCHANGE_QUEUED;
goto Done;
}
// Make sure ERQ has available space.
producer = (USHORT)fcChip->ERQ->producerIndex; // copies for logical arith.
consumer = (USHORT)fcChip->ERQ->consumerIndex;
producer++; // We are testing for full que by incrementing
if( producer >= ERQ_LEN ) // rollover condition?
producer = 0;
if( consumer != producer ) // ERQ not full?
{
// ****************** Need Atomic access to chip registers!!********
// remember ERQ PI for copying IRB
ErqIndex = (USHORT)fcChip->ERQ->producerIndex;
fcChip->ERQ->producerIndex = producer; // this is written to Tachyon
// we have an ERQ slot! If SCSI command, need SEST slot
// otherwise we are done.
// Note that Tachyon requires that bit 15 of the OX_ID or RX_ID be
// set according to direction of data to/from Tachyon for SEST assists.
// For consistency, enforce this rule for Link Service (non-SEST)
// exchanges as well.
// fix-up the X_ID field in IRB
pExchange->IRB.Req_A_Trans_ID = ExchangeID & 0x7FFF; // 15-bit field
// fix-up the X_ID field in fchs -- depends on Originator or Responder,
// outgoing or incoming data?
switch( pExchange->type )
{
// ORIGINATOR types... we're setting our OX_ID and
// defaulting the responder's RX_ID to 0xFFFF
case SCSI_IRE:
// Requirement: set MSB of x_ID for Incoming TL data
// (see "Tachyon TL/TS User's Manual", Rev 6.0, Sept.'98, pg. 50)
InboundData = 0x8000;
case SCSI_IWE:
SestType = TRUE;
pExchange->fchs.ox_rx_id = (ExchangeID | InboundData);
pExchange->fchs.ox_rx_id <<= 16; // MSW shift
pExchange->fchs.ox_rx_id |= 0xffff; // add default RX_ID
// now fix-up the Data HDR OX_ID (TL automatically does rx_id)
// (not necessary for IRE -- data buffer unused)
if( pExchange->type == SCSI_IWE)
{
fcChip->SEST->DataHDR[ ExchangeID ].ox_rx_id =
pExchange->fchs.ox_rx_id;
}
break;
case FCS_NSR: // ext. link service Name Service Request
case ELS_SCR: // ext. link service State Change Registration
case ELS_FDISC:// ext. link service login
case ELS_FLOGI:// ext. link service login
case ELS_LOGO: // FC-PH extended link service logout
case BLS_NOP: // Basic link service No OPeration
case ELS_PLOGI:// ext. link service login (PLOGI)
case ELS_PDISC:// ext. link service login (PDISC)
case ELS_PRLI: // ext. link service process login
pExchange->fchs.ox_rx_id = ExchangeID;
pExchange->fchs.ox_rx_id <<= 16; // MSW shift
pExchange->fchs.ox_rx_id |= 0xffff; // and RX_ID
break;
// RESPONDER types... we must set our RX_ID while preserving
// sender's OX_ID
// outgoing (or no) data
case ELS_RJT: // extended link service reject
case ELS_LOGO_ACC: // FC-PH extended link service logout accept
case ELS_ACC: // ext. generic link service accept
case ELS_PLOGI_ACC:// ext. link service login accept (PLOGI or PDISC)
case ELS_PRLI_ACC: // ext. link service process login accept
CompleteExchange = TRUE; // Reply (ACC or RJT) is end of exchange
pExchange->fchs.ox_rx_id |= (ExchangeID & 0xFFFF);
break;
// since we are a Responder, OX_ID should already be set by
// cpqfcTSBuildExchange(). We need to -OR- in RX_ID
case SCSI_TWE:
SestType = TRUE;
// Requirement: set MSB of x_ID for Incoming TL data
// (see "Tachyon TL/TS User's Manual", Rev 6.0, Sept.'98, pg. 50)
pExchange->fchs.ox_rx_id &= 0xFFFF0000; // clear RX_ID
// Requirement: set MSB of RX_ID for Incoming TL data
// (see "Tachyon TL/TS User's Manual", Rev 6.0, Sept.'98, pg. 50)
pExchange->fchs.ox_rx_id |= (ExchangeID | 0x8000);
break;
case SCSI_TRE:
SestType = TRUE;
// there is no XRDY for SEST target read; the data
// header needs to be updated. Also update the RSP
// exchange IDs for the status frame, in case it is sent automatically
fcChip->SEST->DataHDR[ ExchangeID ].ox_rx_id |= ExchangeID;
fcChip->SEST->RspHDR[ ExchangeID ].ox_rx_id =
fcChip->SEST->DataHDR[ ExchangeID ].ox_rx_id;
// for easier FCP response logic (works for TWE and TRE),
// copy exchange IDs. (Not needed if TRE 'RSP' bit set)
pExchange->fchs.ox_rx_id =
fcChip->SEST->DataHDR[ ExchangeID ].ox_rx_id;
break;
case FCP_RESPONSE: // using existing OX_ID/ RX_ID pair,
// start SFS FCP-RESPONSE frame
// OX/RX_ID should already be set! (See "fcBuild" above)
CompleteExchange = TRUE; // RSP is end of FCP-SCSI exchange
break;
case BLS_ABTS_RJT: // uses new RX_ID, since SEST x_ID non-existent
case BLS_ABTS_ACC: // using existing OX_ID/ RX_ID pair from SEST entry
CompleteExchange = TRUE; // ACC or RJT marks end of FCP-SCSI exchange
case BLS_ABTS: // using existing OX_ID/ RX_ID pair from SEST entry
break;
default:
printk("Error on fcStartExchange: undefined type %Xh(%d)\n",
pExchange->type, pExchange->type);
return INVALID_ARGS;
}
// X_ID fields are entered -- copy IRB to Tachyon's ERQ
memcpy(
&fcChip->ERQ->QEntry[ ErqIndex ], // dest.
&pExchange->IRB,
32); // fixed (hardware) length!
PCI_TRACEO( ExchangeID, 0xA0)
// ACTION! May generate INT and IMQ entry
writel( fcChip->ERQ->producerIndex,
fcChip->Registers.ERQproducerIndex.address);
if( ExchangeID >= TACH_SEST_LEN ) // Link Service Outbound frame?
{
// wait for completion! (TDB -- timeout and chip reset)
PCI_TRACEO( ExchangeID, 0xA4)
enable_irq( cpqfcHBAdata->HostAdapter->irq); // only way to get Sem.
down_interruptible( cpqfcHBAdata->TYOBcomplete);
disable_irq( cpqfcHBAdata->HostAdapter->irq);
PCI_TRACE( 0xA4)
// On login exchanges, BAD_ALPA (non-existent port_id) results in
// FTO (Frame Time Out) on the Outbound Completion message.
// If we got an FTO status, complete the exchange (free up slot)
if( CompleteExchange || // flag from Reply frames
pExchange->status ) // typically, can get FRAME_TO
{
cpqfcTSCompleteExchange( cpqfcHBAdata->PciDev, fcChip, ExchangeID);
}
}
else // SEST Exchange
{
ulStatus = 0; // ship & pray success (e.g. FCP-SCSI)
if( CompleteExchange ) // by Type of exchange (e.g. end-of-xchng)
{
cpqfcTSCompleteExchange( cpqfcHBAdata->PciDev, fcChip, ExchangeID);
}
else
pExchange->status &= ~EXCHANGE_QUEUED; // clear ExchangeQueued flag
}
}
else // ERQ 'producer' = 'consumer' and QUE is full
{
ulStatus = OUTQUE_FULL; // Outbound (ERQ) Que full
}
Done:
PCI_TRACE( 0xA0)
return ulStatus;
}
// Scan fcController->fcExchanges array for a usuable index (a "free"
// exchange).
// Inputs:
// fcChip - pointer to TachLite chip structure
// Return:
// index - exchange array element where exchange can be built
// -1 - exchange array is full
// REMARKS:
// Although this is a (yuk!) linear search, we presume
// that the system will complete exchanges about as quickly as
// they are submitted. A full Exchange array (and hence, max linear
// search time for free exchange slot) almost guarantees a Fibre problem
// of some sort.
// In the interest of making exchanges easier to debug, we want a LRU
// (Least Recently Used) scheme.
static LONG FindFreeExchange( PTACHYON fcChip, ULONG type )
{
FC_EXCHANGES *Exchanges = fcChip->Exchanges;
ULONG i;
ULONG ulStatus=-1; // assume failure
if( type == SCSI_IRE ||
type == SCSI_TRE ||
type == SCSI_IWE ||
type == SCSI_TWE)
{
// SCSI type - X_IDs should be from 0 to TACH_SEST_LEN-1
if( fcChip->fcSestExchangeLRU >= TACH_SEST_LEN) // rollover?
fcChip->fcSestExchangeLRU = 0;
i = fcChip->fcSestExchangeLRU; // typically it's already free!
if( Exchanges->fcExchange[i].type == 0 ) // check for "free" element
{
ulStatus = 0; // success!
}
else
{ // YUK! we need to do a linear search for free element.
// Fragmentation of the fcExchange array is due to excessively
// long completions or timeouts.
while( TRUE )
{
if( ++i >= TACH_SEST_LEN ) // rollover check
i = 0; // beginning of SEST X_IDs
// printk( "looping for SCSI xchng ID: i=%d, type=%Xh\n",
// i, Exchanges->fcExchange[i].type);
if( Exchanges->fcExchange[i].type == 0 ) // "free"?
{
ulStatus = 0; // success!
break;
}
if( i == fcChip->fcSestExchangeLRU ) // wrapped-around array?
{
printk( "SEST X_ID space full\n");
break; // failed - prevent inf. loop
}
}
}
fcChip->fcSestExchangeLRU = i + 1; // next! (rollover check next pass)
}
else // Link Service type - X_IDs should be from TACH_SEST_LEN
// to TACH_MAX_XID
{
if( fcChip->fcLsExchangeLRU >= TACH_MAX_XID || // range check
fcChip->fcLsExchangeLRU < TACH_SEST_LEN ) // (e.g. startup)
fcChip->fcLsExchangeLRU = TACH_SEST_LEN;
i = fcChip->fcLsExchangeLRU; // typically it's already free!
if( Exchanges->fcExchange[i].type == 0 ) // check for "free" element
{
ulStatus = 0; // success!
}
else
{ // YUK! we need to do a linear search for free element
// Fragmentation of the fcExchange array is due to excessively
// long completions or timeouts.
while( TRUE )
{
if( ++i >= TACH_MAX_XID ) // rollover check
i = TACH_SEST_LEN;// beginning of Link Service X_IDs
// printk( "looping for xchng ID: i=%d, type=%Xh\n",
// i, Exchanges->fcExchange[i].type);
if( Exchanges->fcExchange[i].type == 0 ) // "free"?
{
ulStatus = 0; // success!
break;
}
if( i == fcChip->fcLsExchangeLRU ) // wrapped-around array?
{
printk( "LinkService X_ID space full\n");
break; // failed - prevent inf. loop
}
}
}
fcChip->fcLsExchangeLRU = i + 1; // next! (rollover check next pass)
}
if( !ulStatus ) // success?
Exchanges->fcExchange[i].type = type; // allocate it.
else
i = -1; // error - all exchanges "open"
return i;
}
static void
cpqfc_pci_unmap_extended_sg(struct pci_dev *pcidev,
PTACHYON fcChip,
ULONG x_ID)
{
// Unmaps the memory regions used to hold the scatter gather lists
PSGPAGES i;
// Were there any such regions needing unmapping?
if (! USES_EXTENDED_SGLIST(fcChip->SEST, x_ID))
return; // No such regions, we're outta here.
// for each extended scatter gather region needing unmapping...
for (i=fcChip->SEST->sgPages[x_ID] ; i != NULL ; i = i->next)
pci_unmap_single(pcidev, i->busaddr, i->maplen,
PCI_DMA_TODEVICE);
}
// Called also from cpqfcTScontrol.o, so can't be static
void
cpqfc_pci_unmap(struct pci_dev *pcidev,
Scsi_Cmnd *cmd,
PTACHYON fcChip,
ULONG x_ID)
{
// Undo the DMA mappings
if (cmd->use_sg) { // Used scatter gather list for data buffer?
cpqfc_pci_unmap_extended_sg(pcidev, fcChip, x_ID);
pci_unmap_sg(pcidev, cmd->buffer, cmd->use_sg,
cmd->sc_data_direction);
// printk("umsg %d\n", cmd->use_sg);
}
else if (cmd->request_bufflen) {
// printk("ums %p ", fcChip->SEST->u[ x_ID ].IWE.GAddr1);
pci_unmap_single(pcidev, fcChip->SEST->u[ x_ID ].IWE.GAddr1,
cmd->request_bufflen,
cmd->sc_data_direction);
}
}
// We call this routine to free an Exchange for any reason:
// completed successfully, completed with error, aborted, etc.
// returns FALSE if Exchange failed and "retry" is acceptable
// returns TRUE if Exchange was successful, or retry is impossible
// (e.g. port/device gone).
//scompleteexchange
void cpqfcTSCompleteExchange(
struct pci_dev *pcidev,
PTACHYON fcChip,
ULONG x_ID)
{
FC_EXCHANGES *Exchanges = fcChip->Exchanges;
int already_unmapped = 0;
if( x_ID < TACH_SEST_LEN ) // SEST-based (or LinkServ for FCP exchange)
{
if( Exchanges->fcExchange[ x_ID ].Cmnd == NULL ) // what#@!
{
// TriggerHBA( fcChip->Registers.ReMapMemBase, 0);
printk(" x_ID %Xh, type %Xh, NULL ptr!\n", x_ID,
Exchanges->fcExchange[ x_ID ].type);
goto CleanUpSestResources; // this path should be very rare.
}
// we have Linux Scsi Cmnd ptr..., now check our Exchange status
// to decide how to complete this SEST FCP exchange
if( Exchanges->fcExchange[ x_ID ].status ) // perhaps a Tach indicated problem,
// or abnormal exchange completion
{
// set FCP Link statistics
if( Exchanges->fcExchange[ x_ID ].status & FC2_TIMEOUT)
fcChip->fcStats.timeouts++;
if( Exchanges->fcExchange[ x_ID ].status & INITIATOR_ABORT)
fcChip->fcStats.FC4aborted++;
if( Exchanges->fcExchange[ x_ID ].status & COUNT_ERROR)
fcChip->fcStats.CntErrors++;
if( Exchanges->fcExchange[ x_ID ].status & LINKFAIL_TX)
fcChip->fcStats.linkFailTX++;
if( Exchanges->fcExchange[ x_ID ].status & LINKFAIL_RX)
fcChip->fcStats.linkFailRX++;
if( Exchanges->fcExchange[ x_ID ].status & OVERFLOW)
fcChip->fcStats.CntErrors++;
// First, see if the Scsi upper level initiated an ABORT on this
// exchange...
if( Exchanges->fcExchange[ x_ID ].status == INITIATOR_ABORT )
{
printk(" DID_ABORT, x_ID %Xh, Cmnd %p ",
x_ID, Exchanges->fcExchange[ x_ID ].Cmnd);
goto CleanUpSestResources; // (we don't expect Linux _aborts)
}
// Did our driver timeout the Exchange, or did Tachyon indicate
// a failure during transmission? Ask for retry with "SOFT_ERROR"
else if( Exchanges->fcExchange[ x_ID ].status & FC2_TIMEOUT)
{
// printk("result DID_SOFT_ERROR, x_ID %Xh, Cmnd %p\n",
// x_ID, Exchanges->fcExchange[ x_ID ].Cmnd);
Exchanges->fcExchange[ x_ID ].Cmnd->result = (DID_SOFT_ERROR <<16);
}
// Did frame(s) for an open exchange arrive in the SFQ,
// meaning the SEST was unable to process them?
else if( Exchanges->fcExchange[ x_ID ].status & SFQ_FRAME)
{
// printk("result DID_SOFT_ERROR, x_ID %Xh, Cmnd %p\n",
// x_ID, Exchanges->fcExchange[ x_ID ].Cmnd);
Exchanges->fcExchange[ x_ID ].Cmnd->result = (DID_SOFT_ERROR <<16);
}
// Did our driver timeout the Exchange, or did Tachyon indicate
// a failure during transmission? Ask for retry with "SOFT_ERROR"
else if(
(Exchanges->fcExchange[ x_ID ].status & LINKFAIL_TX) ||
(Exchanges->fcExchange[ x_ID ].status & PORTID_CHANGED) ||
(Exchanges->fcExchange[ x_ID ].status & FRAME_TO) ||
(Exchanges->fcExchange[ x_ID ].status & INV_ENTRY) ||
(Exchanges->fcExchange[ x_ID ].status & ABORTSEQ_NOTIFY) )
{
// printk("result DID_SOFT_ERROR, x_ID %Xh, Cmnd %p\n",
// x_ID, Exchanges->fcExchange[ x_ID ].Cmnd);
Exchanges->fcExchange[ x_ID ].Cmnd->result = (DID_SOFT_ERROR <<16);
}
// e.g., a LOGOut happened, or device never logged back in.
else if( Exchanges->fcExchange[ x_ID ].status & DEVICE_REMOVED)
{
// printk(" *LOGOut or timeout on login!* ");
// trigger?
// TriggerHBA( fcChip->Registers.ReMapMemBase, 0);
Exchanges->fcExchange[ x_ID ].Cmnd->result = (DID_BAD_TARGET <<16);
}
// Did Tachyon indicate a CNT error? We need further analysis
// to determine if the exchange is acceptable
else if( Exchanges->fcExchange[ x_ID ].status == COUNT_ERROR)
{
UCHAR ScsiStatus;
FCP_STATUS_RESPONSE *pFcpStatus =
(PFCP_STATUS_RESPONSE)&fcChip->SEST->RspHDR[ x_ID ].pl;
ScsiStatus = pFcpStatus->fcp_status >>24;
// If the command is a SCSI Read/Write type, we don't tolerate
// count errors of any kind; assume the count error is due to
// a dropped frame and ask for retry...
if(( (Exchanges->fcExchange[ x_ID ].Cmnd->cmnd[0] == 0x8) ||
(Exchanges->fcExchange[ x_ID ].Cmnd->cmnd[0] == 0x28) ||
(Exchanges->fcExchange[ x_ID ].Cmnd->cmnd[0] == 0xA) ||
(Exchanges->fcExchange[ x_ID ].Cmnd->cmnd[0] == 0x2A) )
&&
ScsiStatus == 0 )
{
// ask for retry
/* printk("COUNT_ERROR retry, x_ID %Xh, status %Xh, Cmnd %p\n",
x_ID, Exchanges->fcExchange[ x_ID ].status,
Exchanges->fcExchange[ x_ID ].Cmnd);*/
Exchanges->fcExchange[ x_ID ].Cmnd->result = (DID_SOFT_ERROR <<16);
}
else // need more analysis
{
cpqfcTSCheckandSnoopFCP(fcChip, x_ID); // (will set ->result)
}
}
// default: NOTE! We don't ever want to get here. Getting here
// implies something new is happening that we've never had a test
// case for. Need code maintenance! Return "ERROR"
else
{
unsigned int stat = Exchanges->fcExchange[ x_ID ].status;
printk("DEFAULT result %Xh, x_ID %Xh, Cmnd %p",
Exchanges->fcExchange[ x_ID ].status, x_ID,
Exchanges->fcExchange[ x_ID ].Cmnd);
if (stat & INVALID_ARGS) printk(" INVALID_ARGS ");
if (stat & LNKDWN_OSLS) printk(" LNKDWN_OSLS ");
if (stat & LNKDWN_LASER) printk(" LNKDWN_LASER ");
if (stat & OUTQUE_FULL) printk(" OUTQUE_FULL ");
if (stat & DRIVERQ_FULL) printk(" DRIVERQ_FULL ");
if (stat & SEST_FULL) printk(" SEST_FULL ");
if (stat & BAD_ALPA) printk(" BAD_ALPA ");
if (stat & OVERFLOW) printk(" OVERFLOW ");
if (stat & COUNT_ERROR) printk(" COUNT_ERROR ");
if (stat & LINKFAIL_RX) printk(" LINKFAIL_RX ");
if (stat & ABORTSEQ_NOTIFY) printk(" ABORTSEQ_NOTIFY ");
if (stat & LINKFAIL_TX) printk(" LINKFAIL_TX ");
if (stat & HOSTPROG_ERR) printk(" HOSTPROG_ERR ");
if (stat & FRAME_TO) printk(" FRAME_TO ");
if (stat & INV_ENTRY) printk(" INV_ENTRY ");
if (stat & SESTPROG_ERR) printk(" SESTPROG_ERR ");
if (stat & OUTBOUND_TIMEOUT) printk(" OUTBOUND_TIMEOUT ");
if (stat & INITIATOR_ABORT) printk(" INITIATOR_ABORT ");
if (stat & MEMPOOL_FAIL) printk(" MEMPOOL_FAIL ");
if (stat & FC2_TIMEOUT) printk(" FC2_TIMEOUT ");
if (stat & TARGET_ABORT) printk(" TARGET_ABORT ");
if (stat & EXCHANGE_QUEUED) printk(" EXCHANGE_QUEUED ");
if (stat & PORTID_CHANGED) printk(" PORTID_CHANGED ");
if (stat & DEVICE_REMOVED) printk(" DEVICE_REMOVED ");
if (stat & SFQ_FRAME) printk(" SFQ_FRAME ");
printk("\n");
Exchanges->fcExchange[ x_ID ].Cmnd->result = (DID_ERROR <<16);
}
}
else // definitely no Tach problem, but perhaps an FCP problem
{
// set FCP Link statistic
fcChip->fcStats.ok++;
cpqfcTSCheckandSnoopFCP( fcChip, x_ID); // (will set ->result)
}
cpqfc_pci_unmap(pcidev, Exchanges->fcExchange[x_ID].Cmnd,
fcChip, x_ID); // undo DMA mappings.
already_unmapped = 1;
// OK, we've set the Scsi "->result" field, so proceed with calling
// Linux Scsi "done" (if not NULL), and free any kernel memory we
// may have allocated for the exchange.
PCI_TRACEO( (ULONG)Exchanges->fcExchange[x_ID].Cmnd, 0xAC);
// complete the command back to upper Scsi drivers
if( Exchanges->fcExchange[ x_ID ].Cmnd->scsi_done != NULL)
{
// Calling "done" on an Linux _abort() aborted
// Cmnd causes a kernel panic trying to re-free mem.
// Actually, we shouldn't do anything with an _abort CMND
if( Exchanges->fcExchange[ x_ID ].Cmnd->result != (DID_ABORT<<16) )
{
PCI_TRACE(0xAC)
call_scsi_done(Exchanges->fcExchange[ x_ID ].Cmnd);
}
else
{
// printk(" not calling scsi_done on x_ID %Xh, Cmnd %p\n",
// x_ID, Exchanges->fcExchange[ x_ID ].Cmnd);
}
}
else{
printk(" x_ID %Xh, type %Xh, Cdb0 %Xh\n", x_ID,
Exchanges->fcExchange[ x_ID ].type,
Exchanges->fcExchange[ x_ID ].Cmnd->cmnd[0]);
printk(" cpqfcTS: Null scsi_done function pointer!\n");
}
// Now, clean up non-Scsi_Cmnd items...
CleanUpSestResources:
if (!already_unmapped)
cpqfc_pci_unmap(pcidev, Exchanges->fcExchange[x_ID].Cmnd,
fcChip, x_ID); // undo DMA mappings.
// Was an Extended Scatter/Gather page allocated? We know
// this by checking DWORD 4, bit 31 ("LOC") of SEST entry
if( !(fcChip->SEST->u[ x_ID ].IWE.Buff_Off & 0x80000000))
{
PSGPAGES p, next;
// extended S/G list was used -- Free the allocated ext. S/G pages
for (p = fcChip->SEST->sgPages[x_ID]; p != NULL; p = next) {
next = p->next;
kfree(p);
}
fcChip->SEST->sgPages[x_ID] = NULL;
}
Exchanges->fcExchange[ x_ID ].Cmnd = NULL;
} // Done with FCP (SEST) exchanges
// the remaining logic is common to ALL Exchanges:
// FCP(SEST) and LinkServ.
Exchanges->fcExchange[ x_ID ].type = 0; // there -- FREE!
Exchanges->fcExchange[ x_ID ].status = 0;
PCI_TRACEO( x_ID, 0xAC)
return;
} // (END of CompleteExchange function)
// Unfortunately, we must snoop all command completions in
// order to manipulate certain return fields, and take note of
// device types, etc., to facilitate the Fibre-Channel to SCSI
// "mapping".
// (Watch for BIG Endian confusion on some payload fields)
void cpqfcTSCheckandSnoopFCP( PTACHYON fcChip, ULONG x_ID)
{
FC_EXCHANGES *Exchanges = fcChip->Exchanges;
Scsi_Cmnd *Cmnd = Exchanges->fcExchange[ x_ID].Cmnd;
FCP_STATUS_RESPONSE *pFcpStatus =
(PFCP_STATUS_RESPONSE)&fcChip->SEST->RspHDR[ x_ID ].pl;
UCHAR ScsiStatus;
ScsiStatus = pFcpStatus->fcp_status >>24;
#ifdef FCP_COMPLETION_DBG
printk("ScsiStatus = 0x%X\n", ScsiStatus);
#endif
// First, check FCP status
if( pFcpStatus->fcp_status & FCP_RSP_LEN_VALID )
{
// check response code (RSP_CODE) -- most popular is bad len
// 1st 4 bytes of rsp info -- only byte 3 interesting
if( pFcpStatus->fcp_rsp_info & FCP_DATA_LEN_NOT_BURST_LEN )
{
// do we EVER get here?
printk("cpqfcTS: FCP data len not burst len, x_ID %Xh\n", x_ID);
}
}
// for now, go by the ScsiStatus, and manipulate certain
// commands when necessary...
if( ScsiStatus == 0) // SCSI status byte "good"?
{
Cmnd->result = 0; // everything's OK
if( (Cmnd->cmnd[0] == INQUIRY))
{
UCHAR *InquiryData = Cmnd->request_buffer;
PFC_LOGGEDIN_PORT pLoggedInPort;
// We need to manipulate INQUIRY
// strings for COMPAQ RAID controllers to force
// Linux to scan additional LUNs. Namely, set
// the Inquiry string byte 2 (ANSI-approved version)
// to 2.
if( !memcmp( &InquiryData[8], "COMPAQ", 6 ))
{
InquiryData[2] = 0x2; // claim SCSI-2 compliance,
// so multiple LUNs may be scanned.
// (no SCSI-2 problems known in CPQ)
}
// snoop the Inquiry to detect Disk, Tape, etc. type
// (search linked list for the port_id we sent INQUIRY to)
pLoggedInPort = fcFindLoggedInPort( fcChip,
NULL, // DON'T search Scsi Nexus (we will set it)
Exchanges->fcExchange[ x_ID].fchs.d_id & 0xFFFFFF,
NULL, // DON'T search linked list for FC WWN
NULL); // DON'T care about end of list
if( pLoggedInPort )
{
pLoggedInPort->ScsiNexus.InqDeviceType = InquiryData[0];
}
else
{
printk("cpqfcTS: can't find LoggedIn FC port %06X for INQUIRY\n",
Exchanges->fcExchange[ x_ID].fchs.d_id & 0xFFFFFF);
}
}
}
// Scsi Status not good -- pass it back to caller
else
{
Cmnd->result = ScsiStatus; // SCSI status byte is 1st
// check for valid "sense" data
if( pFcpStatus->fcp_status & FCP_SNS_LEN_VALID )
{ // limit Scsi Sense field length!
int SenseLen = pFcpStatus->fcp_sns_len >>24; // (BigEndian) lower byte
SenseLen = SenseLen > sizeof( Cmnd->sense_buffer) ?
sizeof( Cmnd->sense_buffer) : SenseLen;
#ifdef FCP_COMPLETION_DBG
printk("copy sense_buffer %p, len %d, result %Xh\n",
Cmnd->sense_buffer, SenseLen, Cmnd->result);
#endif
// NOTE: There is some dispute over the FCP response
// format. Most FC devices assume that FCP_RSP_INFO
// is 8 bytes long, in spite of the fact that FCP_RSP_LEN
// is (virtually) always 0 and the field is "invalid".
// Some other devices assume that
// the FCP_SNS_INFO begins after FCP_RSP_LEN bytes (i.e. 0)
// when the FCP_RSP is invalid (this almost appears to be
// one of those "religious" issues).
// Consequently, we test the usual position of FCP_SNS_INFO
// for 7Xh, since the SCSI sense format says the first
// byte ("error code") should be 0x70 or 0x71. In practice,
// we find that every device does in fact have 0x70 or 0x71
// in the first byte position, so this test works for all
// FC devices.
// (This logic is especially effective for the CPQ/DEC HSG80
// & HSG60 controllers).
if( (pFcpStatus->fcp_sns_info[0] & 0x70) == 0x70 )
memcpy( Cmnd->sense_buffer,
&pFcpStatus->fcp_sns_info[0], SenseLen);
else
{
unsigned char *sbPtr =
(unsigned char *)&pFcpStatus->fcp_sns_info[0];
sbPtr -= 8; // back up 8 bytes hoping to find the
// start of the sense buffer
memcpy( Cmnd->sense_buffer, sbPtr, SenseLen);
}
// in the special case of Device Reset, tell upper layer
// to immediately retry (with SOFT_ERROR status)
// look for Sense Key Unit Attention (0x6) with ASC Device
// Reset (0x29)
// printk("SenseLen %d, Key = 0x%X, ASC = 0x%X\n",
// SenseLen, Cmnd->sense_buffer[2],
// Cmnd->sense_buffer[12]);
if( ((Cmnd->sense_buffer[2] & 0xF) == 0x6) &&
(Cmnd->sense_buffer[12] == 0x29) ) // Sense Code "reset"
{
Cmnd->result |= (DID_SOFT_ERROR << 16); // "Host" status byte 3rd
}
// check for SenseKey "HARDWARE ERROR", ASC InternalTargetFailure
else if( ((Cmnd->sense_buffer[2] & 0xF) == 0x4) && // "hardware error"
(Cmnd->sense_buffer[12] == 0x44) ) // Addtl. Sense Code
{
// printk("HARDWARE_ERROR, Channel/Target/Lun %d/%d/%d\n",
// Cmnd->channel, Cmnd->target, Cmnd->lun);
Cmnd->result |= (DID_ERROR << 16); // "Host" status byte 3rd
}
} // (end of sense len valid)
// there is no sense data to help out Linux's Scsi layers...
// We'll just return the Scsi status and hope he will "do the
// right thing"
else
{
// as far as we know, the Scsi status is sufficient
Cmnd->result |= (DID_OK << 16); // "Host" status byte 3rd
}
}
}
//PPPPPPPPPPPPPPPPPPPPPPPPP PAYLOAD PPPPPPPPP
// build data PAYLOAD; SCSI FCP_CMND I.U.
// remember BIG ENDIAN payload - DWord values must be byte-reversed
// (hence the affinity for byte pointer building).
static int build_FCP_payload( Scsi_Cmnd *Cmnd,
UCHAR* payload, ULONG type, ULONG fcp_dl )
{
int i;
switch( type)
{
case SCSI_IWE:
case SCSI_IRE:
// 8 bytes FCP_LUN
// Peripheral Device or Volume Set addressing, and LUN mapping
// When the FC port was looked up, we copied address mode
// and any LUN mask to the scratch pad SCp.phase & .mode
*payload++ = (UCHAR)Cmnd->SCp.phase;
// Now, because of "lun masking"
// (aka selective storage presentation),
// the contiguous Linux Scsi lun number may not match the
// device's lun number, so we may have to "map".
*payload++ = (UCHAR)Cmnd->SCp.have_data_in;
// We don't know of anyone in the FC business using these
// extra "levels" of addressing. In fact, confusion still exists
// just using the FIRST level... ;-)
*payload++ = 0; // 2nd level addressing
*payload++ = 0;
*payload++ = 0; // 3rd level addressing
*payload++ = 0;
*payload++ = 0; // 4th level addressing
*payload++ = 0;
// 4 bytes Control Field FCP_CNTL
*payload++ = 0; // byte 0: (MSB) reserved
*payload++ = 0; // byte 1: task codes
// byte 2: task management flags
// another "use" of the spare field to accomplish TDR
// note combination needed
if( (Cmnd->cmnd[0] == RELEASE) &&
(Cmnd->SCp.buffers_residual == FCP_TARGET_RESET) )
{
Cmnd->cmnd[0] = 0; // issue "Test Unit Ready" for TDR
*payload++ = 0x20; // target device reset bit
}
else
*payload++ = 0; // no TDR
// byte 3: (LSB) execution management codes
// bit 0 write, bit 1 read (don't set together)
if( fcp_dl != 0 )
{
if( type == SCSI_IWE ) // WRITE
*payload++ = 1;
else // READ
*payload++ = 2;
}
else
{
// On some devices, if RD or WR bits are set,
// and fcp_dl is 0, they will generate an error on the command.
// (i.e., if direction is specified, they insist on a length).
*payload++ = 0; // no data (necessary for CPQ)
}
// NOTE: clean this up if/when MAX_COMMAND_SIZE is increased to 16
// FCP_CDB allows 16 byte SCSI command descriptor blk;
// Linux SCSI CDB array is MAX_COMMAND_SIZE (12 at this time...)
for( i=0; (i < Cmnd->cmd_len) && i < MAX_COMMAND_SIZE; i++)
*payload++ = Cmnd->cmnd[i];
// if( Cmnd->cmd_len == 16 )
// {
// memcpy( payload, &Cmnd->SCp.buffers_residual, 4);
// }
payload+= (16 - i);
// FCP_DL is largest number of expected data bytes
// per CDB (i.e. read/write command)
*payload++ = (UCHAR)(fcp_dl >>24); // (MSB) 8 bytes data len FCP_DL
*payload++ = (UCHAR)(fcp_dl >>16);
*payload++ = (UCHAR)(fcp_dl >>8);
*payload++ = (UCHAR)fcp_dl; // (LSB)
break;
case SCSI_TWE: // need FCP_XFER_RDY
*payload++ = 0; // (4 bytes) DATA_RO (MSB byte 0)
*payload++ = 0;
*payload++ = 0;
*payload++ = 0; // LSB (byte 3)
// (4 bytes) BURST_LEN
// size of following FCP_DATA payload
*payload++ = (UCHAR)(fcp_dl >>24); // (MSB) 8 bytes data len FCP_DL
*payload++ = (UCHAR)(fcp_dl >>16);
*payload++ = (UCHAR)(fcp_dl >>8);
*payload++ = (UCHAR)fcp_dl; // (LSB)
// 4 bytes RESERVED
*payload++ = 0;
*payload++ = 0;
*payload++ = 0;
*payload++ = 0;
break;
default:
break;
}
return 0;
}