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

Next file: linux/drivers/scsi/cpqfcTSi2c.c
Previous file: linux/drivers/scsi/cpqfcTSchip.h
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/cpqfcTScontrol.c linux/drivers/scsi/cpqfcTScontrol.c
@@ -0,0 +1,2200 @@
+/* Copyright 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
+*/
+/* These functions control the host bus adapter (HBA) hardware.  The main chip
+   control takes place in the interrupt handler where we process the IMQ 
+   (Inbound Message Queue).  The IMQ is Tachyon's way of communicating FC link
+   events and state information to the driver.  The Single Frame Queue (SFQ)
+   buffers incoming FC frames for processing by the driver.  References to 
+   "TL/TS UG" are for:
+   "HP HPFC-5100/5166 Tachyon TL/TS ICs User Guide", August 16, 1999, 1st Ed.
+   Hewlitt Packard Manual Part Number 5968-1083E.
+*/
+
+#define LinuxVersionCode(v, p, s) (((v)<<16)+((p)<<8)+(s))
+
+#include <linux/blk.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/ioport.h>  // request_region() prototype
+#include <linux/sched.h>
+#include <linux/malloc.h>  // need "kfree" for ext. S/G pages
+#include <linux/types.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/unistd.h>
+#include <asm/io.h>  // struct pt_regs for IRQ handler & Port I/O
+#include <asm/irq.h>
+#if LINUX_VERSION_CODE < LinuxVersionCode(2,3,18)
+#include <asm/spinlock.h>
+#else
+#include <linux/spinlock.h>
+#endif
+
+#include "sd.h"
+#include "hosts.h"   // Scsi_Host definition for INT handler
+#include "cpqfcTSchip.h"
+#include "cpqfcTSstructs.h"
+
+//#define IMQ_DEBUG 1
+
+static void fcParseLinkStatusCounters(TACHYON * fcChip);
+static void CpqTsGetSFQEntry(TACHYON * fcChip, 
+	      USHORT pi, ULONG * buffr, BOOLEAN UpdateChip); 
+
+
+// Note special requirements for Q alignment!  (TL/TS UG pg. 190)
+// We place critical index pointers at end of QUE elements to assist
+// in non-symbolic (i.e. memory dump) debugging
+// opcode defines placement of Queues (e.g. local/external RAM)
+
+int CpqTsCreateTachLiteQues( void* pHBA, int opcode)
+{
+  CPQFCHBA *cpqfcHBAdata = (CPQFCHBA*)pHBA;
+  PTACHYON fcChip = &cpqfcHBAdata->fcChip;
+
+  int iStatus=0;
+  unsigned long ulAddr;
+
+
+  // NOTE! fcMemManager() will return system virtual addresses.
+  // System (kernel) virtual addresses, though non-paged, still
+  // aren't physical addresses.  Convert to PHYSICAL_ADDRESS for Tachyon's
+  // DMA use.
+  ENTER("CreateTachLiteQues");
+
+
+  // Allocate primary EXCHANGES array...
+  
+  printk("Allocating %u for %u Exchanges ", 
+	  (ULONG)sizeof(FC_EXCHANGES), TACH_MAX_XID);
+  fcChip->Exchanges = kmalloc( sizeof( FC_EXCHANGES), GFP_KERNEL );
+  printk("@ %p\n", fcChip->Exchanges);
+
+  if( fcChip->Exchanges == NULL ) // fatal error!!
+  {
+    printk("kmalloc failure on Exchanges: fatal error\n");
+    return -1;
+  }
+  // zero out the entire EXCHANGE space
+  memset( fcChip->Exchanges, 0, sizeof( FC_EXCHANGES));  
+
+
+  printk("Allocating %u for LinkQ ", (ULONG)sizeof(FC_LINK_QUE));
+  cpqfcHBAdata->fcLQ = kmalloc( sizeof( FC_LINK_QUE), GFP_KERNEL );
+  printk("@ %p (%u elements)\n", cpqfcHBAdata->fcLQ, FC_LINKQ_DEPTH);
+  memset( cpqfcHBAdata->fcLQ, 0, sizeof( FC_LINK_QUE));
+
+  if( cpqfcHBAdata->fcLQ == NULL ) // fatal error!!
+  {
+    printk("kmalloc failure on fc Link Que: fatal error\n");
+    return -1;
+  }
+  // zero out the entire EXCHANGE space
+  memset( cpqfcHBAdata->fcLQ, 0, sizeof( FC_LINK_QUE));  
+
+
+
+  
+  // Verify that basic Tach I/O registers are not NULL  
+  
+  if( !fcChip->Registers.ReMapMemBase )
+  {
+    printk("HBA base address NULL: fatal error\n");
+    return -1;
+  }
+
+
+  // Initialize the fcMemManager memory pairs (stores allocated/aligned
+  // pairs for future freeing)
+  memset( cpqfcHBAdata->dynamic_mem, 0, sizeof(cpqfcHBAdata->dynamic_mem));
+  
+
+  // Allocate Tach's Exchange Request Queue (each ERQ entry 32 bytes)
+  
+  fcChip->ERQ = fcMemManager( &cpqfcHBAdata->dynamic_mem[0],
+		  sizeof( TachLiteERQ ), 32*(ERQ_LEN), 0L );
+  if( !fcChip->ERQ )
+  {
+    printk("kmalloc/alignment failure on ERQ: fatal error\n");
+    return -1;
+  }
+  fcChip->ERQ->length = ERQ_LEN-1;
+  ulAddr = virt_to_bus( fcChip->ERQ);
+#if BITS_PER_LONG > 32
+  if( (ulAddr >> 32) )
+  {
+    printk(" FATAL! ERQ ptr %p exceeds Tachyon's 32-bit register size\n",
+		    (void*)ulAddr);
+    return -1;  // failed
+  }
+#endif
+  fcChip->ERQ->base = (ULONG)ulAddr;  // copy for quick reference
+
+
+  // Allocate Tach's Inbound Message Queue (32 bytes per entry)
+  
+  fcChip->IMQ = fcMemManager( &cpqfcHBAdata->dynamic_mem[0],
+		  sizeof( TachyonIMQ ), 32*(IMQ_LEN), 0L );
+  if( !fcChip->IMQ )
+  {
+    printk("kmalloc/alignment failure on IMQ: fatal error\n");
+    return -1;
+  }
+  fcChip->IMQ->length = IMQ_LEN-1;
+
+  ulAddr = virt_to_bus( fcChip->IMQ);
+#if BITS_PER_LONG > 32
+  if( (ulAddr >> 32) )
+  {
+    printk(" FATAL! IMQ ptr %p exceeds Tachyon's 32-bit register size\n",
+		    (void*)ulAddr);
+    return -1;  // failed
+  }
+#endif
+  fcChip->IMQ->base = (ULONG)ulAddr;  // copy for quick reference
+
+
+  // Allocate Tach's  Single Frame Queue (64 bytes per entry)
+  fcChip->SFQ = fcMemManager( &cpqfcHBAdata->dynamic_mem[0],
+		  sizeof( TachLiteSFQ ), 64*(SFQ_LEN),0L );
+  if( !fcChip->SFQ )
+  {
+    printk("kmalloc/alignment failure on SFQ: fatal error\n");
+    return -1;
+  }
+  fcChip->SFQ->length = SFQ_LEN-1;      // i.e. Que length [# entries -
+                                       // min. 32; max.  4096 (0xffff)]
+  
+  ulAddr = virt_to_bus( fcChip->SFQ);
+#if BITS_PER_LONG > 32
+  if( (ulAddr >> 32) )
+  {
+    printk(" FATAL! SFQ ptr %p exceeds Tachyon's 32-bit register size\n",
+		    (void*)ulAddr);
+    return -1;  // failed
+  }
+#endif
+  fcChip->SFQ->base = (ULONG)ulAddr;  // copy for quick reference
+
+
+  // Allocate SCSI Exchange State Table; aligned nearest @sizeof
+  // power-of-2 boundary
+  // LIVE DANGEROUSLY!  Assume the boundary for SEST mem will
+  // be on physical page (e.g. 4k) boundary.
+  printk("Allocating %u for TachSEST for %u Exchanges\n", 
+		 (ULONG)sizeof(TachSEST), TACH_SEST_LEN);
+  fcChip->SEST = fcMemManager( &cpqfcHBAdata->dynamic_mem[0],
+		  sizeof(TachSEST),  4, 0L );
+//		  sizeof(TachSEST),  64*TACH_SEST_LEN, 0L );
+  if( !fcChip->SEST )
+  {
+    printk("kmalloc/alignment failure on SEST: fatal error\n");
+    return -1;
+  }
+
+  fcChip->SEST->length = TACH_SEST_LEN;  // e.g. DON'T subtract one 
+                                       // (TL/TS UG, pg 153)
+
+  ulAddr = virt_to_bus( fcChip->SEST);
+#if BITS_PER_LONG > 32
+  if( (ulAddr >> 32) )
+  {
+    printk(" FATAL! SFQ ptr %p exceeds Tachyon's 32-bit register size\n",
+		    (void*)ulAddr);
+    return -1;  // failed
+  }
+#endif
+  fcChip->SEST->base = (ULONG)ulAddr;  // copy for quick reference
+
+
+			      // Now that structures are defined,
+			      // fill in Tachyon chip registers...
+
+			      // EEEEEEEE  EXCHANGE REQUEST QUEUE
+
+  writel( fcChip->ERQ->base, 
+    (fcChip->Registers.ReMapMemBase + TL_MEM_ERQ_BASE));
+      
+  writel( fcChip->ERQ->length,
+    (fcChip->Registers.ReMapMemBase + TL_MEM_ERQ_LENGTH));
+     
+
+  fcChip->ERQ->producerIndex = 0L;
+  writel( fcChip->ERQ->producerIndex,
+    (fcChip->Registers.ReMapMemBase + TL_MEM_ERQ_PRODUCER_INDEX));
+      
+
+		// NOTE! write consumer index last, since the write
+		// causes Tachyon to process the other registers
+
+  ulAddr = virt_to_bus( &fcChip->ERQ->consumerIndex);
+
+  // NOTE! Tachyon DMAs to the ERQ consumer Index host
+		// address; must be correctly aligned
+  writel( (ULONG)ulAddr,
+    (fcChip->Registers.ReMapMemBase + TL_MEM_ERQ_CONSUMER_INDEX_ADR));
+
+
+
+				 // IIIIIIIIIIIII  INBOUND MESSAGE QUEUE
+				 // Tell Tachyon where the Que starts
+
+  // set the Host's pointer for Tachyon to access
+
+  printk("  cpqfcTS: writing IMQ BASE %Xh  ", fcChip->IMQ->base );
+  writel( fcChip->IMQ->base, 
+    (fcChip->Registers.ReMapMemBase + IMQ_BASE));
+
+  writel( fcChip->IMQ->length,
+    (fcChip->Registers.ReMapMemBase + IMQ_LENGTH));
+
+  writel( fcChip->IMQ->consumerIndex,
+    (fcChip->Registers.ReMapMemBase + IMQ_CONSUMER_INDEX));
+
+
+		// NOTE: TachLite DMAs to the producerIndex host address
+		// must be correctly aligned with address bits 1-0 cleared
+    // Writing the BASE register clears the PI register, so write it last
+  ulAddr = virt_to_bus( &fcChip->IMQ->producerIndex);
+#if BITS_PER_LONG > 32
+  if( (ulAddr >> 32) )
+  {
+    printk(" FATAL! IMQ ptr %p exceeds Tachyon's 32-bit register size\n",
+		    (void*)ulAddr);
+    return -1;  // failed
+  }
+#endif
+//#if DBG
+  printk("  PI %Xh\n", (ULONG)ulAddr );
+//#endif
+  writel( (ULONG)ulAddr, 
+    (fcChip->Registers.ReMapMemBase + IMQ_PRODUCER_INDEX));
+
+
+
+				 // SSSSSSSSSSSSSSS SINGLE FRAME SEQUENCE
+				 // Tell TachLite where the Que starts
+
+  writel( fcChip->SFQ->base, 
+    (fcChip->Registers.ReMapMemBase + TL_MEM_SFQ_BASE));
+
+  writel( fcChip->SFQ->length,
+    (fcChip->Registers.ReMapMemBase + TL_MEM_SFQ_LENGTH));
+
+
+         // tell TachLite where SEST table is & how long
+  writel( fcChip->SEST->base,
+    (fcChip->Registers.ReMapMemBase + TL_MEM_SEST_BASE));
+
+  printk("  cpqfcTS: SEST %p(virt): Wrote base %Xh @ %p\n",
+    fcChip->SEST, fcChip->SEST->base, 
+    fcChip->Registers.ReMapMemBase + TL_MEM_SEST_BASE);
+
+  writel( fcChip->SEST->length,
+    (fcChip->Registers.ReMapMemBase + TL_MEM_SEST_LENGTH));
+      
+  writel( (TL_EXT_SG_PAGE_COUNT-1),
+    (fcChip->Registers.ReMapMemBase + TL_MEM_SEST_SG_PAGE));
+
+
+  LEAVE("CreateTachLiteQues");
+
+  return iStatus;
+}
+
+
+
+// function to return TachLite to Power On state
+// 1st - reset tachyon ('SOFT' reset)
+// others - future
+
+int CpqTsResetTachLite(void *pHBA, int type)
+{
+  CPQFCHBA *cpqfcHBAdata = (CPQFCHBA*)pHBA;
+  PTACHYON fcChip = &cpqfcHBAdata->fcChip;
+  ULONG ulBuff, i;
+  int ret_status=0; // def. success
+
+  ENTER("ResetTach");
+  
+  switch(type)
+  {
+
+    case CLEAR_FCPORTS:
+
+      // in case he was running previously, mask Tach's interrupt
+      writeb( 0, (fcChip->Registers.ReMapMemBase + IINTEN));
+      
+     // de-allocate mem for any Logged in ports
+      // (e.g., our module is unloading)
+      // search the forward linked list, de-allocating
+      // the memory we allocated when the port was initially logged in
+      {
+        PFC_LOGGEDIN_PORT pLoggedInPort = fcChip->fcPorts.pNextPort;
+        PFC_LOGGEDIN_PORT ptr;
+//        printk("checking for allocated LoggedInPorts...\n");
+			
+        while( pLoggedInPort )
+        {
+          ptr = pLoggedInPort;
+          pLoggedInPort = ptr->pNextPort;
+//	  printk("kfree(%p) on FC LoggedInPort port_id 0x%06lX\n",
+//			  ptr, ptr->port_id);
+          kfree( ptr );
+        }
+      }
+      // (continue resetting hardware...)
+
+    case 1:                   // RESTART Tachyon (power-up state)
+
+      // in case he was running previously, mask Tach's interrupt
+      writeb( 0, (fcChip->Registers.ReMapMemBase + IINTEN));
+			      // turn OFF laser (NOTE: laser is turned
+                              // off during reset, because GPIO4 is cleared
+                              // to 0 by reset action - see TLUM, sec 7.22)
+                              // However, CPQ 64-bit HBAs have a "health
+                              // circuit" which keeps laser ON for a brief
+                              // period after it is turned off ( < 1s)
+      
+      fcChip->LaserControl( fcChip->Registers.ReMapMemBase, 0);
+  
+
+
+            // soft reset timing constraints require:
+            //   1. set RST to 1
+            //   2. read SOFTRST register 
+            //      (128 times per R. Callison code)
+            //   3. clear PCI ints
+            //   4. clear RST to 0
+      writel( 0xff000001L,
+        (fcChip->Registers.ReMapMemBase + TL_MEM_SOFTRST));
+        
+      for( i=0; i<128; i++)
+        ulBuff = readl( fcChip->Registers.ReMapMemBase + TL_MEM_SOFTRST);
+
+        // clear the soft reset
+      for( i=0; i<8; i++)
+  	writel( 0, (fcChip->Registers.ReMapMemBase + TL_MEM_SOFTRST));
+
+               
+
+			       // clear out our copy of Tach regs,
+			       // because they must be invalid now,
+			       // since TachLite reset all his regs.
+      CpqTsDestroyTachLiteQues(cpqfcHBAdata,0); // remove Host-based Que structs
+      cpqfcTSClearLinkStatusCounters(fcChip);  // clear our s/w accumulators
+                               // lower bits give GBIC info
+      fcChip->Registers.TYstatus.value = 
+	              readl( fcChip->Registers.TYstatus.address );
+      break;
+
+/*
+    case 2:                   // freeze SCSI
+    case 3:                   // reset Outbound command que (ERQ)
+    case 4:                   // unfreeze OSM (Outbound Seq. Man.) 'er'
+    case 5:                   // report status
+
+    break;
+*/
+    default:
+      ret_status = -1;  // invalid option passed to RESET function
+      break;
+  }
+  LEAVE("ResetTach");
+  return ret_status;
+}
+
+
+
+
+
+
+// 'addrBase' is IOBaseU for both TachLite and (older) Tachyon
+int CpqTsLaserControl( void* addrBase, int opcode )
+{
+  ULONG dwBuff;
+
+  dwBuff = readl((addrBase + TL_MEM_TACH_CONTROL) ); // read TL Control reg
+                                                    // (change only bit 4)
+  if( opcode == 1)
+    dwBuff |= ~0xffffffefL; // set - ON
+  else
+    dwBuff &= 0xffffffefL;  // clear - OFF
+  writel( dwBuff, (addrBase + TL_MEM_TACH_CONTROL)); // write TL Control reg
+  return 0;
+}
+
+
+
+
+
+// Use controller's "Options" field to determine loopback mode (if any)
+//   internal loopback (silicon - no GBIC)
+//   external loopback (GBIC - no FC loop)
+//   no loopback: L_PORT, external cable from GBIC required
+
+int CpqTsInitializeFrameManager( void *pChip, int opcode)
+{
+  PTACHYON fcChip;
+  int iStatus;
+  ULONG wwnLo, wwnHi; // for readback verification
+
+  ENTER("InitializeFrameManager");
+  fcChip = (PTACHYON)pChip;
+  if( !fcChip->Registers.ReMapMemBase )   // undefined controller?
+    return -1;
+
+  // TL/TS UG, pg. 184
+  // 0x0065 = 100ms for RT_TOV
+  // 0x01f5 = 500ms for ED_TOV
+  // 0x07D1 = 2000ms 
+  fcChip->Registers.ed_tov.value = 0x006507D1; 
+  writel( fcChip->Registers.ed_tov.value,
+    (fcChip->Registers.ed_tov.address));
+      
+
+  // Set LP_TOV to the FC-AL2 specified 2 secs.
+  // TL/TS UG, pg. 185
+  writel( 0x07d00010, fcChip->Registers.ReMapMemBase +TL_MEM_FM_TIMEOUT2);
+
+
+  // Now try to read the WWN from the adapter's NVRAM
+  iStatus = CpqTsReadWriteWWN( fcChip, 1); // '1' for READ
+
+  if( iStatus )   // NVRAM read failed?
+  {
+    printk(" WARNING! HBA NVRAM WWN read failed - make alias\n");
+    // make up a WWN.  If NULL or duplicated on loop, FC loop may hang!
+
+
+    fcChip->Registers.wwn_hi = (__u32)jiffies;
+    fcChip->Registers.wwn_hi |= 0x50000000L;
+    fcChip->Registers.wwn_lo = 0x44556677L;
+  }
+
+  
+  writel( fcChip->Registers.wwn_hi, 
+	  fcChip->Registers.ReMapMemBase + TL_MEM_FM_WWN_HI);
+  
+  writel( fcChip->Registers.wwn_lo, 
+	  fcChip->Registers.ReMapMemBase + TL_MEM_FM_WWN_LO);
+	  
+
+  // readback for verification:
+  wwnHi = readl( fcChip->Registers.ReMapMemBase + TL_MEM_FM_WWN_HI ); 
+          
+  wwnLo = readl( fcChip->Registers.ReMapMemBase + TL_MEM_FM_WWN_LO);
+  // test for correct chip register WRITE/READ
+  DEBUG_PCI( printk("  WWN %08X%08X\n",
+    fcChip->Registers.wwn_hi, fcChip->Registers.wwn_lo ) );
+    
+  if( wwnHi != fcChip->Registers.wwn_hi ||
+      wwnLo != fcChip->Registers.wwn_lo )
+  {
+    printk( "cpqfcTS: WorldWideName register load failed\n");
+    return -1; // FAILED!
+  }
+
+
+
+			// set Frame Manager Initialize command
+  fcChip->Registers.FMcontrol.value = 0x06;
+
+  // Note: for test/debug purposes, we may use "Hard" address,
+  // but we completely support "soft" addressing, including
+  // dynamically changing our address.
+  if( fcChip->Options.intLoopback == 1 )            // internal loopback
+    fcChip->Registers.FMconfig.value = 0x0f002080L;
+  else if( fcChip->Options.extLoopback == 1 )            // internal loopback
+    fcChip->Registers.FMconfig.value = 0x0f004080L;
+  else                  // L_Port
+    fcChip->Registers.FMconfig.value = 0x55000100L; // hard address (55h start)
+//    fcChip->Registers.FMconfig.value = 0x01000080L; // soft address (can't pick)
+//    fcChip->Registers.FMconfig.value = 0x55000100L; // hard address (55h start)
+		
+  // write config to FM
+
+  if( !fcChip->Options.intLoopback && !fcChip->Options.extLoopback )
+                               // (also need LASER for real LOOP)
+    fcChip->LaserControl( fcChip->Registers.ReMapMemBase, 1); // turn on LASER
+
+  writel( fcChip->Registers.FMconfig.value,
+    fcChip->Registers.FMconfig.address);
+    
+
+			       // issue INITIALIZE command to FM - ACTION!
+  writel( fcChip->Registers.FMcontrol.value,
+    fcChip->Registers.FMcontrol.address);
+    
+  LEAVE("InitializeFrameManager");
+  
+  return 0;
+}
+
+
+
+
+
+// This "look ahead" function examines the IMQ for occurence of
+// "type".  Returns 1 if found, 0 if not.
+static int PeekIMQEntry( PTACHYON fcChip, ULONG type)
+{
+  ULONG CI = fcChip->IMQ->consumerIndex;
+  ULONG PI = fcChip->IMQ->producerIndex; // snapshot of IMQ indexes
+  
+  while( CI != PI )
+  {                             // proceed with search
+    if( (++CI) >= IMQ_LEN ) CI = 0; // rollover check
+    
+    switch( type )
+    {
+      case ELS_LILP_FRAME:
+      {
+      // first, we need to find an Inbound Completion message,
+      // If we find it, check the incoming frame payload (1st word)
+      // for LILP frame
+        if( (fcChip->IMQ->QEntry[CI].type & 0x1FF) == 0x104 )
+        { 
+          TachFCHDR_GCMND* fchs;
+          ULONG ulFibreFrame[2048/4];  // max DWORDS in incoming FC Frame
+	  USHORT SFQpi = (USHORT)(fcChip->IMQ->QEntry[CI].word[0] & 0x0fffL);
+
+	  CpqTsGetSFQEntry( fcChip,
+            SFQpi,        // SFQ producer ndx         
+	    ulFibreFrame, // contiguous dest. buffer
+	    FALSE);       // DON'T update chip--this is a "lookahead"
+          
+	  fchs = (TachFCHDR_GCMND*)&ulFibreFrame;
+          if( fchs->pl[0] == ELS_LILP_FRAME)
+	  {
+            return 1; // found the LILP frame!
+	  }
+	  else
+	  {
+	    // keep looking...
+	  }
+	}  
+      }
+      break;
+
+      case OUTBOUND_COMPLETION:
+        if( (fcChip->IMQ->QEntry[CI].type & 0x1FF) == 0x00 )
+	{
+
+          // any OCM errors?
+          if( fcChip->IMQ->QEntry[CI].word[2] & 0x7a000000L )
+            return 1;   	    // found OCM error
+	}
+      break;
+
+
+      
+      default:
+      break;
+    }
+  }
+  return 0; // failed to find "type"
+}
+
+			
+static void SetTachTOV( CPQFCHBA* cpqfcHBAdata)
+{
+  PTACHYON fcChip = &cpqfcHBAdata->fcChip; 
+  
+  // TL/TS UG, pg. 184
+  // 0x0065 = 100ms for RT_TOV
+  // 0x01f5 = 500ms for ED_TOV
+  // 0x07d1 = 2000ms for ED_TOV
+
+  // SANMark Level 1 requires an "initialization backoff"
+  // (See "SANMark Test Suite Level 1":
+  // initialization_timeout.fcal.SANMark-1.fc)
+  // We have to use 2sec, 24sec, then 128sec when login/
+  // port discovery processes fail to complete.
+  
+  // when port discovery completes (logins done), we set
+  // ED_TOV to 500ms -- this is the normal operational case
+  // On the first Link Down, we'll move to 2 secs (7D1 ms)
+  if( (fcChip->Registers.ed_tov.value &0xFFFF) <= 0x1f5)
+    fcChip->Registers.ed_tov.value = 0x006507D1; 
+  
+  // If we get another LST after we moved TOV to 2 sec,
+  // increase to 24 seconds (5DC1 ms) per SANMark!
+  else if( (fcChip->Registers.ed_tov.value &0xFFFF) <= 0x7D1)
+    fcChip->Registers.ed_tov.value = 0x00655DC1; 
+
+  // If we get still another LST, set the max TOV (Tachyon
+  // has only 16 bits for ms timer, so the max is 65.5 sec)
+  else if( (fcChip->Registers.ed_tov.value &0xFFFF) <= 0x5DC1)
+    fcChip->Registers.ed_tov.value = 0x0065FFFF; 
+
+  writel( fcChip->Registers.ed_tov.value,
+    (fcChip->Registers.ed_tov.address));
+  // keep the same 2sec LP_TOV 
+  writel( 0x07D00010, fcChip->Registers.ReMapMemBase +TL_MEM_FM_TIMEOUT2);
+}	
+
+
+// The IMQ is an array with IMQ_LEN length, each element (QEntry)
+// with eight 32-bit words.  Tachyon PRODUCES a QEntry with each
+// message it wants to send to the host.  The host CONSUMES IMQ entries
+
+// This function copies the current
+// (or oldest not-yet-processed) QEntry to
+// the caller, clears/ re-enables the interrupt, and updates the
+// (Host) Consumer Index.
+// Return value:
+//  0   message processed, none remain (producer and consumer
+//        indexes match)
+//  1   message processed, more messages remain
+// -1   no message processed - none were available to process
+// Remarks:
+//   TL/TS UG specifices that the following actions for
+//   INTA_L handling:
+//   1. read PCI Interrupt Status register (0xff)
+//   2. all IMQ messages should be processed before writing the
+//      IMQ consumer index.
+
+
+int CpqTsProcessIMQEntry(void *host)
+{
+  struct Scsi_Host *HostAdapter = (struct Scsi_Host *)host;
+  CPQFCHBA *cpqfcHBAdata = (CPQFCHBA *)HostAdapter->hostdata;
+  PTACHYON fcChip = &cpqfcHBAdata->fcChip; 
+  FC_EXCHANGES *Exchanges = fcChip->Exchanges;
+  int iStatus;
+  USHORT i, RPCset, DPCset;
+  ULONG x_ID;
+  ULONG ulBuff, dwStatus;
+  TachFCHDR_GCMND* fchs;
+  ULONG ulFibreFrame[2048/4];  // max number of DWORDS in incoming Fibre Frame
+  UCHAR ucInboundMessageType;  // Inbound CM, dword 3 "type" field
+
+  ENTER("ProcessIMQEntry");
+   
+
+				// check TachLite's IMQ producer index -
+				// is a new message waiting for us?
+				// equal indexes means empty que
+
+  if( fcChip->IMQ->producerIndex != fcChip->IMQ->consumerIndex )
+  {                             // need to process message
+
+
+#ifdef IMQ_DEBUG
+    printk("PI %X, CI %X  type: %X\n", 
+      fcChip->IMQ->producerIndex,fcChip->IMQ->consumerIndex,
+      fcChip->IMQ->QEntry[fcChip->IMQ->consumerIndex].type);
+#endif                                
+    // Examine Completion Messages in IMQ
+    // what CM_Type?
+    switch( (UCHAR)(fcChip->IMQ->QEntry[fcChip->IMQ->consumerIndex].type
+                    & 0xffL) )
+    {
+    case OUTBOUND_COMPLETION:
+
+      // Remarks:
+      // x_IDs (OX_ID, RX_ID) are partitioned by SEST entries
+      // (starting at 0), and SFS entries (starting at
+      // SEST_LEN -- outside the SEST space).
+      // Psuedo code:
+      // x_ID (OX_ID or RX_ID) from message is Trans_ID or SEST index
+      // range check - x_ID
+      //   if x_ID outside 'Transactions' length, error - exit
+      // if any OCM error, copy error status to Exchange slot
+      // if FCP ASSIST transaction (x_ID within SEST),
+      //   call fcComplete (to App)
+      // ...
+
+
+      ulBuff = fcChip->IMQ->QEntry[fcChip->IMQ->consumerIndex].word[1];
+      x_ID = ulBuff & 0x7fffL;     // lower 14 bits SEST_Index/Trans_ID
+                                     // Range check CM OX/RX_ID value...
+      if( x_ID < TACH_MAX_XID )   // don't go beyond array space
+      {
+
+
+	if( ulBuff & 0x20000000L ) // RPC -Response Phase Complete?
+          RPCset = 1;              // (SEST transactions only)
+        else
+          RPCset = 0;
+
+        if( ulBuff & 0x40000000L ) // DPC -Data Phase Complete?
+          DPCset = 1;              // (SEST transactions only)
+        else
+          DPCset = 0;
+                // set the status for this Outbound transaction's ID
+        dwStatus = 0L;
+        if( ulBuff & 0x10000000L ) // SPE? (SEST Programming Error)
+            dwStatus |= SESTPROG_ERR;
+
+        ulBuff = fcChip->IMQ->QEntry[fcChip->IMQ->consumerIndex].word[2];
+        if( ulBuff & 0x7a000000L ) // any other errs?
+        {
+          if( ulBuff & 0x40000000L )
+            dwStatus |= INV_ENTRY;
+          if( ulBuff & 0x20000000L )
+            dwStatus |= FRAME_TO;        // FTO
+          if( ulBuff & 0x10000000L )
+            dwStatus |= HOSTPROG_ERR;
+          if( ulBuff & 0x08000000L )
+            dwStatus |= LINKFAIL_TX;
+          if( ulBuff & 0x02000000L )
+            dwStatus |= ABORTSEQ_NOTIFY;  // ASN
+        }
+
+	  
+	if( dwStatus )          // any errors?
+        {
+                  // set the Outbound Completion status
+          Exchanges->fcExchange[ x_ID ].status |= dwStatus;
+
+          // if this Outbound frame was for a SEST entry, automatically
+          // reque it in the case of LINKFAIL (it will restart on PDISC)
+          if( x_ID < TACH_SEST_LEN )
+          {
+
+            printk(" #OCM error %Xh x_ID %X# ", 
+		    dwStatus, x_ID);
+
+	    Exchanges->fcExchange[x_ID].timeOut = 30000; // seconds default
+                                                 
+
+	    // We Q ABTS for each exchange.
+	    // NOTE: We can get FRAME_TO on bad alpa (device gone).  Since
+	    // bad alpa is reported before FRAME_TO, examine the status
+	    // flags to see if the device is removed.  If so, DON'T
+	    // post an ABTS, since it will be terminated by the bad alpa
+	    // message.
+	    if( dwStatus & FRAME_TO ) // check for device removed...
+	    {
+	      if( !(Exchanges->fcExchange[x_ID].status & DEVICE_REMOVED) )
+	      { 
+		// presumes device is still there: send ABTS.
+  
+                cpqfcTSPutLinkQue( cpqfcHBAdata, BLS_ABTS, &x_ID);
+	      }
+	    }
+	    else  // Abort all other errors
+	    {
+              cpqfcTSPutLinkQue( cpqfcHBAdata, BLS_ABTS, &x_ID);
+	    }
+
+            // if the HPE bit is set, we have to CLose the LOOP
+            // (see TL/TS UG, pg. 239)
+
+            if( dwStatus &= HOSTPROG_ERR )
+            // set CL bit (see TL/TS UG, pg. 172)
+              writel( 4, fcChip->Registers.FMcontrol.address);
+          }
+        }
+          // NOTE: we don't necessarily care about ALL completion messages...
+                                      // SCSI resp. complete OR
+        if( ((x_ID < TACH_SEST_LEN) && RPCset)|| 
+             (x_ID >= TACH_SEST_LEN) )  // non-SCSI command
+        {
+              // exchange done; complete to upper levels with status
+              // (if necessary) and free the exchange slot
+            
+
+          if( x_ID >= TACH_SEST_LEN ) // Link Service Outbound frame?
+                                    // A Request or Reply has been sent
+          {                         // signal waiting WorkerThread
+
+            up( cpqfcHBAdata->TYOBcomplete);   // frame is OUT of Tach
+
+                                    // WorkerThread will complete Xchng
+          }
+          else  // X_ID is for FCP assist (SEST)
+          {
+              // TBD (target mode)
+//            fcCompleteExchange( fcChip, x_ID); // TRE completed
+          }
+        }
+      }
+      else  // ERROR CONDITION!  bogus x_ID in completion message
+      {
+
+        printk(" ProcessIMQ (OBCM) x_id out of range %Xh\n", x_ID);
+
+      }
+
+
+
+          // Load the Frame Manager's error counters.  We check them here
+          // because presumably the link is up and healthy enough for the
+          // counters to be meaningful (i.e., don't check them while loop
+          // is initializing).
+      fcChip->Registers.FMLinkStatus1.value =    // get TL's counter
+        readl(fcChip->Registers.FMLinkStatus1.address);
+                  
+      fcChip->Registers.FMLinkStatus2.value =    // get TL's counter
+        readl(fcChip->Registers.FMLinkStatus2.address);
+            
+
+      fcParseLinkStatusCounters( fcChip); // load into 6 s/w accumulators
+    break;
+
+
+
+    case ERROR_IDLE_COMPLETION:  // TachLite Error Idle...
+    
+    // We usually get this when the link goes down during heavy traffic.
+    // For now, presume that if SEST Exchanges are open, we will
+    // get this as our cue to INVALIDATE all SEST entries
+    // (and we OWN all the SEST entries).
+    // See TL/TS UG, pg. 53
+    
+      for( x_ID = 0; x_ID < TACH_SEST_LEN; x_ID++)
+      {
+
+        // Does this VALid SEST entry need to be invalidated for Abort?
+        fcChip->SEST->u[ x_ID].IWE.Hdr_Len &= 0x7FFFFFFF; 
+      }
+      
+      CpqTsUnFreezeTachlite( fcChip, 2); // unfreeze Tachyon, if Link OK
+
+    break;
+
+
+    case INBOUND_SFS_COMPLETION:  //0x04
+          // NOTE! we must process this SFQ message to avoid SFQ filling
+          // up and stopping TachLite.  Incoming commands are placed here,
+          // as well as 'unknown' frames (e.g. LIP loop position data)
+          // write this CM's producer index to global...
+          // TL/TS UG, pg 234:
+          // Type: 0 - reserved
+          //       1 - Unassisted FCP
+          //       2 - BAD FCP
+          //       3 - Unkown Frame
+          //       4-F reserved
+
+
+      fcChip->SFQ->producerIndex = (USHORT)
+        (fcChip->IMQ->QEntry[fcChip->IMQ->consumerIndex].word[0] & 0x0fffL);
+
+
+      ucInboundMessageType = 0;  // default to useless frame
+
+        // we can only process two Types: 1, Unassisted FCP, and 3, Unknown
+        // Also, we aren't interested in processing frame fragments
+        // so don't Que anything with 'LKF' bit set
+      if( !(fcChip->IMQ->QEntry[fcChip->IMQ->consumerIndex].word[2] 
+        & 0x40000000) )  // 'LKF' link failure bit clear?
+      {
+        ucInboundMessageType = (UCHAR)  // ICM DWord3, "Type"
+        (fcChip->IMQ->QEntry[fcChip->IMQ->consumerIndex].word[2] & 0x0fL);
+      }
+      else
+      {
+	fcChip->fcStats.linkFailRX++;
+//        printk("LKF (link failure) bit set on inbound message\n");
+      }
+
+          // clears SFQ entry from Tachyon buffer; copies to contiguous ulBuff
+      CpqTsGetSFQEntry(
+        fcChip,                  // i.e. this Device Object
+        (USHORT)fcChip->SFQ->producerIndex,  // SFQ producer ndx         
+        ulFibreFrame, TRUE);    // contiguous destination buffer, update chip
+                     
+        // analyze the incoming frame outside the INT handler...
+        // (i.e., Worker)
+
+      if( ucInboundMessageType == 1 )
+      {
+        fchs = (TachFCHDR_GCMND*)ulFibreFrame; // cast to examine IB frame
+        // don't fill up our Q with garbage - only accept FCP-CMND  
+        // or XRDY frames
+        if( (fchs->d_id & 0xFF000000) == 0x06000000 ) // CMND
+        {
+	  // someone sent us a SCSI command
+	  
+//          fcPutScsiQue( cpqfcHBAdata, 
+//                        SFQ_UNASSISTED_FCP, ulFibreFrame); 
+	}
+	else if( ((fchs->d_id & 0xFF000000) == 0x07000000) || // RSP (status)
+            (fchs->d_id & 0xFF000000) == 0x05000000 )  // XRDY  
+	{
+	  ULONG x_ID;
+	  // Unfortunately, ABTS requires a Freeze on the chip so
+	  // we can modify the shared memory SEST.  When frozen,
+	  // any received Exchange frames cannot be processed by
+	  // Tachyon, so they will be dumped in here.  It is too
+	  // complex to attempt the reconstruct these frames in
+	  // the correct Exchange context, so we simply seek to
+	  // find status or transfer ready frames, and cause the
+	  // exchange to complete with errors before the timeout
+	  // expires.  We use a Linux Scsi Cmnd result code that
+	  // causes immediate retry.
+	  
+
+	  // Do we have an open exchange that matches this s_id
+	  // and ox_id?
+	  for( x_ID = 0; x_ID < TACH_SEST_LEN; x_ID++)
+	  {
+            if( (fchs->s_id & 0xFFFFFF) == 
+                 (Exchanges->fcExchange[x_ID].fchs.d_id & 0xFFFFFF) 
+		       &&
+                (fchs->ox_rx_id & 0xFFFF0000) == 
+                 (Exchanges->fcExchange[x_ID].fchs.ox_rx_id & 0xFFFF0000) )
+	    {
+    //          printk(" #R/X frame x_ID %08X# ", fchs->ox_rx_id );
+              // simulate the anticipated error - since the
+	      // SEST was frozen, frames were lost...
+              Exchanges->fcExchange[ x_ID ].status |= SFQ_FRAME;
+              
+	      // presumes device is still there: send ABTS.
+              cpqfcTSPutLinkQue( cpqfcHBAdata, BLS_ABTS, &x_ID);
+	      break;  // done
+	    }
+	  }
+	}
+	  
+      }
+          
+      else if( ucInboundMessageType == 3)
+      {
+        // FC Link Service frames (e.g. PLOGI, ACC) come in here.  
+        cpqfcTSPutLinkQue( cpqfcHBAdata, SFQ_UNKNOWN, ulFibreFrame); 
+                          
+      }
+
+      else if( ucInboundMessageType == 2 ) // "bad FCP"?
+      {
+#ifdef IMQ_DEBUG
+        printk("Bad FCP incoming frame discarded\n");
+#endif
+      }
+
+      else // don't know this type
+      {
+#ifdef IMQ_DEBUG 
+        printk("Incoming frame discarded, type: %Xh\n", ucInboundMessageType);
+#endif
+      }
+        
+        // Check the Frame Manager's error counters.  We check them here
+        // because presumably the link is up and healthy enough for the
+        // counters to be meaningful (i.e., don't check them while loop
+        // is initializing).
+      fcChip->Registers.FMLinkStatus1.value =    // get TL's counter
+        readl(fcChip->Registers.FMLinkStatus1.address);
+                  
+
+      fcChip->Registers.FMLinkStatus2.value =    // get TL's counter
+        readl(fcChip->Registers.FMLinkStatus2.address);
+                
+
+      break;
+
+
+
+
+                    // We get this CM because we issued a freeze
+                    // command to stop outbound frames.  We issue the
+                    // freeze command at Link Up time; when this message
+                    // is received, the ERQ base can be switched and PDISC
+                    // frames can be sent.
+
+      
+    case ERQ_FROZEN_COMPLETION:  // note: expect ERQ followed immediately
+                                 // by FCP when freezing TL
+      fcChip->Registers.TYstatus.value =         // read what's frozen
+        readl(fcChip->Registers.TYstatus.address);
+      // (do nothing; wait for FCP frozen message)
+      break;
+    case FCP_FROZEN_COMPLETION:
+      
+      fcChip->Registers.TYstatus.value =         // read what's frozen
+        readl(fcChip->Registers.TYstatus.address);
+      
+      // Signal the kernel thread to proceed with SEST modification
+      up( cpqfcHBAdata->TachFrozen);
+
+      break;
+
+
+
+    case INBOUND_C1_TIMEOUT:
+    case MFS_BUF_WARN:
+    case IMQ_BUF_WARN:
+    break;
+
+
+
+
+
+        // In older Tachyons, we 'clear' the internal 'core' interrupt state
+        // by reading the FMstatus register.  In newer TachLite (Tachyon),
+        // we must WRITE the register
+        // to clear the condition (TL/TS UG, pg 179)
+    case FRAME_MGR_INTERRUPT:
+    {
+      PFC_LOGGEDIN_PORT pLoggedInPort; 
+
+      fcChip->Registers.FMstatus.value = 
+        readl( fcChip->Registers.FMstatus.address );
+                
+      // PROBLEM: It is possible, especially with "dumb" hubs that
+      // don't automatically LIP on by-pass of ports that are going
+      // away, for the hub by-pass process to destroy critical 
+      // ordered sets of a frame.  The result of this is a hung LPSM
+      // (Loop Port State Machine), which on Tachyon results in a
+      // (default 2 sec) Loop State Timeout (LST) FM message.  We 
+      // want to avoid this relatively huge timeout by detecting
+      // likely scenarios which will result in LST.
+      // To do this, we could examine FMstatus for Loss of Synchronization
+      // and/or Elastic Store (ES) errors.  Of these, Elastic Store is better
+      // because we get this indication more quickly than the LOS.
+      // Not all ES errors are harmfull, so we don't want to LIP on every
+      // ES.  Instead, on every ES, detect whether our LPSM in in one
+      // of the LST states: ARBITRATING, OPEN, OPENED, XMITTED CLOSE,
+      // or RECEIVED CLOSE.  (See TL/TS UG, pg. 181)
+      // If any of these LPSM states are detected
+      // in combination with the LIP while LDn is not set, 
+      // send an FM init (LIP F7,F7 for loops)!
+      // It is critical to the physical link stability NOT to reset (LIP)
+      // more than absolutely necessary; this is a basic premise of the
+      // SANMark level 1 spec.
+      {
+	ULONG Lpsm = (fcChip->Registers.FMstatus.value & 0xF0) >>4;
+	
+	if( (fcChip->Registers.FMstatus.value & 0x400)  // ElasticStore?
+                      &&
+            !(fcChip->Registers.FMstatus.value & 0x100) // NOT LDn
+	              &&
+            !(fcChip->Registers.FMstatus.value & 0x1000)) // NOT LF
+	{
+	  if( (Lpsm != 0) || // not MONITORING? or
+	      !(Lpsm & 0x8) )// not already offline?
+	  {
+	  // now check the particular LST states...
+            if( (Lpsm == ARBITRATING) || (Lpsm == OPEN) ||
+	      (Lpsm == OPENED)      || (Lpsm == XMITTD_CLOSE) ||
+	      (Lpsm == RCVD_CLOSE) )
+	    {
+	      // re-init the loop before it hangs itself!
+              printk(" #req FMinit on E-S: LPSM %Xh# ",Lpsm);
+
+
+	      fcChip->fcStats.FMinits++;
+              writel( 6, fcChip->Registers.FMcontrol.address); // LIP
+	    }
+	  }
+	}
+	else if( fcChip->Registers.FMstatus.value & 0x40000 ) // LST?
+	{
+          printk(" #req FMinit on LST, LPSM %Xh# ",Lpsm);
+	 
+          fcChip->fcStats.FMinits++;
+          writel( 6, fcChip->Registers.FMcontrol.address);  // LIP
+	}  
+      }
+
+
+      // clear only the 'interrupting' type bits for this REG read
+      writel( (fcChip->Registers.FMstatus.value & 0xff3fff00L),
+        fcChip->Registers.FMstatus.address);
+                          
+
+               // copy frame manager status to unused ULONG slot
+      fcChip->IMQ->QEntry[fcChip->IMQ->consumerIndex].word[0] =
+          fcChip->Registers.FMstatus.value; // (for debugging)
+
+
+          // Load the Frame Manager's error counters.  We check them here
+          // because presumably the link is up and healthy enough for the
+          // counters to be meaningful (i.e., don't check them while loop
+          // is initializing).
+      fcChip->Registers.FMLinkStatus1.value =   // get TL's counter
+        readl(fcChip->Registers.FMLinkStatus1.address);
+            
+      fcChip->Registers.FMLinkStatus2.value =   // get TL's counter
+        readl(fcChip->Registers.FMLinkStatus2.address);
+          
+          // Get FM BB_Credit Zero Reg - does not clear on READ
+      fcChip->Registers.FMBB_CreditZero.value =   // get TL's counter
+        readl(fcChip->Registers.FMBB_CreditZero.address);
+            
+
+
+      fcParseLinkStatusCounters( fcChip); // load into 6 s/w accumulators
+
+
+               // LINK DOWN
+
+      if( fcChip->Registers.FMstatus.value & 0x100L ) // Link DOWN bit
+      {                                 
+	
+#ifdef IMQ_DEBUG
+        printk("LinkDn\n");
+#endif
+        printk(" #LDn# ");
+        
+        fcChip->fcStats.linkDown++;
+        
+	SetTachTOV( cpqfcHBAdata);  // must set according to SANMark
+
+	// Check the ERQ - force it to be "empty" to prevent Tach
+	// from sending out frames before we do logins.
+
+
+  	if( fcChip->ERQ->producerIndex != fcChip->ERQ->consumerIndex)
+	{
+//	  printk("#ERQ PI != CI#");
+          CpqTsFreezeTachlite( fcChip, 1); // freeze ERQ only	  
+	  fcChip->ERQ->producerIndex = fcChip->ERQ->consumerIndex = 0;
+ 	  writel( fcChip->ERQ->base, 
+	    (fcChip->Registers.ReMapMemBase + TL_MEM_ERQ_BASE));
+          // re-writing base forces ERQ PI to equal CI
+  
+	}
+		
+	// link down transition occurred -- port_ids can change
+        // on next LinkUp, so we must invalidate current logins
+        // (and any I/O in progress) until PDISC or PLOGI/PRLI
+        // completes
+        {
+          pLoggedInPort = &fcChip->fcPorts; 
+          while( pLoggedInPort ) // for all ports which are expecting
+                                 // PDISC after the next LIP, set the
+                                 // logoutTimer
+          {
+
+	    if( pLoggedInPort->pdisc) // expecting PDISC within 2 sec?
+            {
+              pLoggedInPort->LOGO_timer = 3;  // we want 2 seconds
+                                              // but Timer granularity
+                                              // is 1 second
+            }
+                                // suspend any I/O in progress until
+                                // PDISC received...
+            pLoggedInPort->prli = FALSE;   // block FCP-SCSI commands
+	    
+            pLoggedInPort = pLoggedInPort->pNextPort;
+          }  // ... all Previously known ports checked
+        }
+        
+	// since any hot plugging device may NOT support LILP frames
+	// (such as early Tachyon chips), clear this flag indicating
+	// we shouldn't use (our copy of) a LILP map.
+	// If we receive an LILP frame, we'll set it again.
+	fcChip->Options.LILPin = 0; // our LILPmap is invalid
+        cpqfcHBAdata->PortDiscDone = 0; // must re-validate FC ports!
+
+          // also, we want to invalidate (i.e. INITIATOR_ABORT) any
+          // open Login exchanges, in case the LinkDown happened in the
+          // middle of logins.  It's possible that some ports already
+          // ACCepted login commands which we have not processed before
+          // another LinkDown occured.  Any accepted Login exhanges are
+          // invalidated by LinkDown, even before they are acknowledged.
+          // It's also possible for a port to have a Queued Reply or Request
+          // for login which was interrupted by LinkDown; it may come later,
+          // but it will be unacceptable to us.
+
+          // we must scan the entire exchange space, find every Login type
+          // originated by us, and abort it. This is NOT an abort due to
+          // timeout, so we don't actually send abort to the other port -
+          // we just complete it to free up the fcExchange slot.
+
+        for( i=TACH_SEST_LEN; i< TACH_MAX_XID; i++)
+        {                     // looking for Extended Link Serv.Exchanges
+          if( Exchanges->fcExchange[i].type == ELS_PDISC ||
+              Exchanges->fcExchange[i].type == ELS_PLOGI ||
+              Exchanges->fcExchange[i].type == ELS_PRLI ) 
+          {
+              // ABORT the exchange!
+#ifdef IMQ_DEBUG
+            printk("Originator ABORT x_id %Xh, type %Xh, port_id %Xh on LDn\n",
+              i, Exchanges->fcExchange[i].type,
+            Exchanges->fcExchange[i].fchs.d_id);
+#endif
+
+            Exchanges->fcExchange[i].status |= INITIATOR_ABORT;
+            cpqfcTSCompleteExchange( fcChip, i); // abort on LDn
+          }
+        }
+
+      }
+
+             // ################   LINK UP   ##################
+      if( fcChip->Registers.FMstatus.value & 0x200L ) // Link Up bit
+      {                                 // AL_PA could have changed
+
+          // We need the following code, duplicated from LinkDn condition,
+          // because it's possible for the Tachyon to re-initialize (hard
+          // reset) without ever getting a LinkDn indication.
+        pLoggedInPort = &fcChip->fcPorts; 
+        while( pLoggedInPort )   // for all ports which are expecting
+                                 // PDISC after the next LIP, set the
+                                 // logoutTimer
+        {
+          if( pLoggedInPort->pdisc) // expecting PDISC within 2 sec?
+          {
+            pLoggedInPort->LOGO_timer = 3;  // we want 2 seconds
+                                              // but Timer granularity
+                                              // is 1 second
+             
+                                  // suspend any I/O in progress until
+                                  // PDISC received...
+
+          }
+          pLoggedInPort = pLoggedInPort->pNextPort;
+        }  // ... all Previously known ports checked
+ 
+          // CpqTs acquired AL_PA in register AL_PA (ACQ_ALPA)
+        fcChip->Registers.rcv_al_pa.value = 
+          readl(fcChip->Registers.rcv_al_pa.address);
+ 
+	// Now, if our acquired address is DIFFERENT from our
+        // previous one, we are not allow to do PDISC - we
+        // must go back to PLOGI, which will terminate I/O in
+        // progress for ALL logged in FC devices...
+	// (This is highly unlikely).
+
+	if( (fcChip->Registers.my_al_pa & 0xFF) != 
+	    ((fcChip->Registers.rcv_al_pa.value >> 16) &0xFF) )
+	{
+
+//	  printk(" #our HBA port_id changed!# "); // FC port_id changed!!	
+
+	  pLoggedInPort = &fcChip->fcPorts; 
+          while( pLoggedInPort ) // for all ports which are expecting
+                                 // PDISC after the next LIP, set the
+                                 // logoutTimer
+          {
+	    pLoggedInPort->pdisc  = FALSE;
+            pLoggedInPort->prli = FALSE;
+            pLoggedInPort = pLoggedInPort->pNextPort;
+          }  // ... all Previously known ports checked
+
+	  // when the port_id changes, we must terminate
+	  // all open exchanges.
+          cpqfcTSTerminateExchange( cpqfcHBAdata, NULL, PORTID_CHANGED);
+
+	}
+	               
+	// Replace the entire 24-bit port_id.  We only know the
+	// lower 8 bits (alpa) from Tachyon; if a FLOGI is done,
+	// we'll get the upper 16-bits from the FLOGI ACC frame.
+	// If someone plugs into Fabric switch, we'll do FLOGI and
+	// get full 24-bit port_id; someone could then remove and
+	// hot-plug us into a dumb hub.  If we send a 24-bit PLOGI
+	// to a "private" loop device, it might blow up.
+	// Consequently, we force the upper 16-bits of port_id to
+	// be re-set on every LinkUp transition
+        fcChip->Registers.my_al_pa =
+          (fcChip->Registers.rcv_al_pa.value >> 16) & 0xFF;
+
+              
+              // copy frame manager status to unused ULONG slot
+        fcChip->IMQ->QEntry[fcChip->IMQ->consumerIndex].word[1] =
+          fcChip->Registers.my_al_pa; // (for debugging)
+
+              // for TachLite, we need to write the acquired al_pa
+              // back into the FMconfig register, because after
+              // first initialization, the AQ (prev. acq.) bit gets
+              // set, causing TL FM to use the AL_PA field in FMconfig.
+              // (In Tachyon, FM writes the acquired AL_PA for us.)
+        ulBuff = readl( fcChip->Registers.FMconfig.address);
+        ulBuff &= 0x00ffffffL;  // mask out current al_pa
+        ulBuff |= ( fcChip->Registers.my_al_pa << 24 ); // or in acq. al_pa
+        fcChip->Registers.FMconfig.value = ulBuff; // copy it back
+        writel( fcChip->Registers.FMconfig.value,  // put in TachLite
+          fcChip->Registers.FMconfig.address);
+            
+
+#ifdef IMQ_DEBUG
+        printk("#LUp %Xh, FMstat 0x%08X#", 
+		fcChip->Registers.my_al_pa, fcChip->Registers.FMstatus.value);
+#endif
+
+              // also set the WRITE-ONLY My_ID Register (for Fabric
+              // initialization)
+        writel( fcChip->Registers.my_al_pa,
+          fcChip->Registers.ReMapMemBase +TL_MEM_TACH_My_ID);
+          
+
+        fcChip->fcStats.linkUp++;
+
+                                     // reset TL statistics counters
+                                     // (we ignore these error counters
+                                     // while link is down)
+        ulBuff =                     // just reset TL's counter
+                 readl( fcChip->Registers.FMLinkStatus1.address);
+          
+        ulBuff =                     // just reset TL's counter
+                 readl( fcChip->Registers.FMLinkStatus2.address);
+
+          // for initiator, need to start verifying ports (e.g. PDISC)
+
+
+
+         
+      
+      
+	CpqTsUnFreezeTachlite( fcChip, 2); // unfreeze Tachlite, if Link OK
+	
+	// Tachyon creates an interesting problem for us on LILP frames.
+	// Instead of writing the incoming LILP frame into the SFQ before
+	// indicating LINK UP (the actual order of events), Tachyon tells
+	// us LINK UP, and later us the LILP.  So we delay, then examine the
+	// IMQ for an Inbound CM (x04); if found, we can set
+	// LINKACTIVE after processing the LILP.  Otherwise, just proceed.
+	// Since Tachyon imposes this time delay (and doesn't tell us
+	// what it is), we have to impose a delay before "Peeking" the IMQ
+	// for Tach hardware (DMA) delivery.
+	// Processing LILP is required by SANMark
+	udelay( 1000);  // microsec delay waiting for LILP (if it comes)
+        if( PeekIMQEntry( fcChip, ELS_LILP_FRAME) )
+	{  // found SFQ LILP, which will post LINKACTIVE	  
+//	  printk("skipping LINKACTIVE post\n");
+
+	}
+	else
+          cpqfcTSPutLinkQue( cpqfcHBAdata, LINKACTIVE, ulFibreFrame);  
+      }
+
+
+
+      // ******* Set Fabric Login indication ********
+      if( fcChip->Registers.FMstatus.value & 0x2000 )
+      {
+	printk(" #Fabric# ");
+        fcChip->Options.fabric = 1;
+      }
+      else
+        fcChip->Options.fabric = 0;
+
+      
+      
+                             // ******* LIP(F8,x) or BAD AL_PA? ********
+      if( fcChip->Registers.FMstatus.value & 0x30000L )
+      {
+                        // copy the error AL_PAs
+        fcChip->Registers.rcv_al_pa.value = 
+          readl(fcChip->Registers.rcv_al_pa.address);
+            
+                        // Bad AL_PA?
+        if( fcChip->Registers.FMstatus.value & 0x10000L )
+        {
+          PFC_LOGGEDIN_PORT pLoggedInPort;
+        
+                       // copy "BAD" al_pa field
+          fcChip->IMQ->QEntry[fcChip->IMQ->consumerIndex].word[1] =
+              (fcChip->Registers.rcv_al_pa.value & 0xff00L) >> 8;
+
+	  pLoggedInPort = fcFindLoggedInPort( fcChip,
+            NULL,     // DON'T search Scsi Nexus
+            fcChip->IMQ->QEntry[fcChip->IMQ->consumerIndex].word[1], // port id
+            NULL,     // DON'T search linked list for FC WWN
+            NULL);    // DON'T care about end of list
+ 
+	  if( pLoggedInPort )
+	  {
+            // Just in case we got this BAD_ALPA because a device
+	    // quietly disappeared (can happen on non-managed hubs such 
+	    // as the Vixel Rapport 1000),
+	    // do an Implicit Logout.  We never expect this on a Logged
+	    // in port (but do expect it on port discovery).
+	    // (As a reasonable alternative, this could be changed to 
+	    // simply start the implicit logout timer, giving the device
+	    // several seconds to "come back".)
+	    // 
+	    printk(" #BAD alpa %Xh# ",
+		   fcChip->IMQ->QEntry[fcChip->IMQ->consumerIndex].word[1]);
+            cpqfcTSImplicitLogout( cpqfcHBAdata, pLoggedInPort);
+	  }
+        }
+                        // LIP(f8,x)?
+        if( fcChip->Registers.FMstatus.value & 0x20000L )
+        {
+                        // for debugging, copy al_pa field
+          fcChip->IMQ->QEntry[fcChip->IMQ->consumerIndex].word[2] =
+              (fcChip->Registers.rcv_al_pa.value & 0xffL);
+                        // get the other port's al_pa
+                        // (one that sent LIP(F8,?) )
+        }
+      }
+
+                             // Elastic store err
+      if( fcChip->Registers.FMstatus.value & 0x400L )
+      {
+            // don't count e-s if loop is down!
+        if( !(USHORT)(fcChip->Registers.FMstatus.value & 0x80) )
+          fcChip->fcStats.e_stores++;
+          
+      }
+    }
+    break;
+
+
+    case INBOUND_FCP_XCHG_COMPLETION:  // 0x0C
+
+    // Remarks:
+    // On Tachlite TL/TS, we get this message when the data phase
+    // of a SEST inbound transfer is complete.  For example, if a WRITE command
+    // was received with OX_ID 0, we might respond with XFER_RDY with
+    // RX_ID 8001.  This would start the SEST controlled data phases.  When
+    // all data frames are received, we get this inbound completion. This means
+    // we should send a status frame to complete the status phase of the 
+    // FCP-SCSI exchange, using the same OX_ID,RX_ID that we used for data
+    // frames.
+    // See Outbound CM discussion of x_IDs
+    // Psuedo Code
+    //   Get SEST index (x_ID)
+    //     x_ID out of range, return (err condition)
+    //   set status bits from 2nd dword
+    //   free transactionID & SEST entry
+    //   call fcComplete with transactionID & status
+
+      ulBuff = fcChip->IMQ->QEntry[fcChip->IMQ->consumerIndex].word[0];
+      x_ID = ulBuff & 0x7fffL;  // lower 14 bits SEST_Index/Trans_ID
+                                // (mask out MSB "direction" bit)
+                                // Range check CM OX/RX_ID value...
+      if( x_ID < TACH_SEST_LEN )  // don't go beyond SEST array space
+      {
+
+//#define FCP_COMPLETION_DBG 1
+#ifdef FCP_COMPLETION_DBG
+        printk(" FCP_CM x_ID %Xh, status %Xh, Cmnd %p\n", 
+          x_ID, ulBuff, Exchanges->fcExchange[x_ID].Cmnd);
+#endif
+        if( ulBuff & 0x08000000L ) // RPC -Response Phase Complete - or -
+                                   // time to send response frame?
+          RPCset = 1;             // (SEST transaction)
+        else
+          RPCset = 0;
+                // set the status for this Inbound SCSI transaction's ID
+        dwStatus = 0L;
+        if( ulBuff & 0x70000000L ) // any errs?
+        {
+          
+          if( ulBuff & 0x40000000L )
+            dwStatus |= LINKFAIL_RX;
+          
+	  if( ulBuff & 0x20000000L )
+            dwStatus |= COUNT_ERROR;
+          
+          if( ulBuff & 0x10000000L )
+            dwStatus |= OVERFLOW;
+        }
+      
+	
+	  // FCP transaction done - copy status
+        Exchanges->fcExchange[ x_ID ].status = dwStatus;
+
+
+        // Did the exchange get an FCP-RSP response frame?
+        // (Note the little endian/big endian FC payload difference)
+
+        if( RPCset )             // SEST transaction Response frame rec'd
+        {
+    	  // complete the command in our driver...
+          cpqfcTSCompleteExchange( fcChip, x_ID);
+
+        }  // end "RPCset"
+	
+        else  // ("target" logic)
+        {
+            // Tachlite says all data frames have been received - now it's time
+            // to analyze data transfer (successful?), then send a response 
+            // frame for this exchange
+
+          ulFibreFrame[0] = x_ID; // copy for later reference
+
+          // if this was a TWE, we have to send satus response
+          if( Exchanges->fcExchange[ x_ID].type == SCSI_TWE )
+	  {
+//            fcPutScsiQue( cpqfcHBAdata, 
+//                NEED_FCP_RSP, ulFibreFrame);  // (ulFibreFrame not used here)
+	  }
+        }
+      }
+      else  // ERROR CONDITION!  bogus x_ID in completion message
+      {
+        printk("IN FCP_XCHG: bad x_ID: %Xh\n", x_ID);
+      }
+
+    break;
+
+
+
+
+    case INBOUND_SCSI_DATA_COMMAND:
+    case BAD_SCSI_FRAME:
+    case INB_SCSI_STATUS_COMPLETION:
+    case BUFFER_PROCESSED_COMPLETION:
+    break;
+    }
+
+					   // Tachyon is producing;
+					   // we are consuming
+    fcChip->IMQ->consumerIndex++;             // increment OUR consumerIndex
+    if( fcChip->IMQ->consumerIndex >= IMQ_LEN)// check for rollover
+      fcChip->IMQ->consumerIndex = 0L;        // reset it
+
+
+    if( fcChip->IMQ->producerIndex == fcChip->IMQ->consumerIndex )
+    {                           // all Messages are processed -
+      iStatus = 0;              // no more messages to process
+
+    }
+    else
+      iStatus = 1;              // more messages to process
+
+    // update TachLite's ConsumerIndex... (clears INTA_L)
+    // NOTE: according to TL/TS UG, the 
+    // "host must return completion messages in sequential order".
+    // Does this mean one at a time, in the order received?  We
+    // presume so.
+
+    writel( fcChip->IMQ->consumerIndex,
+      (fcChip->Registers.ReMapMemBase + IMQ_CONSUMER_INDEX));
+		    
+#if IMQ_DEBUG
+    printk("Process IMQ: writing consumer ndx %d\n ", 
+      fcChip->IMQ->consumerIndex);
+    printk("PI %X, CI %X\n", 
+    fcChip->IMQ->producerIndex,fcChip->IMQ->consumerIndex );
+#endif
+  
+
+
+  }
+  else
+  {
+   // hmmm... why did we get interrupted/called with no message?
+    iStatus = -1;               // nothing to process
+#if IMQ_DEBUG
+    printk("Process IMQ: no message PI %Xh  CI %Xh", 
+      fcChip->IMQ->producerIndex,
+      fcChip->IMQ->consumerIndex);
+#endif
+  }
+
+  LEAVE("ProcessIMQEntry");
+  
+  return iStatus;
+}
+
+
+
+
+
+// This routine initializes Tachyon according to the following
+// options (opcode1):
+// 1 - RESTART Tachyon, simulate power on condition by shutting
+//     down laser, resetting the hardware, de-allocating all buffers;
+//     continue
+// 2 - Config Tachyon / PCI registers;
+//     continue
+// 3 - Allocating memory and setting Tachyon queues (write Tachyon regs);
+//     continue
+// 4 - Config frame manager registers, initialize, turn on laser
+//
+// Returns:
+//  -1 on fatal error
+//   0 on success
+
+int CpqTsInitializeTachLite( void *pHBA, int opcode1, int opcode2)
+{
+  CPQFCHBA *cpqfcHBAdata = (CPQFCHBA*)pHBA;
+  PTACHYON fcChip = &cpqfcHBAdata->fcChip;
+  ULONG ulBuff;
+  UCHAR bBuff;
+  int iStatus=-1;  // assume failure
+
+  ENTER("InitializeTachLite");
+
+  // verify board's base address (sanity check)
+
+  if( !fcChip->Registers.ReMapMemBase)                // NULL address for card?
+    return -1;                         // FATAL error!
+
+
+
+  switch( opcode1 )
+  {
+    case 1:       // restore hardware to power-on (hard) restart
+
+
+      iStatus = fcChip->ResetTachyon( 
+		  cpqfcHBAdata, opcode2); // laser off, reset hardware
+				      // de-allocate aligned buffers
+
+
+/* TBD      // reset FC link Q (producer and consumer = 0)
+      fcLinkQReset(cpqfcHBAdata); 
+
+*/
+
+      if( iStatus )
+        break;
+
+    case 2:       // Config PCI/Tachyon registers
+      // NOTE: For Tach TL/TS, bit 31 must be set to 1.  For TS chips, a read
+      // of bit 31 indicates state of M66EN signal; if 1, chip may run at 
+      // 33-66MHz  (see TL/TS UG, pg 159)
+
+      ulBuff = 0x80000000;  // TachLite Configuration Register
+
+      writel( ulBuff, fcChip->Registers.TYconfig.address);
+//      ulBuff = 0x0147L;  // CpqTs PCI CFGCMD register
+//      WritePCIConfiguration( fcChip->Backplane.bus,
+//                           fcChip->Backplane.slot, TLCFGCMD, ulBuff, 4);
+//      ulBuff = 0x0L;  // test!
+//      ReadPCIConfiguration( fcChip->Backplane.bus,
+//                           fcChip->Backplane.slot, TLCFGCMD, &ulBuff, 4);
+
+      // read back for reference...
+      fcChip->Registers.TYconfig.value = 
+         readl( fcChip->Registers.TYconfig.address );
+
+      // what is the PCI bus width?
+      pci_read_config_byte( cpqfcHBAdata->PciDev,
+                                0x43, // PCIMCTR offset
+                                &bBuff);
+      
+      fcChip->Registers.PCIMCTR = bBuff;
+
+      // set string identifying the chip on the circuit board
+
+      fcChip->Registers.TYstatus.value =
+        readl( fcChip->Registers.TYstatus.address);
+      
+      {
+// Now that we are supporting multiple boards, we need to change
+// this logic to check for PCI vendor/device IDs...
+// for now, quick & dirty is simply checking Chip rev
+	
+	ULONG RevId = (fcChip->Registers.TYstatus.value &0x3E0)>>5;
+	UCHAR Minor = (UCHAR)(RevId & 0x3);
+	UCHAR Major = (UCHAR)((RevId & 0x1C) >>2);
+  
+        printk("  HBA Tachyon RevId %d.%d\n", Major, Minor);
+  	if( (Major == 1) && (Minor == 2) )
+        {
+	  sprintf( cpqfcHBAdata->fcChip.Name, STACHLITE66_TS12);
+
+	}
+	else if( (Major == 1) && (Minor == 3) )
+        {
+	  sprintf( cpqfcHBAdata->fcChip.Name, STACHLITE66_TS13);
+	}
+	else if( (Major == 2) && (Minor == 1) )
+        {
+	  sprintf( cpqfcHBAdata->fcChip.Name, SAGILENT_XL2_21);
+	}
+	else
+	  sprintf( cpqfcHBAdata->fcChip.Name, STACHLITE_UNKNOWN);
+      }
+
+
+
+    case 3:       // allocate mem, set Tachyon Que registers
+      iStatus = CpqTsCreateTachLiteQues( cpqfcHBAdata, opcode2);
+
+      // now that the Queues exist, Tach can DMA to them, so
+      // we can begin processing INTs
+      // INTEN register - enable INT (TachLite interrupt)
+      writeb( 0x1F, fcChip->Registers.ReMapMemBase + IINTEN);
+
+
+      if( iStatus )
+        break;
+
+
+    case 4:       // Config Fame Manager, Init Loop Command, laser on
+
+                 // L_PORT or loopback
+                 // depending on Options
+      iStatus = CpqTsInitializeFrameManager( fcChip,0 );
+      if( iStatus )
+      {
+           // failed to initialize Frame Manager
+	      break;
+      }
+
+    default:
+      break;
+  }
+  LEAVE("InitializeTachLite");
+  
+  return iStatus;
+}
+
+
+
+
+// Depending on the type of platform memory allocation (e.g. dynamic),
+// it's probably best to free memory in opposite order as it was allocated.
+// Order of allocation: see other function
+
+
+int CpqTsDestroyTachLiteQues( void *pHBA, int opcode)
+{
+  CPQFCHBA *cpqfcHBAdata = (CPQFCHBA*)pHBA;
+  PTACHYON fcChip = &cpqfcHBAdata->fcChip;
+  USHORT i, j, iStatus=0;
+  void* vPtr;  // mem Align manager sets this to the freed address on success
+  unsigned long ulPtr;  // for 64-bit pointer cast (e.g. Alpa machine)
+
+  ENTER("DestroyTachLiteQues");
+
+  if( fcChip->SEST )
+  {
+                // search out and free Pool for Extended S/G list pages
+
+    for( i=0, j=0; i < TACH_SEST_LEN; i++, j=0)  // for each exchange
+    {
+      // It's possible that extended S/G pages were allocated and
+      // not cleared due to error conditions or O/S driver termination.
+      // Make sure they're all gone.
+      while( fcChip->SEST->sgPages[i].PoolPage[j] &&
+        (j < TL_MAX_SGPAGES))
+        kfree( fcChip->SEST->sgPages[i].PoolPage[j++]);
+
+    }
+    ulPtr = (unsigned long)fcChip->SEST;
+    vPtr = fcMemManager( &cpqfcHBAdata->dynamic_mem[0],
+		    0,0, (ULONG)ulPtr ); // 'free' mem
+    fcChip->SEST = 0L;  // null invalid ptr
+    if( !vPtr )
+    {
+      printk("SEST mem not freed\n");
+      iStatus = -1;
+    }
+  }
+
+  if( fcChip->SFQ )
+  {
+
+    ulPtr = (unsigned long)fcChip->SFQ;
+    vPtr = fcMemManager( &cpqfcHBAdata->dynamic_mem[0],
+		    0,0, (ULONG)ulPtr ); // 'free' mem
+    fcChip->SFQ = 0L;  // null invalid ptr
+    if( !vPtr )
+    {
+      printk("SFQ mem not freed\n");
+      iStatus = -2;
+    }
+  }
+
+
+  if( fcChip->IMQ )
+  {
+      // clear Indexes to show empty Queue
+    fcChip->IMQ->producerIndex = 0;
+    fcChip->IMQ->consumerIndex = 0;
+
+    ulPtr = (unsigned long)fcChip->IMQ;
+    vPtr = fcMemManager( &cpqfcHBAdata->dynamic_mem[0],
+		    0,0, (ULONG)ulPtr ); // 'free' mem
+    fcChip->IMQ = 0L;  // null invalid ptr
+    if( !vPtr )
+    {
+      printk("IMQ mem not freed\n");
+      iStatus = -3;
+    }
+  }
+
+  if( fcChip->ERQ )         // release memory blocks used by the queues
+  {
+    ulPtr = (unsigned long)fcChip->ERQ;
+    vPtr = fcMemManager( &cpqfcHBAdata->dynamic_mem[0],
+		    0,0, (ULONG)ulPtr ); // 'free' mem
+    fcChip->ERQ = 0L;  // null invalid ptr
+    if( !vPtr )
+    {
+      printk("ERQ mem not freed\n");
+      iStatus = -4;
+    }
+  }
+    
+  // free up the primary EXCHANGES struct
+  if( fcChip->Exchanges != NULL)
+  {
+//    printk("kfree() on Exchanges @%p\n", fcChip->Exchanges);
+    kfree( fcChip->Exchanges);
+  }
+
+  // free up Link Q
+  if( cpqfcHBAdata->fcLQ != NULL )
+  {
+//    printk("kfree() on LinkQ @%p\n", fcChip->fcLQ);
+    kfree( cpqfcHBAdata->fcLQ);
+  }
+  
+  LEAVE("DestroyTachLiteQues");
+  
+  return iStatus;     // non-zero (failed) if any memory not freed
+}
+
+
+
+
+
+// The SFQ is an array with SFQ_LEN length, each element (QEntry)
+// with eight 32-bit words.  TachLite places incoming FC frames (i.e.
+// a valid FC frame with our AL_PA ) in contiguous SFQ entries
+// and sends a completion message telling the host where the frame is
+// in the que.
+// This function copies the current (or oldest not-yet-processed) QEntry to
+// a caller's contiguous buffer and updates the Tachyon chip's consumer index
+//
+// NOTE:
+//   An FC frame may consume one or many SFQ entries.  We know the total
+//   length from the completion message.  The caller passes a buffer large
+//   enough for the complete message (max 2k).
+
+static void CpqTsGetSFQEntry(
+         PTACHYON fcChip,
+         USHORT producerNdx,
+         ULONG *ulDestPtr,            // contiguous destination buffer
+	 BOOLEAN UpdateChip)
+{
+  ULONG total_bytes=0;
+  ULONG consumerIndex = fcChip->SFQ->consumerIndex;
+  
+				// check passed copy of SFQ producer index -
+				// is a new message waiting for us?
+				// equal indexes means SFS is copied
+
+  while( producerNdx != consumerIndex )
+  {                             // need to process message
+    total_bytes += 64;   // maintain count to prevent writing past buffer
+                   // don't allow copies over Fibre Channel defined length!
+    if( total_bytes <= 2048 )
+    {
+      memcpy( ulDestPtr, 
+              &fcChip->SFQ->QEntry[consumerIndex],
+              64 );  // each SFQ entry is 64 bytes
+      ulDestPtr += 16;   // advance pointer to next 64 byte block
+    }
+		         // Tachyon is producing,
+                         // and we are consuming
+
+    if( ++consumerIndex >= SFQ_LEN)// check for rollover
+      consumerIndex = 0L;        // reset it
+  }
+
+  // if specified, update the Tachlite chip ConsumerIndex...
+  if( UpdateChip )
+  {
+    fcChip->SFQ->consumerIndex = consumerIndex;
+    writel( fcChip->SFQ->consumerIndex,
+      fcChip->Registers.SFQconsumerIndex.address);
+  }
+}
+
+
+
+// TachLite routinely freezes it's core ques - Outbound FIFO, Inbound FIFO,
+// and Exchange Request Queue (ERQ) on error recover - 
+// (e.g. whenever a LIP occurs).  Here
+// we routinely RESUME by clearing these bits, but only if the loop is up
+// to avoid ERROR IDLE messages forever.
+
+void CpqTsUnFreezeTachlite( void *pChip, int type )
+{
+  PTACHYON fcChip = (PTACHYON)pChip;
+  fcChip->Registers.TYcontrol.value = 
+    readl(fcChip->Registers.TYcontrol.address);
+            
+  // (bit 4 of value is GBIC LASER)
+  // if we 'unfreeze' the core machines before the loop is healthy
+  // (i.e. FLT, OS, LS failure bits set in FMstatus)
+  // we can get 'error idle' messages forever.  Verify that
+  // FMstatus (Link Status) is OK before unfreezing.
+
+  if( !(fcChip->Registers.FMstatus.value & 0x07000000L) && // bits clear?
+      !(fcChip->Registers.FMstatus.value & 0x80  ))  // Active LPSM?
+  {
+    fcChip->Registers.TYcontrol.value &=  ~0x300L; // clear FEQ, FFA
+    if( type == 1 )  // unfreeze ERQ only
+    {
+//      printk("Unfreezing ERQ\n");
+      fcChip->Registers.TYcontrol.value |= 0x10000L; // set REQ
+    }
+    else             // unfreeze both ERQ and FCP-ASSIST (SEST)
+    {
+//      printk("Unfreezing ERQ & FCP-ASSIST\n");
+
+                     // set ROF, RIF, REQ - resume Outbound FCP, Inbnd FCP, ERQ
+      fcChip->Registers.TYcontrol.value |= 0x70000L; // set ROF, RIF, REQ
+    }
+
+    writel( fcChip->Registers.TYcontrol.value,
+      fcChip->Registers.TYcontrol.address);
+              
+  }
+          // readback for verify (TachLite still frozen?)
+  fcChip->Registers.TYstatus.value = 
+    readl(fcChip->Registers.TYstatus.address);
+}
+
+
+// Whenever an FC Exchange Abort is required, we must manipulate the
+// Host/Tachyon shared memory SEST table.  Before doing this, we
+// must freeze Tachyon, which flushes certain buffers and ensure we
+// can manipulate the SEST without contention.
+// This freeze function will result in FCP & ERQ FROZEN completion
+// messages (per argument "type").
+
+void CpqTsFreezeTachlite( void *pChip, int type )
+{
+  PTACHYON fcChip = (PTACHYON)pChip;
+  fcChip->Registers.TYcontrol.value = 
+    readl(fcChip->Registers.TYcontrol.address);
+    
+                     //set FFA, FEQ - freezes SCSI assist and ERQ
+  if( type == 1)    // freeze ERQ only
+    fcChip->Registers.TYcontrol.value |= 0x100L; // (bit 4 is laser)
+  else              // freeze both FCP assists (SEST) and ERQ
+    fcChip->Registers.TYcontrol.value |= 0x300L; // (bit 4 is laser)
+  
+  writel( fcChip->Registers.TYcontrol.value,
+    fcChip->Registers.TYcontrol.address);
+              
+}
+
+
+
+
+// TL has two Frame Manager Link Status Registers, with three 8-bit
+// fields each. These eight bit counters are cleared after each read,
+// so we define six 32-bit accumulators for these TL counters. This
+// function breaks out each 8-bit field and adds the value to the existing
+// sum.  (s/w counters cleared independently)
+
+void fcParseLinkStatusCounters(PTACHYON fcChip)
+{
+  UCHAR bBuff;
+  ULONG ulBuff;
+
+
+// The BB0 timer usually increments when TL is initialized, resulting
+// in an initially bogus count.  If our own counter is ZERO, it means we
+// are reading this thing for the first time, so we ignore the first count.
+// Also, reading the register does not clear it, so we have to keep an
+// additional static counter to detect rollover (yuk).
+
+  if( fcChip->fcStats.lastBB0timer == 0L)  // TL was reset? (ignore 1st values)
+  {
+                           // get TL's register counter - the "last" count
+    fcChip->fcStats.lastBB0timer = 
+      fcChip->Registers.FMBB_CreditZero.value & 0x00ffffffL;
+  }
+  else  // subsequent pass - check for rollover
+  {
+                              // "this" count
+    ulBuff = fcChip->Registers.FMBB_CreditZero.value & 0x00ffffffL;
+    if( fcChip->fcStats.lastBB0timer > ulBuff ) // rollover happened
+    {
+                                // counter advanced to max...
+      fcChip->fcStats.BB0_Timer += (0x00FFFFFFL - fcChip->fcStats.lastBB0timer);
+      fcChip->fcStats.BB0_Timer += ulBuff;  // plus some more
+
+
+    }
+    else // no rollover -- more counts or no change
+    {
+      fcChip->fcStats.BB0_Timer +=  (ulBuff - fcChip->fcStats.lastBB0timer);
+
+    }
+
+    fcChip->fcStats.lastBB0timer = ulBuff;
+  }
+
+
+
+  bBuff = (UCHAR)(fcChip->Registers.FMLinkStatus1.value >> 24);
+  fcChip->fcStats.LossofSignal += bBuff;
+
+  bBuff = (UCHAR)(fcChip->Registers.FMLinkStatus1.value >> 16);
+  fcChip->fcStats.BadRXChar += bBuff;
+
+  bBuff = (UCHAR)(fcChip->Registers.FMLinkStatus1.value >> 8);
+  fcChip->fcStats.LossofSync += bBuff;
+
+
+  bBuff = (UCHAR)(fcChip->Registers.FMLinkStatus2.value >> 24);
+  fcChip->fcStats.Rx_EOFa += bBuff;
+
+  bBuff = (UCHAR)(fcChip->Registers.FMLinkStatus2.value >> 16);
+  fcChip->fcStats.Dis_Frm += bBuff;
+
+  bBuff = (UCHAR)(fcChip->Registers.FMLinkStatus2.value >> 8);
+  fcChip->fcStats.Bad_CRC += bBuff;
+}
+
+
+void cpqfcTSClearLinkStatusCounters(PTACHYON fcChip)
+{
+  ENTER("ClearLinkStatusCounters");
+  memset( &fcChip->fcStats, 0, sizeof( FCSTATS));
+  LEAVE("ClearLinkStatusCounters");
+
+}
+
+
+
+
+// The following function reads the I2C hardware to get the adapter's
+// World Wide Name (WWN).
+// If the WWN is "500805f1fadb43e8" (as printed on the card), the
+// Tachyon WWN_hi (32-bit) register is 500805f1, and WWN_lo register
+// is fadb43e8.
+// In the NVRAM, the bytes appear as:
+// [2d] ..
+// [2e] .. 
+// [2f] 50
+// [30] 08
+// [31] 05
+// [32] f1
+// [33] fa
+// [34] db
+// [35] 43
+// [36] e8
+//
+// In the Fibre Channel (Big Endian) format, the FC-AL LISM frame will
+// be correctly loaded by Tachyon silicon.  In the login payload, bytes
+// must be correctly swapped for Big Endian format.
+
+int CpqTsReadWriteWWN( PVOID pChip, int Read)
+{
+  PTACHYON fcChip = (PTACHYON)pChip;
+#define NVRAM_SIZE 512
+  unsigned short i, count = NVRAM_SIZE;
+  UCHAR nvRam[NVRAM_SIZE], WWNbuf[8];
+  ULONG ulBuff;
+  int iStatus=-1;  // assume failure
+  int WWNoffset;
+
+  ENTER("ReadWriteWWN");
+  // Now try to read the WWN from the adapter's NVRAM
+
+  if( Read )  // READing NVRAM WWN?
+  {
+    ulBuff = cpqfcTS_ReadNVRAM( fcChip->Registers.TYstatus.address,
+                              fcChip->Registers.TYcontrol.address,
+                              count, &nvRam[0] );
+
+    if( ulBuff )   // NVRAM read successful?
+    {
+      iStatus = 0; // success!
+      
+                   // for engineering/ prototype boards, the data may be
+                   // invalid (GIGO, usually all "FF"); this prevents the
+                   // parse routine from working correctly, which means
+                   // nothing will be written to our passed buffer.
+
+      WWNoffset = cpqfcTS_GetNVRAM_data( WWNbuf, nvRam );
+
+      if( !WWNoffset ) // uninitialized NVRAM -- copy bytes directly
+      {
+        printk( "CAUTION: Copying NVRAM data on fcChip\n");
+        for( i= 0; i < 8; i++)
+          WWNbuf[i] = nvRam[i +0x2f]; // dangerous! some formats won't work
+      }
+      
+      fcChip->Registers.wwn_hi = 0L;
+      fcChip->Registers.wwn_lo = 0L;
+      for( i=0; i<4; i++)  // WWN bytes are big endian in NVRAM
+      {
+        ulBuff = 0L;
+        ulBuff = (ULONG)(WWNbuf[i]) << (8 * (3-i));
+        fcChip->Registers.wwn_hi |= ulBuff;
+      }
+      for( i=0; i<4; i++)  // WWN bytes are big endian in NVRAM
+      {
+        ulBuff = 0L;
+        ulBuff = (ULONG)(WWNbuf[i+4]) << (8 * (3-i));
+        fcChip->Registers.wwn_lo |= ulBuff;
+      }
+    }  // done reading
+    else
+    {
+
+      printk( "cpqfcTS: NVRAM read failed\n");
+
+    }
+  }
+
+  else  // WRITE
+  {
+
+    // NOTE: WRITE not supported & not used in released driver.
+
+   
+    printk("ReadWriteNRAM: can't write NVRAM; aborting write\n");
+  }
+  
+  LEAVE("ReadWriteWWN");
+  return iStatus;
+}
+
+
+
+
+
+// The following function reads or writes the entire "NVRAM" contents of 
+// the I2C hardware (i.e. the NM24C03).  Note that HP's 5121A (TS 66Mhz)
+// adapter does not use the NM24C03 chip, so this function only works on
+// Compaq's adapters.
+
+int CpqTsReadWriteNVRAM( PVOID pChip, PVOID buf, int Read)
+{
+  PTACHYON fcChip = (PTACHYON)pChip;
+#define NVRAM_SIZE 512
+  ULONG ulBuff;
+  UCHAR *ucPtr = buf; // cast caller's void ptr to UCHAR array
+  int iStatus=-1;  // assume failure
+
+     
+  if( Read )  // READing NVRAM?
+  {
+    ulBuff = cpqfcTS_ReadNVRAM(   // TRUE on success
+                fcChip->Registers.TYstatus.address,
+                fcChip->Registers.TYcontrol.address,
+                256,            // bytes to write
+                ucPtr );        // source ptr
+
+
+    if( ulBuff )
+      iStatus = 0; // success
+    else
+    {
+#ifdef DBG
+      printk( "CAUTION: NVRAM read failed\n");
+#endif
+    }
+  }  // done reading
+
+  else  // WRITING NVRAM 
+  {
+
+    printk("cpqfcTS: WRITE of FC Controller's NVRAM disabled\n");
+  }
+    
+  return iStatus;
+}

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