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


#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/slab.h>
#include <linux/vmalloc.h>	// ioremap()
#include <linux/completion.h>
#include <linux/init.h>
#ifdef __alpha__
#define __KERNEL_SYSCALLS__
#endif
#include <asm/unistd.h>
#include <asm/io.h>
#include <asm/uaccess.h>	// ioctl related
#include <asm/irq.h>
#include <linux/spinlock.h>
#include "sd.h"
#include <scsi/scsi_ioctl.h>
#include "hosts.h"
#include "cpqfcTSchip.h"
#include "cpqfcTSstructs.h"
#include "cpqfcTStrigger.h"

#include "cpqfcTS.h"

#include <linux/config.h>
#include <linux/module.h>
#include <linux/version.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.1.2");
MODULE_LICENSE("GPL");

int cpqfcTS_TargetDeviceReset(Scsi_Device * ScsiDev, unsigned int reset_flags);

#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);

/* 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 = PciDev->resource[5].start; // NULL for HP TS adapter

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

	/* must unlock before kernel_thread(), for it may cause a reschedule. */
	spin_unlock_irq(&io_request_lock);
	kernel_thread((int (*)(void *)) cpqfcTSWorkerThread, (void *) HostAdapter, 0);
	/*
	 * Now wait for the kernel error thread to initialize itself

	 */
	down(&sem);
	spin_lock_irq(&io_request_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

	if (pci_present() == 0)	// no PCI busses?
	{
		printk("  no PCI bus?@#!\n");
		return NumberOfAdapters;
	}

	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) != 0) {
				printk(KERN_WARNING "cpqfc: pci_enable_devive failed, skipping.\n");
				continue;
			}
			if (pci_set_dma_mask(PciDev, CPQFCTS_DMA_MASK) != 0) {
				printk(KERN_WARNING "cpqfc: HBA cannot support required DMA mask, skipping.\n");
				continue;
			}
			// NOTE: (kernel 2.2.12-32) limits allocation to 128k bytes...
			printk(" scsi_register allocating %d bytes for FC HBA\n", (u32) sizeof(CPQFCHBA));

			HostAdapter = scsi_register(ScsiHostTemplate, sizeof(CPQFCHBA));

			if (HostAdapter == NULL)
				continue;
			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_pci_device(HostAdapter, PciDev);
			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;
			cpqfcHBAdata->hba_spinlock = SPIN_LOCK_UNLOCKED;

			// 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...
			if (cpqfcHBAdata->fcChip.InitializeTachyon(cpqfcHBAdata, 1, 1)) {
				printk(KERN_WARNING "cpqfc: initialization of HBA hardware failed.\n");
				// FIXME: might want to do something better than nothing here.
			}

			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(&io_request_lock);
				stop_time = jiffies + 4 * HZ;
				while (time_before(jiffies, stop_time))
					schedule();	// (our worker task needs to run)

				spin_lock_irq(&io_request_lock);
			}

			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->CPQFC_WAITING != NULL)
		CPQFC_COMPLETE(req->CPQFC_WAITING);
}



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;

	ENTER("cpqfcTS_ioctl ");

	// 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;
	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.
				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_CMND to pass down...
				// This function allocates and sets Scsi_Cmnd ptrs such as
				//  ->channel, ->target, ->host
				ScsiPassThruCmnd = scsi_allocate_device(ScsiDev, 1, 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)
					if (copy_from_user(buf, vendor_cmd->bufp, vendor_cmd->len)) {
						kfree(buf);
						return (-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".

				{
					CPQFC_DECLARE_COMPLETION(wait);
					ScsiPassThruCmnd->request.CPQFC_WAITING = &wait;
					// 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
					// Other I/Os can now resume; we wait for our ioctl
					// command to complete
					CPQFC_WAIT_FOR_COMPLETION(&wait);
					ScsiPassThruCmnd->request.CPQFC_WAITING = 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->scpnt_wait);

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

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

u8 *ScsiToAscii(u8 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
// (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->host;
	CPQFCHBA *cpqfcHBAdata = (CPQFCHBA *) HostAdapter->hostdata;
	PTACHYON fcChip = &cpqfcHBAdata->fcChip;
	TachFCHDR_GCMND fchs;	// only use for FC destination id field  
	PFC_LOGGEDIN_PORT pLoggedInPort;
	u32 ulStatus, SESTtype;
	s32 ExchangeID;




	ENTER("cpqfcTS_queuecommand");

	PCI_TRACEO((u32) 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->lun >= CPQFCTS_MAX_LUN) {
			printk(KERN_WARNING "cpqfc: Invalid LUN: %d\n", Cmnd->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((u32) 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];
	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;

	// allocate with wait = true, interruptible = false 
	SCpnt = scsi_allocate_device(ScsiDev, 1, 0);
	{
		CPQFC_DECLARE_COMPLETION(wait);

		SCpnt->SCp.buffers_residual = FCP_TARGET_RESET;

		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_release_command(SCpnt);
	SCpnt = NULL;

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

	wake_up(&SDpnt->scpnt_wait);
	// printk("   LEAVING cpqfcTS_TargetDeviceReset() - return SUCCESS \n");
	return SUCCESS;
}


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(&io_request_lock);
	retval = cpqfcTS_TargetDeviceReset(SDpnt, 0);
	spin_lock_irq(&io_request_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(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
	u8 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) {
				u8 IntStat;
				printk(" cpqfcTS adapter PCI error detected\n");
				IntStat = readb(cpqfcHBA->fcChip.Registers.INTSTAT.address);
				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(&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

	u32 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;
}

// 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, u32 n_alloc, u32 ab, u32 u32_AlignedAddress, dma_addr_t * dma_handle)
{
	u16 allocBoundary = 1;	// compiler specific - worst case 1
	// best case - replace malloc() call
	// with function that allocates exactly
	// at desired boundary

	unsigned long ulAddress;
	u32 t_alloc, i;
	void *alloc_address = 0;	// def. error code / address not found
	s32 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 = (s32) (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)
						    ((((u32) 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 = (u32) (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 = CPQFCTS;

#include "scsi_module.c"
