blob: 7f5b643431a3a04680e56caac30d47ffb3f27274 [file] [log] [blame]
/* 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/blk.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/smp_lock.h>
#define __KERNEL_SYSCALLS__
#define SHUTDOWN_SIGS (sigmask(SIGKILL)|sigmask(SIGINT)|sigmask(SIGTERM))
#include <linux/unistd.h>
#include <asm/system.h>
#include <asm/irq.h>
#include <asm/dma.h>
#include "scsi.h"
#include "hosts.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->target == ScsiNexus->target)
&&
(Exchanges->fcExchange[x_ID].Cmnd->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->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->host->hostdata;
// Was this command a cpqfc passthru ioctl ?
if (Cmnd->sc_request != NULL && Cmnd->host != NULL &&
Cmnd->host->hostdata != NULL &&
is_private_data_of_cpqfc((CPQFCHBA *) Cmnd->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;
Scsi_Cmnd *Cmnd;
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"
// now use the port's Scsi Command buffer for the
// Report Luns Command
Cmnd = &pLoggedInPort->ScsiCmnd;
ucBuff = pLoggedInPort->ReportLunsPayload;
memset( Cmnd, 0, sizeof(Scsi_Cmnd));
memset( ucBuff, 0, REPORT_LUNS_PL);
Cmnd->scsi_done = ScsiReportLunsDone;
Cmnd->host = cpqfcHBAdata->HostAdapter;
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->channel = pLoggedInPort->ScsiNexus.channel;
Cmnd->target = 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)
</