/* 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/blk.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/sched.h>
#include <linux/types.h>
#include <linux/pci.h>
#include <linux/delay.h>
#include <linux/timer.h>
#include <linux/ioport.h>  // request_region() prototype
#include <linux/vmalloc.h> // ioremap()
#ifdef __alpha__
#define __KERNEL_SYSCALLS__
#endif
#include <asm/unistd.h>
#include <asm/io.h>
#include <asm/uaccess.h>   // ioctl related
#include <asm/irq.h>
#if LINUX_VERSION_CODE < LinuxVersionCode(2,3,18)
#include <asm/spinlock.h>
#else
#include <linux/spinlock.h>
#endif
#include "sd.h"
#include <scsi/scsi_ioctl.h>
#include "hosts.h"
#include "cpqfcTSchip.h"
#include "cpqfcTSstructs.h"

#include "cpqfcTS.h"

#include <linux/config.h>  
#include <linux/module.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");
  
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



/* 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->base_address[1] & PCI_BASE_ADDRESS_IO_MASK;
  
  cpqfcHBAdata->fcChip.Registers.IOBaseU = 
    PciDev->base_address[2] & PCI_BASE_ADDRESS_IO_MASK;
  
  // 32-bit memory addresses
  cpqfcHBAdata->fcChip.Registers.MemBase = 
    PciDev->base_address[3] & PCI_BASE_ADDRESS_MEM_MASK;

  cpqfcHBAdata->fcChip.Registers.ReMapMemBase = 
    ioremap( PciDev->base_address[3] & PCI_BASE_ADDRESS_MEM_MASK,
             0x200);
  
  cpqfcHBAdata->fcChip.Registers.RAMBase = 
    PciDev->base_address[4];
  
  cpqfcHBAdata->fcChip.Registers.SROMBase =  // NULL for HP TS adapter
    PciDev->base_address[5];
  
  // 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;

 

}


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

  kernel_thread((int (*)(void *))cpqfcTSWorkerThread, 
                          (void *) HostAdapter, 0);
  /*
   * Now wait for the kernel error thread to initialize itself

   */
  down (&sem);
  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 
#define HBA_TYPES 2


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;
  SupportedPCIcards PCIids[HBA_TYPES];
  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
  
  if( pci_present() == 0) // no PCI busses?
  {
    printk( "  no PCI bus?@#!\n");
    return NumberOfAdapters;
  }

  // what HBA adapters are we supporting?
  PCIids[0].vendor_id = PCI_VENDOR_ID_COMPAQ;
  PCIids[0].device_id = CPQ_DEVICE_ID;
  PCIids[1].vendor_id = PCI_VENDOR_ID_HP; // i.e. 103Ch (Agilent == HP for now)
  PCIids[1].device_id = AGILENT_XL2_ID;   // i.e. 1029h

  for( i=0; i < HBA_TYPES; i++)
  {
    // look for all HBAs of each type

    while( (PciDev =
      pci_find_device( PCIids[i].vendor_id, PCIids[i].device_id, PciDev) ))
    {
      // 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 ) );
      DEBUG_PCI( printk("  HBA found!\n"));
      DEBUG_PCI( printk("  HostAdapter->PciDev->irq = %u\n", PciDev->irq) );
      DEBUG_PCI(printk("  PciDev->baseaddress[]= %lx\n", PciDev->base_address[0]));
      DEBUG_PCI(printk("  PciDev->baseaddress[]= %lx\n", PciDev->base_address[1]));
      DEBUG_PCI(printk("  PciDev->baseaddress[]= %lx\n", PciDev->base_address[2]));
      DEBUG_PCI(printk("  PciDev->baseaddress[]= %lx\n", PciDev->base_address[3]));

      
      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->base_address[1] & 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;


      // request necessary resources and check for conflicts
      if( request_irq( HostAdapter->irq,
		       cpqfcTS_intr_handler,
	               SA_INTERRUPT | SA_SHIRQ,
	               DEV_NAME,
		       HostAdapter) )
      {
	printk(" IRQ %u already used\n", HostAdapter->irq);
        scsi_unregister( HostAdapter);
	continue;
      }

      // Since we have two 256-byte I/O port ranges (upper
      // and lower), check them both
      if( check_region( cpqfcHBAdata->fcChip.Registers.IOBaseU, 0xff) )
      {
	printk("  cpqfcTS address in use: %x\n", 
			cpqfcHBAdata->fcChip.Registers.IOBaseU);
	free_irq( HostAdapter->irq, HostAdapter);
        scsi_unregister( HostAdapter);
	continue;
      }	
      
      if( check_region( cpqfcHBAdata->fcChip.Registers.IOBaseL, 0xff) )
      {
  	printk("  cpqfcTS address in use: %x\n", 
	      			cpqfcHBAdata->fcChip.Registers.IOBaseL);
	free_irq( HostAdapter->irq, HostAdapter);
        scsi_unregister( HostAdapter);
	continue;
      }	
      
      // OK, we should be able to grab everything we need now.
      request_region( cpqfcHBAdata->fcChip.Registers.IOBaseL, 0xff, DEV_NAME);
      request_region( cpqfcHBAdata->fcChip.Registers.IOBaseU, 0xff, DEV_NAME);
      DEBUG_PCI(printk("  Requesting 255 I/O addresses @ %x\n",
        cpqfcHBAdata->fcChip.Registers.IOBaseL ));
      DEBUG_PCI(printk("  Requesting 255 I/O addresses @ %x\n",
        cpqfcHBAdata->fcChip.Registers.IOBaseU ));

      
      // start our kernel worker thread

      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...

      cpqfcHBAdata->fcChip.InitializeTachyon( cpqfcHBAdata, 1,1);

      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
	
	int wait_time;
	unsigned long flags=0;
 
        spin_unlock_irqrestore(&io_request_lock, flags);
        for( wait_time = jiffies + 4*HZ; wait_time > jiffies; )
	  schedule();  // (our worker task needs to run)
	spin_lock_irqsave(&io_request_lock, flags);
      }
      
      NumberOfAdapters++; 
    } // end of while()
  }

  LEAVE("cpqfcTS_detect");
 
  return NumberOfAdapters;
}


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->sem != NULL) {
	up(req->sem);
    }
}   



int cpqfcTS_ioctl( 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;
  Scsi_Cmnd DumCmnd;
  int i, j;
  VENDOR_IOCTL_REQ ioc;
  cpqfc_passthru_t *vendor_cmd;
  Scsi_Device *SDpnt;
  Scsi_Cmnd *ScsiPassThruCmnd;
  unsigned long flags;

  printk(" Enter cpqfcTS_ioctl ");
  
  // can we find an FC device mapping to this SCSI target?
  DumCmnd.channel = ScsiDev->channel;		// For searching
  DumCmnd.target  = ScsiDev->id;
  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
 
  if( pLoggedInPort == NULL )      // not found!
  {
    result = -ENXIO;
  }
 
  else  // we know what FC device to operate on...
  {
    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
	
	if( !arg)
	  return -EINVAL;

	// must be super user to send stuff directly to the
	// controller and/or physical drives...
	if( !suser() )
	  return -EPERM;

	// copy the caller's struct to our space.
        copy_from_user_ret( &ioc, arg, 
			  sizeof( VENDOR_IOCTL_REQ), -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_CMND to pass down...
	// This function allocates and sets Scsi_Cmnd ptrs such as
	//  ->channel, ->target, ->host
        ScsiPassThruCmnd = scsi_allocate_device(NULL, ScsiDev, 1);

        // Need data from user?
	// make sure caller's buffer is in kernel space.
	if( (vendor_cmd->rw_flag == VENDOR_WRITE_OPCODE) &&
	    vendor_cmd->len)
          copy_from_user_ret( buf, vendor_cmd->bufp, vendor_cmd->len, -EFAULT);
	    
	// copy the CDB (if/when MAX_COMMAND_SIZE is 16, remove copy below)
        memcpy( &ScsiPassThruCmnd->cmnd[0], 
		&vendor_cmd->cdb[0], 
		MAX_COMMAND_SIZE);  
        // we want to copy all 16 bytes into the FCP-SCSI CDB,
	// although the actual passthru only uses up to the
	// first 12.
	
	ScsiPassThruCmnd->cmd_len = 16; // sizeof FCP-SCSI CDB

	// Unfortunately, the SCSI command cmnd[] field has only
	// 12 bytes.  Ideally the MAX_COMMAND_SIZE should be increased
	// to 16 for newer Fibre Channel and SCSI-3 larger CDBs.
	// However, to avoid a mandatory kernel rebuild, we use the SCp
	// spare field to store the extra 4 bytes ( ugly :-(

	if( MAX_COMMAND_SIZE < 16)
	{
          memcpy( &ScsiPassThruCmnd->SCp.buffers_residual,
		  &vendor_cmd->cdb[12], 4);
	}	  
                  
	
        ScsiPassThruCmnd->SCp.sent_command = 1; // PASSTHRU!
	                                        // suppress LUN masking
	                                        // and VSA logic

	// Use spare fields to copy FCP-SCSI LUN address info...
        ScsiPassThruCmnd->SCp.phase = vendor_cmd->bus;
	ScsiPassThruCmnd->SCp.have_data_in = vendor_cmd->pdrive;



        // We copy the scheme used by scsi.c 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".
	
        spin_lock_irqsave(&io_request_lock, flags);
        {
          DECLARE_MUTEX_LOCKED(sem);
          ScsiPassThruCmnd->request.sem = &sem;
          // eventually gets us to our own _quecommand routine
          scsi_do_cmd( ScsiPassThruCmnd, &vendor_cmd->cdb[0], 
	       buf, 
	       vendor_cmd->len, 
	       my_ioctl_done, 
	       10*HZ, 1);// timeout,retries
          spin_unlock_irqrestore(&io_request_lock, flags);
          // Other I/Os can now resume; we wait for our ioctl
	  // command to complete
	  down(&sem);
          spin_lock_irqsave(&io_request_lock, flags);
          ScsiPassThruCmnd->request.sem = NULL;
        }
	
        result = ScsiPassThruCmnd->result;

        // copy any sense data back to caller
        if( result != 0 )
	{
	  memcpy( vendor_cmd->sense_data, // see struct def - size=40
		  ScsiPassThruCmnd->sense_buffer, 
		  sizeof(ScsiPassThruCmnd->sense_buffer)); 
	}
        SDpnt = ScsiPassThruCmnd->device;
        scsi_release_command(ScsiPassThruCmnd); // "de-allocate"
        ScsiPassThruCmnd = NULL;

        if (!SDpnt->was_reset && SDpnt->scsi_request_fn)
          (*SDpnt->scsi_request_fn)();

        wake_up(&SDpnt->device_wait);
        spin_unlock_irqrestore(&io_request_lock, flags);

	// need to pass data back to user (space)?
	if( (vendor_cmd->rw_flag == VENDOR_READ_OPCODE) &&
	     vendor_cmd->len )
          copy_to_user_ret( vendor_cmd->bufp, buf, vendor_cmd->len, -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); 
	      
        copy_to_user_ret( arg, &pciinfo, 
			  sizeof(cpqfc_pci_info_struct), -EFAULT);
        return 0;
      }

      case CPQFCTS_GETDRIVVER:
      {
	DriverVer_type DriverVer = 
		CPQFCTS_DRIVER_VER( VER_MAJOR,VER_MINOR,VER_SUBMINOR);
	
	if( !arg)
	  return -EINVAL;

        copy_to_user_ret( arg, &DriverVer, 
			  sizeof(DriverVer), -EFAULT);
        return 0;
      }



      case SCSI_IOCTL_FC_TARGET_ADDRESS:
      result = 
        verify_area(VERIFY_WRITE, arg, sizeof(Scsi_FCTargAddress));
      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 SCSI_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;
    
  }

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

  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 (char *buffer, char **start, off_t offset, int length, 
		       int hostno, int inout)
{
  struct Scsi_Host *host;
  Scsi_Cmnd DumCmnd;
  int Chan, Targ, i;
  struct info_str info;
  CPQFCHBA *cpqfcHBA;
  PTACHYON fcChip;
  PFC_LOGGEDIN_PORT pLoggedInPort;
  char buf[81];

  // Search the Scsi host list for our controller
  for (host=scsi_hostlist; host; host=host->next)
    if (host->host_no == hostno)
      break;

  if (!host) return -ESRCH;

  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
  copy_info(&info, "WWN database: (\"port_id: 000000\" means disconnected)\n");
  for ( Chan=0; Chan <= host->max_channel; Chan++) {
    DumCmnd.channel = Chan;
    for (Targ=0; Targ <= host->max_id; Targ++) {
      DumCmnd.target = 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: ",
			   hostno, 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); 
      }
    }
  }
#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 "hosts.h" says not to call scsi_done from
// inside _queuecommand, so we'll do it from the heartbeat timer

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->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  // 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->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 it's 
      // 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

int cpqfcTS_TargetDeviceReset( Scsi_Device *ScsiDev, 
                               unsigned int reset_flags)
{
  int timeout = 10*HZ;
  int retries = 1;
  char scsi_cdb[12];
  unsigned long flags;
  int result;
  Scsi_Cmnd * SCpnt;
  Scsi_Device * SDpnt;


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

  spin_lock_irqsave(&io_request_lock, flags);

  SCpnt = scsi_allocate_device(NULL, ScsiDev, 1);
  {
    struct semaphore sem = MUTEX_LOCKED;
        
    SCpnt->SCp.buffers_residual = FCP_TARGET_RESET;

	SCpnt->request.sem = &sem;
	scsi_do_cmd(SCpnt,  scsi_cdb, NULL,  0, my_ioctl_done,  timeout, retries);
	spin_unlock_irqrestore(&io_request_lock, flags);
	down(&sem);
    spin_lock_irqsave(&io_request_lock, flags);
    SCpnt->request.sem = 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_release_command(SCpnt);
  SCpnt = NULL;

  if (!SDpnt->was_reset && SDpnt->scsi_request_fn)
	(*SDpnt->scsi_request_fn)();

  wake_up(&SDpnt->device_wait);
  spin_unlock_irqrestore(&io_request_lock, flags);
  // printk("   LEAVING cpqfcTS_TargetDeviceReset() - return SUCCESS \n");
  return SUCCESS;
}


int cpqfcTS_eh_device_reset(Scsi_Cmnd *Cmnd)
{
  Scsi_Device *SDpnt = Cmnd->device;
  // printk("   ENTERING cpqfcTS_eh_device_reset() \n");
  return cpqfcTS_TargetDeviceReset( SDpnt, 0);
}

	
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(Disk *disk, kdev_t n, int ip[])
{
  int size = disk->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;
}    



void 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;
  
  ENTER("intr_handler");

  spin_lock_irqsave( &io_request_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 )
  {
    
    // 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 )
  	printk(" cpqfcTS adapter PCI error detected\n");
    }      
  }
  spin_unlock_irqrestore( &io_request_lock, flags);
  LEAVE("intr_handler");
}




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/malloc.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 allignment, 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( ALIGNED_MEM *dynamic_mem, ULONG n_alloc, ULONG ab,
                   ULONG u32_AlignedAddress)
{
  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
        kfree( dynamic_mem[i].BaseAllocated);  // return pages to kernel
        dynamic_mem[i].BaseAllocated = 0;   // clear for next use
        dynamic_mem[i].AlignedAddress = 0;
        break;                        // quit for loop; done
      }
    }
  }
  else if( n_alloc )                   // want new memory?
  {
    t_alloc = n_alloc + (ab - allocBoundary); // pad bytes for alignment
//    printk("kmalloc() for Tach alignment: %ld bytes\n", t_alloc);

    alloc_address =                  // total bytes (NumberOfBytes)
      kmalloc( t_alloc, GFP_KERNEL); // allow thread block to free pages 


                                  // 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...
      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
          break;
        }
      }
      mask = (LONG)(ab - 1);            // mask all low-order bits
      mask = ~mask;                            // invert bits

      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
}




#ifdef MODULE

static Scsi_Host_Template driver_template = CPQFCTS;

#include "scsi_module.c"


#endif


