2097 lines
64 KiB
C
2097 lines
64 KiB
C
/* 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"
|
|
|