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 | |
download | linux-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.tar.bz2 |
Linux-2.6.12-rc2v2.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 %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" + |