diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/scsi/cpqfcTSinit.c |
Linux-2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/scsi/cpqfcTSinit.c')
-rw-r--r-- | drivers/scsi/cpqfcTSinit.c | 2098 |
1 files changed, 2098 insertions, 0 deletions
diff --git a/drivers/scsi/cpqfcTSinit.c b/drivers/scsi/cpqfcTSinit.c new file mode 100644 index 000000000000..2eeb493f5a2b --- /dev/null +++ b/drivers/scsi/cpqfcTSinit.c @@ -0,0 +1,2098 @@ +/* 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)); + + scsi_set_device(HostAdapter, &PciDev->dev); + 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 = SCSI_DATA_WRITE; + } else if (vendor_cmd->rw_flag == VENDOR_READ_OPCODE) { + ScsiPassThruReq->sr_data_direction = SCSI_DATA_READ; + } else + // maybe this means a bug in the user app + ScsiPassThruReq->sr_data_direction = SCSI_DATA_NONE; + + 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; + + if( buf) + 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 |