patch-2.2.18 linux/drivers/scsi/cpqfcTSworker.c

Next file: linux/drivers/scsi/cpqioctl.c
Previous file: linux/drivers/scsi/cpqfcTStrigger.c
Back to the patch index
Back to the overall index

diff -u --new-file --recursive --exclude-from /usr/src/exclude v2.2.17/drivers/scsi/cpqfcTSworker.c linux/drivers/scsi/cpqfcTSworker.c
@@ -0,0 +1,6238 @@
+/* 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/malloc.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 "sd.h"
+#include "hosts.h"   // struct Scsi_Host definition for T handler
+#include "cpqfcTSchip.h"
+#include "cpqfcTSstructs.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 accomodate 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
+  struct fs_struct *fs;
+  DECLARE_MUTEX_LOCKED(fcQueReady);
+  DECLARE_MUTEX_LOCKED(fcTYOBcomplete); 
+  DECLARE_MUTEX_LOCKED(TachFrozen);  
+  DECLARE_MUTEX_LOCKED(BoardLock);  
+
+  ENTER("WorkerThread");
+
+  lock_kernel();
+	/*
+	 * If we were started as result of loading a module, close all of the
+	 * user space pages.  We don't need them, and if we didn't close them
+	 * they would be locked into memory.
+	 */
+  exit_mm(current);
+
+  current->session = 1;
+  current->pgrp = 1;
+	
+  /* Become as one with the init task */
+	
+  exit_fs(current);	/* current->fs->count--; */
+  fs = init_task.fs;
+  // Some kernels compiled for SMP, while actually running
+  // on a uniproc machine, will return NULL for this call
+  if( !fs)
+  {
+    printk(" cpqfcTS FATAL: fs is NULL! Is this an SMP kernel on uniproc machine?\n ");
+  }
+ 
+  else
+  { 
+    current->fs = fs;
+    atomic_inc(&fs->count);
+  }
+
+  siginitsetinv(&current->blocked, SHUTDOWN_SIGS);
+
+
+  /*
+   * Set the name of this process.
+   */
+  sprintf(current->comm, "cpqfcTS_wt_%d", HostAdapter->host_no);
+
+  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( &io_request_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( &io_request_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 occured, 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( 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
+}
+
+  
+// 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( fcChip, (fchs->ox_rx_id >>16)); // complete
+	  
+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( fcChip, (fchs->ox_rx_id >>16)); // complete
+	  
+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( 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( 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( 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( 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:  
+}
+
+// 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)
+        // 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:
+
+}
+
+
+
+
+
+
+
+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);
+      if( Cmnd->scsi_done != NULL)
+        (*Cmnd->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;
+
+  spin_lock_irqsave( &io_request_lock, flags); // STOP _que function
+
+  PCI_TRACE( 0xA8)
+
+
+  cpqfcHBAdata->BoardLock = &BoardLock; // stop Linux SCSI command queuing
+  
+  // release the IO lock (and re-enable interrupts)
+  spin_unlock_irqrestore( &io_request_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).
+
+  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);
+      if( Cmnd->scsi_done != NULL)
+        (*Cmnd->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( 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
+
+
+// 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
+  {
+    for( i=0; i<8; i++)  // valid WWN passed?  NULL WWN invalid
+    {
+      if( wwn ) // non-null arg? (OK to pass NULL when not searching WWN)
+      {
+        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->target)
+                &&
+          (pLoggedInPort->ScsiNexus.channel == Cmnd->channel))
+      {
+        // For "passthru" modes, the IOCTL caller is responsible
+	// for setting the FCP-LUN addressing
+	if( !Cmnd->SCp.sent_command ) // NOT 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) 
+	  {
+            // we KNOW all the valid LUNs... 0xFF is invalid!
+            Cmnd->SCp.have_data_in = pLoggedInPort->ScsiNexus.lun[Cmnd->lun];
+	  }
+	  else
+	    Cmnd->SCp.have_data_in = Cmnd->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)
+{
+//  Scsi_Device *sdev = HostAdapter->host_queue;
+  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->target == pLoggedInPort->ScsiNexus.target)
+	       &&
+          (Cmnd->channel == pLoggedInPort->ScsiNexus.channel) )
+      {
+        Cmnd->result = (DID_SOFT_ERROR <<16); // force retry
+        if( Cmnd->scsi_done != NULL)
+          (*Cmnd->scsi_done)(Cmnd);
+        else
+          printk("LinkDnCmnd scsi_done ptr null, port_id %Xh\n",
+		  pLoggedInPort->port_id);
+        *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( 
+  ULONG *SESTalPairStart,
+  Scsi_Cmnd *Cmnd,
+  ULONG *sgPairs,
+  PSGPAGES sgPages  // 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 transfered
+  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 = virt_to_bus( dataHDR );
+      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 = virt_to_bus(
+                         &fcChip->SEST->RspHDR[ *fcExchangeIndex ]);
+
+                   // Do we need local or extended gather list?
+                   // depends on size - we can handle 3 len/addr pairs
+                   // locally.
+
+      fcp_dl = build_SEST_sgList( 
+        &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 = virt_to_bus(
+                              &fcChip->SEST->RspHDR[ *fcExchangeIndex ]);
+      
+      
+                   // Do we need local or extended gather list?
+                   // depends on size - we can handle 3 len/addr pairs
+                   // locally.
+
+      fcp_dl = build_SEST_sgList( 
+        &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( 
+        &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 = virt_to_bus( dataHDR );
+      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 = virt_to_bus( rspHDR );
+
+
+                   // Do we need local or extended gather list?
+                   // depends on size - we can handle 3 len/addr pairs
+                   // locally.
+
+      fcp_dl = build_SEST_sgList( 
+        &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 = virt_to_bus(CMDfchs); // TL needs physical addr
+                                                  // of frame to send
+      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( 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
+    }
+  }
+}
+
+
+
+
+// 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 ULONG build_SEST_sgList( 
+    ULONG *SESTalPairStart,  // the 3 len/address buffers in SEST
+    Scsi_Cmnd *Cmnd,
+    ULONG *sgPairs, 
+    PSGPAGES sgPages)  // 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 alignedPageAddress;  // TL hardware alignment requirement
+  int PairCount;
+  unsigned long ulBuff;
+  ULONG total_data_len=0; // (in bytes)
+  ULONG bytes_to_go = Cmnd->request_bufflen; // total xfer (S/G sum)
+  ULONG thisMappingLen;
+  struct scatterlist *sgl;  // S/G list (Linux format)
+
+
+
+  if( !Cmnd->use_sg )  // no S/G list?
+  {
+    *sgPairs = 1;      // use "local" S/G pair in SEST entry
+                       // (for now, ignore address bits above #31)
+    *alPair++ = bytes_to_go & 0x7ffff; // bits 18-0, length
+    ulBuff = virt_to_bus( Cmnd->request_buffer);
+#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;
+  }
+
+
+  // [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;
+  sgl = (struct scatterlist*)Cmnd->request_buffer;  
+  
+  if( *sgPairs <= 3 ) // need "local" SEST list
+  {
+    while( bytes_to_go)
+    {
+      thisMappingLen = sgl->length;  // we want them ALL on every pass
+      bytes_to_go = bytes_to_go - thisMappingLen;
+
+      // we have L/A pair; L = thisMappingLen, A = physicalAddress
+      // load into SEST...
+      total_data_len += thisMappingLen & 0x7ffff;  // mask in valid bits
+                                                   // per SEST format
+      *alPair = thisMappingLen & 0x7ffff; // bits 18-0, length
+//      physicalAddress.HighPart <= 19;  // shift to bit 19
+
+                   // pick up bits 44-32 of upper 64-bit address
+                   // and load into 31-19 LBAU (upper addr) of SEST entry
+//      *alPair++ |=(ULONG)((physicalAddress.HighPart & 0xFFF8)); 
+      // on Tachlite TS's local S/G, we can handle 13 extra address bits
+      // i.e., bits 31-19 are actually bits  44-32 of physicalAddress
+
+      alPair++;
+
+      ulBuff = virt_to_bus( sgl->address);
+#if BITS_PER_LONG > 32
+      if( ulBuff >>32 )
+      {
+        printk("cqpfcTS: Tach DMA address %p > 32 bits\n", (void*)ulBuff );
+        return 0;
+      }
+#endif
+      *alPair++ = (ULONG)ulBuff; // lower 32 bits (31-0)
+
+      ++sgl;  // next S/G pair
+#ifdef DBG_SEST_SGLIST
+      printk(" thisLen %d ", thisMappingLen);
+      printk(" remain %d\n", bytes_to_go);
+#endif
+
+    }
+  }
+
+
+
+
+  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
+
+    while( bytes_to_go )
+    {
+
+
+      // Per SEST format, we can support 524287 byte lenghts 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 like 256Meg), it's possible to exceed this
+      // length in a single S/G address/len mapping.
+      //
+      // Check for Tachyon length boundary
+      //
+      if( sgl->length > 0x7ffff )
+      {
+        // never ask for more than we can handle
+  	thisMappingLen = sgl->length & 0x7ffff;  
+      }
+      else
+        thisMappingLen = sgl->length;        
+      
+
+
+      // should we load into "this" extended S/G page, or allocate
+      // new page?
+
+      if( PairCount >= TL_EXT_SG_PAGE_COUNT )
+      {
+        // have we exceeded the max possible extended pages?      
+        if( AllocatedPages >= TL_MAX_SGPAGES)
+        {
+          printk("Error: aborted loop on %d Ext. S/G page allocations\n",
+            AllocatedPages);
+
+          total_data_len = 0;  // failure!! Ext. S/G is All-or-none affair
+          break; // failed
+        }
+        
+        // Allocate the TL Extended S/G list page from O/S pool.  We have
+        // to allocated 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
+
+        sgPages->PoolPage[ AllocatedPages] = 
+          kmalloc( TL_EXT_SG_PAGE_BYTELEN*2,GFP_ATOMIC); // double for alignment
+
+        
+        if( !sgPages->PoolPage[ AllocatedPages] )  // Allocation failed?
+        {
+
+          printk("Error: Allocation failed @ %d S/G page allocations\n",
+            AllocatedPages);
+
+          total_data_len = 0;  // failure!! Ext. S/G is All-or-none affair
+          break;               // give up
+        }
+                               // clear out memory we just allocated                     
+        memset( sgPages->PoolPage[AllocatedPages], 0,
+          TL_EXT_SG_PAGE_BYTELEN*2);
+
+      
+        // 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 = virt_to_bus( sgPages->PoolPage[AllocatedPages]);
+
+#if BITS_PER_LONG > 32
+        if( ulBuff >>32 )
+        {
+          printk("cqpfcTS: Tach ext. S/G DMA address %p > 32 bits\n", 
+		  (void*)ulBuff );
+          return 0;
+        }
+#endif
+	
+        ulBuff += TL_EXT_SG_PAGE_BYTELEN; // ensures we pass align. boundary
+        ulBuff &= (0xFFFFFFFF - (TL_EXT_SG_PAGE_BYTELEN -1) );// mask off LSBs
+         
+        alignedPageAddress = (ULONG)ulBuff;
+#ifdef DBG_SEST_SGLIST
+        printk("new PoolPage: %p, alignedPageAddress %lXh\n", 
+          sgPages->PoolPage[AllocatedPages], ulBuff);
+#endif
+
+
+        // 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.
+        if( alPair == SESTalPairStart) // initial Ext. S/G list?
+          *alPair = 0;
+        else // not the SEST entry... Len must be non-0, so
+             // arbitrarily set it to number bytes remaining
+          *alPair = ( bytes_to_go & 0x7ffff);
+
+#ifdef DBG_SEST_SGLIST
+        printk("PairCount %d @%p even %Xh, ", 
+          PairCount, alPair, *alPair);
+#endif
+        alPair++;  // next DWORD
+
+        *alPair = alignedPageAddress; // TL needs 32-bit physical
+#ifdef DBG_SEST_SGLIST
+        printk("odd %Xh\n", *alPair);
+#endif
+                   
+        // now reset the pointer to the ACTUAL (Extended) S/G page
+        // which will accept the Len/ PhysicalAddress pairs
+        alPair = bus_to_virt(alignedPageAddress);
+        
+        AllocatedPages++;
+        PairCount = 1;  // starting new Ext. S/G page
+      }  // end of new TL Ext. S/G page allocation
+
+      
+      *alPair = thisMappingLen; // bits 18-0, length (range check above)
+      
+      
+//      physicalAddress.HighPart <= 19;  // shift to bit 19
+      
+                   // pick up bits 44-32 of upper 64-bit address
+                   // and load into 31-19 LBAU (upper addr) of SEST entry
+//      *alPair |=(ULONG)((physicalAddress.HighPart & 0xFFF8)); 
+
+      
+#ifdef DBG_SEST_SGLIST
+      printk("PairCount %d @%p, even %Xh, ", 
+        PairCount, alPair, *alPair);
+#endif
+
+      alPair++;    // next DWORD
+      // on Tachlite TS's local S/G, we can handle 13 extra address bits
+      // i.e., bits 31-19 are actually bits  44-32 of physicalAddress
+
+
+      ulBuff = virt_to_bus( sgl->address);
+#if BITS_PER_LONG > 32
+      if( ulBuff >>32 )
+      {
+        printk("cqpfcTS: Tach DMA address %p > 32 bits\n", (void*)ulBuff );
+        return 0;
+      }
+#endif
+      *alPair = (ULONG)ulBuff; // lower 32 bits (31-0)
+
+
+#ifdef DBG_SEST_SGLIST
+      printk("odd %Xh\n", *alPair);
+#endif
+      alPair++;    // next DWORD
+                                           
+
+      PairCount++; // next Length/Address pair
+      bytes_to_go -= thisMappingLen;
+      total_data_len += thisMappingLen;  
+      sgl++;  // next S/G pair
+    }
+  }
+  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( 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( 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;  
+}
+
+
+
+
+
+// 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( 
+       PTACHYON fcChip, 
+       ULONG x_ID)
+{
+  FC_EXCHANGES *Exchanges = fcChip->Exchanges;
+  
+  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
+      {
+        printk("DEFAULT result %Xh, x_ID %Xh, Cmnd %p\n", 
+          Exchanges->fcExchange[ x_ID ].status, x_ID, 
+	  Exchanges->fcExchange[ x_ID ].Cmnd);
+        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)    
+    }
+
+    // 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)
+        (*Exchanges->fcExchange[ x_ID ].Cmnd->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:
+    
+    // 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))
+    {
+      int i = 0;
+
+      // extended S/G list was used -- Free the allocated ext. S/G pages
+
+      while( fcChip->SEST->sgPages[x_ID].PoolPage[i] && 
+             (i < TL_MAX_SGPAGES) )
+      {
+        kfree( fcChip->SEST->sgPages[x_ID].PoolPage[i]);
+        fcChip->SEST->sgPages[x_ID].PoolPage[i] = NULL;
+        i++;
+      }
+    }
+  
+    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
+      *payload++ = 0;    // byte 2: task management flags
+		      // 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;
+}
+

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)