Commit ca61f10a authored by James Bottomley's avatar James Bottomley

[SCSI] remove broken driver cpqfc

Hopefully there should be a brand new replacement driver for this heap
of junk by the beginning of next year.

Acked By: Martin K. Petersen <mkp@mkp.net>
Signed-off-by: default avatarJames Bottomley <James.Bottomley@SteelEye.com>
parent 80e23bab
...@@ -620,19 +620,6 @@ config SCSI_OMIT_FLASHPOINT ...@@ -620,19 +620,6 @@ config SCSI_OMIT_FLASHPOINT
substantial, so users of MultiMaster Host Adapters may wish to omit substantial, so users of MultiMaster Host Adapters may wish to omit
it. it.
#
# This is marked broken because it uses over 4kB of stack in
# just two routines:
# 2076 CpqTsProcessIMQEntry
# 2052 PeekIMQEntry
#
config SCSI_CPQFCTS
tristate "Compaq Fibre Channel 64-bit/66Mhz HBA support"
depends on PCI && SCSI && BROKEN
help
Say Y here to compile in support for the Compaq StorageWorks Fibre
Channel 64-bit/66Mhz Host Bus Adapter.
config SCSI_DMX3191D config SCSI_DMX3191D
tristate "DMX3191D SCSI support" tristate "DMX3191D SCSI support"
depends on PCI && SCSI depends on PCI && SCSI
......
...@@ -120,7 +120,6 @@ obj-$(CONFIG_JAZZ_ESP) += NCR53C9x.o jazz_esp.o ...@@ -120,7 +120,6 @@ obj-$(CONFIG_JAZZ_ESP) += NCR53C9x.o jazz_esp.o
obj-$(CONFIG_SUN3X_ESP) += NCR53C9x.o sun3x_esp.o obj-$(CONFIG_SUN3X_ESP) += NCR53C9x.o sun3x_esp.o
obj-$(CONFIG_SCSI_DEBUG) += scsi_debug.o obj-$(CONFIG_SCSI_DEBUG) += scsi_debug.o
obj-$(CONFIG_SCSI_FCAL) += fcal.o obj-$(CONFIG_SCSI_FCAL) += fcal.o
obj-$(CONFIG_SCSI_CPQFCTS) += cpqfc.o
obj-$(CONFIG_SCSI_LASI700) += 53c700.o lasi700.o obj-$(CONFIG_SCSI_LASI700) += 53c700.o lasi700.o
obj-$(CONFIG_SCSI_NSP32) += nsp32.o obj-$(CONFIG_SCSI_NSP32) += nsp32.o
obj-$(CONFIG_SCSI_IPR) += ipr.o obj-$(CONFIG_SCSI_IPR) += ipr.o
...@@ -165,8 +164,6 @@ ncr53c8xx-flags-$(CONFIG_SCSI_ZALON) \ ...@@ -165,8 +164,6 @@ ncr53c8xx-flags-$(CONFIG_SCSI_ZALON) \
CFLAGS_ncr53c8xx.o := $(ncr53c8xx-flags-y) $(ncr53c8xx-flags-m) CFLAGS_ncr53c8xx.o := $(ncr53c8xx-flags-y) $(ncr53c8xx-flags-m)
zalon7xx-objs := zalon.o ncr53c8xx.o zalon7xx-objs := zalon.o ncr53c8xx.o
NCR_Q720_mod-objs := NCR_Q720.o ncr53c8xx.o NCR_Q720_mod-objs := NCR_Q720.o ncr53c8xx.o
cpqfc-objs := cpqfcTSinit.o cpqfcTScontrol.o cpqfcTSi2c.o \
cpqfcTSworker.o cpqfcTStrigger.o
libata-objs := libata-core.o libata-scsi.o libata-objs := libata-core.o libata-scsi.o
# Files generated that shall be removed upon make clean # Files generated that shall be removed upon make clean
......
#ifndef CPQFCTS_H
#define CPQFCTS_H
#include "cpqfcTSstructs.h"
// These functions are required by the Linux SCSI layers
extern int cpqfcTS_detect(Scsi_Host_Template *);
extern int cpqfcTS_release(struct Scsi_Host *);
extern const char * cpqfcTS_info(struct Scsi_Host *);
extern int cpqfcTS_proc_info(struct Scsi_Host *, char *, char **, off_t, int, int);
extern int cpqfcTS_queuecommand(Scsi_Cmnd *, void (* done)(Scsi_Cmnd *));
extern int cpqfcTS_abort(Scsi_Cmnd *);
extern int cpqfcTS_reset(Scsi_Cmnd *, unsigned int);
extern int cpqfcTS_eh_abort(Scsi_Cmnd *Cmnd);
extern int cpqfcTS_eh_device_reset(Scsi_Cmnd *);
extern int cpqfcTS_biosparam(struct scsi_device *, struct block_device *,
sector_t, int[]);
extern int cpqfcTS_ioctl( Scsi_Device *ScsiDev, int Cmnd, void *arg);
#endif /* CPQFCTS_H */
/* Copyright(c) 2000, Compaq Computer Corporation
* Fibre Channel Host Bus Adapter
* 64-bit, 66MHz PCI
* Originally developed and tested on:
* (front): [chip] Tachyon TS HPFC-5166A/1.2 L2C1090 ...
* SP# P225CXCBFIEL6T, Rev XC
* SP# 161290-001, Rev XD
* (back): Board No. 010008-001 A/W Rev X5, FAB REV X5
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
* Written by Don Zimmerman
*/
#ifndef CPQFCTSCHIP_H
#define CPQFCTSCHIP_H
#ifndef TACHYON_CHIP_INC
// FC-PH (Physical) specification levels for Login payloads
// NOTE: These are NOT strictly complied with by any FC vendors
#define FC_PH42 0x08
#define FC_PH43 0x09
#define FC_PH3 0x20
#define TACHLITE_TS_RX_SIZE 1024 // max inbound frame size
// "I" prefix is for Include
#define IVENDID 0x00 // word
#define IDEVID 0x02
#define ITLCFGCMD 0x04
#define IMEMBASE 0x18 // Tachyon
#define ITLMEMBASE 0x1C // Tachlite
#define IIOBASEL 0x10 // Tachyon I/O base address, lower 256 bytes
#define IIOBASEU 0x14 // Tachyon I/O base address, upper 256 bytes
#define ITLIOBASEL 0x14 // TachLite I/O base address, lower 256 bytes
#define ITLIOBASEU 0x18 // TachLite I/O base address, upper 256 bytes
#define ITLRAMBASE 0x20 // TL on-board RAM start
#define ISROMBASE 0x24
#define IROMBASE 0x30
#define ICFGCMD 0x04 // PCI config - PCI config access (word)
#define ICFGSTAT 0x06 // PCI status (R - word)
#define IRCTR_WCTR 0x1F2 // ROM control / pre-fetch wait counter
#define IPCIMCTR 0x1F3 // PCI master control register
#define IINTPEND 0x1FD // Interrupt pending (I/O Upper - Tachyon & TL)
#define IINTEN 0x1FE // Interrupt enable (I/O Upper - Tachyon & TL)
#define IINTSTAT 0x1FF // Interrupt status (I/O Upper - Tachyon & TL)
#define IMQ_BASE 0x80
#define IMQ_LENGTH 0x84
#define IMQ_CONSUMER_INDEX 0x88
#define IMQ_PRODUCER_INDEX 0x8C // Tach copies its INDX to bits 0-7 of value
/*
// IOBASE UPPER
#define SFSBQ_BASE 0x00 // single-frame sequences
#define SFSBQ_LENGTH 0x04
#define SFSBQ_PRODUCER_INDEX 0x08
#define SFSBQ_CONSUMER_INDEX 0x0C // (R)
#define SFS_BUFFER_LENGTH 0X10
// SCSI-FCP hardware assists
#define SEST_BASE 0x40 // SSCI Exchange State Table
#define SEST_LENGTH 0x44
#define SCSI_BUFFER_LENGTH 0x48
#define SEST_LINKED_LIST 0x4C
#define TACHYON_My_ID 0x6C
#define TACHYON_CONFIGURATION 0x84 // (R/W) reset val 2
#define TACHYON_CONTROL 0x88
#define TACHYON_STATUS 0x8C // (R)
#define TACHYON_FLUSH_SEST 0x90 // (R/W)
#define TACHYON_EE_CREDIT_TMR 0x94 // (R)
#define TACHYON_BB_CREDIT_TMR 0x98 // (R)
#define TACHYON_RCV_FRAME_ERR 0x9C // (R)
#define FRAME_MANAGER_CONFIG 0xC0 // (R/W)
#define FRAME_MANAGER_CONTROL 0xC4
#define FRAME_MANAGER_STATUS 0xC8 // (R)
#define FRAME_MANAGER_ED_TOV 0xCC
#define FRAME_MANAGER_LINK_ERR1 0xD0 // (R)
#define FRAME_MANAGER_LINK_ERR2 0xD4 // (R)
#define FRAME_MANAGER_TIMEOUT2 0xD8 // (W)
#define FRAME_MANAGER_BB_CREDIT 0xDC // (R)
#define FRAME_MANAGER_WWN_HI 0xE0 // (R/W)
#define FRAME_MANAGER_WWN_LO 0xE4 // (R/W)
#define FRAME_MANAGER_RCV_AL_PA 0xE8 // (R)
#define FRAME_MANAGER_PRIMITIVE 0xEC // {K28.5} byte1 byte2 byte3
*/
#define TL_MEM_ERQ_BASE 0x0 //ERQ Base
#define TL_IO_ERQ_BASE 0x0 //ERQ base
#define TL_MEM_ERQ_LENGTH 0x4 //ERQ Length
#define TL_IO_ERQ_LENGTH 0x4 //ERQ Length
#define TL_MEM_ERQ_PRODUCER_INDEX 0x8 //ERQ Producer Index register
#define TL_IO_ERQ_PRODUCER_INDEX 0x8 //ERQ Producer Index register
#define TL_MEM_ERQ_CONSUMER_INDEX_ADR 0xC //ERQ Consumer Index address register
#define TL_IO_ERQ_CONSUMER_INDEX_ADR 0xC //ERQ Consumer Index address register
#define TL_MEM_ERQ_CONSUMER_INDEX 0xC //ERQ Consumer Index
#define TL_IO_ERQ_CONSUMER_INDEX 0xC //ERQ Consumer Index
#define TL_MEM_SFQ_BASE 0x50 //SFQ Base
#define TL_IO_SFQ_BASE 0x50 //SFQ base
#define TL_MEM_SFQ_LENGTH 0x54 //SFQ Length
#define TL_IO_SFQ_LENGTH 0x54 //SFQ Length
#define TL_MEM_SFQ_CONSUMER_INDEX 0x58 //SFQ Consumer Index
#define TL_IO_SFQ_CONSUMER_INDEX 0x58 //SFQ Consumer Index
#define TL_MEM_IMQ_BASE 0x80 //IMQ Base
#define TL_IO_IMQ_BASE 0x80 //IMQ base
#define TL_MEM_IMQ_LENGTH 0x84 //IMQ Length
#define TL_IO_IMQ_LENGTH 0x84 //IMQ Length
#define TL_MEM_IMQ_CONSUMER_INDEX 0x88 //IMQ Consumer Index
#define TL_IO_IMQ_CONSUMER_INDEX 0x88 //IMQ Consumer Index
#define TL_MEM_IMQ_PRODUCER_INDEX_ADR 0x8C //IMQ Producer Index address register
#define TL_IO_IMQ_PRODUCER_INDEX_ADR 0x8C //IMQ Producer Index address register
#define TL_MEM_SEST_BASE 0x140 //SFQ Base
#define TL_IO_SEST_BASE 0x40 //SFQ base
#define TL_MEM_SEST_LENGTH 0x144 //SFQ Length
#define TL_IO_SEST_LENGTH 0x44 //SFQ Length
#define TL_MEM_SEST_LINKED_LIST 0x14C
#define TL_MEM_SEST_SG_PAGE 0x168 // Extended Scatter/Gather page size
#define TL_MEM_TACH_My_ID 0x16C
#define TL_IO_TACH_My_ID 0x6C //My AL_PA ID
#define TL_MEM_TACH_CONFIG 0x184 //Tachlite Configuration register
#define TL_IO_CONFIG 0x84 //Tachlite Configuration register
#define TL_MEM_TACH_CONTROL 0x188 //Tachlite Control register
#define TL_IO_CTR 0x88 //Tachlite Control register
#define TL_MEM_TACH_STATUS 0x18C //Tachlite Status register
#define TL_IO_STAT 0x8C //Tachlite Status register
#define TL_MEM_FM_CONFIG 0x1C0 //Frame Manager Configuration register
#define TL_IO_FM_CONFIG 0xC0 //Frame Manager Configuration register
#define TL_MEM_FM_CONTROL 0x1C4 //Frame Manager Control
#define TL_IO_FM_CTL 0xC4 //Frame Manager Control
#define TL_MEM_FM_STATUS 0x1C8 //Frame Manager Status
#define TL_IO_FM_STAT 0xC8 //Frame Manager Status
#define TL_MEM_FM_LINK_STAT1 0x1D0 //Frame Manager Link Status 1
#define TL_IO_FM_LINK_STAT1 0xD0 //Frame Manager Link Status 1
#define TL_MEM_FM_LINK_STAT2 0x1D4 //Frame Manager Link Status 2
#define TL_IO_FM_LINK_STAT2 0xD4 //Frame Manager Link Status 2
#define TL_MEM_FM_TIMEOUT2 0x1D8 // (W)
#define TL_MEM_FM_BB_CREDIT0 0x1DC
#define TL_MEM_FM_WWN_HI 0x1E0 //Frame Manager World Wide Name High
#define TL_IO_FM_WWN_HI 0xE0 //Frame Manager World Wide Name High
#define TL_MEM_FM_WWN_LO 0x1E4 //Frame Manager World Wide Name LOW
#define TL_IO_FM_WWN_LO 0xE4 //Frame Manager World Wide Name Low
#define TL_MEM_FM_RCV_AL_PA 0x1E8 //Frame Manager AL_PA Received register
#define TL_IO_FM_ALPA 0xE8 //Frame Manager AL_PA Received register
#define TL_MEM_FM_ED_TOV 0x1CC
#define TL_IO_ROMCTR 0xFA //TL PCI ROM Control Register
#define TL_IO_PCIMCTR 0xFB //TL PCI Master Control Register
#define TL_IO_SOFTRST 0xFC //Tachlite Configuration register
#define TL_MEM_SOFTRST 0x1FC //Tachlite Configuration register
// completion message types (bit 8 set means Interrupt generated)
// CM_Type
#define OUTBOUND_COMPLETION 0
#define ERROR_IDLE_COMPLETION 0x01
#define OUT_HI_PRI_COMPLETION 0x01
#define INBOUND_MFS_COMPLETION 0x02
#define INBOUND_000_COMPLETION 0x03
#define INBOUND_SFS_COMPLETION 0x04 // Tachyon & TachLite
#define ERQ_FROZEN_COMPLETION 0x06 // TachLite
#define INBOUND_C1_TIMEOUT 0x05
#define INBOUND_BUSIED_FRAME 0x06
#define SFS_BUF_WARN 0x07
#define FCP_FROZEN_COMPLETION 0x07 // TachLite
#define MFS_BUF_WARN 0x08
#define IMQ_BUF_WARN 0x09
#define FRAME_MGR_INTERRUPT 0x0A
#define READ_STATUS 0x0B
#define INBOUND_SCSI_DATA_COMPLETION 0x0C
#define INBOUND_FCP_XCHG_COMPLETION 0x0C // TachLite
#define INBOUND_SCSI_DATA_COMMAND 0x0D
#define BAD_SCSI_FRAME 0x0E
#define INB_SCSI_STATUS_COMPLETION 0x0F
#define BUFFER_PROCESSED_COMPLETION 0x11
// FC-AL (Tachyon) Loop Port State Machine defs
// (loop "Up" states)
#define MONITORING 0x0
#define ARBITRATING 0x1
#define ARBITRAT_WON 0x2
#define OPEN 0x3
#define OPENED 0x4
#define XMITTD_CLOSE 0x5
#define RCVD_CLOSE 0x6
#define TRANSFER 0x7
// (loop "Down" states)
#define INITIALIZING 0x8
#define O_I_INIT 0x9
#define O_I_PROTOCOL 0xa
#define O_I_LIP_RCVD 0xb
#define HOST_CONTROL 0xc
#define LOOP_FAIL 0xd
// (no 0xe)
#define OLD_PORT 0xf
#define TACHYON_CHIP_INC
#endif
#endif /* CPQFCTSCHIP_H */
/* 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;
}
/* Copyright(c) 2000, Compaq Computer Corporation
* Fibre Channel Host Bus Adapter
* 64-bit, 66MHz PCI
* Originally developed and tested on:
* (front): [chip] Tachyon TS HPFC-5166A/1.2 L2C1090 ...
* SP# P225CXCBFIEL6T, Rev XC
* SP# 161290-001, Rev XD
* (back): Board No. 010008-001 A/W Rev X5, FAB REV X5
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
* Written by Don Zimmerman
*/
// These functions control the NVRAM I2C hardware on
// non-intelligent Fibre Host Adapters.
// The primary purpose is to read the HBA's NVRAM to get adapter's
// manufactured WWN to copy into Tachyon chip registers
// Orignal source author unknown
#include <linux/types.h>
enum boolean { FALSE, TRUE } ;
#ifndef UCHAR
typedef __u8 UCHAR;
#endif
#ifndef BOOLEAN
typedef __u8 BOOLEAN;
#endif
#ifndef USHORT
typedef __u16 USHORT;
#endif
#ifndef ULONG
typedef __u32 ULONG;
#endif
#include <linux/string.h>
#include <linux/pci.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <asm/io.h> // struct pt_regs for IRQ handler & Port I/O
#include "cpqfcTSchip.h"
static void tl_i2c_tx_byte( void* GPIOout, UCHAR data );
/*static BOOLEAN tl_write_i2c_page_portion( void* GPIOin, void* GPIOout,
USHORT startOffset, // e.g. 0x2f for WWN start
USHORT count,
UCHAR *buf );
*/
//
// Tachlite GPIO2, GPIO3 (I2C) DEFINES
// The NVRAM chip NM24C03 defines SCL (serial clock) and SDA (serial data)
// GPIO2 drives SDA, and GPIO3 drives SCL
//
// Since Tachlite inverts the state of the GPIO 0-3 outputs, SET writes 0
// and clear writes 1. The input lines (read in TL status) is NOT inverted
// This really helps confuse the code and debugging.
#define SET_DATA_HI 0x0
#define SET_DATA_LO 0x8
#define SET_CLOCK_HI 0x0
#define SET_CLOCK_LO 0x4
#define SENSE_DATA_HI 0x8
#define SENSE_DATA_LO 0x0
#define SENSE_CLOCK_HI 0x4
#define SENSE_CLOCK_LO 0x0
#define SLAVE_READ_ADDRESS 0xA1
#define SLAVE_WRITE_ADDRESS 0xA0
static void i2c_delay(ULONG mstime);
static void tl_i2c_clock_pulse( UCHAR , void* GPIOout);
static UCHAR tl_read_i2c_data( void* );
//-----------------------------------------------------------------------------
//
// Name: I2C_RX_ACK
//
// This routine receives an acknowledge over the I2C bus.
//
//-----------------------------------------------------------------------------
static unsigned short tl_i2c_rx_ack( void* GPIOin, void* GPIOout )
{
unsigned long value;
// do clock pulse, let data line float high
tl_i2c_clock_pulse( SET_DATA_HI, GPIOout );
// slave must drive data low for acknowledge
value = tl_read_i2c_data( GPIOin);
if (value & SENSE_DATA_HI )
return( FALSE );
return( TRUE );
}
//-----------------------------------------------------------------------------
//
// Name: READ_I2C_REG
//
// This routine reads the I2C control register using the global
// IO address stored in gpioreg.
//
//-----------------------------------------------------------------------------
static UCHAR tl_read_i2c_data( void* gpioreg )
{
return( (UCHAR)(readl( gpioreg ) & 0x08L) ); // GPIO3
}
//-----------------------------------------------------------------------------
//
// Name: WRITE_I2C_REG
//
// This routine writes the I2C control register using the global
// IO address stored in gpioreg.
// In Tachlite, we don't want to modify other bits in TL Control reg.
//
//-----------------------------------------------------------------------------
static void tl_write_i2c_reg( void* gpioregOUT, UCHAR value )
{
ULONG temp;
// First read the register and clear out the old bits
temp = readl( gpioregOUT ) & 0xfffffff3L;
// Now or in the new data and send it back out
writel( temp | value, gpioregOUT);
}
//-----------------------------------------------------------------------------
//
// Name: I2C_TX_START
//
// This routine transmits a start condition over the I2C bus.
// 1. Set SCL (clock, GPIO2) HIGH, set SDA (data, GPIO3) HIGH,
// wait 5us to stabilize.
// 2. With SCL still HIGH, drive SDA low. The low transition marks
// the start condition to NM24Cxx (the chip)
// NOTE! In TL control reg., output 1 means chip sees LOW
//
//-----------------------------------------------------------------------------
static unsigned short tl_i2c_tx_start( void* GPIOin, void* GPIOout )
{
unsigned short i;
ULONG value;
if ( !(tl_read_i2c_data(GPIOin) & SENSE_DATA_HI))
{
// start with clock high, let data float high
tl_write_i2c_reg( GPIOout, SET_DATA_HI | SET_CLOCK_HI );
// keep sending clock pulses if slave is driving data line
for (i = 0; i < 10; i++)
{
tl_i2c_clock_pulse( SET_DATA_HI, GPIOout );
if ( tl_read_i2c_data(GPIOin) & SENSE_DATA_HI )
break;
}
// if he's still driving data low after 10 clocks, abort
value = tl_read_i2c_data( GPIOin ); // read status
if (!(value & 0x08) )
return( FALSE );
}
// To START, bring data low while clock high
tl_write_i2c_reg( GPIOout, SET_CLOCK_HI | SET_DATA_LO );
i2c_delay(0);
return( TRUE ); // TX start successful
}
//-----------------------------------------------------------------------------
//
// Name: I2C_TX_STOP
//
// This routine transmits a stop condition over the I2C bus.
//
//-----------------------------------------------------------------------------
static unsigned short tl_i2c_tx_stop( void* GPIOin, void* GPIOout )
{
int i;
for (i = 0; i < 10; i++)
{
// Send clock pulse, drive data line low
tl_i2c_clock_pulse( SET_DATA_LO, GPIOout );
// To STOP, bring data high while clock high
tl_write_i2c_reg( GPIOout, SET_DATA_HI | SET_CLOCK_HI );
// Give the data line time to float high
i2c_delay(0);
// If slave is driving data line low, there's a problem; retry
if ( tl_read_i2c_data(GPIOin) & SENSE_DATA_HI )
return( TRUE ); // TX STOP successful!
}
return( FALSE ); // error
}
//-----------------------------------------------------------------------------
//
// Name: I2C_TX_uchar
//
// This routine transmits a byte across the I2C bus.
//
//-----------------------------------------------------------------------------
static void tl_i2c_tx_byte( void* GPIOout, UCHAR data )
{
UCHAR bit;
for (bit = 0x80; bit; bit >>= 1)
{
if( data & bit )
tl_i2c_clock_pulse( (UCHAR)SET_DATA_HI, GPIOout);
else
tl_i2c_clock_pulse( (UCHAR)SET_DATA_LO, GPIOout);
}
}
//-----------------------------------------------------------------------------
//
// Name: I2C_RX_uchar
//
// This routine receives a byte across the I2C bus.
//
//-----------------------------------------------------------------------------
static UCHAR tl_i2c_rx_byte( void* GPIOin, void* GPIOout )
{
UCHAR bit;
UCHAR data = 0;
for (bit = 0x80; bit; bit >>= 1) {
// do clock pulse, let data line float high
tl_i2c_clock_pulse( SET_DATA_HI, GPIOout );
// read data line
if ( tl_read_i2c_data( GPIOin) & 0x08 )
data |= bit;
}
return (data);
}
//*****************************************************************************
//*****************************************************************************
// Function: read_i2c_nvram
// Arguments: UCHAR count number of bytes to read
// UCHAR *buf area to store the bytes read
// Returns: 0 - failed
// 1 - success
//*****************************************************************************
//*****************************************************************************
unsigned long cpqfcTS_ReadNVRAM( void* GPIOin, void* GPIOout , USHORT count,
UCHAR *buf )
{
unsigned short i;
if( !( tl_i2c_tx_start(GPIOin, GPIOout) ))
return FALSE;
// Select the NVRAM for "dummy" write, to set the address
tl_i2c_tx_byte( GPIOout , SLAVE_WRITE_ADDRESS );
if ( !tl_i2c_rx_ack(GPIOin, GPIOout ) )
return( FALSE );
// Now send the address where we want to start reading
tl_i2c_tx_byte( GPIOout , 0 );
if ( !tl_i2c_rx_ack(GPIOin, GPIOout ) )
return( FALSE );
// Send a repeated start condition and select the
// slave for reading now.
if( tl_i2c_tx_start(GPIOin, GPIOout) )
tl_i2c_tx_byte( GPIOout, SLAVE_READ_ADDRESS );
if ( !tl_i2c_rx_ack(GPIOin, GPIOout) )
return( FALSE );
// this loop will now read out the data and store it
// in the buffer pointed to by buf
for ( i=0; i<count; i++)
{
*buf++ = tl_i2c_rx_byte(GPIOin, GPIOout);
// Send ACK by holding data line low for 1 clock
if ( i < (count-1) )
tl_i2c_clock_pulse( 0x08, GPIOout );
else {
// Don't send ack for final byte
tl_i2c_clock_pulse( SET_DATA_HI, GPIOout );
}
}
tl_i2c_tx_stop(GPIOin, GPIOout);
return( TRUE );
}
//****************************************************************
//
//
//
// routines to set and clear the data and clock bits
//
//
//
//****************************************************************
static void tl_set_clock(void* gpioreg)
{
ULONG ret_val;
ret_val = readl( gpioreg );
ret_val &= 0xffffffFBL; // clear GPIO2 (SCL)
writel( ret_val, gpioreg);
}
static void tl_clr_clock(void* gpioreg)
{
ULONG ret_val;
ret_val = readl( gpioreg );
ret_val |= SET_CLOCK_LO;
writel( ret_val, gpioreg);
}
//*****************************************************************
//
//
// This routine will advance the clock by one period
//
//
//*****************************************************************
static void tl_i2c_clock_pulse( UCHAR value, void* GPIOout )
{
ULONG ret_val;
// clear the clock bit
tl_clr_clock( GPIOout );
i2c_delay(0);
// read the port to preserve non-I2C bits
ret_val = readl( GPIOout );
// clear the data & clock bits
ret_val &= 0xFFFFFFf3;
// write the value passed in...
// data can only change while clock is LOW!
ret_val |= value; // the data
ret_val |= SET_CLOCK_LO; // the clock
writel( ret_val, GPIOout );
i2c_delay(0);
//set clock bit
tl_set_clock( GPIOout);
}
//*****************************************************************
//
//
// This routine returns the 64-bit WWN
//
//
//*****************************************************************
int cpqfcTS_GetNVRAM_data( UCHAR *wwnbuf, UCHAR *buf )
{
ULONG len;
ULONG sub_len;
ULONG ptr_inc;
ULONG i;
ULONG j;
UCHAR *data_ptr;
UCHAR z;
UCHAR name;
UCHAR sub_name;
UCHAR done;
int iReturn=0; // def. 0 offset is failure to find WWN field
data_ptr = (UCHAR *)buf;
done = FALSE;
i = 0;
while ( (i < 128) && (!done) )
{
z = data_ptr[i];\
if ( !(z & 0x80) )
{
len = 1 + (z & 0x07);
name = (z & 0x78) >> 3;
if (name == 0x0F)
done = TRUE;
}
else
{
name = z & 0x7F;
len = 3 + data_ptr[i+1] + (data_ptr[i+2] << 8);
switch (name)
{
case 0x0D:
//
j = i + 3;
//
if ( data_ptr[j] == 0x3b ) {
len = 6;
break;
}
while ( j<(i+len) ) {
sub_name = (data_ptr[j] & 0x3f);
sub_len = data_ptr[j+1] +
(data_ptr[j+2] << 8);
ptr_inc = sub_len + 3;
switch (sub_name)
{
case 0x3C:
memcpy( wwnbuf, &data_ptr[j+3], 8);
iReturn = j+3;
break;
default:
break;
}
j += ptr_inc;
}
break;
default:
break;
}
}
//
i += len;
} // end while
return iReturn;
}
// define a short 5 micro sec delay, and longer (ms) delay
static void i2c_delay(ULONG mstime)
{
ULONG i;
// NOTE: we only expect to use these delays when reading
// our adapter's NVRAM, which happens only during adapter reset.
// Delay technique from "Linux Device Drivers", A. Rubini
// (1st Ed.) pg 137.
// printk(" delay %lx ", mstime);
if( mstime ) // ms delay?
{
// delay technique
for( i=0; i < mstime; i++)
udelay(1000); // 1ms per loop
}
else // 5 micro sec delay
udelay( 5 ); // micro secs
// printk("done\n");
}
/* Copyright(c) 2000, Compaq Computer Corporation
* Fibre Channel Host Bus Adapter
* 64-bit, 66MHz PCI
* Originally developed and tested on:
* (front): [chip] Tachyon TS HPFC-5166A/1.2 L2C1090 ...
* SP# P225CXCBFIEL6T, Rev XC
* SP# 161290-001, Rev XD
* (back): Board No. 010008-001 A/W Rev X5, FAB REV X5
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
* Written by Don Zimmerman
* IOCTL and procfs added by Jouke Numan
* SMP testing by Chel Van Gennip
*
* portions copied from:
* QLogic CPQFCTS SCSI-FCP
* Written by Erik H. Moe, ehm@cris.com
* Copyright 1995, Erik H. Moe
* Renamed and updated to 1.3.x by Michael Griffith <grif@cs.ucr.edu>
* Chris Loveland <cwl@iol.unh.edu> to support the isp2100 and isp2200
*/
#define LinuxVersionCode(v, p, s) (((v)<<16)+((p)<<8)+(s))
#include <linux/config.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/blkdev.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/pci.h>
#include <linux/delay.h>
#include <linux/timer.h>
#include <linux/init.h>
#include <linux/ioport.h> // request_region() prototype
#include <linux/completion.h>
#include <asm/io.h>
#include <asm/uaccess.h> // ioctl related
#include <asm/irq.h>
#include <linux/spinlock.h>
#include "scsi.h"
#include <scsi/scsi_host.h>
#include <scsi/scsi_ioctl.h>
#include "cpqfcTSchip.h"
#include "cpqfcTSstructs.h"
#include "cpqfcTStrigger.h"
#include "cpqfcTS.h"
/* Embedded module documentation macros - see module.h */
MODULE_AUTHOR("Compaq Computer Corporation");
MODULE_DESCRIPTION("Driver for Compaq 64-bit/66Mhz PCI Fibre Channel HBA v. 2.5.4");
MODULE_LICENSE("GPL");
int cpqfcTS_TargetDeviceReset( Scsi_Device *ScsiDev, unsigned int reset_flags);
// This struct was originally defined in
// /usr/src/linux/include/linux/proc_fs.h
// since it's only partially implemented, we only use first
// few fields...
// NOTE: proc_fs changes in 2.4 kernel
#if LINUX_VERSION_CODE < LinuxVersionCode(2,3,27)
static struct proc_dir_entry proc_scsi_cpqfcTS =
{
PROC_SCSI_CPQFCTS, // ushort low_ino (enumerated list)
7, // ushort namelen
DEV_NAME, // const char* name
S_IFDIR | S_IRUGO | S_IXUGO, // mode_t mode
2 // nlink_t nlink
// etc. ...
};
#endif
#if LINUX_VERSION_CODE >= LinuxVersionCode(2,4,7)
# define CPQFC_DECLARE_COMPLETION(x) DECLARE_COMPLETION(x)
# define CPQFC_WAITING waiting
# define CPQFC_COMPLETE(x) complete(x)
# define CPQFC_WAIT_FOR_COMPLETION(x) wait_for_completion(x);
#else
# define CPQFC_DECLARE_COMPLETION(x) DECLARE_MUTEX_LOCKED(x)
# define CPQFC_WAITING sem
# define CPQFC_COMPLETE(x) up(x)
# define CPQFC_WAIT_FOR_COMPLETION(x) down(x)
#endif
static int cpqfc_alloc_private_data_pool(CPQFCHBA *hba);
/* local function to load our per-HBA (local) data for chip
registers, FC link state, all FC exchanges, etc.
We allocate space and compute address offsets for the
most frequently accessed addresses; others (like World Wide
Name) are not necessary.
*/
static void Cpqfc_initHBAdata(CPQFCHBA *cpqfcHBAdata, struct pci_dev *PciDev )
{
cpqfcHBAdata->PciDev = PciDev; // copy PCI info ptr
// since x86 port space is 64k, we only need the lower 16 bits
cpqfcHBAdata->fcChip.Registers.IOBaseL =
PciDev->resource[1].start & PCI_BASE_ADDRESS_IO_MASK;
cpqfcHBAdata->fcChip.Registers.IOBaseU =
PciDev->resource[2].start & PCI_BASE_ADDRESS_IO_MASK;
// 32-bit memory addresses
cpqfcHBAdata->fcChip.Registers.MemBase =
PciDev->resource[3].start & PCI_BASE_ADDRESS_MEM_MASK;
cpqfcHBAdata->fcChip.Registers.ReMapMemBase =
ioremap( PciDev->resource[3].start & PCI_BASE_ADDRESS_MEM_MASK,
0x200);
cpqfcHBAdata->fcChip.Registers.RAMBase =
PciDev->resource[4].start;
cpqfcHBAdata->fcChip.Registers.SROMBase = // NULL for HP TS adapter
PciDev->resource[5].start;
// now the Tachlite chip registers
// the REGISTER struct holds both the physical address & last
// written value (some TL registers are WRITE ONLY)
cpqfcHBAdata->fcChip.Registers.SFQconsumerIndex.address =
cpqfcHBAdata->fcChip.Registers.ReMapMemBase + TL_MEM_SFQ_CONSUMER_INDEX;
cpqfcHBAdata->fcChip.Registers.ERQproducerIndex.address =
cpqfcHBAdata->fcChip.Registers.ReMapMemBase + TL_MEM_ERQ_PRODUCER_INDEX;
// TL Frame Manager
cpqfcHBAdata->fcChip.Registers.FMconfig.address =
cpqfcHBAdata->fcChip.Registers.ReMapMemBase + TL_MEM_FM_CONFIG;
cpqfcHBAdata->fcChip.Registers.FMcontrol.address =
cpqfcHBAdata->fcChip.Registers.ReMapMemBase + TL_MEM_FM_CONTROL;
cpqfcHBAdata->fcChip.Registers.FMstatus.address =
cpqfcHBAdata->fcChip.Registers.ReMapMemBase + TL_MEM_FM_STATUS;
cpqfcHBAdata->fcChip.Registers.FMLinkStatus1.address =
cpqfcHBAdata->fcChip.Registers.ReMapMemBase + TL_MEM_FM_LINK_STAT1;
cpqfcHBAdata->fcChip.Registers.FMLinkStatus2.address =
cpqfcHBAdata->fcChip.Registers.ReMapMemBase + TL_MEM_FM_LINK_STAT2;
cpqfcHBAdata->fcChip.Registers.FMBB_CreditZero.address =
cpqfcHBAdata->fcChip.Registers.ReMapMemBase + TL_MEM_FM_BB_CREDIT0;
// TL Control Regs
cpqfcHBAdata->fcChip.Registers.TYconfig.address =
cpqfcHBAdata->fcChip.Registers.ReMapMemBase + TL_MEM_TACH_CONFIG;
cpqfcHBAdata->fcChip.Registers.TYcontrol.address =
cpqfcHBAdata->fcChip.Registers.ReMapMemBase + TL_MEM_TACH_CONTROL;
cpqfcHBAdata->fcChip.Registers.TYstatus.address =
cpqfcHBAdata->fcChip.Registers.ReMapMemBase + TL_MEM_TACH_STATUS;
cpqfcHBAdata->fcChip.Registers.rcv_al_pa.address =
cpqfcHBAdata->fcChip.Registers.ReMapMemBase + TL_MEM_FM_RCV_AL_PA;
cpqfcHBAdata->fcChip.Registers.ed_tov.address =
cpqfcHBAdata->fcChip.Registers.ReMapMemBase + TL_MEM_FM_ED_TOV;
cpqfcHBAdata->fcChip.Registers.INTEN.address =
cpqfcHBAdata->fcChip.Registers.ReMapMemBase + IINTEN;
cpqfcHBAdata->fcChip.Registers.INTPEND.address =
cpqfcHBAdata->fcChip.Registers.ReMapMemBase + IINTPEND;
cpqfcHBAdata->fcChip.Registers.INTSTAT.address =
cpqfcHBAdata->fcChip.Registers.ReMapMemBase + IINTSTAT;
DEBUG_PCI(printk(" cpqfcHBAdata->fcChip.Registers. :\n"));
DEBUG_PCI(printk(" IOBaseL = %x\n",
cpqfcHBAdata->fcChip.Registers.IOBaseL));
DEBUG_PCI(printk(" IOBaseU = %x\n",
cpqfcHBAdata->fcChip.Registers.IOBaseU));
/* printk(" ioremap'd Membase: %p\n", cpqfcHBAdata->fcChip.Registers.ReMapMemBase); */
DEBUG_PCI(printk(" SFQconsumerIndex.address = %p\n",
cpqfcHBAdata->fcChip.Registers.SFQconsumerIndex.address));
DEBUG_PCI(printk(" ERQproducerIndex.address = %p\n",
cpqfcHBAdata->fcChip.Registers.ERQproducerIndex.address));
DEBUG_PCI(printk(" TYconfig.address = %p\n",
cpqfcHBAdata->fcChip.Registers.TYconfig.address));
DEBUG_PCI(printk(" FMconfig.address = %p\n",
cpqfcHBAdata->fcChip.Registers.FMconfig.address));
DEBUG_PCI(printk(" FMcontrol.address = %p\n",
cpqfcHBAdata->fcChip.Registers.FMcontrol.address));
// set default options for FC controller (chip)
cpqfcHBAdata->fcChip.Options.initiator = 1; // default: SCSI initiator
cpqfcHBAdata->fcChip.Options.target = 0; // default: SCSI target
cpqfcHBAdata->fcChip.Options.extLoopback = 0;// default: no loopback @GBIC
cpqfcHBAdata->fcChip.Options.intLoopback = 0;// default: no loopback inside chip
// set highest and lowest FC-PH version the adapter/driver supports
// (NOT strict compliance)
cpqfcHBAdata->fcChip.highest_FCPH_ver = FC_PH3;
cpqfcHBAdata->fcChip.lowest_FCPH_ver = FC_PH43;
// set function points for this controller / adapter
cpqfcHBAdata->fcChip.ResetTachyon = CpqTsResetTachLite;
cpqfcHBAdata->fcChip.FreezeTachyon = CpqTsFreezeTachlite;
cpqfcHBAdata->fcChip.UnFreezeTachyon = CpqTsUnFreezeTachlite;
cpqfcHBAdata->fcChip.CreateTachyonQues = CpqTsCreateTachLiteQues;
cpqfcHBAdata->fcChip.DestroyTachyonQues = CpqTsDestroyTachLiteQues;
cpqfcHBAdata->fcChip.InitializeTachyon = CpqTsInitializeTachLite;
cpqfcHBAdata->fcChip.LaserControl = CpqTsLaserControl;
cpqfcHBAdata->fcChip.ProcessIMQEntry = CpqTsProcessIMQEntry;
cpqfcHBAdata->fcChip.InitializeFrameManager = CpqTsInitializeFrameManager;
cpqfcHBAdata->fcChip.ReadWriteWWN = CpqTsReadWriteWWN;
cpqfcHBAdata->fcChip.ReadWriteNVRAM = CpqTsReadWriteNVRAM;
if (cpqfc_alloc_private_data_pool(cpqfcHBAdata) != 0) {
printk(KERN_WARNING
"cpqfc: unable to allocate pool for passthru ioctls. "
"Passthru ioctls disabled.\n");
}
}
/* (borrowed from linux/drivers/scsi/hosts.c) */
static void launch_FCworker_thread(struct Scsi_Host *HostAdapter)
{
DECLARE_MUTEX_LOCKED(sem);
CPQFCHBA *cpqfcHBAdata = (CPQFCHBA *)HostAdapter->hostdata;
ENTER("launch_FC_worker_thread");
cpqfcHBAdata->notify_wt = &sem;
/* must unlock before kernel_thread(), for it may cause a reschedule. */
spin_unlock_irq(HostAdapter->host_lock);
kernel_thread((int (*)(void *))cpqfcTSWorkerThread,
(void *) HostAdapter, 0);
/*
* Now wait for the kernel error thread to initialize itself
*/
down (&sem);
spin_lock_irq(HostAdapter->host_lock);
cpqfcHBAdata->notify_wt = NULL;
LEAVE("launch_FC_worker_thread");
}
/* "Entry" point to discover if any supported PCI
bus adapter can be found
*/
/* We're supporting:
* Compaq 64-bit, 66MHz HBA with Tachyon TS
* Agilent XL2
* HP Tachyon
*/
#define HBA_TYPES 3
#ifndef PCI_DEVICE_ID_COMPAQ_
#define PCI_DEVICE_ID_COMPAQ_TACHYON 0xa0fc
#endif
static struct SupportedPCIcards cpqfc_boards[] __initdata = {
{PCI_VENDOR_ID_COMPAQ, PCI_DEVICE_ID_COMPAQ_TACHYON},
{PCI_VENDOR_ID_HP, PCI_DEVICE_ID_HP_TACHLITE},
{PCI_VENDOR_ID_HP, PCI_DEVICE_ID_HP_TACHYON},
};
int cpqfcTS_detect(Scsi_Host_Template *ScsiHostTemplate)
{
int NumberOfAdapters=0; // how many of our PCI adapters are found?
struct pci_dev *PciDev = NULL;
struct Scsi_Host *HostAdapter = NULL;
CPQFCHBA *cpqfcHBAdata = NULL;
struct timer_list *cpqfcTStimer = NULL;
int i;
ENTER("cpqfcTS_detect");
#if LINUX_VERSION_CODE < LinuxVersionCode(2,3,27)
ScsiHostTemplate->proc_dir = &proc_scsi_cpqfcTS;
#else
ScsiHostTemplate->proc_name = "cpqfcTS";
#endif
for( i=0; i < HBA_TYPES; i++)
{
// look for all HBAs of each type
while((PciDev = pci_find_device(cpqfc_boards[i].vendor_id,
cpqfc_boards[i].device_id, PciDev)))
{
if (pci_enable_device(PciDev)) {
printk(KERN_ERR
"cpqfc: can't enable PCI device at %s\n", pci_name(PciDev));
goto err_continue;
}
if (pci_set_dma_mask(PciDev, CPQFCTS_DMA_MASK) != 0) {
printk(KERN_WARNING
"cpqfc: HBA cannot support required DMA mask, skipping.\n");
goto err_disable_dev;
}
// NOTE: (kernel 2.2.12-32) limits allocation to 128k bytes...
/* printk(" scsi_register allocating %d bytes for FC HBA\n",
(ULONG)sizeof(CPQFCHBA)); */
HostAdapter = scsi_register( ScsiHostTemplate, sizeof( CPQFCHBA ) );
if(HostAdapter == NULL) {
printk(KERN_WARNING
"cpqfc: can't register SCSI HBA, skipping.\n");
goto err_disable_dev;
}
DEBUG_PCI( printk(" HBA found!\n"));
DEBUG_PCI( printk(" HostAdapter->PciDev->irq = %u\n", PciDev->irq) );
DEBUG_PCI(printk(" PciDev->baseaddress[0]= %lx\n",
PciDev->resource[0].start));
DEBUG_PCI(printk(" PciDev->baseaddress[1]= %lx\n",
PciDev->resource[1].start));
DEBUG_PCI(printk(" PciDev->baseaddress[2]= %lx\n",
PciDev->resource[2].start));
DEBUG_PCI(printk(" PciDev->baseaddress[3]= %lx\n",
PciDev->resource[3].start));
HostAdapter->irq = PciDev->irq; // copy for Scsi layers
// HP Tachlite uses two (255-byte) ranges of Port I/O (lower & upper),
// for a total I/O port address space of 512 bytes.
// mask out the I/O port address (lower) & record
HostAdapter->io_port = (unsigned int)
PciDev->resource[1].start & PCI_BASE_ADDRESS_IO_MASK;
HostAdapter->n_io_port = 0xff;
// i.e., expect 128 targets (arbitrary number), while the
// RA-4000 supports 32 LUNs
HostAdapter->max_id = 0; // incremented as devices log in
HostAdapter->max_lun = CPQFCTS_MAX_LUN; // LUNs per FC device
HostAdapter->max_channel = CPQFCTS_MAX_CHANNEL; // multiple busses?
// get the pointer to our HBA specific data... (one for
// each HBA on the PCI bus(ses)).
cpqfcHBAdata = (CPQFCHBA *)HostAdapter->hostdata;
// make certain our data struct is clear
memset( cpqfcHBAdata, 0, sizeof( CPQFCHBA ) );
// initialize our HBA info
cpqfcHBAdata->HBAnum = NumberOfAdapters;
cpqfcHBAdata->HostAdapter = HostAdapter; // back ptr
Cpqfc_initHBAdata( cpqfcHBAdata, PciDev ); // fill MOST fields
cpqfcHBAdata->HBAnum = NumberOfAdapters;
spin_lock_init(&cpqfcHBAdata->hba_spinlock);
// request necessary resources and check for conflicts
if( request_irq( HostAdapter->irq,
cpqfcTS_intr_handler,
SA_INTERRUPT | SA_SHIRQ,
DEV_NAME,
HostAdapter) )
{
printk(KERN_WARNING "cpqfc: IRQ %u already used\n", HostAdapter->irq);
goto err_unregister;
}
// Since we have two 256-byte I/O port ranges (upper
// and lower), check them both
if( !request_region( cpqfcHBAdata->fcChip.Registers.IOBaseU,
0xff, DEV_NAME ) )
{
printk(KERN_WARNING "cpqfc: address in use: %x\n",
cpqfcHBAdata->fcChip.Registers.IOBaseU);
goto err_free_irq;
}
if( !request_region( cpqfcHBAdata->fcChip.Registers.IOBaseL,
0xff, DEV_NAME ) )
{
printk(KERN_WARNING "cpqfc: address in use: %x\n",
cpqfcHBAdata->fcChip.Registers.IOBaseL);
goto err_release_region_U;
}
// OK, we have grabbed everything we need now.
DEBUG_PCI(printk(" Reserved 255 I/O addresses @ %x\n",
cpqfcHBAdata->fcChip.Registers.IOBaseL ));
DEBUG_PCI(printk(" Reserved 255 I/O addresses @ %x\n",
cpqfcHBAdata->fcChip.Registers.IOBaseU ));
// start our kernel worker thread
spin_lock_irq(HostAdapter->host_lock);
launch_FCworker_thread(HostAdapter);
// start our TimerTask...
cpqfcTStimer = &cpqfcHBAdata->cpqfcTStimer;
init_timer( cpqfcTStimer); // Linux clears next/prev values
cpqfcTStimer->expires = jiffies + HZ; // one second
cpqfcTStimer->data = (unsigned long)cpqfcHBAdata; // this adapter
cpqfcTStimer->function = cpqfcTSheartbeat; // handles timeouts, housekeeping
add_timer( cpqfcTStimer); // give it to Linux
// now initialize our hardware...
if (cpqfcHBAdata->fcChip.InitializeTachyon( cpqfcHBAdata, 1,1)) {
printk(KERN_WARNING "cpqfc: initialization of HBA hardware failed.\n");
goto err_release_region_L;
}
cpqfcHBAdata->fcStatsTime = jiffies; // (for FC Statistics delta)
// give our HBA time to initialize and login current devices...
{
// The Brocade switch (e.g. 2400, 2010, etc.) as of March 2000,
// has the following algorithm for FL_Port startup:
// Time(sec) Action
// 0: Device Plugin and LIP(F7,F7) transmission
// 1.0 LIP incoming
// 1.027 LISA incoming, no CLS! (link not up)
// 1.028 NOS incoming (switch test for N_Port)
// 1.577 ED_TOV expired, transmit LIPs again
// 3.0 LIP(F8,F7) incoming (switch passes Tach Prim.Sig)
// 3.028 LILP received, link up, FLOGI starts
// slowest(worst) case, measured on 1Gb Finisar GT analyzer
unsigned long stop_time;
spin_unlock_irq(HostAdapter->host_lock);
stop_time = jiffies + 4*HZ;
while ( time_before(jiffies, stop_time) )
schedule(); // (our worker task needs to run)
}
spin_lock_irq(HostAdapter->host_lock);
NumberOfAdapters++;
spin_unlock_irq(HostAdapter->host_lock);
continue;
err_release_region_L:
release_region( cpqfcHBAdata->fcChip.Registers.IOBaseL, 0xff );
err_release_region_U:
release_region( cpqfcHBAdata->fcChip.Registers.IOBaseU, 0xff );
err_free_irq:
free_irq( HostAdapter->irq, HostAdapter);
err_unregister:
scsi_unregister( HostAdapter);
err_disable_dev:
pci_disable_device( PciDev );
err_continue:
continue;
} // end of while()
}
LEAVE("cpqfcTS_detect");
return NumberOfAdapters;
}
#ifdef SUPPORT_RESET
static void my_ioctl_done (Scsi_Cmnd * SCpnt)
{
struct request * req;
req = SCpnt->request;
req->rq_status = RQ_SCSI_DONE; /* Busy, but indicate request done */
if (req->CPQFC_WAITING != NULL)
CPQFC_COMPLETE(req->CPQFC_WAITING);
}
#endif
static int cpqfc_alloc_private_data_pool(CPQFCHBA *hba)
{
hba->private_data_bits = NULL;
hba->private_data_pool = NULL;
hba->private_data_bits =
kmalloc(((CPQFC_MAX_PASSTHRU_CMDS+BITS_PER_LONG-1) /
BITS_PER_LONG)*sizeof(unsigned long),
GFP_KERNEL);
if (hba->private_data_bits == NULL)
return -1;
memset(hba->private_data_bits, 0,
((CPQFC_MAX_PASSTHRU_CMDS+BITS_PER_LONG-1) /
BITS_PER_LONG)*sizeof(unsigned long));
hba->private_data_pool = kmalloc(sizeof(cpqfc_passthru_private_t) *
CPQFC_MAX_PASSTHRU_CMDS, GFP_KERNEL);
if (hba->private_data_pool == NULL) {
kfree(hba->private_data_bits);
hba->private_data_bits = NULL;
return -1;
}
return 0;
}
static void cpqfc_free_private_data_pool(CPQFCHBA *hba)
{
kfree(hba->private_data_bits);
kfree(hba->private_data_pool);
}
int is_private_data_of_cpqfc(CPQFCHBA *hba, void *pointer)
{
/* Is pointer within our private data pool?
We use Scsi_Request->upper_private_data (normally
reserved for upper layer drivers, e.g. the sg driver)
We check to see if the pointer is ours by looking at
its address. Is this ok? Hmm, it occurs to me that
a user app might do something bad by using sg to send
a cpqfc passthrough ioctl with upper_data_private
forged to be somewhere in our pool..., though they'd
normally have to be root already to do this. */
return (pointer != NULL &&
pointer >= (void *) hba->private_data_pool &&
pointer < (void *) hba->private_data_pool +
sizeof(*hba->private_data_pool) *
CPQFC_MAX_PASSTHRU_CMDS);
}
cpqfc_passthru_private_t *cpqfc_alloc_private_data(CPQFCHBA *hba)
{
int i;
do {
i = find_first_zero_bit(hba->private_data_bits,
CPQFC_MAX_PASSTHRU_CMDS);
if (i == CPQFC_MAX_PASSTHRU_CMDS)
return NULL;
} while ( test_and_set_bit(i & (BITS_PER_LONG - 1),
hba->private_data_bits+(i/BITS_PER_LONG)) != 0);
return &hba->private_data_pool[i];
}
void cpqfc_free_private_data(CPQFCHBA *hba, cpqfc_passthru_private_t *data)
{
int i;
i = data - hba->private_data_pool;
clear_bit(i&(BITS_PER_LONG-1),
hba->private_data_bits+(i/BITS_PER_LONG));
}
int cpqfcTS_ioctl( struct scsi_device *ScsiDev, int Cmnd, void *arg)
{
int result = 0;
struct Scsi_Host *HostAdapter = ScsiDev->host;
CPQFCHBA *cpqfcHBAdata = (CPQFCHBA *)HostAdapter->hostdata;
PTACHYON fcChip = &cpqfcHBAdata->fcChip;
PFC_LOGGEDIN_PORT pLoggedInPort = NULL;
struct scsi_cmnd *DumCmnd;
int i, j;
VENDOR_IOCTL_REQ ioc;
cpqfc_passthru_t *vendor_cmd;
Scsi_Device *SDpnt;
Scsi_Request *ScsiPassThruReq;
cpqfc_passthru_private_t *privatedata;
ENTER("cpqfcTS_ioctl ");
// printk("ioctl CMND %d", Cmnd);
switch (Cmnd) {
// Passthrough provides a mechanism to bypass the RAID
// or other controller and talk directly to the devices
// (e.g. physical disk drive)
// Passthrough commands, unfortunately, tend to be vendor
// specific; this is tailored to COMPAQ's RAID (RA4x00)
case CPQFCTS_SCSI_PASSTHRU:
{
void *buf = NULL; // for kernel space buffer for user data
/* Check that our pool got allocated ok. */
if (cpqfcHBAdata->private_data_pool == NULL)
return -ENOMEM;
if( !arg)
return -EINVAL;
// must be super user to send stuff directly to the
// controller and/or physical drives...
if( !capable(CAP_SYS_RAWIO) )
return -EPERM;
// copy the caller's struct to our space.
if( copy_from_user( &ioc, arg, sizeof( VENDOR_IOCTL_REQ)))
return( -EFAULT);
vendor_cmd = ioc.argp; // i.e., CPQ specific command struct
// If necessary, grab a kernel/DMA buffer
if( vendor_cmd->len)
{
buf = kmalloc( vendor_cmd->len, GFP_KERNEL);
if( !buf)
return -ENOMEM;
}
// Now build a Scsi_Request to pass down...
ScsiPassThruReq = scsi_allocate_request(ScsiDev, GFP_KERNEL);
if (ScsiPassThruReq == NULL) {
kfree(buf);
return -ENOMEM;
}
ScsiPassThruReq->upper_private_data =
cpqfc_alloc_private_data(cpqfcHBAdata);
if (ScsiPassThruReq->upper_private_data == NULL) {
kfree(buf);
scsi_release_request(ScsiPassThruReq); // "de-allocate"
return -ENOMEM;
}
if (vendor_cmd->rw_flag == VENDOR_WRITE_OPCODE) {
if (vendor_cmd->len) { // Need data from user?
if (copy_from_user(buf, vendor_cmd->bufp,
vendor_cmd->len)) {
kfree(buf);
cpqfc_free_private_data(cpqfcHBAdata,
ScsiPassThruReq->upper_private_data);
scsi_release_request(ScsiPassThruReq);
return( -EFAULT);
}
}
ScsiPassThruReq->sr_data_direction = DMA_TO_DEVICE;
} else if (vendor_cmd->rw_flag == VENDOR_READ_OPCODE) {
ScsiPassThruReq->sr_data_direction = DMA_FROM_DEVICE;
} else
// maybe this means a bug in the user app
ScsiPassThruReq->sr_data_direction = DMA_BIDIRECTIONAL;
ScsiPassThruReq->sr_cmd_len = 0; // set correctly by scsi_do_req()
ScsiPassThruReq->sr_sense_buffer[0] = 0;
ScsiPassThruReq->sr_sense_buffer[2] = 0;
// We copy the scheme used by sd.c:spinup_disk() to submit commands
// to our own HBA. We do this in order to stall the
// thread calling the IOCTL until it completes, and use
// the same "_quecommand" function for synchronizing
// FC Link events with our "worker thread".
privatedata = ScsiPassThruReq->upper_private_data;
privatedata->bus = vendor_cmd->bus;
privatedata->pdrive = vendor_cmd->pdrive;
// eventually gets us to our own _quecommand routine
scsi_wait_req(ScsiPassThruReq,
&vendor_cmd->cdb[0], buf, vendor_cmd->len,
10*HZ, // timeout
1); // retries
result = ScsiPassThruReq->sr_result;
// copy any sense data back to caller
if( result != 0 )
{
memcpy( vendor_cmd->sense_data, // see struct def - size=40
ScsiPassThruReq->sr_sense_buffer,
sizeof(ScsiPassThruReq->sr_sense_buffer) <
sizeof(vendor_cmd->sense_data) ?
sizeof(ScsiPassThruReq->sr_sense_buffer) :
sizeof(vendor_cmd->sense_data)
);
}
SDpnt = ScsiPassThruReq->sr_device;
/* upper_private_data is already freed in call_scsi_done() */
scsi_release_request(ScsiPassThruReq); // "de-allocate"
ScsiPassThruReq = NULL;
// need to pass data back to user (space)?
if( (vendor_cmd->rw_flag == VENDOR_READ_OPCODE) &&
vendor_cmd->len )
if( copy_to_user( vendor_cmd->bufp, buf, vendor_cmd->len))
result = -EFAULT;
kfree(buf);
return result;
}
case CPQFCTS_GETPCIINFO:
{
cpqfc_pci_info_struct pciinfo;
if( !arg)
return -EINVAL;
pciinfo.bus = cpqfcHBAdata->PciDev->bus->number;
pciinfo.dev_fn = cpqfcHBAdata->PciDev->devfn;
pciinfo.board_id = cpqfcHBAdata->PciDev->device |
(cpqfcHBAdata->PciDev->vendor <<16);
if(copy_to_user( arg, &pciinfo, sizeof(cpqfc_pci_info_struct)))
return( -EFAULT);
return 0;
}
case CPQFCTS_GETDRIVVER:
{
DriverVer_type DriverVer =
CPQFCTS_DRIVER_VER( VER_MAJOR,VER_MINOR,VER_SUBMINOR);
if( !arg)
return -EINVAL;
if(copy_to_user( arg, &DriverVer, sizeof(DriverVer)))
return( -EFAULT);
return 0;
}
case CPQFC_IOCTL_FC_TARGET_ADDRESS:
// can we find an FC device mapping to this SCSI target?
/* DumCmnd.channel = ScsiDev->channel; */ // For searching
/* DumCmnd.target = ScsiDev->id; */
/* DumCmnd.lun = ScsiDev->lun; */
DumCmnd = scsi_get_command (ScsiDev, GFP_KERNEL);
if (!DumCmnd)
return -ENOMEM;
pLoggedInPort = fcFindLoggedInPort( fcChip,
DumCmnd, // search Scsi Nexus
0, // DON'T search linked list for FC port id
NULL, // DON'T search linked list for FC WWN
NULL); // DON'T care about end of list
scsi_put_command (DumCmnd);
if (pLoggedInPort == NULL) {
result = -ENXIO;
break;
}
result = access_ok(VERIFY_WRITE, arg, sizeof(Scsi_FCTargAddress)) ? 0 : -EFAULT;
if (result) break;
put_user(pLoggedInPort->port_id,
&((Scsi_FCTargAddress *) arg)->host_port_id);
for( i=3,j=0; i>=0; i--) // copy the LOGIN port's WWN
put_user(pLoggedInPort->u.ucWWN[i],
&((Scsi_FCTargAddress *) arg)->host_wwn[j++]);
for( i=7; i>3; i--) // copy the LOGIN port's WWN
put_user(pLoggedInPort->u.ucWWN[i],
&((Scsi_FCTargAddress *) arg)->host_wwn[j++]);
break;
case CPQFC_IOCTL_FC_TDR:
result = cpqfcTS_TargetDeviceReset( ScsiDev, 0);
break;
default:
result = -EINVAL;
break;
}
LEAVE("cpqfcTS_ioctl");
return result;
}
/* "Release" the Host Bus Adapter...
disable interrupts, stop the HBA, release the interrupt,
and free all resources */
int cpqfcTS_release(struct Scsi_Host *HostAdapter)
{
CPQFCHBA *cpqfcHBAdata = (CPQFCHBA *)HostAdapter->hostdata;
ENTER("cpqfcTS_release");
DEBUG_PCI( printk(" cpqfcTS: delete timer...\n"));
del_timer( &cpqfcHBAdata->cpqfcTStimer);
// disable the hardware...
DEBUG_PCI( printk(" disable hardware, destroy queues, free mem\n"));
cpqfcHBAdata->fcChip.ResetTachyon( cpqfcHBAdata, CLEAR_FCPORTS);
// kill kernel thread
if( cpqfcHBAdata->worker_thread ) // (only if exists)
{
DECLARE_MUTEX_LOCKED(sem); // synchronize thread kill
cpqfcHBAdata->notify_wt = &sem;
DEBUG_PCI( printk(" killing kernel thread\n"));
send_sig( SIGKILL, cpqfcHBAdata->worker_thread, 1);
down( &sem);
cpqfcHBAdata->notify_wt = NULL;
}
cpqfc_free_private_data_pool(cpqfcHBAdata);
// free Linux resources
DEBUG_PCI( printk(" cpqfcTS: freeing resources...\n"));
free_irq( HostAdapter->irq, HostAdapter);
scsi_unregister( HostAdapter);
release_region( cpqfcHBAdata->fcChip.Registers.IOBaseL, 0xff);
release_region( cpqfcHBAdata->fcChip.Registers.IOBaseU, 0xff);
/* we get "vfree: bad address" executing this - need to investigate...
if( (void*)((unsigned long)cpqfcHBAdata->fcChip.Registers.MemBase) !=
cpqfcHBAdata->fcChip.Registers.ReMapMemBase)
vfree( cpqfcHBAdata->fcChip.Registers.ReMapMemBase);
*/
pci_disable_device( cpqfcHBAdata->PciDev);
LEAVE("cpqfcTS_release");
return 0;
}
const char * cpqfcTS_info(struct Scsi_Host *HostAdapter)
{
static char buf[300];
CPQFCHBA *cpqfcHBA;
int BusSpeed, BusWidth;
// get the pointer to our Scsi layer HBA buffer
cpqfcHBA = (CPQFCHBA *)HostAdapter->hostdata;
BusWidth = (cpqfcHBA->fcChip.Registers.PCIMCTR &0x4) > 0 ?
64 : 32;
if( cpqfcHBA->fcChip.Registers.TYconfig.value & 0x80000000)
BusSpeed = 66;
else
BusSpeed = 33;
sprintf(buf,
"%s: WWN %08X%08X\n on PCI bus %d device 0x%02x irq %d IObaseL 0x%x, MEMBASE 0x%x\nPCI bus width %d bits, bus speed %d MHz\nFCP-SCSI Driver v%d.%d.%d",
cpqfcHBA->fcChip.Name,
cpqfcHBA->fcChip.Registers.wwn_hi,
cpqfcHBA->fcChip.Registers.wwn_lo,
cpqfcHBA->PciDev->bus->number,
cpqfcHBA->PciDev->device,
HostAdapter->irq,
cpqfcHBA->fcChip.Registers.IOBaseL,
cpqfcHBA->fcChip.Registers.MemBase,
BusWidth,
BusSpeed,
VER_MAJOR, VER_MINOR, VER_SUBMINOR
);
cpqfcTSDecodeGBICtype( &cpqfcHBA->fcChip, &buf[ strlen(buf)]);
cpqfcTSGetLPSM( &cpqfcHBA->fcChip, &buf[ strlen(buf)]);
return buf;
}
//
// /proc/scsi support. The following routines allow us to do 'normal'
// sprintf like calls to return the currently requested piece (buflenght
// chars, starting at bufoffset) of the file. Although procfs allows for
// a 1 Kb bytes overflow after te supplied buffer, I consider it bad
// programming to use it to make programming a little simpler. This piece
// of coding is borrowed from ncr53c8xx.c with some modifications
//
struct info_str
{
char *buffer; // Pointer to output buffer
int buflength; // It's length
int bufoffset; // File offset corresponding with buf[0]
int buffillen; // Current filled length
int filpos; // Current file offset
};
static void copy_mem_info(struct info_str *info, char *data, int datalen)
{
if (info->filpos < info->bufoffset) { // Current offset before buffer offset
if (info->filpos + datalen <= info->bufoffset) {
info->filpos += datalen; // Discard if completely before buffer
return;
} else { // Partial copy, set to begin
data += (info->bufoffset - info->filpos);
datalen -= (info->bufoffset - info->filpos);
info->filpos = info->bufoffset;
}
}
info->filpos += datalen; // Update current offset
if (info->buffillen == info->buflength) // Buffer full, discard
return;
if (info->buflength - info->buffillen < datalen) // Overflows buffer ?
datalen = info->buflength - info->buffillen;
memcpy(info->buffer + info->buffillen, data, datalen);
info->buffillen += datalen;
}
static int copy_info(struct info_str *info, char *fmt, ...)
{
va_list args;
char buf[400];
int len;
va_start(args, fmt);
len = vsprintf(buf, fmt, args);
va_end(args);
copy_mem_info(info, buf, len);
return len;
}
// Routine to get data for /proc RAM filesystem
//
int cpqfcTS_proc_info (struct Scsi_Host *host, char *buffer, char **start, off_t offset, int length,
int inout)
{
struct scsi_cmnd *DumCmnd;
struct scsi_device *ScsiDev;
int Chan, Targ, i;
struct info_str info;
CPQFCHBA *cpqfcHBA;
PTACHYON fcChip;
PFC_LOGGEDIN_PORT pLoggedInPort;
char buf[81];
if (inout) return -EINVAL;
// get the pointer to our Scsi layer HBA buffer
cpqfcHBA = (CPQFCHBA *)host->hostdata;
fcChip = &cpqfcHBA->fcChip;
*start = buffer;
info.buffer = buffer;
info.buflength = length;
info.bufoffset = offset;
info.filpos = 0;
info.buffillen = 0;
copy_info(&info, "Driver version = %d.%d.%d", VER_MAJOR, VER_MINOR, VER_SUBMINOR);
cpqfcTSDecodeGBICtype( &cpqfcHBA->fcChip, &buf[0]);
cpqfcTSGetLPSM( &cpqfcHBA->fcChip, &buf[ strlen(buf)]);
copy_info(&info, "%s\n", buf);
#define DISPLAY_WWN_INFO
#ifdef DISPLAY_WWN_INFO
ScsiDev = scsi_get_host_dev (host);
if (!ScsiDev)
return -ENOMEM;
DumCmnd = scsi_get_command (ScsiDev, GFP_KERNEL);
if (!DumCmnd) {
scsi_free_host_dev (ScsiDev);
return -ENOMEM;
}
copy_info(&info, "WWN database: (\"port_id: 000000\" means disconnected)\n");
for ( Chan=0; Chan <= host->max_channel; Chan++) {
DumCmnd->device->channel = Chan;
for (Targ=0; Targ <= host->max_id; Targ++) {
DumCmnd->device->id = Targ;
if ((pLoggedInPort = fcFindLoggedInPort( fcChip,
DumCmnd, // search Scsi Nexus
0, // DON'T search list for FC port id
NULL, // DON'T search list for FC WWN
NULL))){ // DON'T care about end of list
copy_info(&info, "Host: scsi%d Channel: %02d TargetId: %02d -> WWN: ",
host->host_no, Chan, Targ);
for( i=3; i>=0; i--) // copy the LOGIN port's WWN
copy_info(&info, "%02X", pLoggedInPort->u.ucWWN[i]);
for( i=7; i>3; i--) // copy the LOGIN port's WWN
copy_info(&info, "%02X", pLoggedInPort->u.ucWWN[i]);
copy_info(&info, " port_id: %06X\n", pLoggedInPort->port_id);
}
}
}
scsi_put_command (DumCmnd);
scsi_free_host_dev (ScsiDev);
#endif
// Unfortunately, the proc_info buffer isn't big enough
// for everything we would like...
// For FC stats, compile this and turn off WWN stuff above
//#define DISPLAY_FC_STATS
#ifdef DISPLAY_FC_STATS
// get the Fibre Channel statistics
{
int DeltaSecs = (jiffies - cpqfcHBA->fcStatsTime) / HZ;
int days,hours,minutes,secs;
days = DeltaSecs / (3600*24); // days
hours = (DeltaSecs% (3600*24)) / 3600; // hours
minutes = (DeltaSecs%3600 /60); // minutes
secs = DeltaSecs%60; // secs
copy_info( &info, "Fibre Channel Stats (time dd:hh:mm:ss %02u:%02u:%02u:%02u\n",
days, hours, minutes, secs);
}
cpqfcHBA->fcStatsTime = jiffies; // (for next delta)
copy_info( &info, " LinkUp %9u LinkDown %u\n",
fcChip->fcStats.linkUp, fcChip->fcStats.linkDown);
copy_info( &info, " Loss of Signal %9u Loss of Sync %u\n",
fcChip->fcStats.LossofSignal, fcChip->fcStats.LossofSync);
copy_info( &info, " Discarded Frames %9u Bad CRC Frame %u\n",
fcChip->fcStats.Dis_Frm, fcChip->fcStats.Bad_CRC);
copy_info( &info, " TACH LinkFailTX %9u TACH LinkFailRX %u\n",
fcChip->fcStats.linkFailTX, fcChip->fcStats.linkFailRX);
copy_info( &info, " TACH RxEOFa %9u TACH Elastic Store %u\n",
fcChip->fcStats.Rx_EOFa, fcChip->fcStats.e_stores);
copy_info( &info, " BufferCreditWait %9uus TACH FM Inits %u\n",
fcChip->fcStats.BB0_Timer*10, fcChip->fcStats.FMinits );
copy_info( &info, " FC-2 Timeouts %9u FC-2 Logouts %u\n",
fcChip->fcStats.timeouts, fcChip->fcStats.logouts);
copy_info( &info, " FC-2 Aborts %9u FC-4 Aborts %u\n",
fcChip->fcStats.FC2aborted, fcChip->fcStats.FC4aborted);
// clear the counters
cpqfcTSClearLinkStatusCounters( fcChip);
#endif
return info.buffillen;
}
#if DEBUG_CMND
UCHAR *ScsiToAscii( UCHAR ScsiCommand)
{
/*++
Routine Description:
Converts a SCSI command to a text string for debugging purposes.
Arguments:
ScsiCommand -- hex value SCSI Command
Return Value:
An ASCII, null-terminated string if found, else returns NULL.
Original code from M. McGowen, Compaq
--*/
switch (ScsiCommand)
{
case 0x00:
return( "Test Unit Ready" );
case 0x01:
return( "Rezero Unit or Rewind" );
case 0x02:
return( "Request Block Address" );
case 0x03:
return( "Requese Sense" );
case 0x04:
return( "Format Unit" );
case 0x05:
return( "Read Block Limits" );
case 0x07:
return( "Reassign Blocks" );
case 0x08:
return( "Read (6)" );
case 0x0a:
return( "Write (6)" );
case 0x0b:
return( "Seek (6)" );
case 0x12:
return( "Inquiry" );
case 0x15:
return( "Mode Select (6)" );
case 0x16:
return( "Reserve" );
case 0x17:
return( "Release" );
case 0x1a:
return( "ModeSen(6)" );
case 0x1b:
return( "Start/Stop Unit" );
case 0x1c:
return( "Receive Diagnostic Results" );
case 0x1d:
return( "Send Diagnostic" );
case 0x25:
return( "Read Capacity" );
case 0x28:
return( "Read (10)" );
case 0x2a:
return( "Write (10)" );
case 0x2b:
return( "Seek (10)" );
case 0x2e:
return( "Write and Verify" );
case 0x2f:
return( "Verify" );
case 0x34:
return( "Pre-Fetch" );
case 0x35:
return( "Synchronize Cache" );
case 0x37:
return( "Read Defect Data (10)" );
case 0x3b:
return( "Write Buffer" );
case 0x3c:
return( "Read Buffer" );
case 0x3e:
return( "Read Long" );
case 0x3f:
return( "Write Long" );
case 0x41:
return( "Write Same" );
case 0x4c:
return( "Log Select" );
case 0x4d:
return( "Log Sense" );
case 0x56:
return( "Reserve (10)" );
case 0x57:
return( "Release (10)" );
case 0xa0:
return( "ReportLuns" );
case 0xb7:
return( "Read Defect Data (12)" );
case 0xca:
return( "Peripheral Device Addressing SCSI Passthrough" );
case 0xcb:
return( "Compaq Array Firmware Passthrough" );
default:
return( NULL );
}
} // end ScsiToAscii()
void cpqfcTS_print_scsi_cmd(Scsi_Cmnd * cmd)
{
printk("cpqfcTS: (%s) chnl 0x%02x, trgt = 0x%02x, lun = 0x%02x, cmd_len = 0x%02x\n",
ScsiToAscii( cmd->cmnd[0]), cmd->channel, cmd->target, cmd->lun, cmd->cmd_len);
if( cmd->cmnd[0] == 0) // Test Unit Ready?
{
int i;
printk("Cmnd->request_bufflen = 0x%X, ->use_sg = %d, ->bufflen = %d\n",
cmd->request_bufflen, cmd->use_sg, cmd->bufflen);
printk("Cmnd->request_buffer = %p, ->sglist_len = %d, ->buffer = %p\n",
cmd->request_buffer, cmd->sglist_len, cmd->buffer);
for (i = 0; i < cmd->cmd_len; i++)
printk("0x%02x ", cmd->cmnd[i]);
printk("\n");
}
}
#endif /* DEBUG_CMND */
static void QueCmndOnBoardLock( CPQFCHBA *cpqfcHBAdata, Scsi_Cmnd *Cmnd)
{
int i;
for( i=0; i< CPQFCTS_REQ_QUEUE_LEN; i++)
{ // find spare slot
if( cpqfcHBAdata->BoardLockCmnd[i] == NULL )
{
cpqfcHBAdata->BoardLockCmnd[i] = Cmnd;
// printk(" BoardLockCmnd[%d] %p Queued, chnl/target/lun %d/%d/%d\n",
// i,Cmnd, Cmnd->channel, Cmnd->target, Cmnd->lun);
break;
}
}
if( i >= CPQFCTS_REQ_QUEUE_LEN)
{
printk(" cpqfcTS WARNING: Lost Cmnd %p on BoardLock Q full!", Cmnd);
}
}
static void QueLinkDownCmnd( CPQFCHBA *cpqfcHBAdata, Scsi_Cmnd *Cmnd)
{
int indx;
// Remember the command ptr so we can return; we'll complete when
// the device comes back, causing immediate retry
for( indx=0; indx < CPQFCTS_REQ_QUEUE_LEN; indx++)//, SCptr++)
{
if( cpqfcHBAdata->LinkDnCmnd[indx] == NULL ) // available?
{
#ifdef DUMMYCMND_DBG
printk(" @add Cmnd %p to LnkDnCmnd[%d]@ ", Cmnd,indx);
#endif
cpqfcHBAdata->LinkDnCmnd[indx] = Cmnd;
break;
}
}
if( indx >= CPQFCTS_REQ_QUEUE_LEN ) // no space for Cmnd??
{
// this will result in an _abort call later (with possible trouble)
printk("no buffer for LinkDnCmnd!! %p\n", Cmnd);
}
}
// The file <scsi/scsi_host.h> says not to call scsi_done from
// inside _queuecommand, so we'll do it from the heartbeat timer
// (clarification: Turns out it's ok to call scsi_done from queuecommand
// for cases that don't go to the hardware like scsi cmds destined
// for LUNs we know don't exist, so this code might be simplified...)
static void QueBadTargetCmnd( CPQFCHBA *cpqfcHBAdata, Scsi_Cmnd *Cmnd)
{
int i;
// printk(" can't find target %d\n", Cmnd->target);
for( i=0; i< CPQFCTS_MAX_TARGET_ID; i++)
{ // find spare slot
if( cpqfcHBAdata->BadTargetCmnd[i] == NULL )
{
cpqfcHBAdata->BadTargetCmnd[i] = Cmnd;
// printk(" BadTargetCmnd[%d] %p Queued, chnl/target/lun %d/%d/%d\n",
// i,Cmnd, Cmnd->channel, Cmnd->target, Cmnd->lun);
break;
}
}
}
// This is the "main" entry point for Linux Scsi commands --
// it all starts here.
int cpqfcTS_queuecommand(Scsi_Cmnd *Cmnd, void (* done)(Scsi_Cmnd *))
{
struct Scsi_Host *HostAdapter = Cmnd->device->host;
CPQFCHBA *cpqfcHBAdata = (CPQFCHBA *)HostAdapter->hostdata;
PTACHYON fcChip = &cpqfcHBAdata->fcChip;
TachFCHDR_GCMND fchs; // only use for FC destination id field
PFC_LOGGEDIN_PORT pLoggedInPort;
ULONG ulStatus, SESTtype;
LONG ExchangeID;
ENTER("cpqfcTS_queuecommand");
PCI_TRACEO( (ULONG)Cmnd, 0x98)
Cmnd->scsi_done = done;
#ifdef DEBUG_CMND
cpqfcTS_print_scsi_cmd( Cmnd);
#endif
// prevent board contention with kernel thread...
if( cpqfcHBAdata->BoardLock )
{
// printk(" @BrdLck Hld@ ");
QueCmndOnBoardLock( cpqfcHBAdata, Cmnd);
}
else
{
// in the current system (2.2.12), this routine is called
// after spin_lock_irqsave(), so INTs are disabled. However,
// we might have something pending in the LinkQ, which
// might cause the WorkerTask to run. In case that
// happens, make sure we lock it out.
PCI_TRACE( 0x98)
CPQ_SPINLOCK_HBA( cpqfcHBAdata)
PCI_TRACE( 0x98)
// can we find an FC device mapping to this SCSI target?
pLoggedInPort = fcFindLoggedInPort( fcChip,
Cmnd, // search Scsi Nexus
0, // DON'T search linked list for FC port id
NULL, // DON'T search linked list for FC WWN
NULL); // DON'T care about end of list
if( pLoggedInPort == NULL ) // not found!
{
// printk(" @Q bad targ cmnd %p@ ", Cmnd);
QueBadTargetCmnd( cpqfcHBAdata, Cmnd);
}
else if (Cmnd->device->lun >= CPQFCTS_MAX_LUN)
{
printk(KERN_WARNING "cpqfc: Invalid LUN: %d\n", Cmnd->device->lun);
QueBadTargetCmnd( cpqfcHBAdata, Cmnd);
}
else // we know what FC device to send to...
{
// does this device support FCP target functions?
// (determined by PRLI field)
if( !(pLoggedInPort->fcp_info & TARGET_FUNCTION) )
{
printk(" Doesn't support TARGET functions port_id %Xh\n",
pLoggedInPort->port_id );
QueBadTargetCmnd( cpqfcHBAdata, Cmnd);
}
// In this case (previous login OK), the device is temporarily
// unavailable waiting for re-login, in which case we expect it
// to be back in between 25 - 500ms.
// If the FC port doesn't log back in within several seconds
// (i.e. implicit "logout"), or we get an explicit logout,
// we set "device_blocked" in Scsi_Device struct; in this
// case 30 seconds will elapse before Linux/Scsi sends another
// command to the device.
else if( pLoggedInPort->prli != TRUE )
{
// printk("Device (Chnl/Target %d/%d) invalid PRLI, port_id %06lXh\n",
// Cmnd->channel, Cmnd->target, pLoggedInPort->port_id);
QueLinkDownCmnd( cpqfcHBAdata, Cmnd);
// Need to use "blocked" flag??
// Cmnd->device->device_blocked = TRUE; // just let it timeout
}
else // device supports TARGET functions, and is logged in...
{
// (context of fchs is to "reply" to...)
fchs.s_id = pLoggedInPort->port_id; // destination FC address
// what is the data direction? For data TO the device,
// we need IWE (Intiator Write Entry). Otherwise, IRE.
if( Cmnd->cmnd[0] == WRITE_10 ||
Cmnd->cmnd[0] == WRITE_6 ||
Cmnd->cmnd[0] == WRITE_BUFFER ||
Cmnd->cmnd[0] == VENDOR_WRITE_OPCODE || // CPQ specific
Cmnd->cmnd[0] == MODE_SELECT )
{
SESTtype = SCSI_IWE; // data from HBA to Device
}
else
SESTtype = SCSI_IRE; // data from Device to HBA
ulStatus = cpqfcTSBuildExchange(
cpqfcHBAdata,
SESTtype, // e.g. Initiator Read Entry (IRE)
&fchs, // we are originator; only use d_id
Cmnd, // Linux SCSI command (with scatter/gather list)
&ExchangeID );// fcController->fcExchanges index, -1 if failed
if( !ulStatus ) // Exchange setup?
{
if( cpqfcHBAdata->BoardLock )
{
TriggerHBA( fcChip->Registers.ReMapMemBase, 0);
printk(" @bl! %d, xID %Xh@ ", current->pid, ExchangeID);
}
ulStatus = cpqfcTSStartExchange( cpqfcHBAdata, ExchangeID );
if( !ulStatus )
{
PCI_TRACEO( ExchangeID, 0xB8)
// submitted to Tach's Outbound Que (ERQ PI incremented)
// waited for completion for ELS type (Login frames issued
// synchronously)
}
else
// check reason for Exchange not being started - we might
// want to Queue and start later, or fail with error
{
printk("quecommand: cpqfcTSStartExchange failed: %Xh\n", ulStatus );
}
} // end good BuildExchange status
else // SEST table probably full -- why? hardware hang?
{
printk("quecommand: cpqfcTSBuildExchange faild: %Xh\n", ulStatus);
}
} // end can't do FCP-SCSI target functions
} // end can't find target (FC device)
CPQ_SPINUNLOCK_HBA( cpqfcHBAdata)
}
PCI_TRACEO( (ULONG)Cmnd, 0x9C)
LEAVE("cpqfcTS_queuecommand");
return 0;
}
// Entry point for upper Scsi layer intiated abort. Typically
// this is called if the command (for hard disk) fails to complete
// in 30 seconds. This driver intends to complete all disk commands
// within Exchange ".timeOut" seconds (now 7) with target status, or
// in case of ".timeOut" expiration, a DID_SOFT_ERROR which causes
// immediate retry.
// If any disk commands get the _abort call, except for the case that
// the physical device was removed or unavailable due to hardware
// errors, it should be considered a driver error and reported to
// the author.
int cpqfcTS_abort(Scsi_Cmnd *Cmnd)
{
// printk(" cpqfcTS_abort called?? \n");
return 0;
}
int cpqfcTS_eh_abort(Scsi_Cmnd *Cmnd)
{
struct Scsi_Host *HostAdapter = Cmnd->device->host;
// get the pointer to our Scsi layer HBA buffer
CPQFCHBA *cpqfcHBAdata = (CPQFCHBA *)HostAdapter->hostdata;
PTACHYON fcChip = &cpqfcHBAdata->fcChip;
FC_EXCHANGES *Exchanges = fcChip->Exchanges;
int i;
ENTER("cpqfcTS_eh_abort");
Cmnd->result = DID_ABORT <<16; // assume we'll find it
printk(" @Linux _abort Scsi_Cmnd %p ", Cmnd);
// See if we can find a Cmnd pointer that matches...
// The most likely case is we accepted the command
// from Linux Scsi (e.g. ceated a SEST entry) and it
// got lost somehow. If we can't find any reference
// to the passed pointer, we can only presume it
// got completed as far as our driver is concerned.
// If we found it, we will try to abort it through
// common mechanism. If FC ABTS is successful (ACC)
// or is rejected (RJT) by target, we will call
// Scsi "done" quickly. Otherwise, the ABTS will timeout
// and we'll call "done" later.
// Search the SEST exchanges for a matching Cmnd ptr.
for( i=0; i< TACH_SEST_LEN; i++)
{
if( Exchanges->fcExchange[i].Cmnd == Cmnd )
{
// found it!
printk(" x_ID %Xh, type %Xh\n", i, Exchanges->fcExchange[i].type);
Exchanges->fcExchange[i].status = INITIATOR_ABORT; // seconds default
Exchanges->fcExchange[i].timeOut = 10; // seconds default (changed later)
// Since we need to immediately return the aborted Cmnd to Scsi
// upper layers, we can't make future reference to any of its
// fields (e.g the Nexus).
cpqfcTSPutLinkQue( cpqfcHBAdata, BLS_ABTS, &i);
break;
}
}
if( i >= TACH_SEST_LEN ) // didn't find Cmnd ptr in chip's SEST?
{
// now search our non-SEST buffers (i.e. Cmnd waiting to
// start on the HBA or waiting to complete with error for retry).
// first check BadTargetCmnd
for( i=0; i< CPQFCTS_MAX_TARGET_ID; i++)
{
if( cpqfcHBAdata->BadTargetCmnd[i] == Cmnd )
{
cpqfcHBAdata->BadTargetCmnd[i] = NULL;
printk("in BadTargetCmnd Q\n");
goto Done; // exit
}
}
// if not found above...
for( i=0; i < CPQFCTS_REQ_QUEUE_LEN; i++)
{
if( cpqfcHBAdata->LinkDnCmnd[i] == Cmnd )
{
cpqfcHBAdata->LinkDnCmnd[i] = NULL;
printk("in LinkDnCmnd Q\n");
goto Done;
}
}
for( i=0; i< CPQFCTS_REQ_QUEUE_LEN; i++)
{ // find spare slot
if( cpqfcHBAdata->BoardLockCmnd[i] == Cmnd )
{
cpqfcHBAdata->BoardLockCmnd[i] = NULL;
printk("in BoardLockCmnd Q\n");
goto Done;
}
}
Cmnd->result = DID_ERROR <<16; // Hmmm...
printk("Not found! ");
// panic("_abort");
}
Done:
// panic("_abort");
LEAVE("cpqfcTS_eh_abort");
return 0; // (see scsi.h)
}
// FCP-SCSI Target Device Reset
// See dpANS Fibre Channel Protocol for SCSI
// X3.269-199X revision 12, pg 25
#ifdef SUPPORT_RESET
int cpqfcTS_TargetDeviceReset( Scsi_Device *ScsiDev,
unsigned int reset_flags)
{
int timeout = 10*HZ;
int retries = 1;
char scsi_cdb[12];
int result;
Scsi_Cmnd * SCpnt;
Scsi_Device * SDpnt;
// FIXME, cpqfcTS_TargetDeviceReset needs to be fixed
// similarly to how the passthrough ioctl was fixed
// around the 2.5.30 kernel. Scsi_Cmnd replaced with
// Scsi_Request, etc.
// For now, so people don't fall into a hole...
// printk(" ENTERING cpqfcTS_TargetDeviceReset() - flag=%d \n",reset_flags);
if (ScsiDev->host->eh_active) return FAILED;
memset( scsi_cdb, 0, sizeof( scsi_cdb));
scsi_cdb[0] = RELEASE;
SCpnt = scsi_get_command(ScsiDev, GFP_KERNEL);
{
CPQFC_DECLARE_COMPLETION(wait);
SCpnt->SCp.buffers_residual = FCP_TARGET_RESET;
// FIXME: this would panic, SCpnt->request would be NULL.
SCpnt->request->CPQFC_WAITING = &wait;
scsi_do_cmd(SCpnt, scsi_cdb, NULL, 0, my_ioctl_done, timeout, retries);
CPQFC_WAIT_FOR_COMPLETION(&wait);
SCpnt->request->CPQFC_WAITING = NULL;
}
if(driver_byte(SCpnt->result) != 0)
switch(SCpnt->sense_buffer[2] & 0xf) {
case ILLEGAL_REQUEST:
if(cmd[0] == ALLOW_MEDIUM_REMOVAL) dev->lockable = 0;
else printk("SCSI device (ioctl) reports ILLEGAL REQUEST.\n");
break;
case NOT_READY: // This happens if there is no disc in drive
if(dev->removable && (cmd[0] != TEST_UNIT_READY)){
printk(KERN_INFO "Device not ready. Make sure there is a disc in the drive.\n");
break;
}
case UNIT_ATTENTION:
if (dev->removable){
dev->changed = 1;
SCpnt->result = 0; // This is no longer considered an error
// gag this error, VFS will log it anyway /axboe
// printk(KERN_INFO "Disc change detected.\n");
break;
};
default: // Fall through for non-removable media
printk("SCSI error: host %d id %d lun %d return code = %x\n",
dev->host->host_no,
dev->id,
dev->lun,
SCpnt->result);
printk("\tSense class %x, sense error %x, extended sense %x\n",
sense_class(SCpnt->sense_buffer[0]),
sense_error(SCpnt->sense_buffer[0]),
SCpnt->sense_buffer[2] & 0xf);
};
result = SCpnt->result;
SDpnt = SCpnt->device;
scsi_put_command(SCpnt);
SCpnt = NULL;
// printk(" LEAVING cpqfcTS_TargetDeviceReset() - return SUCCESS \n");
return SUCCESS;
}
#else
int cpqfcTS_TargetDeviceReset( Scsi_Device *ScsiDev,
unsigned int reset_flags)
{
return -ENOTSUPP;
}
#endif /* SUPPORT_RESET */
int cpqfcTS_eh_device_reset(Scsi_Cmnd *Cmnd)
{
int retval;
Scsi_Device *SDpnt = Cmnd->device;
// printk(" ENTERING cpqfcTS_eh_device_reset() \n");
spin_unlock_irq(Cmnd->device->host->host_lock);
retval = cpqfcTS_TargetDeviceReset( SDpnt, 0);
spin_lock_irq(Cmnd->device->host->host_lock);
return retval;
}
int cpqfcTS_reset(Scsi_Cmnd *Cmnd, unsigned int reset_flags)
{
ENTER("cpqfcTS_reset");
LEAVE("cpqfcTS_reset");
return SCSI_RESET_ERROR; /* Bus Reset Not supported */
}
/* This function determines the bios parameters for a given
harddisk. These tend to be numbers that are made up by the
host adapter. Parameters:
size, device number, list (heads, sectors,cylinders).
(from hosts.h)
*/
int cpqfcTS_biosparam(struct scsi_device *sdev, struct block_device *n,
sector_t capacity, int ip[])
{
int size = capacity;
ENTER("cpqfcTS_biosparam");
ip[0] = 64;
ip[1] = 32;
ip[2] = size >> 11;
if( ip[2] > 1024 )
{
ip[0] = 255;
ip[1] = 63;
ip[2] = size / (ip[0] * ip[1]);
}
LEAVE("cpqfcTS_biosparam");
return 0;
}
irqreturn_t cpqfcTS_intr_handler( int irq,
void *dev_id,
struct pt_regs *regs)
{
unsigned long flags, InfLoopBrk=0;
struct Scsi_Host *HostAdapter = dev_id;
CPQFCHBA *cpqfcHBA = (CPQFCHBA *)HostAdapter->hostdata;
int MoreMessages = 1; // assume we have something to do
UCHAR IntPending;
int handled = 0;
ENTER("intr_handler");
spin_lock_irqsave( HostAdapter->host_lock, flags);
// is this our INT?
IntPending = readb( cpqfcHBA->fcChip.Registers.INTPEND.address);
// broken boards can generate messages forever, so
// prevent the infinite loop
#define INFINITE_IMQ_BREAK 10000
if( IntPending )
{
handled = 1;
// mask our HBA interrupts until we handle it...
writeb( 0, cpqfcHBA->fcChip.Registers.INTEN.address);
if( IntPending & 0x4) // "INT" - Tach wrote to IMQ
{
while( (++InfLoopBrk < INFINITE_IMQ_BREAK) && (MoreMessages ==1) )
{
MoreMessages = CpqTsProcessIMQEntry( HostAdapter); // ret 0 when done
}
if( InfLoopBrk >= INFINITE_IMQ_BREAK )
{
printk("WARNING: Compaq FC adapter generating excessive INTs -REPLACE\n");
printk("or investigate alternate causes (e.g. physical FC layer)\n");
}
else // working normally - re-enable INTs and continue
writeb( 0x1F, cpqfcHBA->fcChip.Registers.INTEN.address);
} // (...ProcessIMQEntry() clears INT by writing IMQ consumer)
else // indications of errors or problems...
// these usually indicate critical system hardware problems.
{
if( IntPending & 0x10 )
printk(" cpqfcTS adapter external memory parity error detected\n");
if( IntPending & 0x8 )
printk(" cpqfcTS adapter PCI master address crossed 45-bit boundary\n");
if( IntPending & 0x2 )
printk(" cpqfcTS adapter DMA error detected\n");
if( IntPending & 0x1 ) {
UCHAR IntStat;
printk(" cpqfcTS adapter PCI error detected\n");
IntStat = readb( cpqfcHBA->fcChip.Registers.INTSTAT.address);
printk("cpqfc: ISR = 0x%02x\n", IntStat);
if (IntStat & 0x1) {
__u16 pcistat;
/* read the pci status register */
pci_read_config_word(cpqfcHBA->PciDev, 0x06, &pcistat);
printk("PCI status register is 0x%04x\n", pcistat);
if (pcistat & 0x8000) printk("Parity Error Detected.\n");
if (pcistat & 0x4000) printk("Signalled System Error\n");
if (pcistat & 0x2000) printk("Received Master Abort\n");
if (pcistat & 0x1000) printk("Received Target Abort\n");
if (pcistat & 0x0800) printk("Signalled Target Abort\n");
}
if (IntStat & 0x4) printk("(INT)\n");
if (IntStat & 0x8)
printk("CRS: PCI master address crossed 46 bit bouandary\n");
if (IntStat & 0x10) printk("MRE: external memory parity error.\n");
}
}
}
spin_unlock_irqrestore( HostAdapter->host_lock, flags);
LEAVE("intr_handler");
return IRQ_RETVAL(handled);
}
int cpqfcTSDecodeGBICtype( PTACHYON fcChip, char cErrorString[])
{
// Verify GBIC type (if any) and correct Tachyon Port State Machine
// (GBIC) module definition is:
// GPIO1, GPIO0, GPIO4 for MD2, MD1, MD0. The input states appear
// to be inverted -- i.e., a setting of 111 is read when there is NO
// GBIC present. The Module Def (MD) spec says 000 is "no GBIC"
// Hard code the bit states to detect Copper,
// Long wave (single mode), Short wave (multi-mode), and absent GBIC
ULONG ulBuff;
sprintf( cErrorString, "\nGBIC detected: ");
ulBuff = fcChip->Registers.TYstatus.value & 0x13;
switch( ulBuff )
{
case 0x13: // GPIO4, GPIO1, GPIO0 = 111; no GBIC!
sprintf( &cErrorString[ strlen( cErrorString)],
"NONE! ");
return FALSE;
case 0x11: // Copper GBIC detected
sprintf( &cErrorString[ strlen( cErrorString)],
"Copper. ");
break;
case 0x10: // Long-wave (single mode) GBIC detected
sprintf( &cErrorString[ strlen( cErrorString)],
"Long-wave. ");
break;
case 0x1: // Short-wave (multi mode) GBIC detected
sprintf( &cErrorString[ strlen( cErrorString)],
"Short-wave. ");
break;
default: // unknown GBIC - presumably it will work (?)
sprintf( &cErrorString[ strlen( cErrorString)],
"Unknown. ");
break;
} // end switch GBIC detection
return TRUE;
}
int cpqfcTSGetLPSM( PTACHYON fcChip, char cErrorString[])
{
// Tachyon's Frame Manager LPSM in LinkDown state?
// (For non-loop port, check PSM instead.)
// return string with state and FALSE is Link Down
int LinkUp;
if( fcChip->Registers.FMstatus.value & 0x80 )
LinkUp = FALSE;
else
LinkUp = TRUE;
sprintf( &cErrorString[ strlen( cErrorString)],
" LPSM %Xh ",
(fcChip->Registers.FMstatus.value >>4) & 0xf );
switch( fcChip->Registers.FMstatus.value & 0xF0)
{
// bits set in LPSM
case 0x10:
sprintf( &cErrorString[ strlen( cErrorString)], "ARB");
break;
case 0x20:
sprintf( &cErrorString[ strlen( cErrorString)], "ARBwon");
break;
case 0x30:
sprintf( &cErrorString[ strlen( cErrorString)], "OPEN");
break;
case 0x40:
sprintf( &cErrorString[ strlen( cErrorString)], "OPENed");
break;
case 0x50:
sprintf( &cErrorString[ strlen( cErrorString)], "XmitCLS");
break;
case 0x60:
sprintf( &cErrorString[ strlen( cErrorString)], "RxCLS");
break;
case 0x70:
sprintf( &cErrorString[ strlen( cErrorString)], "Xfer");
break;
case 0x80:
sprintf( &cErrorString[ strlen( cErrorString)], "Init");
break;
case 0x90:
sprintf( &cErrorString[ strlen( cErrorString)], "O-IInitFin");
break;
case 0xa0:
sprintf( &cErrorString[ strlen( cErrorString)], "O-IProtocol");
break;
case 0xb0:
sprintf( &cErrorString[ strlen( cErrorString)], "O-ILipRcvd");
break;
case 0xc0:
sprintf( &cErrorString[ strlen( cErrorString)], "HostControl");
break;
case 0xd0:
sprintf( &cErrorString[ strlen( cErrorString)], "LoopFail");
break;
case 0xe0:
sprintf( &cErrorString[ strlen( cErrorString)], "Offline");
break;
case 0xf0:
sprintf( &cErrorString[ strlen( cErrorString)], "OldPort");
break;
case 0:
default:
sprintf( &cErrorString[ strlen( cErrorString)], "Monitor");
break;
}
return LinkUp;
}
#include "linux/slab.h"
// Dynamic memory allocation alignment routines
// HP's Tachyon Fibre Channel Controller chips require
// certain memory queues and register pointers to be aligned
// on various boundaries, usually the size of the Queue in question.
// Alignment might be on 2, 4, 8, ... or even 512 byte boundaries.
// Since most O/Ss don't allow this (usually only Cache aligned -
// 32-byte boundary), these routines provide generic alignment (after
// O/S allocation) at any boundary, and store the original allocated
// pointer for deletion (O/S free function). Typically, we expect
// these functions to only be called at HBA initialization and
// removal time (load and unload times)
// ALGORITHM notes:
// Memory allocation varies by compiler and platform. In the worst case,
// we are only assured BYTE alignment, but in the best case, we can
// request allocation on any desired boundary. Our strategy: pad the
// allocation request size (i.e. waste memory) so that we are assured
// of passing desired boundary near beginning of contiguous space, then
// mask out lower address bits.
// We define the following algorithm:
// allocBoundary - compiler/platform specific address alignment
// in number of bytes (default is single byte; i.e. 1)
// n_alloc - number of bytes application wants @ aligned address
// ab - alignment boundary, in bytes (e.g. 4, 32, ...)
// t_alloc - total allocation needed to ensure desired boundary
// mask - to clear least significant address bits for boundary
// Compute:
// t_alloc = n_alloc + (ab - allocBoundary)
// allocate t_alloc bytes @ alloc_address
// mask = NOT (ab - 1)
// (e.g. if ab=32 _0001 1111 -> _1110 0000
// aligned_address = alloc_address & mask
// set n_alloc bytes to 0
// return aligned_address (NULL if failed)
//
// If u32_AlignedAddress is non-zero, then search for BaseAddress (stored
// from previous allocation). If found, invoke call to FREE the memory.
// Return NULL if BaseAddress not found
// we need about 8 allocations per HBA. Figuring at most 10 HBAs per server
// size the dynamic_mem array at 80.
void* fcMemManager( struct pci_dev *pdev, ALIGNED_MEM *dynamic_mem,
ULONG n_alloc, ULONG ab, ULONG u32_AlignedAddress,
dma_addr_t *dma_handle)
{
USHORT allocBoundary=1; // compiler specific - worst case 1
// best case - replace malloc() call
// with function that allocates exactly
// at desired boundary
unsigned long ulAddress;
ULONG t_alloc, i;
void *alloc_address = 0; // def. error code / address not found
LONG mask; // must be 32-bits wide!
ENTER("fcMemManager");
if( u32_AlignedAddress ) // are we freeing existing memory?
{
// printk(" freeing AlignedAddress %Xh\n", u32_AlignedAddress);
for( i=0; i<DYNAMIC_ALLOCATIONS; i++) // look for the base address
{
// printk("dynamic_mem[%u].AlignedAddress %lX\n", i, dynamic_mem[i].AlignedAddress);
if( dynamic_mem[i].AlignedAddress == u32_AlignedAddress )
{
alloc_address = dynamic_mem[i].BaseAllocated; // 'success' status
pci_free_consistent(pdev,dynamic_mem[i].size,
alloc_address,
dynamic_mem[i].dma_handle);
dynamic_mem[i].BaseAllocated = 0; // clear for next use
dynamic_mem[i].AlignedAddress = 0;
dynamic_mem[i].size = 0;
break; // quit for loop; done
}
}
}
else if( n_alloc ) // want new memory?
{
dma_addr_t handle;
t_alloc = n_alloc + (ab - allocBoundary); // pad bytes for alignment
// printk("pci_alloc_consistent() for Tach alignment: %ld bytes\n", t_alloc);
// (would like to) allow thread block to free pages
alloc_address = // total bytes (NumberOfBytes)
pci_alloc_consistent(pdev, t_alloc, &handle);
// now mask off least sig. bits of address
if( alloc_address ) // (only if non-NULL)
{
// find place to store ptr, so we
// can free it later...
mask = (LONG)(ab - 1); // mask all low-order bits
mask = ~mask; // invert bits
for( i=0; i<DYNAMIC_ALLOCATIONS; i++) // look for free slot
{
if( dynamic_mem[i].BaseAllocated == 0) // take 1st available
{
dynamic_mem[i].BaseAllocated = alloc_address;// address from O/S
dynamic_mem[i].dma_handle = handle;
if (dma_handle != NULL)
{
// printk("handle = %p, ab=%d, boundary = %d, mask=0x%08x\n",
// handle, ab, allocBoundary, mask);
*dma_handle = (dma_addr_t)
((((ULONG)handle) + (ab - allocBoundary)) & mask);
}
dynamic_mem[i].size = t_alloc;
break;
}
}
ulAddress = (unsigned long)alloc_address;
ulAddress += (ab - allocBoundary); // add the alignment bytes-
// then truncate address...
alloc_address = (void*)(ulAddress & mask);
dynamic_mem[i].AlignedAddress =
(ULONG)(ulAddress & mask); // 32bit Tach address
memset( alloc_address, 0, n_alloc ); // clear new memory
}
else // O/S dynamic mem alloc failed!
alloc_address = 0; // (for debugging breakpt)
}
LEAVE("fcMemManager");
return alloc_address; // good (or NULL) address
}
static Scsi_Host_Template driver_template = {
.detect = cpqfcTS_detect,
.release = cpqfcTS_release,
.info = cpqfcTS_info,
.proc_info = cpqfcTS_proc_info,
.ioctl = cpqfcTS_ioctl,
.queuecommand = cpqfcTS_queuecommand,
.eh_device_reset_handler = cpqfcTS_eh_device_reset,
.eh_abort_handler = cpqfcTS_eh_abort,
.bios_param = cpqfcTS_biosparam,
.can_queue = CPQFCTS_REQ_QUEUE_LEN,
.this_id = -1,
.sg_tablesize = SG_ALL,
.cmd_per_lun = CPQFCTS_CMD_PER_LUN,
.use_clustering = ENABLE_CLUSTERING,
};
#include "scsi_module.c"
// for user apps, make sure data size types are defined
// with
#define CCPQFCTS_IOC_MAGIC 'Z'
typedef struct
{
__u8 bus;
__u8 dev_fn;
__u32 board_id;
} cpqfc_pci_info_struct;
typedef __u32 DriverVer_type;
/*
typedef union
{
struct // Peripheral Unit Device
{
__u8 Bus:6;
__u8 Mode:2; // b00
__u8 Dev;
} PeripDev;
struct // Volume Set Address
{
__u8 DevMSB:6;
__u8 Mode:2; // b01
__u8 DevLSB;
} LogDev;
struct // Logical Unit Device (SCSI-3, SCC-2 defined)
{
__u8 Targ:6;
__u8 Mode:2; // b10
__u8 Dev:5;
__u8 Bus:3;
} LogUnit;
} SCSI3Addr_struct;
typedef struct
{
SCSI3Addr_struct FCP_Nexus;
__u8 cdb[16];
} PassThru_Command_struct;
*/
/* this is nearly duplicated in idashare.h */
typedef struct {
int lc; /* Controller number */
int node; /* Node (box) number */
int ld; /* Logical Drive on this box, if required */
__u32 nexus; /* SCSI Nexus */
void *argp; /* Argument pointer */
} VENDOR_IOCTL_REQ;
typedef struct {
char cdb[16]; /* SCSI CDB for the pass-through */
ushort bus; /* Target bus on the box */
ushort pdrive; /* Physical drive on the box */
int len; /* Length of the data area of the CDB */
int sense_len; /* Length of the sense data */
char sense_data[40]; /* Sense data */
void *bufp; /* Data area for the CDB */
char rw_flag; /* Read CDB or Write CDB */
} cpqfc_passthru_t;
/*
** Defines for the IOCTLS.
*/
#define VENDOR_READ_OPCODE 0x26
#define VENDOR_WRITE_OPCODE 0x27
#define CPQFCTS_GETPCIINFO _IOR( CCPQFCTS_IOC_MAGIC, 1, cpqfc_pci_info_struct)
#define CPQFCTS_GETDRIVVER _IOR( CCPQFCTS_IOC_MAGIC, 9, DriverVer_type)
#define CPQFCTS_SCSI_PASSTHRU _IOWR( CCPQFCTS_IOC_MAGIC,11, VENDOR_IOCTL_REQ)
/* We would rather have equivalent generic, low-level driver agnostic
ioctls that do what CPQFC_IOCTL_FC_TARGET_ADDRESS and
CPQFC_IOCTL_FC_TDR 0x5388 do, but currently, we do not have them,
consequently applications would have to know they are talking to cpqfc. */
/* Used to get Fibre Channel WWN and port_id from device */
// #define CPQFC_IOCTL_FC_TARGET_ADDRESS 0x5387
#define CPQFC_IOCTL_FC_TARGET_ADDRESS \
_IOR( CCPQFCTS_IOC_MAGIC, 13, Scsi_FCTargAddress)
/* Used to invoke Target Defice Reset for Fibre Channel */
// #define CPQFC_IOCTL_FC_TDR 0x5388
#define CPQFC_IOCTL_FC_TDR _IO( CCPQFCTS_IOC_MAGIC, 15)
/* Copyright(c) 2000, Compaq Computer Corporation
* Fibre Channel Host Bus Adapter 64-bit, 66MHz PCI
* Originally developed and tested on:
* (front): [chip] Tachyon TS HPFC-5166A/1.2 L2C1090 ...
* SP# P225CXCBFIEL6T, Rev XC
* SP# 161290-001, Rev XD
* (back): Board No. 010008-001 A/W Rev X5, FAB REV X5
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
* Written by Don Zimmerman
*/
#ifndef CPQFCTSSTRUCTS_H
#define CPQFCTSSTRUCTS_H
#include <linux/timer.h> // timer declaration in our host data
#include <linux/interrupt.h>
#include <asm/atomic.h>
#include "cpqfcTSioctl.h"
#define DbgDelay(secs) { int wait_time; printk( " DbgDelay %ds ", secs); \
for( wait_time=jiffies + (secs*HZ); \
time_before(jiffies, wait_time) ;) ; }
#define CPQFCTS_DRIVER_VER(maj,min,submin) ((maj<<16)|(min<<8)|(submin))
// don't forget to also change MODULE_DESCRIPTION in cpqfcTSinit.c
#define VER_MAJOR 2
#define VER_MINOR 5
#define VER_SUBMINOR 4
// Macros for kernel (esp. SMP) tracing using a PCI analyzer
// (e.g. x86).
//#define PCI_KERNEL_TRACE
#ifdef PCI_KERNEL_TRACE
#define PCI_TRACE(x) inl( fcChip->Registers.IOBaseL +x);
#define PCI_TRACEO(x,y) outl( x, (fcChip->Registers.IOBaseL +y));
#else
#define PCI_TRACE(x)
#define PCI_TRACEO(x,y)
#endif
//#define DEBUG_CMND 1 // debug output for Linux Scsi CDBs
//#define DUMMYCMND_DBG 1
//#define DEBUG_CPQFCTS 1
//#undef DEBUG_CPQFCTS
#ifdef DEBUG_CPQFCTS
#define ENTER(x) printk("cpqfcts : entering %s()\n", x);
#define LEAVE(x) printk("cpqfcts : leaving %s()\n", x);
#define DEBUG(x) x
#else
#define ENTER(x)
#define LEAVE(x)
#define DEBUG(x)
#endif /* DEBUG_CPQFCTS */
//#define DEBUG_CPQFCTS_PCI 1
//#undef DEBUG_CPQFCTS_PCI
#if DEBUG_CPQFCTS_PCI
#define DEBUG_PCI(x) x
#else
#define DEBUG_PCI(x)
#endif /* DEBUG_CPQFCTS_PCI */
#define STACHLITE66_TS12 "Compaq FibreChannel HBA Tachyon TS HPFC-5166A/1.2"
#define STACHLITE66_TS13 "Compaq FibreChannel HBA Tachyon TS HPFC-5166A/1.3"
#define STACHLITE_UNKNOWN "Compaq FibreChannel HBA Tachyon Chip/Board Ver??"
#define SAGILENT_XL2_21 "Agilent FC HBA, Tachyon XL2 HPFC-5200B/2.1"
// PDA is Peripheral Device Address, VSA is Volume Set Addressing
// Linux SCSI parameters
#define CPQFCTS_MAX_TARGET_ID 64
// Note, changing CPQFCTS_MAX_LUN to less than 32 (e.g, 8) will result in
// strange behavior if a box with more than, e.g. 8, is on the loop.
#define CPQFCTS_MAX_LUN 32 // The RA-4x00 supports 32 (Linux SCSI supports 8)
#define CPQFCTS_MAX_CHANNEL 0 // One FC port on cpqfcTS HBA
#define CPQFCTS_CMD_PER_LUN 15 // power of 2 -1, must be >0
#define CPQFCTS_REQ_QUEUE_LEN (TACH_SEST_LEN/2) // must be < TACH_SEST_LEN
#define LinuxVersionCode(v, p, s) (((v)<<16)+((p)<<8)+(s))
#ifndef DECLARE_MUTEX_LOCKED
#define DECLARE_MUTEX_LOCKED(sem) struct semaphore sem = MUTEX_LOCKED
#endif
#define DEV_NAME "cpqfcTS"
struct SupportedPCIcards
{
__u16 vendor_id;
__u16 device_id;
};
// nn:nn denotes bit field
// TachyonHeader struct def.
// the fields shared with ODB
// need to have same value
#ifndef BYTE
//typedef UCHAR BYTE;
typedef __u8 BYTE;
#endif
#ifndef UCHAR
typedef __u8 UCHAR;
#endif
#ifndef LONG
typedef __s32 LONG;
#endif
#ifndef ULONG
typedef __u32 ULONG;
#endif
#ifndef PVOID
typedef void * PVOID;
#endif
#ifndef USHORT
typedef __u16 USHORT;
#endif
#ifndef BOOLEAN
typedef __u8 BOOLEAN;
#endif
// macro for FC-PH reject codes
// payload format for LS_RJT (FC payloads are big endian):
// byte 0 1 2 3 (MSB)
// DWORD 0 01 00 00 00
// DWORD 1 resvd code expl. vendor
#define LS_RJT_REASON( code, expl) (( code<<8) | (expl <<16))
#define TachLiteSTATUS 0x12
// Fibre Channel EXCHANGE status codes for Tachyon chips/ driver software
// 32-bit ERROR word defines
#define INVALID_ARGS 0x1
#define LNKDWN_OSLS 0x2
#define LNKDWN_LASER 0x4
#define OUTQUE_FULL 0x8
#define DRIVERQ_FULL 0x10
#define SEST_FULL 0x20
#define BAD_ALPA 0x40
#define OVERFLOW 0x80 // inbound CM
#define COUNT_ERROR 0x100 // inbound CM
#define LINKFAIL_RX 0x200 // inbound CM
#define ABORTSEQ_NOTIFY 0x400 // outbound CM
#define LINKFAIL_TX 0x800 // outbound CM
#define HOSTPROG_ERR 0x1000 // outbound CM
#define FRAME_TO 0x2000 // outbound CM
#define INV_ENTRY 0x4000 // outbound CM
#define SESTPROG_ERR 0x8000 // outbound CM
#define OUTBOUND_TIMEOUT 0x10000L // timeout waiting for Tachyon outbound CM
#define INITIATOR_ABORT 0x20000L // initiator exchange timeout or O/S ABORT
#define MEMPOOL_FAIL 0x40000L // O/S memory pool allocation failed
#define FC2_TIMEOUT 0x80000L // driver timeout for lost frames
#define TARGET_ABORT 0x100000L // ABTS received from FC port
#define EXCHANGE_QUEUED 0x200000L // e.g. Link State was LDn on fcStart
#define PORTID_CHANGED 0x400000L // fc Port address changed
#define DEVICE_REMOVED 0x800000L // fc Port address changed
// Several error scenarios result in SEST Exchange frames
// unexpectedly arriving in the SFQ
#define SFQ_FRAME 0x1000000L // SFQ frames from open Exchange
// Maximum number of Host Bus Adapters (HBA) / controllers supported
// only important for mem allocation dimensions - increase as necessary
#define MAX_ADAPTERS 8
#define MAX_RX_PAYLOAD 1024 // hardware dependent max frame payload
// Tach header struc defines
#define SOFi3 0x7
#define SOFf 0x8
#define SOFn3 0xB
#define EOFn 0x5
#define EOFt 0x6
// FCP R_CTL defines
#define FCP_CMND 0x6
#define FCP_XFER_RDY 0x5
#define FCP_RSP 0x7
#define FCP_RESPONSE 0x777 // (arbitrary #)
#define NEED_FCP_RSP 0x77 // (arbitrary #)
#define FCP_DATA 0x1
#define RESET_TACH 0x100 // Reset Tachyon/TachLite
#define SCSI_IWE 0x2000 // initiator write entry (for SEST)
#define SCSI_IRE 0x3000 // initiator read entry (for SEST)
#define SCSI_TRE 0x400 // target read entry (for SEST)
#define SCSI_TWE 0x500 // target write entry (for SEST)
#define TOGGLE_LASER 0x800
#define LIP 0x900
#define CLEAR_FCPORTS 99 // (arbitrary #) free mem for Logged in ports
#define FMINIT 0x707 // (arbitrary) for Frame Manager Init command
// BLS == Basic Link Service
// ELS == Extended Link Service
#define BLS_NOP 4
#define BLS_ABTS 0x10 // FC-PH Basic Link Service Abort Sequence
#define BLS_ABTS_ACC 0x100 // FC-PH Basic Link Service Abort Sequence Accept
#define BLS_ABTS_RJT 0x101 // FC-PH Basic Link Service Abort Sequence Reject
#define ELS_PLOGI 0x03 // FC-PH Port Login (arbitrary assign)
#define ELS_SCR 0x70 // (arb assign) State Change Registration (Fabric)
#define FCS_NSR 0x72 // (arb assign) Name Service Request (Fabric)
#define ELS_FLOGI 0x44 // (arb assign) Fabric Login
#define ELS_FDISC 0x41 // (arb assign) Fabric Discovery (Login)
#define ELS_PDISC 0x50 // FC-PH2 Port Discovery
#define ELS_ABTX 0x06 // FC-PH Abort Exchange
#define ELS_LOGO 0x05 // FC-PH Port Logout
#define ELS_PRLI 0x20 // FCP-SCSI Process Login
#define ELS_PRLO 0x21 // FCP-SCSI Process Logout
#define ELS_LOGO_ACC 0x07 // {FC-PH} Port Logout Accept
#define ELS_PLOGI_ACC 0x08 // {FC-PH} Port Login Accept
#define ELS_ACC 0x18 // {FC-PH} (generic) ACCept
#define ELS_PRLI_ACC 0x22 // {FCP-SCSI} Process Login Accept
#define ELS_RJT 0x1000000
#define SCSI_REPORT_LUNS 0x0A0
#define FCP_TARGET_RESET 0x200
#define ELS_LILP_FRAME 0x00000711 // 1st payload word of LILP frame
#define SFQ_UNASSISTED_FCP 1 // ICM, DWord3, "Type" unassisted FCP
#define SFQ_UNKNOWN 0x31 // (arbitrary) ICM, DWord3, "Type" unknown
// these "LINK" bits refer to loop or non-loop
#define LINKACTIVE 0x2 // fcLinkQ type - LINK UP Tachyon FM 'Lup' bit set
#define LINKDOWN 0xf2 // fcLinkQ type - LINK DOWN Tachyon FM 'Ldn' bit set
//#define VOLUME_SET_ADDRESSING 1 // "channel" or "bus" 1
typedef struct // 32 bytes hdr ONLY (e.g. FCP_DATA buffer for SEST)
{
ULONG reserved; // dword 0 (don't use)
ULONG sof_eof;
ULONG d_id; // dword 2 - 31:24 R_CTL, 23:0 D_ID
ULONG s_id; // dword 3 - 31:24 CS_CTL, 23:0 S_ID
ULONG f_ctl; // dword 4 - 31:24 Type, 23:0 F_CTL
ULONG seq_cnt; // dword 5 - 31:24 SEQ_ID, 23:16 DF_CTL, 15:0 SEQ_CNT
ULONG ox_rx_id; // dword 6 - 31:16 OX_ID, 15:0 RX_ID
ULONG ro; // dword 7 - relative offset
} TachFCHDR;
// NOTE!! the following struct MUST be 64 bytes.
typedef struct // 32 bytes hdr + 32 bytes payload
{
ULONG reserved; // dword 0 (don't use - must clear to 0)
ULONG sof_eof; // dword 1 - 31:24 SOF:EOF, UAM,CLS, LCr, TFV, TimeStamp
ULONG d_id; // dword 2 - 31:24 R_CTL, 23:0 D_ID
ULONG s_id; // dword 3 - 31:24 CS_CTL, 23:0 S_ID
ULONG f_ctl; // dword 4 - 31:24 Type, 23:0 F_CTL
ULONG seq_cnt; // dword 5 - 31:24 SEQ_ID, 23:16 DF_CTL, 15:0 SEQ_CNT
ULONG ox_rx_id; // dword 6 - 31:16 OX_ID, 15:0 RX_ID
ULONG ro; // dword 7 - relative offset
//---------
__u32 pl[8]; // dwords 8-15 frame data payload
} TachFCHDR_CMND;
typedef struct // 32 bytes hdr + 120 bytes payload
{
ULONG reserved; // dword 0 (don't use - must clear to 0)
ULONG sof_eof; // dword 1 - 31:24 SOF:EOF, UAM,CLS, LCr, TFV, TimeStamp
ULONG d_id; // dword 2 - 31:24 R_CTL, 23:0 D_ID
ULONG s_id; // dword 3 - 31:24 CS_CTL, 23:0 S_ID
ULONG f_ctl; // dword 4 - 31:24 Type, 23:0 F_CTL
ULONG seq_cnt; // dword 5 - 31:24 SEQ_ID, 23:16 DF_CTL, 15:0 SEQ_CNT
ULONG ox_rx_id; // dword 6 - 31:16 OX_ID, 15:0 RX_ID
ULONG ro; // dword 7 - relative offset
//---------
__u32 pl[30]; // largest necessary payload (for LOGIN cmnds)
} TachFCHDR_GCMND;
typedef struct // 32 bytes hdr + 64 bytes payload
{
ULONG reserved; // dword 0 (don't use)
ULONG sof_eof;
ULONG d_id; // dword 2 - 31:24 R_CTL, 23:0 D_ID
ULONG s_id; // dword 3 - 31:24 CS_CTL, 23:0 S_ID
ULONG f_ctl; // dword 4 - 31:24 Type, 23:0 F_CTL
ULONG seq_cnt; // dword 5 - 31:24 SEQ_ID, 23:16 DF_CTL, 15:0 SEQ_CNT
ULONG ox_rx_id; // dword 6 - 31:16 OX_ID, 15:0 RX_ID
ULONG ro; // dword 7 - relative offset
//---------
__u32 pl[18]; // payload for FCP-RSP (response buffer) RA-4x00 is 72bytes
} TachFCHDR_RSP;
// Inbound Message Queue structures...
typedef struct // each entry 8 words (32 bytes)
{
ULONG type; // IMQ completion message types
ULONG word[7]; // remainder of structure
// interpreted by IMQ type
} TachyonIMQE;
// Queues for TachLite not in original Tachyon
// ERQ - Exchange Request Queue (for outbound commands)
// SFQ - Single Frame Queue (for incoming frames)
// Define Tachyon Outbound Command Que
// (Since many Tachyon registers are Read
// only, maintain copies for debugging)
// most Tach ques need power-of-2 sizes,
// where registers are loaded with po2 -1
#define TACH_SEST_LEN 512 // TachLite SEST
#define ELS_EXCHANGES 64 // e.g. PLOGI, RSCN, ...
// define the total number of outstanding (simultaneous) exchanges
#define TACH_MAX_XID (TACH_SEST_LEN + ELS_EXCHANGES) // ELS exchanges
#define ERQ_LEN 128 // power of 2, max 4096
// Inbound Message Queue structures...
#define IMQ_LEN 512 // minimum 4 entries [(power of 2) - 1]
typedef struct // 8 words - 32 bytes
{
TachyonIMQE QEntry[IMQ_LEN];
ULONG producerIndex; // IMQ Producer Index register
// @32 byte align
ULONG consumerIndex; // Consumer Index register (in Tachyon)
ULONG length; // Length register
ULONG base;
} TachyonIMQ; // @ 32 * IMQ_LEN align
typedef struct // inbound completion message
{
ULONG Type;
ULONG Index;
ULONG TransferLength;
} TachyonInbCM;
// arbitrary numeric tags for TL structures
#define TL_FCHS 1 // TachLite Fibre Channel Header Structure
#define TL_IWE 2 // initiator write entry (for SEST)
#define TL_TWE 3 // target write entry (for SEST)
#define TL_IRE 4 // initiator read entry (for SEST)
#define TL_TRE 5 // target read entry (for SEST)
#define TL_IRB 6 // I/O request block
// for INCOMING frames
#define SFQ_LEN 32 // minimum 32 entries, max 4096
typedef struct // Single Frame Que
{
TachFCHDR_CMND QEntry[SFQ_LEN]; // must be 64 bytes!!
ULONG producerIndex; // IMQ Producer Index register
// @32 byte align
ULONG consumerIndex; // Consumer Index register (in Tachyon)
ULONG length; // Length register
ULONG base;
} TachLiteSFQ;
typedef struct // I/O Request Block flags
{
UCHAR BRD : 1;
UCHAR : 1; // reserved
UCHAR SFA : 1;
UCHAR DNC : 1;
UCHAR DIN : 1;
UCHAR DCM : 1;
UCHAR CTS : 1;
UCHAR SBV : 1; // IRB entry valid - IRB'B' only
} IRBflags;
typedef struct // I/O Request Block
{ // Request 'A'
ULONG Req_A_SFS_Len; // total frame len (hdr + payload), min 32
ULONG Req_A_SFS_Addr; // 32-bit pointer to FCHS struct (to be sent)
ULONG Req_A_SFS_D_ID; // 24-bit FC destination (i.e. 8 bit al_pa)
ULONG Req_A_Trans_ID; // X_ID (OX_ID or RX_ID) and/or Index in SEST
// Request 'B'
ULONG Req_B_SFS_Len; // total frame len (hdr + payload), min 32
ULONG Req_B_SFS_Addr; // 32-bit pointer to FCHS struct (to be sent)
ULONG Req_B_SFS_D_ID; // 24-bit FC destination (i.e. 8 bit al_pa)
ULONG Req_B_Trans_ID; // X_ID (OX_ID or RX_ID) and/or Index in SEST
} TachLiteIRB;
typedef struct // TachLite placeholder for IRBs
{ // aligned @sizeof(ERQ) for TachLite
// MAX commands is sum of SEST len and ERQ
// we know that each SEST entry requires an
// IRB (ERQ) entry; in addition, we provide
// ERQ_LEN
TachLiteIRB QEntry[ERQ_LEN]; // Base register; entries 32 bytes ea.
ULONG consumerIndex; // Consumer Index register
ULONG producerIndex; // ERQ Producer Index register
ULONG length; // Length register
ULONG base; // copy of base ptr for debug
// struct is sized for largest expected cmnd (LOGIN)
} TachLiteERQ;
// for now, just 32 bit DMA, eventually 40something, with code changes
#define CPQFCTS_DMA_MASK ((unsigned long) (0x00000000FFFFFFFF))
#define TL_MAX_SG_ELEM_LEN 0x7ffff // Max buffer length a single S/G entry
// may represent (a hardware limitation). The
// only reason to ever change this is if you
// want to exercise very-hard-to-reach code in
// cpqfcTSworker.c:build_SEST_sglist().
#define TL_DANGER_SGPAGES 7 // arbitrary high water mark for # of S/G pages
// we must exceed to elicit a warning indicative
// of EXTREMELY large data transfers or
// EXTREME memory fragmentation.
// (means we just used up 2048 S/G elements,
// Never seen this is real life, only in
// testing with tricked up driver.)
#define TL_EXT_SG_PAGE_COUNT 256 // Number of Extended Scatter/Gather a/l PAIRS
// Tachyon register (IOBaseU 0x68)
// power-of-2 value ONLY! 4 min, 256 max
// byte len is #Pairs * 2 ULONG/Pair * 4 bytes/ULONG
#define TL_EXT_SG_PAGE_BYTELEN (TL_EXT_SG_PAGE_COUNT *2 *4)
// SEST entry types: IWE, IRE, TWE, TRE
typedef struct
{
ULONG Hdr_Len;
ULONG Hdr_Addr;
ULONG RSP_Len;
ULONG RSP_Addr;
ULONG Buff_Off;
#define USES_EXTENDED_SGLIST(this_sest, x_ID) \
(!((this_sest)->u[ x_ID ].IWE.Buff_Off & 0x80000000))
ULONG Link;
ULONG RX_ID;
ULONG Data_Len;
ULONG Exp_RO;
ULONG Exp_Byte_Cnt;
// --- extended/local Gather Len/Address pairs
ULONG GLen1;
ULONG GAddr1;
ULONG GLen2;
ULONG GAddr2;
ULONG GLen3;
ULONG GAddr3;
} TachLiteIWE;
typedef struct
{
ULONG Seq_Accum;
ULONG reserved; // must clear to 0
ULONG RSP_Len;
ULONG RSP_Addr;
ULONG Buff_Off;
ULONG Buff_Index; // ULONG 5
ULONG Exp_RO;
ULONG Byte_Count;
ULONG reserved_; // ULONG 8
ULONG Exp_Byte_Cnt;
// --- extended/local Scatter Len/Address pairs
ULONG SLen1;
ULONG SAddr1;
ULONG SLen2;
ULONG SAddr2;
ULONG SLen3;
ULONG SAddr3;
} TachLiteIRE;
typedef struct // Target Write Entry
{
ULONG Seq_Accum; // dword 0
ULONG reserved; // dword 1 must clear to 0
ULONG Remote_Node_ID;
ULONG reserved1; // dword 3 must clear to 0
ULONG Buff_Off;
ULONG Buff_Index; // ULONG 5
ULONG Exp_RO;
ULONG Byte_Count;
ULONG reserved_; // ULONG 8
ULONG Exp_Byte_Cnt;
// --- extended/local Scatter Len/Address pairs
ULONG SLen1;
ULONG SAddr1;
ULONG SLen2;
ULONG SAddr2;
ULONG SLen3;
ULONG SAddr3;
} TachLiteTWE;
typedef struct
{
ULONG Hdr_Len;
ULONG Hdr_Addr;
ULONG RSP_Len; // DWord 2
ULONG RSP_Addr;
ULONG Buff_Off;
ULONG Buff_Index; // DWord 5
ULONG reserved;
ULONG Data_Len;
ULONG reserved_;
ULONG reserved__;
// --- extended/local Gather Len/Address pairs
ULONG GLen1; // DWord A
ULONG GAddr1;
ULONG GLen2;
ULONG GAddr2;
ULONG GLen3;
ULONG GAddr3;
} TachLiteTRE;
typedef struct ext_sg_page_ptr_t *PSGPAGES;
typedef struct ext_sg_page_ptr_t
{
unsigned char page[TL_EXT_SG_PAGE_BYTELEN * 2]; // 2x for alignment
dma_addr_t busaddr; // need the bus addresses and
unsigned int maplen; // lengths for later pci unmapping.
PSGPAGES next;
} SGPAGES; // linked list of S/G pairs, by Exchange
typedef struct // SCSI Exchange State Table
{
union // Entry can be IWE, IRE, TWE, TRE
{ // 64 bytes per entry
TachLiteIWE IWE;
TachLiteIRE IRE;
TachLiteTWE TWE;
TachLiteTRE TRE;
} u[TACH_SEST_LEN];
TachFCHDR DataHDR[TACH_SEST_LEN]; // for SEST FCP_DATA frame hdr (no pl)
TachFCHDR_RSP RspHDR[TACH_SEST_LEN]; // space for SEST FCP_RSP frame
PSGPAGES sgPages[TACH_SEST_LEN]; // head of linked list of Pool-allocations
ULONG length; // Length register
ULONG base; // copy of base ptr for debug
} TachSEST;
typedef struct // each register has it's own address
// and value (used for write-only regs)
{
void* address;
volatile ULONG value;
} FCREGISTER;
typedef struct // Host copy - TachLite Registers
{
ULONG IOBaseL, IOBaseU; // I/O port lower and upper TL register addresses
ULONG MemBase; // memory mapped register addresses
void* ReMapMemBase; // O/S VM reference for MemBase
ULONG wwn_hi; // WWN is set once at startup
ULONG wwn_lo;
ULONG my_al_pa; // al_pa received after LIP()
ULONG ROMCTR; // flags for on-board RAM/ROM
ULONG RAMBase; // on-board RAM (i.e. some Tachlites)
ULONG SROMBase; // on-board EEPROM (some Tachlites)
ULONG PCIMCTR; // PCI Master Control Reg (has bus width)
FCREGISTER INTEN; // copy of interrupt enable mask
FCREGISTER INTPEND; // interrupt pending
FCREGISTER INTSTAT; // interrupt status
FCREGISTER SFQconsumerIndex;
FCREGISTER ERQproducerIndex;
FCREGISTER TYconfig; // TachYon (chip level)
FCREGISTER TYcontrol;
FCREGISTER TYstatus;
FCREGISTER FMconfig; // Frame Manager (FC loop level)
FCREGISTER FMcontrol;
FCREGISTER FMstatus;
FCREGISTER FMLinkStatus1;
FCREGISTER FMLinkStatus2;
FCREGISTER FMBB_CreditZero;
FCREGISTER status;
FCREGISTER ed_tov; // error detect time-out value
FCREGISTER rcv_al_pa; // received arb. loop physical address
FCREGISTER primitive; // e.g. LIP(), OPN(), ...
} TL_REGISTERS;
typedef struct
{
ULONG ok;
ULONG invalidArgs;
ULONG linkDown;
ULONG linkUp;
ULONG outQueFull;
ULONG SESTFull;
ULONG hpe; // host programming err (from Tach)
ULONG FC4aborted; // aborts from Application or upper driver layer
ULONG FC2aborted; // aborts from our driver's timeouts
ULONG timeouts; // our driver timeout (on individual exchanges)
ULONG logouts; // explicit - sent LOGO; implicit - device removed
ULONG retries;
ULONG linkFailTX;
ULONG linkFailRX;
ULONG CntErrors; // byte count expected != count received (typ. SEST)
ULONG e_stores; // elastic store errs
ULONG resets; // hard or soft controller resets
ULONG FMinits; // TACH Frame Manager Init (e.g. LIPs)
ULONG lnkQueFull; // too many LOGIN, loop commands
ULONG ScsiQueFull; // too many FCP-SCSI inbound frames
ULONG LossofSignal; // FM link status 1 regs
ULONG BadRXChar; // FM link status 1 regs
ULONG LossofSync; // FM link status 1 regs
ULONG Rx_EOFa; // FM link status 2 regs (received EOFa)
ULONG Dis_Frm; // FM link status 2 regs (discarded frames)
ULONG Bad_CRC; // FM link status 2 regs
ULONG BB0_Timer; // FM BB_Credit Zero Timer Reg
ULONG loopBreaks; // infinite loop exits
ULONG lastBB0timer; // static accum. buffer needed by Tachlite
} FCSTATS;
typedef struct // Config Options
{ // LS Bit first
USHORT : 1; // bit0:
USHORT flogi : 1; // bit1: We sent FLOGI - wait for Fabric logins
USHORT fabric: 1; // bit2: Tachyon detected Fabric (FM stat LG)
USHORT LILPin: 1; // bit3: We can use an FC-AL LILP frame
USHORT target: 1; // bit4: this Port has SCSI target capability
USHORT initiator: 1; // bit5: this Port has SCSI initiator capability
USHORT extLoopback: 1; // bit6: loopback at GBIC
USHORT intLoopback: 1; // bit7: loopback in HP silicon
USHORT : 1; // bit8:
USHORT : 1; // bit9:
USHORT : 1; // bit10:
USHORT : 1; // bit11:
USHORT : 1; // bit12:
USHORT : 1; // bit13:
USHORT : 1; // bit14:
USHORT : 1; // bit15:
} FC_OPTIONS;
typedef struct dyn_mem_pair
{
void *BaseAllocated; // address as allocated from O/S;
unsigned long AlignedAddress; // aligned address (used by Tachyon DMA)
dma_addr_t dma_handle;
size_t size;
} ALIGNED_MEM;
// these structs contain only CRUCIAL (stuff we actually use) parameters
// from FC-PH(n) logins. (Don't save entire LOGIN payload to save mem.)
// Implicit logout happens when the loop goes down - we require PDISC
// to restore. Explicit logout is when WE decide never to talk to someone,
// or when a target refuses to talk to us, i.e. sends us a LOGO frame or
// LS_RJT reject in response to our PLOGI request.
#define IMPLICIT_LOGOUT 1
#define EXPLICIT_LOGOUT 2
typedef struct
{
UCHAR channel; // SCSI "bus"
UCHAR target;
UCHAR InqDeviceType; // byte 0 from SCSI Inquiry response
UCHAR VolumeSetAddressing; // FCP-SCSI LUN coding (40h for VSA)
UCHAR LunMasking; // True if selective presentation supported
UCHAR lun[CPQFCTS_MAX_LUN];
} SCSI_NEXUS;
typedef struct
{
union
{
UCHAR ucWWN[8]; // a FC 64-bit World Wide Name/ PortID of target
// addressing of single target on single loop...
u64 liWWN;
} u;
ULONG port_id; // a FC 24-bit address of port (lower 8 bits = al_pa)
#define REPORT_LUNS_PL 256
UCHAR ReportLunsPayload[REPORT_LUNS_PL];
SCSI_NEXUS ScsiNexus; // LUNs per FC device
ULONG LOGO_counter; // might try several times before logging out for good
ULONG LOGO_timer; // after LIP, ports expecting PDISC must time-out and
// LOGOut if successful PDISC not completed in 2 secs
ULONG concurrent_seq; // must be 1 or greater
ULONG rx_data_size; // e.g. 128, 256, 1024, 2048 per FC-PH spec
ULONG BB_credit;
ULONG EE_credit;
ULONG fcp_info; // from PRLI (i.e. INITIATOR/ TARGET flags)
// flags for login process
BOOLEAN Originator; // Login sequence Originated (if false, we
// responded to another port's login sequence)
BOOLEAN plogi; // PLOGI frame ACCepted (originated or responded)
BOOLEAN pdisc; // PDISC frame was ORIGINATED (self-login logic)
BOOLEAN prli; // PRLI frame ACCepted (originated or responded)
BOOLEAN flogi; // FLOGI frame ACCepted (originated or responded)
BOOLEAN logo; // port permanently logged out (invalid login param)
BOOLEAN flogiReq; // Fabric login required (set in LIP process)
UCHAR highest_ver;
UCHAR lowest_ver;
// when the "target" (actually FC Port) is waiting for login
// (e.g. after Link reset), set the device_blocked bit;
// after Port completes login, un-block target.
UCHAR device_blocked; // see Scsi_Device struct
// define singly-linked list of logged-in ports
// once a port_id is identified, it is remembered,
// even if the port is removed indefinitely
PVOID pNextPort; // actually, type PFC_LOGGEDIN_PORT; void for Compiler
} FC_LOGGEDIN_PORT, *PFC_LOGGEDIN_PORT;
// This serves as the ESB (Exchange Status Block),
// and has timeout counter; used for ABORTs
typedef struct
{ // FC-1 X_IDs
ULONG type; // ELS_PLOGI, SCSI_IWE, ... (0 if free)
PFC_LOGGEDIN_PORT pLoggedInPort; // FC device on other end of Exchange
Scsi_Cmnd *Cmnd; // Linux SCSI command packet includes S/G list
ULONG timeOut; // units of ??, DEC by driver, Abort when 0
ULONG reTries; // need one or more retries?
ULONG status; // flags indicating errors (0 if none)
TachLiteIRB IRB; // I/O Request Block, gets copied to ERQ
TachFCHDR_GCMND fchs; // location of IRB's Req_A_SFS_Addr
} FC_EXCHANGE, *PFC_EXCHANGE;
// Unfortunately, Linux limits our kmalloc() allocations to 128k.
// Because of this and the fact that our ScsiRegister allocation
// is also constrained, we move this large structure out for
// allocation after Scsi Register.
// (In other words, this cumbersome indirection is necessary
// because of kernel memory allocation constraints!)
typedef struct // we will allocate this dynamically
{
FC_EXCHANGE fcExchange[ TACH_MAX_XID ];
} FC_EXCHANGES;
typedef struct
{
char Name[64]; // name of controller ("HP Tachlite TL Rev2.0, 33MHz, 64bit bus")
//PVOID pAdapterDevExt; // back pointer to device object/extension
ULONG ChipType; // local numeric key for Tachyon Type / Rev.
ULONG status; // our Driver - logical status
TL_REGISTERS Registers; // reg addresses & host memory copies
// FC-4 mapping of 'transaction' to X_IDs
UCHAR LILPmap[32*4]; // Loop Position Map of ALPAs (late FC-AL only)
FC_OPTIONS Options; // e.g. Target, Initiator, loopback...
UCHAR highest_FCPH_ver; // FC-PH version limits
UCHAR lowest_FCPH_ver; // FC-PH version limits
FC_EXCHANGES *Exchanges;
ULONG fcLsExchangeLRU; // Least Recently Used counter (Link Service)
ULONG fcSestExchangeLRU; // Least Recently Used counter (FCP-SCSI)
FC_LOGGEDIN_PORT fcPorts; // linked list of every FC port ever seen
FCSTATS fcStats; // FC comm err counters
// Host memory QUEUE pointers
TachLiteERQ *ERQ; // Exchange Request Que
TachyonIMQ *IMQ; // Inbound Message Que
TachLiteSFQ *SFQ; // Single Frame Queue
TachSEST *SEST; // SCSI Exchange State Table
dma_addr_t exch_dma_handle;
// these function pointers are for "generic" functions, which are
// replaced with Host Bus Adapter types at
// runtime.
int (*CreateTachyonQues)( void* , int);
int (*DestroyTachyonQues)( void* , int);
int (*LaserControl)(void*, int ); // e.g. On/Off
int (*ResetTachyon)(void*, int );
void (*FreezeTachyon)(void*, int );
void (*UnFreezeTachyon)(void*, int );
int (*InitializeTachyon)(void*, int, int );
int (*InitializeFrameManager)(void*, int );
int (*ProcessIMQEntry)(void*);
int (*ReadWriteWWN)(void*, int ReadWrite);
int (*ReadWriteNVRAM)(void*, void*, int ReadWrite);
} TACHYON, *PTACHYON;
void cpqfcTSClearLinkStatusCounters(TACHYON * fcChip);
int CpqTsCreateTachLiteQues( void* pHBA, int opcode);
int CpqTsDestroyTachLiteQues( void* , int);
int CpqTsInitializeTachLite( void *pHBA, int opcode1, int opcode2);
int CpqTsProcessIMQEntry(void* pHBA);
int CpqTsResetTachLite(void *pHBA, int type);
void CpqTsFreezeTachlite(void *pHBA, int type);
void CpqTsUnFreezeTachlite(void *pHBA, int type);
int CpqTsInitializeFrameManager(void *pHBA, int);
int CpqTsLaserControl( void* addrBase, int opcode );
int CpqTsReadWriteWWN(void*, int ReadWrite);
int CpqTsReadWriteNVRAM(void*, void* data, int ReadWrite);
void cpqfcTS_WorkTask( struct Scsi_Host *HostAdapter);
void cpqfcTSWorkerThread( void *host);
int cpqfcTS_GetNVRAM_data( UCHAR *wwnbuf, UCHAR *buf );
ULONG cpqfcTS_ReadNVRAM( void* GPIOin, void* GPIOout , USHORT count,
UCHAR *buf );
BOOLEAN tl_write_i2c_nvram( void* GPIOin, void* GPIOout,
USHORT startOffset, // e.g. 0x2f for WWN start
USHORT count,
UCHAR *buf );
// define misc functions
int cpqfcTSGetLPSM( PTACHYON fcChip, char cErrorString[]);
int cpqfcTSDecodeGBICtype( PTACHYON fcChip, char cErrorString[]);
void* fcMemManager( struct pci_dev *pdev,
ALIGNED_MEM *dyn_mem_pair, ULONG n_alloc, ULONG ab,
ULONG ulAlignedAddress, dma_addr_t *dma_handle);
void BigEndianSwap( UCHAR *source, UCHAR *dest, USHORT cnt);
//ULONG virt_to_phys( PVOID virtaddr );
// Linux interrupt handler
irqreturn_t cpqfcTS_intr_handler( int irq,void *dev_id,struct pt_regs *regs);
void cpqfcTSheartbeat( unsigned long ptr );
// The biggest Q element we deal with is Aborts - we
// need 4 bytes for x_ID, and a Scsi_Cmnd (~284 bytes)
//#define LINKQ_ITEM_SIZE ((4+sizeof(Scsi_Cmnd)+3)/4)
#define LINKQ_ITEM_SIZE (3*16)
typedef struct
{
ULONG Type; // e.g. LINKUP, SFQENTRY, PDISC, BLS_ABTS, ...
ULONG ulBuff[ LINKQ_ITEM_SIZE ];
} LINKQ_ITEM;
#define FC_LINKQ_DEPTH TACH_MAX_XID
typedef struct
{
ULONG producer;
ULONG consumer; // when producer equals consumer, Q empty
LINKQ_ITEM Qitem[ FC_LINKQ_DEPTH ];
} FC_LINK_QUE, *PFC_LINK_QUE;
// DPC routines post to here on Inbound SCSI frames
// User thread processes
#define FC_SCSIQ_DEPTH 32
typedef struct
{
int Type; // e.g. SCSI
ULONG ulBuff[ 3*16 ];
} SCSIQ_ITEM;
typedef struct
{
ULONG producer;
ULONG consumer; // when producer equals consumer, Q empty
SCSIQ_ITEM Qitem[ FC_SCSIQ_DEPTH ];
} FC_SCSI_QUE, *PFC_SCSI_QUE;
typedef struct {
/* This is tacked on to a Scsi_Request in upper_private_data
for pasthrough ioctls, as a place to hold data that can't
be stashed anywhere else in the Scsi_Request. We differentiate
this from _real_ upper_private_data by checking if the virt addr
is within our special pool. */
ushort bus;
ushort pdrive;
} cpqfc_passthru_private_t;
#define CPQFC_MAX_PASSTHRU_CMDS 100
#define DYNAMIC_ALLOCATIONS 4 // Tachyon aligned allocations: ERQ,IMQ,SFQ,SEST
// Linux space allocated per HBA (chip state, etc.)
typedef struct
{
struct Scsi_Host *HostAdapter; // back pointer to Linux Scsi struct
TACHYON fcChip; // All Tachyon registers, Queues, functions
ALIGNED_MEM dynamic_mem[DYNAMIC_ALLOCATIONS];
struct pci_dev *PciDev;
dma_addr_t fcLQ_dma_handle;
Scsi_Cmnd *LinkDnCmnd[CPQFCTS_REQ_QUEUE_LEN]; // collects Cmnds during LDn
// (for Acceptable targets)
Scsi_Cmnd *BoardLockCmnd[CPQFCTS_REQ_QUEUE_LEN]; // SEST was full
Scsi_Cmnd *BadTargetCmnd[CPQFCTS_MAX_TARGET_ID]; // missing targets
u_char HBAnum; // 0-based host number
struct timer_list cpqfcTStimer; // FC utility timer for implicit
// logouts, FC protocol timeouts, etc.
int fcStatsTime; // Statistics delta reporting time
struct task_struct *worker_thread; // our kernel thread
int PortDiscDone; // set by SendLogins(), cleared by LDn
struct semaphore *TachFrozen;
struct semaphore *TYOBcomplete; // handshake for Tach outbound frames
struct semaphore *fcQueReady; // FibreChannel work for our kernel thread
struct semaphore *notify_wt; // synchronizes kernel thread kill
struct semaphore *BoardLock;
PFC_LINK_QUE fcLQ; // the WorkerThread operates on this
spinlock_t hba_spinlock; // held/released by WorkerThread
cpqfc_passthru_private_t *private_data_pool;
unsigned long *private_data_bits;
} CPQFCHBA;
#define CPQ_SPINLOCK_HBA( x ) spin_lock(&x->hba_spinlock);
#define CPQ_SPINUNLOCK_HBA(x) spin_unlock(&x->hba_spinlock);
void cpqfcTSImplicitLogout( CPQFCHBA* cpqfcHBAdata,
PFC_LOGGEDIN_PORT pFcPort);
void cpqfcTSTerminateExchange( CPQFCHBA*, SCSI_NEXUS *target, int );
PFC_LOGGEDIN_PORT fcPortLoggedIn(
CPQFCHBA *cpqfcHBAdata,
TachFCHDR_GCMND* fchs,
BOOLEAN,
BOOLEAN);
void fcProcessLoggedIn(
CPQFCHBA *cpqfcHBAdata, TachFCHDR_GCMND* fchs);
ULONG cpqfcTSBuildExchange(
CPQFCHBA *cpqfcHBAdata,
ULONG type, // e.g. PLOGI
TachFCHDR_GCMND* InFCHS, // incoming FCHS
void *Data, // the CDB, scatter/gather, etc.
LONG *ExchangeID ); // allocated exchange ID
ULONG cpqfcTSStartExchange(
CPQFCHBA *cpqfcHBAdata,
LONG ExchangeID );
void cpqfcTSCompleteExchange(
struct pci_dev *pcidev,
PTACHYON fcChip,
ULONG exchange_ID);
PFC_LOGGEDIN_PORT fcFindLoggedInPort(
PTACHYON fcChip,
Scsi_Cmnd *Cmnd, // (We want the channel/target/lun Nexus from Cmnd)
ULONG port_id, // search linked list for al_pa, or
UCHAR wwn[8], // search linked list for WWN, or...
PFC_LOGGEDIN_PORT *pLastLoggedInPort
);
void cpqfcTSPutLinkQue(
CPQFCHBA *cpqfcHBAdata,
int Type,
void *QueContent);
void fcPutScsiQue(
CPQFCHBA *cpqfcHBAdata,
int Type,
void *QueContent);
void fcLinkQReset(
CPQFCHBA *);
void fcScsiQReset(
CPQFCHBA *);
void fcSestReset(
CPQFCHBA *);
void cpqfc_pci_unmap(struct pci_dev *pcidev,
Scsi_Cmnd *cmd,
PTACHYON fcChip,
ULONG x_ID);
extern const UCHAR valid_al_pa[];
extern const int number_of_al_pa;
#define FCP_RESID_UNDER 0x80000
#define FCP_RESID_OVER 0x40000
#define FCP_SNS_LEN_VALID 0x20000
#define FCP_RSP_LEN_VALID 0x10000
// RSP_CODE definitions (dpANS Fibre Channel Protocol for SCSI, pg 34)
#define FCP_DATA_LEN_NOT_BURST_LEN 0x1000000
#define FCP_CMND_FIELD_INVALID 0x2000000
#define FCP_DATA_RO_NOT_XRDY_RO 0x3000000
#define FCP_TASKFUNCTION_NS 0x4000000
#define FCP_TASKFUNCTION_FAIL 0x5000000
// FCP-SCSI response status struct
typedef struct // see "TachFCHDR_RSP" definition - 64 bytes
{
__u32 reserved;
__u32 reserved1;
__u32 fcp_status; // field validity and SCSI status
__u32 fcp_resid;
__u32 fcp_sns_len; // length of FCP_SNS_INFO field
__u32 fcp_rsp_len; // length of FCP_RSP_INFO field (expect 8)
__u32 fcp_rsp_info; // 4 bytes of FCP protocol response information
__u32 fcp_rsp_info2; // (4 more bytes, since most implementations use 8)
__u8 fcp_sns_info[36]; // bytes for SCSI sense (ASC, ASCQ)
} FCP_STATUS_RESPONSE, *PFCP_STATUS_RESPONSE;
// Fabric State Change Registration
typedef struct scrpl
{
__u32 command;
__u32 function;
} SCR_PL;
// Fabric Name Service Request
typedef struct nsrpl
{
__u32 CT_Rev; // (& IN_ID) WORD 0
__u32 FCS_Type; // WORD 1
__u32 Command_code; // WORD 2
__u32 reason_code; // WORD 3
__u32 FCP; // WORD 4 (lower byte)
} NSR_PL;
// "FC.H"
#define MAX_RX_SIZE 0x800 // Max Receive Buffer Size is 2048
#define MIN_RX_SIZE 0x100 // Min Size is 256, per FC-PLDA Spec
#define MAX_TARGET_RXIDS SEST_DEPTH
#define TARGET_RX_SIZE SEST_BUFFER_LENGTH
#define CLASS_1 0x01
#define CLASS_2 0x02
#define CLASS_3 0x03
#define FC_PH42 0x08
#define FC_PH43 0x09
#define FC_PH3 0x20
#define RR_TOV 2 // Minimum Time for target to wait for
// PDISC after a LIP.
#define E_D_TOV 2 // Minimum Time to wait for Sequence
// Completion.
#define R_A_TOV 0 // Minimum Time for Target to wait
// before reclaiming resources.
//
// R_CTL Field
//
// Routing Bits (31-28)
//
#define FC4_DEVICE_DATA 0x00000000
#define EXT_LINK_DATA 0x20000000
#define FC4_LINK_DATA 0x30000000
#define VIDEO_DATA 0x40000000
#define BASIC_LINK_DATA 0x80000000
#define LINK_CONTROL 0xC0000000
#define ROUTING_MASK 0xF0000000
//
// Information Bits (27-24)
//
#define UNCAT_INFORMATION 0x00000000
#define SOLICITED_DATA 0x01000000
#define UNSOLICITED_CONTROL 0x02000000
#define SOLICITED_CONTROL 0x03000000
#define UNSOLICITED_DATA 0x04000000
#define DATA_DESCRIPTOR 0x05000000
#define UNSOLICITED_COMMAND 0x06000000
#define COMMAND_STATUS 0x07000000
#define INFO_MASK 0x0F000000
//
// (Link Control Codes)
//
#define ACK_1 0x00000000
#define ACK_0_OR_N 0x01000000
#define P_RJT 0x02000000
#define F_RJT 0x03000000
#define P_BSY 0x04000000
#define FABRIC_BUSY_TO_DF 0x05000000 // Fabric Busy to Data Frame
#define FABRIC_BUSY_TO_LC 0x06000000 // Fabric Busy to Link Ctl Frame
#define LINK_CREDIT_RESET 0x07000000
//
// (Link Service Command Codes)
//
//#define LS_RJT 0x01000000 // LS Reject
#define LS_ACC 0x02000000 // LS Accept
#define LS_PLOGI 0x03000000 // N_PORT Login
#define LS_FLOGI 0x04000000 // F_PORT Login
#define LS_LOGO 0x05000000 // Logout
#define LS_ABTX 0x06000000 // Abort Exchange
#define LS_RCS 0x07000000 // Read Connection Status
#define LS_RES 0x08000000 // Read Exchange Status
#define LS_RSS 0x09000000 // Read Sequence Status
#define LS_RSI 0x0A000000 // Request Seq Initiative
#define LS_ESTS 0x0B000000 // Establish Steaming
#define LS_ESTC 0x0C000000 // Estimate Credit
#define LS_ADVC 0x0D000000 // Advice Credit
#define LS_RTV 0x0E000000 // Read Timeout Value
#define LS_RLS 0x0F000000 // Read Link Status
#define LS_ECHO 0x10000000 // Echo
#define LS_TEST 0x11000000 // Test
#define LS_RRQ 0x12000000 // Reinstate Rec. Qual.
#define LS_PRLI 0x20000000 // Process Login
#define LS_PRLO 0x21000000 // Process Logout
#define LS_TPRLO 0x24000000 // 3rd Party Process Logout
#define LS_PDISC 0x50000000 // Process Discovery
#define LS_FDISC 0x51000000 // Fabric Discovery
#define LS_ADISC 0x52000000 // Discover Address
#define LS_RNC 0x53000000 // Report Node Capability
#define LS_SCR 0x62000000 // State Change Registration
#define LS_MASK 0xFF000000
//
// TYPE Bit Masks
//
#define BASIC_LINK_SERVICE 0x00000000
#define EXT_LINK_SERVICE 0x01000000
#define LLC 0x04000000
#define LLC_SNAP 0x05000000
#define SCSI_FCP 0x08000000
#define SCSI_GPP 0x09000000
#define IPI3_MASTER 0x11000000
#define IPI3_SLAVE 0x12000000
#define IPI3_PEER 0x13000000
#define CP_IPI3_MASTER 0x15000000
#define CP_IPI3_SLAVE 0x16000000
#define CP_IPI3_PEER 0x17000000
#define SBCCS_CHANNEL 0x19000000
#define SBCCS_CONTROL 0x1A000000
#define FIBRE_SERVICES 0x20000000
#define FC_FG 0x21000000
#define FC_XS 0x22000000
#define FC_AL 0x23000000
#define SNMP 0x24000000
#define HIPPI_FP 0x40000000
#define TYPE_MASK 0xFF000000
typedef struct {
UCHAR seq_id_valid;
UCHAR seq_id;
USHORT reserved; // 2 bytes reserved
ULONG ox_rx_id;
USHORT low_seq_cnt;
USHORT high_seq_cnt;
} BA_ACC_PAYLOAD;
typedef struct {
UCHAR reserved;
UCHAR reason_code;
UCHAR reason_explain;
UCHAR vendor_unique;
} BA_RJT_PAYLOAD;
typedef struct {
ULONG command_code;
ULONG sid;
USHORT ox_id;
USHORT rx_id;
} RRQ_MESSAGE;
typedef struct {
ULONG command_code;
UCHAR vendor;
UCHAR explain;
UCHAR reason;
UCHAR reserved;
} REJECT_MESSAGE;
#define N_OR_F_PORT 0x1000
#define RANDOM_RELATIVE_OFFSET 0x4000
#define CONTINUOSLY_INCREASING 0x8000
#define CLASS_VALID 0x8000
#define INTERMIX_MODE 0x4000
#define TRANSPARENT_STACKED 0x2000
#define LOCKDOWN_STACKED 0x1000
#define SEQ_DELIVERY 0x800
#define XID_NOT_SUPPORTED 0x00
#define XID_SUPPORTED 0x4000
#define XID_REQUIRED 0xC000
#define ASSOCIATOR_NOT_SUPPORTED 0x00
#define ASSOCIATOR_SUPPORTED 0x1000
#define ASSOCIATOR_REQUIRED 0x3000
#define INIT_ACK0_SUPPORT 0x800
#define INIT_ACKN_SUPPORT 0x400
#define RECIP_ACK0_SUPPORT 0x8000
#define RECIP_ACKN_SUPPORT 0x4000
#define X_ID_INTERLOCK 0x2000
#define ERROR_POLICY 0x1800 // Error Policy Supported
#define ERROR_DISCARD 0x00 // Only Discard Supported
#define ERROR_DISC_PROCESS 0x02 // Discard and process supported
#define NODE_ID 0x01
#define IEEE_EXT 0x20
//
// Categories Supported Per Sequence
//
#define CATEGORIES_PER_SEQUENCE 0x300
#define ONE_CATEGORY_SEQUENCE 0x00 // 1 Category per Sequence
#define TWO_CATEGORY_SEQUENCE 0x01 // 2 Categories per Sequence
#define MANY_CATEGORY_SEQUENCE 0x03 // > 2 Categories/Sequence
typedef struct {
USHORT initiator_control;
USHORT service_options;
USHORT rx_data_size;
USHORT recipient_control;
USHORT ee_credit;
USHORT concurrent_sequences;
USHORT reserved;
USHORT open_sequences;
} CLASS_PARAMETERS;
typedef struct {
ULONG login_cmd;
//
// Common Service Parameters
//
struct {
USHORT bb_credit;
UCHAR lowest_ver;
UCHAR highest_ver;
USHORT bb_rx_size;
USHORT common_features;
USHORT rel_offset;
USHORT concurrent_seq;
ULONG e_d_tov;
} cmn_services;
//
// Port Name
//
UCHAR port_name[8];
//
// Node/Fabric Name
//
UCHAR node_name[8];
//
// Class 1, 2 and 3 Service Parameters
//
CLASS_PARAMETERS class1;
CLASS_PARAMETERS class2;
CLASS_PARAMETERS class3;
ULONG reserved[4];
//
// Vendor Version Level
//
UCHAR vendor_id[2];
UCHAR vendor_version[6];
ULONG buffer_size;
USHORT rxid_start;
USHORT total_rxids;
} LOGIN_PAYLOAD;
typedef struct
{
ULONG cmd; // 4 bytes
UCHAR n_port_identifier[3];
UCHAR reserved;
UCHAR port_name[8];
} LOGOUT_PAYLOAD;
//
// PRLI Request Service Parameter Defines
//
#define PRLI_ACC 0x01
#define PRLI_REQ 0x02
#define ORIG_PROCESS_ASSOC_VALID 0x8000
#define RESP_PROCESS_ASSOC_VALID 0x4000
#define ESTABLISH_PAIR 0x2000
#define DATA_OVERLAY_ALLOWED 0x40
#define INITIATOR_FUNCTION 0x20
#define TARGET_FUNCTION 0x10
#define CMD_DATA_MIXED 0x08
#define DATA_RESP_MIXED 0x04
#define READ_XFER_RDY 0x02
#define WRITE_XFER_RDY 0x01
#define RESPONSE_CODE_MASK 0xF00
#define REQUEST_EXECUTED 0x100
#define NO_RESOURCES 0x200
#define INIT_NOT_COMPLETE 0x300
#define IMAGE_DOES_NOT_EXIST 0x400
#define BAD_PREDEFINED_COND 0x500
#define REQ_EXEC_COND 0x600
#define NO_MULTI_PAGE 0x700
typedef struct {
USHORT payload_length;
UCHAR page_length;
UCHAR cmd;
ULONG valid;
ULONG orig_process_associator;
ULONG resp_process_associator;
ULONG fcp_info;
} PRLI_REQUEST;
typedef struct {
USHORT payload_length;
UCHAR page_length;
UCHAR cmd;
ULONG valid;
ULONG orig_process_associator;
ULONG resp_process_associator;
ULONG reserved;
} PRLO_REQUEST;
typedef struct {
ULONG cmd;
ULONG hard_address;
UCHAR port_name[8];
UCHAR node_name[8];
ULONG s_id;
} ADISC_PAYLOAD;
struct ext_sg_entry_t {
__u32 len:18; /* buffer length, bits 0-17 */
__u32 uba:13; /* upper bus address bits 18-31 */
__u32 lba; /* lower bus address bits 0-31 */
};
// J. McCarty's LINK.H
//
// LS_RJT Reason Codes
//
#define INVALID_COMMAND_CODE 0x01
#define LOGICAL_ERROR 0x03
#define LOGICAL_BUSY 0x05
#define PROTOCOL_ERROR 0x07
#define UNABLE_TO_PERFORM 0x09
#define COMMAND_NOT_SUPPORTED 0x0B
#define LS_VENDOR_UNIQUE 0xFF
//
// LS_RJT Reason Codes Explanations
//
#define NO_REASON 0x00
#define OPTIONS_ERROR 0x01
#define INITIATOR_CTL_ERROR 0x03
#define RECIPIENT_CTL_ERROR 0x05
#define DATA_FIELD_SIZE_ERROR 0x07
#define CONCURRENT_SEQ_ERROR 0x09
#define CREDIT_ERROR 0x0B
#define INVALID_PORT_NAME 0x0D
#define INVALID_NODE_NAME 0x0E
#define INVALID_CSP 0x0F // Invalid Service Parameters
#define INVALID_ASSOC_HDR 0x11 // Invalid Association Header
#define ASSOC_HDR_REQUIRED 0x13 // Association Header Required
#define LS_INVALID_S_ID 0x15
#define INVALID_OX_RX_ID 0x17 // Invalid OX_ID RX_ID Combination
#define CMD_IN_PROCESS 0x19
#define INVALID_IDENTIFIER 0x1F // Invalid N_PORT Identifier
#define INVALID_SEQ_ID 0x21
#define ABT_INVALID_XCHNG 0x23 // Attempt to Abort an invalid Exchange
#define ABT_INACTIVE_XCHNG 0x25 // Attempt to Abort an inactive Exchange
#define NEED_REC_QUAL 0x27 // Recovery Qualifier required
#define NO_LOGIN_RESOURCES 0x29 // No resources to support login
#define NO_DATA 0x2A // Unable to supply requested data
#define REQUEST_NOT_SUPPORTED 0x2C // Request Not Supported
//
// Link Control Codes
//
//
// P_BSY Action Codes
//
#define SEQUENCE_TERMINATED 0x01000000
#define SEQUENCE_ACTIVE 0x02000000
//
// P_BSY Reason Codes
//
#define PHYS_NPORT_BUSY 0x010000
#define NPORT_RESOURCE_BUSY 0x020000
//
// P_RJT, F_RJT Action Codes
//
#define RETRYABLE_ERROR 0x01000000
#define NON_RETRYABLE_ERROR 0x02000000
//
// P_RJT, F_RJT Reason Codes
//
#define INVALID_D_ID 0x010000
#define INVALID_S_ID 0x020000
#define NPORT_NOT_AVAIL_TMP 0x030000
#define NPORT_NOT_AVAIL_PERM 0x040000
#define CLASS_NOT_SUPPORTED 0x050000
#define USAGE_ERROR 0x060000
#define TYPE_NOT_SUPPORTED 0x070000
#define INVAL_LINK_CONTROL 0x080000
#define INVAL_R_CTL 0x090000
#define INVAL_F_CTL 0x0A0000
#define INVAL_OX_ID 0x0B0000
#define INVAL_RX_ID 0x0C0000
#define INVAL_SEQ_ID 0x0D0000
#define INVAL_DF_CTL 0x0E0000
#define INVAL_SEQ_CNT 0x0F0000
#define INVAL_PARAMS 0x100000
#define EXCHANGE_ERROR 0x110000
#define LS_PROTOCOL_ERROR 0x120000
#define INCORRECT_LENGTH 0x130000
#define UNEXPECTED_ACK 0x140000
#define LOGIN_REQ 0x160000
#define EXCESSIVE_SEQ 0x170000
#define NO_EXCHANGE 0x180000
#define SEC_HDR_NOT_SUPPORTED 0x190000
#define NO_FABRIC 0x1A0000
#define P_VENDOR_UNIQUE 0xFF0000
//
// BA_RJT Reason Codes
//
#define BA_INVALID_COMMAND 0x00010000
#define BA_LOGICAL_ERROR 0x00030000
#define BA_LOGICAL_BUSY 0x00050000
#define BA_PROTOCOL_ERROR 0x00070000
#define BA_UNABLE_TO_PERFORM 0x00090000
//
// BA_RJT Reason Explanation Codes
//
#define BA_NO_REASON 0x00000000
#define BA_INVALID_OX_RX 0x00000300
#define BA_SEQUENCE_ABORTED 0x00000500
#endif /* CPQFCTSSTRUCTS_H */
// Routine to trigger Finisar GTA analyzer. Runs of GPIO2
// NOTE: DEBUG ONLY! Could interfere with FCMNGR/Miniport operation
// since it writes directly to the Tachyon board. This function
// developed for Compaq HBA Tachyon TS v1.2 (Rev X5 PCB)
#include "cpqfcTStrigger.h"
#if TRIGGERABLE_HBA
#include <linux/kernel.h>
#include <linux/ioport.h>
#include <linux/types.h>
#include <linux/pci.h>
#include <asm/io.h>
void TriggerHBA( void* IOBaseUpper, int Print)
{
__u32 long value;
// get initial value in hopes of not modifying any other GPIO line
IOBaseUpper += 0x188; // TachTL/TS Control reg
value = readl( IOBaseUpper);
// set HIGH to trigger external analyzer (tested on Dolche Finisar 1Gb GTA)
// The Finisar anaylzer triggers on low-to-high TTL transition
value |= 0x01; // set bit 0
writel( value, IOBaseUpper);
if( Print)
printk( " -GPIO0 set- ");
}
#endif
// don't do this unless you have the right hardware!
#define TRIGGERABLE_HBA 0
#if TRIGGERABLE_HBA
void TriggerHBA( void*, int);
#else
#define TriggerHBA(x, y)
#endif
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment