2232 строки
73 KiB
C
2232 строки
73 KiB
C
/* 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/blkdev.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/string.h>
|
|
#include <linux/ioport.h> // request_region() prototype
|
|
#include <linux/sched.h>
|
|
#include <linux/slab.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>
|
|
#include <linux/spinlock.h>
|
|
|
|
#include "scsi.h"
|
|
#include <scsi/scsi_host.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);
|
|
|
|
static void
|
|
cpqfc_free_dma_consistent(CPQFCHBA *cpqfcHBAdata)
|
|
{
|
|
// free up the primary EXCHANGES struct and Link Q
|
|
PTACHYON fcChip = &cpqfcHBAdata->fcChip;
|
|
|
|
if (fcChip->Exchanges != NULL)
|
|
pci_free_consistent(cpqfcHBAdata->PciDev, sizeof(FC_EXCHANGES),
|
|
fcChip->Exchanges, fcChip->exch_dma_handle);
|
|
fcChip->Exchanges = NULL;
|
|
if (cpqfcHBAdata->fcLQ != NULL)
|
|
pci_free_consistent(cpqfcHBAdata->PciDev, sizeof(FC_LINK_QUE),
|
|
cpqfcHBAdata->fcLQ, cpqfcHBAdata->fcLQ_dma_handle);
|
|
cpqfcHBAdata->fcLQ = NULL;
|
|
}
|
|
|
|
// 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;
|
|
dma_addr_t ERQdma, IMQdma, SPQdma, SESTdma;
|
|
int i;
|
|
|
|
// 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...
|
|
fcChip->Exchanges = NULL;
|
|
cpqfcHBAdata->fcLQ = NULL;
|
|
|
|
/* printk("Allocating %u for %u Exchanges ",
|
|
(ULONG)sizeof(FC_EXCHANGES), TACH_MAX_XID); */
|
|
fcChip->Exchanges = pci_alloc_consistent(cpqfcHBAdata->PciDev,
|
|
sizeof(FC_EXCHANGES), &fcChip->exch_dma_handle);
|
|
/* printk("@ %p\n", fcChip->Exchanges); */
|
|
|
|
if( fcChip->Exchanges == NULL ) // fatal error!!
|
|
{
|
|
printk("pci_alloc_consistent 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 = pci_alloc_consistent(cpqfcHBAdata->PciDev,
|
|
sizeof( FC_LINK_QUE), &cpqfcHBAdata->fcLQ_dma_handle);
|
|
/* printk("@ %p (%u elements)\n", cpqfcHBAdata->fcLQ, FC_LINKQ_DEPTH); */
|
|
|
|
if( cpqfcHBAdata->fcLQ == NULL ) // fatal error!!
|
|
{
|
|
cpqfc_free_dma_consistent(cpqfcHBAdata);
|
|
printk("pci_alloc_consistent() 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 )
|
|
{
|
|
cpqfc_free_dma_consistent(cpqfcHBAdata);
|
|
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->PciDev,
|
|
&cpqfcHBAdata->dynamic_mem[0],
|
|
sizeof( TachLiteERQ ), 32*(ERQ_LEN), 0L, &ERQdma);
|
|
if( !fcChip->ERQ )
|
|
{
|
|
cpqfc_free_dma_consistent(cpqfcHBAdata);
|
|
printk("pci_alloc_consistent/alignment failure on ERQ: fatal error\n");
|
|
return -1;
|
|
}
|
|
fcChip->ERQ->length = ERQ_LEN-1;
|
|
ulAddr = (ULONG) ERQdma;
|
|
#if BITS_PER_LONG > 32
|
|
if( (ulAddr >> 32) )
|
|
{
|
|
cpqfc_free_dma_consistent(cpqfcHBAdata);
|
|
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->PciDev,
|
|
&cpqfcHBAdata->dynamic_mem[0],
|
|
sizeof( TachyonIMQ ), 32*(IMQ_LEN), 0L, &IMQdma );
|
|
if( !fcChip->IMQ )
|
|
{
|
|
cpqfc_free_dma_consistent(cpqfcHBAdata);
|
|
printk("pci_alloc_consistent/alignment failure on IMQ: fatal error\n");
|
|
return -1;
|
|
}
|
|
fcChip->IMQ->length = IMQ_LEN-1;
|
|
|
|
ulAddr = IMQdma;
|
|
#if BITS_PER_LONG > 32
|
|
if( (ulAddr >> 32) )
|
|
{
|
|
cpqfc_free_dma_consistent(cpqfcHBAdata);
|
|
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->PciDev,
|
|
&cpqfcHBAdata->dynamic_mem[0],
|
|
sizeof( TachLiteSFQ ), 64*(SFQ_LEN),0L, &SPQdma );
|
|
if( !fcChip->SFQ )
|
|
{
|
|
cpqfc_free_dma_consistent(cpqfcHBAdata);
|
|
printk("pci_alloc_consistent/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 = SPQdma;
|
|
#if BITS_PER_LONG > 32
|
|
if( (ulAddr >> 32) )
|
|
{
|
|
cpqfc_free_dma_consistent(cpqfcHBAdata);
|
|
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->PciDev,
|
|
&cpqfcHBAdata->dynamic_mem[0],
|
|
sizeof(TachSEST), 4, 0L, &SESTdma );
|
|
// sizeof(TachSEST), 64*TACH_SEST_LEN, 0L );
|
|
if( !fcChip->SEST )
|
|
{
|
|
cpqfc_free_dma_consistent(cpqfcHBAdata);
|
|
printk("pci_alloc_consistent/alignment failure on SEST: fatal error\n");
|
|
return -1;
|
|
}
|
|
|
|
for( i=0; i < TACH_SEST_LEN; i++) // for each exchange
|
|
fcChip->SEST->sgPages[i] = NULL;
|
|
|
|
fcChip->SEST->length = TACH_SEST_LEN; // e.g. DON'T subtract one
|
|
// (TL/TS UG, pg 153)
|
|
|
|
ulAddr = SESTdma;
|
|
#if BITS_PER_LONG > 32
|
|
if( (ulAddr >> 32) )
|
|
{
|
|
cpqfc_free_dma_consistent(cpqfcHBAdata);
|
|
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 = ((unsigned long)&fcChip->ERQ->consumerIndex -
|
|
(unsigned long)fcChip->ERQ) + (unsigned long) ERQdma;
|
|
|
|
// 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 = ((unsigned long)&fcChip->IMQ->producerIndex -
|
|
(unsigned long)fcChip->IMQ) + (unsigned long) IMQdma;
|
|
|
|
#if BITS_PER_LONG > 32
|
|
if( (ulAddr >> 32) )
|
|
{
|
|
cpqfc_free_dma_consistent(cpqfcHBAdata);
|
|
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 occurrence 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;
|
|
#error This is too much stack
|
|
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;
|
|
#error This is too much stack
|
|
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 occurred. 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( cpqfcHBAdata->PciDev, 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( cpqfcHBAdata->PciDev,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);
|
|
|
|
if( iStatus )
|
|
break;
|
|
|
|
// 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);
|
|
|
|
// Fall through
|
|
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, 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)
|
|
FC_EXCHANGES *Exchanges = fcChip->Exchanges;
|
|
PSGPAGES j, next;
|
|
|
|
ENTER("DestroyTachLiteQues");
|
|
|
|
if( fcChip->SEST )
|
|
{
|
|
// search out and free Pool for Extended S/G list pages
|
|
|
|
for( i=0; i < TACH_SEST_LEN; i++) // for each exchange
|
|
{
|
|
// It's possible that extended S/G pages were allocated, mapped, and
|
|
// not cleared due to error conditions or O/S driver termination.
|
|
// Make sure they're all gone.
|
|
if (Exchanges->fcExchange[i].Cmnd != NULL)
|
|
cpqfc_pci_unmap(cpqfcHBAdata->PciDev, Exchanges->fcExchange[i].Cmnd,
|
|
fcChip, i); // undo DMA mappings.
|
|
|
|
for (j=fcChip->SEST->sgPages[i] ; j != NULL ; j = next) {
|
|
next = j->next;
|
|
kfree(j);
|
|
}
|
|
fcChip->SEST->sgPages[i] = NULL;
|
|
}
|
|
ulPtr = (unsigned long)fcChip->SEST;
|
|
vPtr = fcMemManager( cpqfcHBAdata->PciDev,
|
|
&cpqfcHBAdata->dynamic_mem[0],
|
|
0,0, (ULONG)ulPtr, NULL ); // '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->PciDev,
|
|
&cpqfcHBAdata->dynamic_mem[0],
|
|
0,0, (ULONG)ulPtr, NULL ); // '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->PciDev, &cpqfcHBAdata->dynamic_mem[0],
|
|
0,0, (ULONG)ulPtr, NULL ); // '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->PciDev, &cpqfcHBAdata->dynamic_mem[0],
|
|
0,0, (ULONG)ulPtr, NULL ); // 'free' mem
|
|
fcChip->ERQ = 0L; // null invalid ptr
|
|
if( !vPtr )
|
|
{
|
|
printk("ERQ mem not freed\n");
|
|
iStatus = -4;
|
|
}
|
|
}
|
|
|
|
// free up the primary EXCHANGES struct and Link Q
|
|
cpqfc_free_dma_consistent(cpqfcHBAdata);
|
|
|
|
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;
|
|
}
|