| /* 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; |
| } |