/**********************************************************************
 * iph5526.c: IP/SCSI driver for the Interphase 5526 PCI Fibre Channel
 *			  Card.
 * Copyright (C) 1999 Vineet M Abraham <vma@iol.unh.edu>
 *
 * 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.
 *********************************************************************/
/**********************************************************************
Log:
Vineet M Abraham
02.12.99 Support multiple cards.
03.15.99 Added Fabric support.
04.04.99 Added N_Port support.
04.15.99 Added SCSI support.
06.18.99 Added ABTS Protocol.
06.24.99 Fixed data corruption when multiple XFER_RDYs are received.
07.07.99 Can be loaded as part of the Kernel. Changed semaphores. Added
         more checks before invalidating SEST entries.
07.08.99 Added Broadcast IP stuff and fixed an unicast timeout bug.
***********************************************************************/
/* TODO:
	R_T_TOV set to 15msec in Loop topology. Need to be 100 msec.
    SMP testing.
	Fix ADISC Tx before completing FLOGI. 
*/	

static const char *version =
    "iph5526.c:v1.0 07.08.99 Vineet Abraham (vma@iol.unh.edu)\n";

#include <linux/module.h>
#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/pci.h>
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/delay.h>
#include <linux/skbuff.h>
#include <linux/if_arp.h>
#include <linux/timer.h>
#include <asm/system.h>
#include <asm/io.h>
#include <asm/spinlock.h>

#include <linux/netdevice.h>
#include <linux/fcdevice.h> /* had the declarations for init_fcdev among others + includes if_fcdevice.h */

#include <linux/blk.h>
#include "../../scsi/sd.h"
#include "../../scsi/scsi.h"
#include "../../scsi/hosts.h"
#include "../../fc4/fcp.h"

/* driver specific header files */
#include "tach.h"
#include "tach_structs.h"
#include "iph5526_ip.h"
#include "iph5526_scsi.h"
#include "iph5526_novram.c"

#define RUN_AT(x) (jiffies + (x))

#define DEBUG_5526_0 0
#define DEBUG_5526_1 0
#define DEBUG_5526_2 0

#if DEBUG_5526_0
#define DPRINTK(format, a...) {printk("%s: ", fi->name); \
							   printk(format, ##a); \
							   printk("\n");}
#define ENTER(x)	{printk("%s: ", fi->name); \
					 printk("iph5526.c : entering %s()\n", x);}
#define LEAVE(x)	{printk("%s: ", fi->name); \
					 printk("iph5526.c : leaving %s()\n",x);}

#else
#define DPRINTK(format, a...) {}
#define ENTER(x)	{}
#define LEAVE(x)	{}
#endif

#if DEBUG_5526_1
#define DPRINTK1(format, a...) {printk("%s: ", fi->name); \
							   printk(format, ##a); \
							   printk("\n");}
#else
#define DPRINTK1(format, a...) {}
#endif

#if DEBUG_5526_2
#define DPRINTK2(format, a...) {printk("%s: ", fi->name); \
							   printk(format, ##a); \
							   printk("\n");}
#else
#define DPRINTK2(format, a...) {}
#endif

#define T_MSG(format, a...) {printk("%s: ", fi->name); \
							 printk(format, ##a);\
							 printk("\n");}

#define ALIGNED_SFS_ADDR(addr) ((((unsigned long)(addr) + (SFS_BUFFER_SIZE - 1)) & ~(SFS_BUFFER_SIZE - 1)) - (unsigned long)(addr))
#define ALIGNED_ADDR(addr, len) ((((unsigned long)(addr) + (len - 1)) & ~(len - 1)) - (unsigned long)(addr))


#define MAX_FC_CARDS 2
static struct fc_info *fc[MAX_FC_CARDS+1];
static unsigned int pci_irq_line = 0;
static struct {
	unsigned short vendor_id;
	unsigned short device_id;
	char *name;
}
clone_list[] __initdata  = {
	{PCI_VENDOR_ID_INTERPHASE, PCI_DEVICE_ID_INTERPHASE_5526, "Interphase Fibre Channel HBA"},
	{PCI_VENDOR_ID_INTERPHASE, PCI_DEVICE_ID_INTERPHASE_55x6, "Interphase Fibre Channel HBA"},
	{0,}
};

static void tachyon_interrupt(int irq, void *dev_id, struct pt_regs *regs);
static void tachyon_interrupt_handler(int irq, void* dev_id, struct pt_regs* regs);

static int initialize_register_pointers(struct fc_info *fi);
void clean_up_memory(struct fc_info *fi);

static int tachyon_init(struct fc_info *fi);
static int build_queues(struct fc_info *fi);
static void build_tachyon_header(struct fc_info *fi, u_int my_id, u_int r_ctl, u_int d_id, u_int type, u_char seq_id, u_char df_ctl, u_short ox_id, u_short rx_id, char *data);
static int get_free_header(struct fc_info *fi);
static void build_EDB(struct fc_info *fi, char *data, u_short flags, u_short len);
static int get_free_EDB(struct fc_info *fi);
static void build_ODB(struct fc_info *fi, u_char seq_id, u_int d_id, u_int len, u_int cntl, u_short mtu, u_short ox_id, u_short rx_id, int NW_header, int int_required, u_int frame_class);
static void write_to_tachyon_registers(struct fc_info *fi);
static void reset_latch(struct fc_info *fi);
static void reset_tachyon(struct fc_info *fi, u_int value);
static void take_tachyon_offline(struct fc_info *fi);
static void read_novram(struct fc_info *fi);
static void reset_ichip(struct fc_info *fi);
static void update_OCQ_indx(struct fc_info *fi);
static void update_IMQ_indx(struct fc_info *fi, int count);
static void update_SFSBQ_indx(struct fc_info *fi);
static void update_MFSBQ_indx(struct fc_info *fi, int count);
static void update_tachyon_header_indx(struct fc_info *fi);
static void update_EDB_indx(struct fc_info *fi);
static void handle_FM_interrupt(struct fc_info *fi);
static void handle_MFS_interrupt(struct fc_info *fi);
static void handle_OOO_interrupt(struct fc_info *fi);
static void handle_SFS_interrupt(struct fc_info *fi);
static void handle_OCI_interrupt(struct fc_info *fi);
static void handle_SFS_BUF_WARN_interrupt(struct fc_info *fi);
static void handle_MFS_BUF_WARN_interrupt(struct fc_info *fi);
static void handle_IMQ_BUF_WARN_interrupt(struct fc_info *fi);
static void handle_Unknown_Frame_interrupt(struct fc_info *fi);
static void handle_Busied_Frame_interrupt(struct fc_info *fi);
static void handle_Bad_SCSI_Frame_interrupt(struct fc_info *fi);
static void handle_Inbound_SCSI_Status_interrupt(struct fc_info *fi);
static void handle_Inbound_SCSI_Command_interrupt(struct fc_info *fi);
static void completion_message_handler(struct fc_info *fi, u_int imq_int_type);
static void fill_login_frame(struct fc_info *fi, u_int logi);

static int tx_exchange(struct fc_info *fi, char *data, u_int len, u_int r_ctl, u_int type, u_int d_id, u_int mtu, int int_required, u_short ox_id, u_int frame_class);
static int tx_sequence(struct fc_info *fi, char *data, u_int len, u_int mtu, u_int d_id, u_short ox_id, u_short rx_id, u_char seq_id, int NW_flag, int int_required, u_int frame_class);
static int validate_login(struct fc_info *fi, u_int *base_ptr);
static void add_to_address_cache(struct fc_info *fi, u_int *base_ptr);
static void remove_from_address_cache(struct fc_info *fi, u_int *data, u_int cmnd_code);
static int node_logged_in_prev(struct fc_info *fi, u_int *buff_addr);
static int sid_logged_in(struct fc_info *fi, u_int s_id);
static struct fc_node_info *look_up_cache(struct fc_info *fi, char *data);
static int display_cache(struct fc_info *fi);

static void tx_logi(struct fc_info *fi, u_int logi, u_int d_id);
static void tx_logi_acc(struct fc_info *fi, u_int logi, u_int d_id, u_short received_ox_id);
static void tx_prli(struct fc_info *fi, u_int command_code, u_int d_id, u_short received_ox_id);
static void tx_logo(struct fc_info *fi, u_int d_id, u_short received_ox_id);
static void tx_adisc(struct fc_info *fi, u_int cmnd_code, u_int d_id, u_short received_ox_id);
static void tx_ls_rjt(struct fc_info *fi, u_int d_id, u_short received_ox_id, u_short reason_code, u_short expln_code);
static u_int plogi_ok(struct fc_info *fi, u_int *buff_addr, int size);
static void tx_acc(struct fc_info *fi, u_int d_id, u_short received_ox_id);
static void tx_name_server_req(struct fc_info *fi, u_int req);
static void rscn_handler(struct fc_info *fi, u_int node_id);
static void tx_scr(struct fc_info *fi);
static void scr_timer(unsigned long data);
static void explore_fabric(struct fc_info *fi, u_int *buff_addr);
static void perform_adisc(struct fc_info *fi);
static void local_port_discovery(struct fc_info *fi);
static void add_to_ox_id_list(struct fc_info *fi, u_int transaction_id, u_int cmnd_code);
static u_int remove_from_ox_id_list(struct fc_info *fi, u_short received_ox_id);
static void add_display_cache_timer(struct fc_info *fi);

/* Timers... */
static void nos_ols_timer(unsigned long data);
static void loop_timer(unsigned long data);
static void fabric_explore_timer(unsigned long data);
static void port_discovery_timer(unsigned long data);
static void display_cache_timer(unsigned long data);

/* SCSI Stuff */
static int add_to_sest(struct fc_info *fi, Scsi_Cmnd *Cmnd, struct fc_node_info *ni);
static struct fc_node_info *resolve_target(struct fc_info *fi, u_char target);
static void update_FCP_CMND_indx(struct fc_info *fi);
static int get_free_SDB(struct fc_info *fi);
static void update_SDB_indx(struct fc_info *fi);
static void mark_scsi_sid(struct fc_info *fi, u_int *buff_addr, u_char action);
static void invalidate_SEST_entry(struct fc_info *fi, u_short received_ox_id);
static int abort_exchange(struct fc_info *fi, u_short ox_id);
static void flush_tachyon_cache(struct fc_info *fi, u_short ox_id);
static int get_scsi_oxid(struct fc_info *fi);
static void update_scsi_oxid(struct fc_info *fi);

Scsi_Host_Template driver_template = IPH5526_SCSI_FC;


#ifdef CONFIG_PCI
static int iph5526_probe_pci(struct device *dev);
#endif


__initfunc(int iph5526_probe(struct device *dev))
{
#ifdef CONFIG_PCI
	if (pci_present() && (iph5526_probe_pci(dev) == 0))
		return 0;
#endif
    return -ENODEV;
}

#ifdef CONFIG_PCI
__initfunc(static int iph5526_probe_pci(struct device *dev))
{
#ifndef MODULE
struct fc_info *fi;
static int count = 0;
#endif
#ifdef MODULE
struct fc_info *fi = (struct fc_info *)dev->priv;
#endif

#ifndef MODULE
	if(fc[count] != NULL) {
		if (dev == NULL) {
			dev = init_fcdev(NULL, 0);
			if (dev == NULL)
				return -ENOMEM;
		}
		fi = fc[count];
#endif
		fi->dev = dev;
		dev->base_addr = fi->base_addr;
		dev->irq = fi->irq;
		if (dev->priv == NULL) 
			dev->priv = fi; 
		fcdev_init(dev);
		/* Assign ur MAC address.
		 */
		dev->dev_addr[0] = (fi->g.my_port_name_high & 0x0000FF00) >> 8;
		dev->dev_addr[1] = fi->g.my_port_name_high;
		dev->dev_addr[2] = (fi->g.my_port_name_low & 0xFF000000) >> 24;
		dev->dev_addr[3] = (fi->g.my_port_name_low & 0x00FF0000) >> 16;
		dev->dev_addr[4] = (fi->g.my_port_name_low & 0x0000FF00) >> 8;
		dev->dev_addr[5] = fi->g.my_port_name_low;
#ifndef MODULE
		count++;
	}
	else
		return -ENODEV;
#endif
	display_cache(fi);
	return 0;
}
#endif  /* CONFIG_PCI */

__initfunc(static int fcdev_init(struct device *dev))
{
	dev->open = iph5526_open;
	dev->stop = iph5526_close;
	dev->hard_start_xmit = iph5526_send_packet;
	dev->get_stats = iph5526_get_stats;
	dev->set_multicast_list = NULL;
	dev->change_mtu = iph5526_change_mtu; 
#ifndef MODULE
	fc_setup(dev);
#endif
	return 0;
}

/* initialize tachyon and take it OnLine */
static int tachyon_init(struct fc_info *fi)
{
	ENTER("tachyon_init");
	if (build_queues(fi) == 0) {
		T_MSG("build_queues() failed");
		return 0;
	}

	/* Retrieve your port/node name.
	 */
	read_novram(fi);

	reset_ichip(fi);

	reset_tachyon(fi, SOFTWARE_RESET);

	LEAVE("tachyon_init");
	return 1;
}

/* Build the 4 Qs - IMQ, OCQ, MFSBQ, SFSBQ */
/* Lots of dma_pages needed as Tachyon DMAs almost everything into 
 * host memory.
 */
static int build_queues(struct fc_info *fi)
{
int i,j;
u_char *addr;
	ENTER("build_queues");
	/* Initializing Queue Variables.
	 */
	fi->q.ptr_host_ocq_cons_indx = NULL;
	fi->q.ptr_host_hpcq_cons_indx = NULL;
	fi->q.ptr_host_imq_prod_indx = NULL;

	fi->q.ptr_ocq_base = NULL;
	fi->q.ocq_len = 0;
	fi->q.ocq_end = 0;
	fi->q.ocq_prod_indx = 0;

	fi->q.ptr_imq_base = NULL;
	fi->q.imq_len = 0;
	fi->q.imq_end = 0;
	fi->q.imq_cons_indx = 0;
	fi->q.imq_prod_indx = 0;

	fi->q.ptr_mfsbq_base = NULL;
	fi->q.mfsbq_len = 0;
	fi->q.mfsbq_end = 0;
	fi->q.mfsbq_prod_indx = 0;
	fi->q.mfsbq_cons_indx = 0;
	fi->q.mfsbuff_len = 0;
	fi->q.mfsbuff_end = 0;
	fi->g.mfs_buffer_count = 0;

	fi->q.ptr_sfsbq_base = NULL;
	fi->q.sfsbq_len = 0;
	fi->q.sfsbq_end = 0;
	fi->q.sfsbq_prod_indx = 0;
	fi->q.sfsbq_cons_indx = 0;
	fi->q.sfsbuff_len = 0;
	fi->q.sfsbuff_end = 0;

	fi->q.sdb_indx = 0;
	fi->q.fcp_cmnd_indx = 0;

	fi->q.ptr_edb_base = NULL;
	fi->q.edb_buffer_indx = 0;
	fi->q.ptr_tachyon_header_base = NULL;
	fi->q.tachyon_header_indx = 0;
	fi->node_info_list = NULL;
	fi->ox_id_list = NULL;
	fi->g.loop_up = FALSE;
	fi->g.ptp_up = FALSE;
	fi->g.link_up = FALSE;
	fi->g.fabric_present = FALSE;
	fi->g.n_port_try = FALSE;
	fi->g.dont_init = FALSE;
	fi->g.nport_timer_set = FALSE;
	fi->g.lport_timer_set = FALSE;
	fi->g.no_of_targets = 0;
	fi->g.sem = 0;
	fi->g.perform_adisc = FALSE;
	fi->g.e_i = 0;

	/* build OCQ */
	if ( (fi->q.ptr_ocq_base = (u_int *)__get_free_pages(GFP_KERNEL, 0)) == 0) {
		T_MSG("failed to get OCQ page");
		return 0;
	}
	/* set up the OCQ structures */
	for (i = 0; i < OCQ_LENGTH; i++)
		fi->q.ptr_odb[i] = fi->q.ptr_ocq_base + NO_OF_ENTRIES*i;

	/* build IMQ */
	if ( (fi->q.ptr_imq_base = (u_int *)__get_free_pages(GFP_KERNEL, 0)) == 0) {
		T_MSG("failed to get IMQ page");
		return 0;
	}
	for (i = 0; i < IMQ_LENGTH; i++)
		fi->q.ptr_imqe[i] = fi->q.ptr_imq_base + NO_OF_ENTRIES*i;

	/* build MFSBQ */
	if ( (fi->q.ptr_mfsbq_base = (u_int *)__get_free_pages(GFP_KERNEL, 0)) == 0) {
		T_MSG("failed to get MFSBQ page");
		return 0;
	}
	memset((char *)fi->q.ptr_mfsbq_base, 0, MFSBQ_LENGTH * 32);
	/* Allocate one huge chunk of memory... helps while reassembling
	 * frames.
	 */
	if ( (addr = (u_char *)__get_free_pages(GFP_KERNEL, 5) ) == 0) {
		T_MSG("failed to get MFSBQ page");
		return 0;
	}
	/* fill in addresses of empty buffers */
	for (i = 0; i < MFSBQ_LENGTH; i++) {
		for (j = 0; j < NO_OF_ENTRIES; j++) {
				*(fi->q.ptr_mfsbq_base + i*NO_OF_ENTRIES + j) = htonl(virt_to_bus(addr));
				addr += MFS_BUFFER_SIZE;
		}
	}

	/* The number of entries in each MFS buffer is 8. There are 8
	 * MFS buffers. That leaves us with 4096-256 bytes. We use them
	 * as temporary space for ELS frames. This is done to make sure that
	 * the addresses are aligned.
	 */
	fi->g.els_buffer[0] = fi->q.ptr_mfsbq_base + MFSBQ_LENGTH*NO_OF_ENTRIES;
	for (i = 1; i < MAX_PENDING_FRAMES; i++)
		fi->g.els_buffer[i] = fi->g.els_buffer[i-1] + 64;
	
	/* build SFSBQ */
	if ( (fi->q.ptr_sfsbq_base = (u_int *)__get_free_pages(GFP_KERNEL, 0)) == 0) {
		T_MSG("failed to get SFSBQ page");
		return 0;
	}
	memset((char *)fi->q.ptr_sfsbq_base, 0, SFSBQ_LENGTH * 32);
	/* fill in addresses of empty buffers */
	for (i = 0; i < SFSBQ_LENGTH; i++)
		for (j = 0; j < NO_OF_ENTRIES; j++){
			addr = kmalloc(SFS_BUFFER_SIZE*2, GFP_KERNEL);
			if (addr == NULL){ 
				T_MSG("ptr_sfs_buffer : memory not allocated");
				return 0;
			}
			else {
			int offset = ALIGNED_SFS_ADDR(addr);
				memset((char *)addr, 0, SFS_BUFFER_SIZE);
				fi->q.ptr_sfs_buffers[i*NO_OF_ENTRIES +j] = (u_int *)addr;
				addr += offset;
				*(fi->q.ptr_sfsbq_base + i*NO_OF_ENTRIES + j) = htonl(virt_to_bus(addr));
			}
		}

	/* The number of entries in each SFS buffer is 8. There are 8
	 * MFS buffers. That leaves us with 4096-256 bytes. We use them
	 * as temporary space for ARP frames. This is done inorder to 
	 * support HW_Types of 0x1 and 0x6. 
	 */
	fi->g.arp_buffer = (char *)fi->q.ptr_sfsbq_base + SFSBQ_LENGTH*NO_OF_ENTRIES*4;
	
	/* build EDB */
	if ((fi->q.ptr_edb_base = (u_int *)__get_free_pages(GFP_KERNEL, 5) ) == 0) {
		T_MSG("failed to get EDB page");
		return 0;
	}
	for (i = 0; i < EDB_LEN; i++)
		fi->q.ptr_edb[i] = fi->q.ptr_edb_base + 2*i;

	/* build SEST */

	/* OX_IDs range from 0x0 - 0x4FFF.
	 */
	if ((fi->q.ptr_sest_base = (u_int *)__get_free_pages(GFP_KERNEL, 5)) == 0) {
		T_MSG("failed to get SEST page");
		return 0;
	}
	for (i = 0; i < SEST_LENGTH; i++)
		fi->q.ptr_sest[i] = fi->q.ptr_sest_base + NO_OF_ENTRIES*i;
	
	if ((fi->q.ptr_sdb_base = (u_int *)__get_free_pages(GFP_KERNEL, 5)) == 0) {
		T_MSG("failed to get SDB page");
		return 0;
	}
	for (i = 0 ; i < NO_OF_SDB_ENTRIES; i++)
		fi->q.ptr_sdb_slot[i] = fi->q.ptr_sdb_base + (SDB_SIZE/4)*i;

	if ((fi->q.ptr_fcp_cmnd_base = (u_int *)__get_free_pages(GFP_KERNEL, 0)) == 0) {
		T_MSG("failed to get FCP_CMND page");
		return 0;
	}
	for (i = 0; i < NO_OF_FCP_CMNDS; i++)
		fi->q.ptr_fcp_cmnd[i] = fi->q.ptr_fcp_cmnd_base + NO_OF_ENTRIES*i;

	/* Allocate space for Tachyon Header as well... 
	 */
	if ((fi->q.ptr_tachyon_header_base = (u_int *)__get_free_pages(GFP_KERNEL, 0) ) == 0) {
		T_MSG("failed to get tachyon_header page");
		return 0;
	}
	for (i = 0; i < NO_OF_TACH_HEADERS; i++) 
		fi->q.ptr_tachyon_header[i] = fi->q.ptr_tachyon_header_base + 16*i;
	
	/* Allocate memory for indices.
	 * Indices should be aligned on 32 byte boundries. 
	 */
	fi->q.host_ocq_cons_indx = kmalloc(2*32, GFP_KERNEL);
	if (fi->q.host_ocq_cons_indx == NULL){ 
		T_MSG("fi->q.host_ocq_cons_indx : memory not allocated");
		return 0;
	}
	fi->q.ptr_host_ocq_cons_indx = fi->q.host_ocq_cons_indx; 
	if ((u_long)(fi->q.host_ocq_cons_indx) % 32)
		fi->q.host_ocq_cons_indx++;
	
	fi->q.host_hpcq_cons_indx = kmalloc(2*32, GFP_KERNEL);
	if (fi->q.host_hpcq_cons_indx == NULL){ 
		T_MSG("fi->q.host_hpcq_cons_indx : memory not allocated");
		return 0;
	}
	fi->q.ptr_host_hpcq_cons_indx= fi->q.host_hpcq_cons_indx;
	if ((u_long)(fi->q.host_hpcq_cons_indx) % 32)
		fi->q.host_hpcq_cons_indx++;

	fi->q.host_imq_prod_indx = kmalloc(2*32, GFP_KERNEL);
	if (fi->q.host_imq_prod_indx == NULL){ 
		T_MSG("fi->q.host_imq_prod_indx : memory not allocated");
		return 0;
	}
	fi->q.ptr_host_imq_prod_indx = fi->q.host_imq_prod_indx;
	if ((u_long)(fi->q.host_imq_prod_indx) % 32)
		fi->q.host_imq_prod_indx++;

	LEAVE("build_queues");
	return 1;
}


static void write_to_tachyon_registers(struct fc_info *fi)
{
u_int bus_addr, bus_indx_addr, i;

	ENTER("write_to_tachyon_registers");

	/* Clear Queues each time Tachyon is reset */
	memset((char *)fi->q.ptr_ocq_base, 0, OCQ_LENGTH * 32);
	memset((char *)fi->q.ptr_imq_base, 0, IMQ_LENGTH * 32);
	memset((char *)fi->q.ptr_edb_base, 0, EDB_LEN * 8);
	memset((char *)fi->q.ptr_sest_base, 0, SEST_LENGTH * 32);
	memset((char *)fi->q.ptr_sdb_base, 0, NO_OF_SDB_ENTRIES * SDB_SIZE);
	memset((char *)fi->q.ptr_tachyon_header_base, 0xFF, NO_OF_TACH_HEADERS * TACH_HEADER_SIZE);
	for (i = 0; i < SEST_LENGTH; i++)
		fi->q.free_scsi_oxid[i] = OXID_AVAILABLE;
	for (i = 0; i < NO_OF_SDB_ENTRIES; i++)
		fi->q.sdb_slot_status[i] = SDB_FREE;

	take_tachyon_offline(fi);
	writel(readl(fi->t_r.ptr_tach_config_reg) | SCSI_ENABLE | WRITE_STREAM_SIZE | READ_STREAM_SIZE | PARITY_EVEN | OOO_REASSEMBLY_DISABLE, fi->t_r.ptr_tach_config_reg);

	/* Write OCQ registers */
	fi->q.ocq_prod_indx = 0;
	*(fi->q.host_ocq_cons_indx) = 0;
	
	/* The Tachyon needs to be passed the "real" address */
	bus_addr = virt_to_bus(fi->q.ptr_ocq_base);
	writel(bus_addr, fi->t_r.ptr_ocq_base_reg);
	writel(OCQ_LENGTH - 1, fi->t_r. ptr_ocq_len_reg);
	bus_indx_addr = virt_to_bus(fi->q.host_ocq_cons_indx);
	writel(bus_indx_addr, fi->t_r.ptr_ocq_cons_indx_reg);

	/* Write IMQ registers */
	fi->q.imq_cons_indx = 0;
	*(fi->q.host_imq_prod_indx) = 0;
	bus_addr = virt_to_bus(fi->q.ptr_imq_base);
	writel(bus_addr, fi->t_r.ptr_imq_base_reg);
	writel(IMQ_LENGTH - 1, fi->t_r.ptr_imq_len_reg);
	bus_indx_addr = virt_to_bus(fi->q.host_imq_prod_indx);
	writel(bus_indx_addr, fi->t_r.ptr_imq_prod_indx_reg);
	
	/* Write MFSBQ registers */
	fi->q.mfsbq_prod_indx = MFSBQ_LENGTH - 1;
	fi->q.mfsbuff_end = MFS_BUFFER_SIZE - 1;
	fi->q.mfsbq_cons_indx = 0;
	bus_addr = virt_to_bus(fi->q.ptr_mfsbq_base);
	writel(bus_addr, fi->t_r.ptr_mfsbq_base_reg);
	writel(MFSBQ_LENGTH - 1, fi->t_r.ptr_mfsbq_len_reg);
	writel(fi->q.mfsbuff_end, fi->t_r.ptr_mfsbuff_len_reg);
	/* Do this last as tachyon will prefetch the 
	 * first entry as soon as we write to it.
	 */
	writel(fi->q.mfsbq_prod_indx, fi->t_r.ptr_mfsbq_prod_reg);

	/* Write SFSBQ registers */
	fi->q.sfsbq_prod_indx = SFSBQ_LENGTH - 1;
	fi->q.sfsbuff_end = SFS_BUFFER_SIZE - 1;
	fi->q.sfsbq_cons_indx = 0;
	bus_addr = virt_to_bus(fi->q.ptr_sfsbq_base);
	writel(bus_addr, fi->t_r.ptr_sfsbq_base_reg);
	writel(SFSBQ_LENGTH - 1, fi->t_r.ptr_sfsbq_len_reg);
	writel(fi->q.sfsbuff_end, fi->t_r.ptr_sfsbuff_len_reg);
	/* Do this last as tachyon will prefetch the first 
	 * entry as soon as we write to it. 
	 */
	writel(fi->q.sfsbq_prod_indx, fi->t_r.ptr_sfsbq_prod_reg);

	/* Write SEST registers */
	bus_addr = virt_to_bus(fi->q.ptr_sest_base);
	writel(bus_addr, fi->t_r.ptr_sest_base_reg);
	writel(SEST_LENGTH - 1, fi->t_r.ptr_sest_len_reg);
	/* the last 2 bits _should_ be 1 */
	writel(SEST_BUFFER_SIZE - 1, fi->t_r.ptr_scsibuff_len_reg);

	/* write AL_TIME & E_D_TOV into the registers */
	writel(TOV_VALUES, fi->t_r.ptr_fm_tov_reg);
	/* Tell Tachyon to pick a Soft Assigned AL_PA */
	writel(LOOP_INIT_SOFT_ADDRESS, fi->t_r.ptr_fm_config_reg);

	/* Read the WWN from EEPROM . But, for now we assign it here. */
	writel(WORLD_WIDE_NAME_LOW, fi->t_r.ptr_fm_wwn_low_reg);
	writel(WORLD_WIDE_NAME_HIGH, fi->t_r.ptr_fm_wwn_hi_reg);

	DPRINTK1("TACHYON initializing as L_Port...\n");
	writel(INITIALIZE, fi->t_r.ptr_fm_control_reg);
			
	LEAVE("write_to_tachyon_registers");
}


static void tachyon_interrupt(int irq, void* dev_id, struct pt_regs* regs)
{
struct Scsi_Host *host = dev_id;
struct iph5526_hostdata *hostdata = (struct iph5526_hostdata *)host->hostdata;
struct fc_info *fi = hostdata->fi; 
u_long flags;
	spin_lock_irqsave(&fi->fc_lock, flags);
	tachyon_interrupt_handler(irq, dev_id, regs);
	spin_unlock_irqrestore(&fi->fc_lock, flags);
}

static void tachyon_interrupt_handler(int irq, void* dev_id, struct pt_regs* regs)
{
struct Scsi_Host *host = dev_id;
struct iph5526_hostdata *hostdata = (struct iph5526_hostdata *)host->hostdata;
struct fc_info *fi = hostdata->fi; 
u_int *ptr_imq_entry;
u_int imq_int_type, current_IMQ_index = 0, prev_IMQ_index;
int index, no_of_entries = 0;

	DPRINTK("\n");
	ENTER("tachyon_interrupt");
	if (fi->q.host_imq_prod_indx != NULL) {
		current_IMQ_index =  ntohl(*(fi->q.host_imq_prod_indx));
	}
	else {
		/* _Should not_ happen */
		T_MSG("IMQ_indx NULL. DISABLING INTERRUPTS!!!\n");
		writel(0x0, fi->i_r.ptr_ichip_hw_control_reg);
	}

	if (current_IMQ_index > fi->q.imq_cons_indx)
		no_of_entries = current_IMQ_index - fi->q.imq_cons_indx;
	else
	if (current_IMQ_index < fi->q.imq_cons_indx)
		no_of_entries = IMQ_LENGTH - (fi->q.imq_cons_indx - current_IMQ_index);

	if (no_of_entries == 0) {
	u_int ichip_status;
		ichip_status = readl(fi->i_r.ptr_ichip_hw_status_reg);
		if (ichip_status & 0x20) {
			/* Should _never_ happen. Might require a hard reset */
			T_MSG("Too bad... PCI Bus Error. Resetting (i)chip"); 
			reset_ichip(fi);
			T_MSG("DISABLING INTERRUPTS!!!\n");
			writel(0x0, fi->i_r.ptr_ichip_hw_control_reg);
		}
	}

	prev_IMQ_index = current_IMQ_index;
	for (index = 0; index < no_of_entries; index++) {
		ptr_imq_entry = fi->q.ptr_imqe[fi->q.imq_cons_indx];
		imq_int_type = ntohl(*ptr_imq_entry);

		completion_message_handler(fi, imq_int_type);
		if ((fi->g.link_up == FALSE) && ((imq_int_type == MFS_BUF_WARN) || (imq_int_type == SFS_BUF_WARN) || (imq_int_type == IMQ_BUF_WARN))) 
			break;
		update_IMQ_indx(fi, 1);
	
		/* Check for more entries */
		current_IMQ_index =  ntohl(*(fi->q.host_imq_prod_indx));
		if (current_IMQ_index != prev_IMQ_index) {
			no_of_entries++;
			prev_IMQ_index = current_IMQ_index;
		}
	} /*end of for loop*/		
	return;
	LEAVE("tachyon_interrupt");
}


static void handle_SFS_BUF_WARN_interrupt(struct fc_info *fi)
{
int i;
	ENTER("handle_SFS_BUF_WARN_interrupt");
	if (fi->g.link_up == FALSE) {
		reset_tachyon(fi, SOFTWARE_RESET);
		return;
	}
	/* Free up all but one entry in the Q. 
	 */
	for (i = 0; i < ((SFSBQ_LENGTH - 1) * NO_OF_ENTRIES); i++) {
		handle_SFS_interrupt(fi);
		update_IMQ_indx(fi, 1);
	}
	LEAVE("handle_SFS_BUF_WARN_interrupt");
}

/* Untested_Code_Begin */ 
static void handle_MFS_BUF_WARN_interrupt(struct fc_info *fi)
{
int i;
	ENTER("handle_MFS_BUF_WARN_interrupt");
	if (fi->g.link_up == FALSE) {
		reset_tachyon(fi, SOFTWARE_RESET);
		return;
	}
	/* FIXME: freeing up 8 entries. 
	 */
	for (i = 0; i < NO_OF_ENTRIES; i++) {
		handle_MFS_interrupt(fi);
		update_IMQ_indx(fi, 1);
	}
	LEAVE("handle_MFS_BUF_WARN_interrupt");
}
/*Untested_Code_End */

static void handle_IMQ_BUF_WARN_interrupt(struct fc_info *fi)
{
u_int *ptr_imq_entry;
u_int imq_int_type, current_IMQ_index = 0, temp_imq_cons_indx;
int index, no_of_entries = 0;

	ENTER("handle_IMQ_BUF_WARN_interrupt");
	if (fi->g.link_up == FALSE) {
		reset_tachyon(fi, SOFTWARE_RESET);
		return;
	}
	current_IMQ_index =  ntohl(*(fi->q.host_imq_prod_indx));

	if (current_IMQ_index > fi->q.imq_cons_indx)
 		no_of_entries = current_IMQ_index - fi->q.imq_cons_indx;
	else
		if (current_IMQ_index < fi->q.imq_cons_indx)
			no_of_entries = IMQ_LENGTH - (fi->q.imq_cons_indx - current_IMQ_index);
	/* We dont want to look at the same IMQ entry again. 
	 */
	temp_imq_cons_indx = fi->q.imq_cons_indx + 1;
	if (no_of_entries != 0)
		no_of_entries -= 1;
	for (index = 0; index < no_of_entries; index++) {
		ptr_imq_entry = fi->q.ptr_imqe[temp_imq_cons_indx];
		imq_int_type = ntohl(*ptr_imq_entry);
		if (imq_int_type != IMQ_BUF_WARN)
			completion_message_handler(fi, imq_int_type);
		temp_imq_cons_indx++;
		if (temp_imq_cons_indx == IMQ_LENGTH)
			temp_imq_cons_indx = 0;
	} /*end of for loop*/	
	if (no_of_entries != 0)
		update_IMQ_indx(fi, no_of_entries);
	LEAVE("handle_IMQ_BUF_WARN_interrupt");
}

static void completion_message_handler(struct fc_info *fi, u_int imq_int_type)
{
	switch(imq_int_type) {
		case OUTBOUND_COMPLETION:
			DPRINTK("OUTBOUND_COMPLETION message received");
			break;
		case OUTBOUND_COMPLETION_I:
			DPRINTK("OUTBOUND_COMPLETION_I message received");
			handle_OCI_interrupt(fi);
			break;
		case OUT_HI_PRI_COMPLETION:
			DPRINTK("OUT_HI_PRI_COMPLETION message received");
			break;
		case OUT_HI_PRI_COMPLETION_I:
			DPRINTK("OUT_HI_PRI_COMPLETION_I message received");
			break;
		case INBOUND_MFS_COMPLETION:
			DPRINTK("INBOUND_MFS_COMPLETION message received");
			handle_MFS_interrupt(fi);
			break;
		case INBOUND_OOO_COMPLETION:
			DPRINTK("INBOUND_OOO_COMPLETION message received");
			handle_OOO_interrupt(fi);
			break;
		case INBOUND_SFS_COMPLETION:
			DPRINTK("INBOUND_SFS_COMPLETION message received");
			handle_SFS_interrupt(fi);
			break;
		case INBOUND_UNKNOWN_FRAME_I:
			DPRINTK("INBOUND_UNKNOWN_FRAME message received");
			handle_Unknown_Frame_interrupt(fi);
			break;
		case INBOUND_BUSIED_FRAME:
			DPRINTK("INBOUND_BUSIED_FRAME message received");
			handle_Busied_Frame_interrupt(fi);
			break;
		case FRAME_MGR_INTERRUPT:
			DPRINTK("FRAME_MGR_INTERRUPT message received");
			handle_FM_interrupt(fi);
			break;
		case READ_STATUS:
			DPRINTK("READ_STATUS message received");
			break;
		case SFS_BUF_WARN:
			DPRINTK("SFS_BUF_WARN message received");
			handle_SFS_BUF_WARN_interrupt(fi);
			break;
		case MFS_BUF_WARN:
			DPRINTK("MFS_BUF_WARN message received");
			handle_MFS_BUF_WARN_interrupt(fi);
			break;
		case IMQ_BUF_WARN:
			DPRINTK("IMQ_BUF_WARN message received");
			handle_IMQ_BUF_WARN_interrupt(fi);
			break;
		case INBOUND_C1_TIMEOUT:
			DPRINTK("INBOUND_C1_TIMEOUT message received");
			break;
		case BAD_SCSI_FRAME:
			DPRINTK("BAD_SCSI_FRAME message received");
			handle_Bad_SCSI_Frame_interrupt(fi);
			break;
		case INB_SCSI_STATUS_COMPLETION:
			DPRINTK("INB_SCSI_STATUS_COMPL message received");
			handle_Inbound_SCSI_Status_interrupt(fi);
			break;
		case INBOUND_SCSI_COMMAND:
			DPRINTK("INBOUND_SCSI_COMMAND message received");
			handle_Inbound_SCSI_Command_interrupt(fi);
			break;
		case INBOUND_SCSI_DATA_COMPLETION:
			DPRINTK("INBOUND_SCSI_DATA message received");
			/* Only for targets */
			break;
		default:		
			T_MSG("DEFAULT message received, type = %x", imq_int_type);
			return;
	}
	reset_latch(fi);
}

static void handle_OCI_interrupt(struct fc_info *fi)
{
u_int *ptr_imq_entry;
u_long transaction_id = 0;
unsigned short status, seq_count, transmitted_ox_id;
struct Scsi_Host *host = fi->host;
struct iph5526_hostdata *hostdata = (struct iph5526_hostdata *)host->hostdata;
Scsi_Cmnd *Cmnd;
u_int tag;

	ENTER("handle_OCI_interrupt");
	ptr_imq_entry = fi->q.ptr_imqe[fi->q.imq_cons_indx];
	transaction_id = ntohl(*(ptr_imq_entry + 1));
	status = ntohl(*(ptr_imq_entry + 2)) >> 16;
	seq_count = ntohl(*(ptr_imq_entry + 3));
	DPRINTK("transaction_id= %x", (u_int)transaction_id);
	tag = transaction_id & 0xFFFF0000;
	transmitted_ox_id = transaction_id;

	/* The INT could be either due to TIME_OUT | BAD_ALPA. 
	 * But we check only for TimeOuts. Bad AL_PA will 
	 * caught by FM_interrupt handler. 
	 */

	if ((status == OCM_TIMEOUT_OR_BAD_ALPA) && (!fi->g.port_discovery) && (!fi->g.perform_adisc)){
		DPRINTK("Frame TimeOut on OX_ID = %x", (u_int)transaction_id);

		/* Is it a SCSI frame that is timing out ? Not a very good check... 
		 */
		if ((transmitted_ox_id <= MAX_SCSI_OXID) && ((tag == FC_SCSI_BAD_TARGET) || (tag < 0x00FF0000))) {
			/* If it is a Bad AL_PA, we report it as BAD_TARGET.
			 * Else, we allow the command to time-out. A Link
			 * re-initialization could be taking place.
			 */
			if (tag == FC_SCSI_BAD_TARGET) {
				Cmnd = hostdata->cmnd_handler[transmitted_ox_id & MAX_SCSI_XID];
				hostdata->cmnd_handler[transmitted_ox_id & MAX_SCSI_XID] = NULL;
				if (Cmnd != NULL) {
					Cmnd->result = DID_BAD_TARGET << 16;
					(*Cmnd->scsi_done) (Cmnd);
				}
				else
					T_MSG("NULL Command out of handler!");
			} /* if Bad Target */
			else {
			u_char missing_target = tag >> 16;
			struct fc_node_info *q = fi->node_info_list;
				/* A Node that we thought was logged in has gone
				 * away. We are the optimistic kind and we keep
				 * hoping that our dear little Target will come back
				 * to us. For now we log him out.
				 */
				DPRINTK2("Missing Target = %d", missing_target);
				while (q != NULL) {
					if (q->target_id == missing_target) {
						T_MSG("Target %d Logged out", q->target_id);
						q->login = LOGIN_ATTEMPTED;
						if (fi->num_nodes > 0)
							fi->num_nodes--;
						tx_logi(fi, ELS_PLOGI, q->d_id);
						break;
					}
					else
						q = q->next;
				}
			}
		} /* End of SCSI frame timing out. */
		else {
			if (seq_count > 1) {
				/* An IP frame was transmitted to a Bad AL_PA. Free up
			 	 * the skb used.
			 	 */
				dev_kfree_skb((struct sk_buff *)(bus_to_virt(transaction_id)));
			}
		} /* End of IP frame timing out. */
	} /* End of frame timing out. */
	else {
		/* Frame was transmitted successfully. Check if it was an ELS
		 * frame or an IP frame or a Bad_Target_Notification frame (in
		 * case of a ptp_link). Ugly!
		 */
		if ((status == 0) && (seq_count == 0)) {
		u_int tag = transaction_id & 0xFFFF0000;
		/* Continue with port discovery after an ELS is successfully 
		 * transmitted. (status == 0). 
		 */
			DPRINTK("tag = %x", tag);
			switch(tag) {
				case ELS_FLOGI:
					/* Letz use the Name Server instead */
					fi->g.explore_fabric = TRUE;
					fi->g.port_discovery = FALSE;
					fi->g.alpa_list_index = MAX_NODES;
					add_to_ox_id_list(fi, transaction_id, tag);
					break;
				case ELS_PLOGI:
					if (fi->g.fabric_present && (fi->g.name_server == FALSE))
						add_to_ox_id_list(fi,transaction_id,ELS_NS_PLOGI);
					else
						add_to_ox_id_list(fi, transaction_id, tag);
					break;
				case FC_SCSI_BAD_TARGET:
					Cmnd = hostdata->cmnd_handler[transmitted_ox_id & MAX_SCSI_XID];
					hostdata->cmnd_handler[transmitted_ox_id & MAX_SCSI_XID] = NULL;
					if (Cmnd != NULL) {
						Cmnd->result = DID_BAD_TARGET << 16;
						(*Cmnd->scsi_done) (Cmnd);
					}
					else
						T_MSG("NULL Command out of handler!");
					break;
				default:
					add_to_ox_id_list(fi, transaction_id, tag);
			}
		
			if (fi->g.alpa_list_index >= MAX_NODES) {
				if (fi->g.port_discovery == TRUE) {
					fi->g.port_discovery = FALSE;
					add_display_cache_timer(fi);
				}
				fi->g.alpa_list_index = MAX_NODES;
			}
			if (fi->g.port_discovery == TRUE) 
				local_port_discovery(fi);
		}
		else {
			/* An IP frame has been successfully transmitted.
			 * Free the skb that was used for this IP frame.
			 */
			if ((status == 0) && (seq_count > 1)) {
				dev_kfree_skb((struct sk_buff *)(bus_to_virt(transaction_id)));
			}
		}
	}
	LEAVE("handle_OCI_interrupt");
}

/* Right now we discard OOO frames */
static void handle_OOO_interrupt(struct fc_info *fi)
{
u_int *ptr_imq_entry;
int queue_indx, offset, payload_size;
int no_of_buffers = 1; /* header is in a separate buffer */
	ptr_imq_entry = fi->q.ptr_imqe[fi->q.imq_cons_indx];
	offset = ntohl(*(ptr_imq_entry + 1)) & 0x00000007;
	queue_indx = ntohl(*(ptr_imq_entry + 1)) & 0xFFFF0000;
	queue_indx = queue_indx >> 16;
	payload_size = ntohl(*(ptr_imq_entry + 2)) - TACHYON_HEADER_LEN;
	/* Calculate total number of buffers */
	no_of_buffers += payload_size / MFS_BUFFER_SIZE;
	if (payload_size % MFS_BUFFER_SIZE)
		no_of_buffers++;

	/* provide Tachyon will another set of buffers */
	fi->g.mfs_buffer_count += no_of_buffers;
	if (fi->g.mfs_buffer_count >= NO_OF_ENTRIES) {
	int count = fi->g.mfs_buffer_count / NO_OF_ENTRIES;
		fi->g.mfs_buffer_count -= NO_OF_ENTRIES * count;
		update_MFSBQ_indx(fi, count);
	}
}

static void handle_MFS_interrupt(struct fc_info *fi)
{
u_int *ptr_imq_entry, *buff_addr;
u_int type_of_frame, s_id;
int queue_indx, offset, payload_size, starting_indx, starting_offset;
u_short received_ox_id;
int no_of_buffers = 1; /* header is in a separate buffer */
struct sk_buff *skb;
int wrap_around = FALSE, no_of_wrap_buffs = NO_OF_ENTRIES - 1;
	ENTER("handle_MFS_interrupt");
	ptr_imq_entry = fi->q.ptr_imqe[fi->q.imq_cons_indx];
	offset = ntohl(*(ptr_imq_entry + 1)) & 0x00000007;
	queue_indx = ntohl(*(ptr_imq_entry + 1)) & 0xFFFF0000;
	queue_indx = queue_indx >> 16;
	DPRINTK("queue_indx = %d, offset  = %d\n", queue_indx, offset);
	payload_size = ntohl(*(ptr_imq_entry + 2)) - TACHYON_HEADER_LEN;
	DPRINTK("payload_size = %d", payload_size);
	/* Calculate total number of buffers */
	no_of_buffers += payload_size / MFS_BUFFER_SIZE;
	if (payload_size % MFS_BUFFER_SIZE)
		no_of_buffers++;
	DPRINTK("no_of_buffers = %d", no_of_buffers);

	if ((no_of_buffers - 1) <= offset) {
		starting_offset = offset - (no_of_buffers - 1);
		starting_indx = queue_indx;
	}
	else {
	int temp = no_of_buffers - (offset + 1);
	int no_of_queues = temp / NO_OF_ENTRIES;
		starting_offset = temp % NO_OF_ENTRIES;
		if (starting_offset != 0) {
			no_of_wrap_buffs = starting_offset - 1; //exclude header
			starting_offset = NO_OF_ENTRIES - starting_offset;
			no_of_queues++;
		}
		starting_indx = queue_indx - no_of_queues;
		if (starting_indx < 0) {
			no_of_wrap_buffs -= (starting_indx + 1) * NO_OF_ENTRIES; 
			starting_indx = MFSBQ_LENGTH + starting_indx;
			wrap_around = TRUE;
		}
	}
	
	DPRINTK("starting_indx = %d, starting offset = %d no_of_wrap_buffs = %d\n", starting_indx, starting_offset, no_of_wrap_buffs);
	/* Get Tachyon Header from first buffer */
	buff_addr = bus_to_virt(ntohl(*(fi->q.ptr_mfsbq_base + starting_indx*NO_OF_ENTRIES + starting_offset)));
	

	/* extract Type of Frame */
	type_of_frame = (u_int)ntohl(*(buff_addr + 4)) & 0xFF000000;
	s_id = (u_int)ntohl(*(buff_addr + 3)) & 0x00FFFFFF;
	received_ox_id = ntohl(*(buff_addr + 6)) >> 16;
	buff_addr += MFS_BUFFER_SIZE/4;
	DPRINTK("type_of_frame = %x, s_id = %x, ox_id = %x", type_of_frame, s_id, received_ox_id);

 	switch(type_of_frame) {
	  case TYPE_LLC_SNAP:
		skb = dev_alloc_skb(payload_size);
		if (skb == NULL) {
			printk(KERN_NOTICE "%s: In handle_MFS_interrupt() Memory squeeze, dropping packet.\n", fi->name);
			fi->fc_stats.rx_dropped++;
			fi->g.mfs_buffer_count += no_of_buffers;
			if (fi->g.mfs_buffer_count >= NO_OF_ENTRIES) {
			int count = fi->g.mfs_buffer_count / NO_OF_ENTRIES;
				fi->g.mfs_buffer_count -= NO_OF_ENTRIES * count;
				update_MFSBQ_indx(fi, count);
				return;
			}
		}
		if (wrap_around) {
		int wrap_size = no_of_wrap_buffs * MFS_BUFFER_SIZE;
		int tail_size = payload_size - wrap_size;
			DPRINTK("wrap_size = %d, tail_size = %d\n", wrap_size, tail_size);
			if (no_of_wrap_buffs) 
				memcpy(skb_put(skb, wrap_size), buff_addr, wrap_size);
			buff_addr = bus_to_virt(ntohl(*(fi->q.ptr_mfsbq_base)));
			memcpy(skb_put(skb, tail_size), buff_addr, tail_size);
		}
		else
			memcpy(skb_put(skb, payload_size), buff_addr, payload_size);
		rx_net_mfs_packet(fi, skb);
	  	break;
	default:
		T_MSG("Unknown Frame Type received. Type = %x", type_of_frame);
	}

	/* provide Tachyon will another set of buffers */
	fi->g.mfs_buffer_count += no_of_buffers;
	if (fi->g.mfs_buffer_count >= NO_OF_ENTRIES) {
	int count = fi->g.mfs_buffer_count / NO_OF_ENTRIES;
		fi->g.mfs_buffer_count -= NO_OF_ENTRIES * count;
		update_MFSBQ_indx(fi, count);
	}
	LEAVE("handle_MFS_interrupt");
}

static void handle_Unknown_Frame_interrupt(struct fc_info *fi)
{
u_int *ptr_imq_entry;
int queue_indx, offset;
	ENTER("handle_Unknown_Frame_interrupt");
	ptr_imq_entry = fi->q.ptr_imqe[fi->q.imq_cons_indx];
	offset = ntohl(*(ptr_imq_entry + 1)) & 0x00000007;
	queue_indx = ntohl(*(ptr_imq_entry + 1)) & 0xFFFF0000;
	queue_indx = queue_indx >> 16;
	/* We discard the "unknown" frame */
	/* provide Tachyon will another set of buffers */
	if (offset == (NO_OF_ENTRIES - 1))
		update_SFSBQ_indx(fi);
	LEAVE("handle_Unknown_Frame_interrupt");
}

static void handle_Busied_Frame_interrupt(struct fc_info *fi)
{
u_int *ptr_imq_entry;
int queue_indx, offset;
	ENTER("handle_Busied_Frame_interrupt");
	ptr_imq_entry = fi->q.ptr_imqe[fi->q.imq_cons_indx];
	offset = ntohl(*(ptr_imq_entry + 1)) & 0x00000007;
	queue_indx = ntohl(*(ptr_imq_entry + 1)) & 0xFFFF0000;
	queue_indx = queue_indx >> 16;
	/* We discard the "busied" frame */
	/* provide Tachyon will another set of buffers */
	if (offset == (NO_OF_ENTRIES - 1))
		update_SFSBQ_indx(fi);
	LEAVE("handle_Busied_Frame_interrupt");
}

static void handle_Bad_SCSI_Frame_interrupt(struct fc_info *fi)
{
u_int *ptr_imq_entry, *buff_addr, *tach_header, *ptr_edb;
u_int s_id, rctl, frame_class, burst_len, transfered_len, len = 0;
int queue_indx, offset, payload_size, i;
u_short ox_id, rx_id, x_id, mtu = 512;
u_char target_id = 0xFF;

	ENTER("handle_Bad_SCSI_Frame_interrupt");
	ptr_imq_entry = fi->q.ptr_imqe[fi->q.imq_cons_indx];
	offset = ntohl(*(ptr_imq_entry + 1)) & 0x00000007;
	queue_indx = ntohl(*(ptr_imq_entry + 1)) & 0xFFFF0000;
	queue_indx = queue_indx >> 16;
	payload_size = ntohl(*(ptr_imq_entry + 2));

	buff_addr = bus_to_virt(ntohl(*(fi->q.ptr_sfsbq_base + queue_indx*NO_OF_ENTRIES + offset)));

	rctl = ntohl(*(buff_addr + 2)) & 0xFF000000;
	s_id = ntohl(*(buff_addr + 3)) & 0x00FFFFFF;
	ox_id = ntohl(*(buff_addr + 6)) >> 16;
	rx_id = ntohl(*(buff_addr + 6));
	x_id = ox_id & MAX_SCSI_XID;

	/* Any frame that comes in with OX_ID that matches an OX_ID 
	 * that has been allocated for SCSI, will be called a Bad
	 * SCSI frame if the Exchange is not valid any more.
	 *
	 * We will also get a Bad SCSI frame interrupt if we receive
	 * a XFER_RDY with offset != 0. Tachyon washes its hands off
	 * this Exchange. We have to take care of ourselves. Grrr...
	 */
	if (rctl == DATA_DESCRIPTOR) {
	struct fc_node_info *q = fi->node_info_list;
		while (q != NULL) {
			if (q->d_id == s_id) {
				target_id = q->target_id;
				mtu = q->mtu;
				break;
			}
			else
				q = q->next;
		}
		frame_class = target_id;
		transfered_len = ntohl(*(buff_addr + 8));
		burst_len = ntohl(*(buff_addr + 9));

		build_ODB(fi, fi->g.seq_id, s_id, burst_len, 0, mtu, ox_id, rx_id, 0, 0, frame_class << 16);
		/* Update the SEQ_ID and Relative Offset in the 
		 * Tachyon Header Structure.
		 */
		tach_header = bus_to_virt(ntohl(*(fi->q.ptr_sest[x_id] + 5)));
		*(tach_header + 5) = htonl(fi->g.seq_id << 24);
		*(tach_header + 7) = htonl(transfered_len);
		fi->g.odb.hdr_addr = *(fi->q.ptr_sest[x_id] + 5);

		/* Invalidate the EDBs used 
		 */
		ptr_edb = bus_to_virt(ntohl(*(fi->q.ptr_sest[x_id] + 7)));

		for (i = 0; i < EDB_LEN; i++)
			if (fi->q.ptr_edb[i] == ptr_edb)
				break;
		ptr_edb--;	
		
		if (i < EDB_LEN) {
		int j;
			do {
				ptr_edb += 2;
				len += (htonl(*ptr_edb) & 0xFFFF);
				j = i;
				fi->q.free_edb_list[i++] = EDB_FREE;
				if (i == EDB_LEN) {
					i = 0;
					ptr_edb = fi->q.ptr_edb_base - 1;
				}
			} while (len < transfered_len);
			if (len > transfered_len) {
				ptr_edb--;
				fi->q.free_edb_list[j] = EDB_BUSY;
			}
			else
				ptr_edb++;
		}
		else {
			T_MSG("EDB not found while freeing");
			if (offset == (NO_OF_ENTRIES - 1))
				update_SFSBQ_indx(fi);
			return;
		}

		/* Update the EDB pointer in the ODB.
		 */
		fi->g.odb.edb_addr = htonl(virt_to_bus(ptr_edb));
		memcpy(fi->q.ptr_odb[fi->q.ocq_prod_indx], &(fi->g.odb), sizeof(ODB));
		/* Update the EDB pointer in the SEST entry. We might need
		 * this if get another XFER_RDY for the same Exchange.
		 */
		*(fi->q.ptr_sest[x_id] + 7) = htonl(virt_to_bus(ptr_edb));

		update_OCQ_indx(fi);
		if (fi->g.seq_id == MAX_SEQ_ID)
			fi->g.seq_id = 0;
		else
			fi->g.seq_id++;
	}
	else 
	/* Could be a BA_ACC or a BA_RJT.
	 */
	if (rctl == RCTL_BASIC_ACC) {
	u_int bls_type = remove_from_ox_id_list(fi, ox_id);
		DPRINTK1("BA_ACC received from S_ID 0x%x with OX_ID = %x in response to %x", s_id, ox_id, bls_type);
		if (bls_type == RCTL_BASIC_ABTS) {
		u_int STE_bit;
			/* Invalidate resources for that Exchange.
			 */
			STE_bit = ntohl(*fi->q.ptr_sest[x_id]);
			if (STE_bit & SEST_V) {
				*(fi->q.ptr_sest[x_id]) &= htonl(SEST_INV);
				invalidate_SEST_entry(fi, ox_id);
			}
		}
	}
	else
	if (rctl == RCTL_BASIC_RJT) {
	u_int bls_type = remove_from_ox_id_list(fi, ox_id);
		DPRINTK1("BA_RJT received from S_ID 0x%x with OX_ID = %x in response to %x", s_id, ox_id, bls_type);
		if (bls_type == RCTL_BASIC_ABTS) {
		u_int STE_bit;
			/* Invalidate resources for that Exchange.
			 */
			STE_bit = ntohl(*fi->q.ptr_sest[x_id]);
			if (STE_bit & SEST_V) {
				*(fi->q.ptr_sest[x_id]) &= htonl(SEST_INV);
				invalidate_SEST_entry(fi, ox_id);
			}
		}
	}
	else
		DPRINTK1("Frame with R_CTL = %x received from S_ID 0x%x with OX_ID %x", rctl, s_id, ox_id);

	/* Else, discard the "Bad" SCSI frame.
	 */

	/* provide Tachyon will another set of buffers 
	 */
	if (offset == (NO_OF_ENTRIES - 1))
		update_SFSBQ_indx(fi);
	LEAVE("handle_Bad_SCSI_Frame_interrupt");
}

static void handle_Inbound_SCSI_Status_interrupt(struct fc_info *fi)
{
struct Scsi_Host *host = fi->host;
struct iph5526_hostdata *hostdata = (struct iph5526_hostdata *)host->hostdata;
u_int *ptr_imq_entry, *buff_addr, *ptr_rsp_info, *ptr_sense_info = NULL;
int queue_indx, offset, payload_size;
u_short received_ox_id, x_id;
Scsi_Cmnd *Cmnd;
u_int fcp_status, fcp_rsp_info_len = 0, fcp_sense_info_len = 0, s_id;
	ENTER("handle_SCSI_status_interrupt");

	ptr_imq_entry = fi->q.ptr_imqe[fi->q.imq_cons_indx];
	offset = ntohl(*(ptr_imq_entry + 1)) & 0x00000007;
	queue_indx = ntohl(*(ptr_imq_entry + 1)) & 0xFFFF0000;
	queue_indx = queue_indx >> 16;
	buff_addr = bus_to_virt(ntohl(*(fi->q.ptr_sfsbq_base + queue_indx*NO_OF_ENTRIES + offset)));
	payload_size = ntohl(*(ptr_imq_entry + 2));
	received_ox_id = ntohl(*(buff_addr + 6)) >> 16;

	buff_addr = bus_to_virt(ntohl(*(fi->q.ptr_sfsbq_base + queue_indx*NO_OF_ENTRIES + offset)));

	fcp_status = ntohl(*(buff_addr + 10));
	ptr_rsp_info = buff_addr + 14;
	if (fcp_status & FCP_STATUS_RSP_LEN)
		fcp_rsp_info_len = ntohl(*(buff_addr + 13));
		
	if (fcp_status & FCP_STATUS_SENSE_LEN) {
		ptr_sense_info = ptr_rsp_info + fcp_rsp_info_len / 4;
		fcp_sense_info_len = ntohl(*(buff_addr + 12));
		DPRINTK("sense_info = %x", (u_int)ntohl(*ptr_sense_info));
	}
	DPRINTK("fcp_status = %x, fcp_rsp_len = %x", fcp_status, fcp_rsp_info_len);
	x_id = received_ox_id & MAX_SCSI_XID;
	Cmnd = hostdata->cmnd_handler[x_id];
	hostdata->cmnd_handler[x_id] = NULL;
	if (Cmnd != NULL) {
		memset(Cmnd->sense_buffer, 0, sizeof(Cmnd->sense_buffer));
		/* Check if there is a Sense field */
		if (fcp_status & FCP_STATUS_SENSE_LEN) {
		int size = sizeof(Cmnd->sense_buffer);
			if (fcp_sense_info_len < size)
				size = fcp_sense_info_len;
			memcpy(Cmnd->sense_buffer, (char *)ptr_sense_info, size);
		}
		Cmnd->result = fcp_status & FCP_STATUS_MASK;
		(*Cmnd->scsi_done) (Cmnd);
	}
	else
		T_MSG("NULL Command out of handler!");

	invalidate_SEST_entry(fi, received_ox_id);
	s_id = ntohl(*(buff_addr + 3)) & 0x00FFFFFF;
	fi->q.free_scsi_oxid[x_id] = OXID_AVAILABLE;

	/* provide Tachyon will another set of buffers */
	if (offset == (NO_OF_ENTRIES - 1))
		update_SFSBQ_indx(fi);
	LEAVE("handle_SCSI_status_interrupt");
}

static void invalidate_SEST_entry(struct fc_info *fi, u_short received_ox_id)
{
u_short x_id = received_ox_id & MAX_SCSI_XID;
	/* Invalidate SEST entry if it is an OutBound SEST Entry 
	 */
	if (!(received_ox_id & SCSI_READ_BIT)) {
	u_int *ptr_tach_header, *ptr_edb;
	u_short temp_ox_id = NOT_SCSI_XID;
	int i;
		*(fi->q.ptr_sest[x_id]) &= htonl(SEST_INV);

		/* Invalidate the Tachyon Header structure 
		 */
		ptr_tach_header = bus_to_virt(ntohl(*(fi->q.ptr_sest[x_id] + 5)));
		for (i = 0; i < NO_OF_TACH_HEADERS; i++) 
			if(fi->q.ptr_tachyon_header[i] == ptr_tach_header)
				break;
		if (i < NO_OF_TACH_HEADERS) 
			memset(ptr_tach_header, 0xFF, 32);
		else
			T_MSG("Tachyon Header not found while freeing in invalidate_SEST_entry()");

		/* Invalidate the EDB used 
		 */
		ptr_edb = bus_to_virt(ntohl(*(fi->q.ptr_sest[x_id] + 7)));
		for (i = 0; i < EDB_LEN; i++)
			if (fi->q.ptr_edb[i] == ptr_edb)
				break;
		ptr_edb--;	
		if (i < EDB_LEN) {
			do {
				ptr_edb += 2;
				fi->q.free_edb_list[i++] = EDB_FREE;
				if (i == EDB_LEN) {
					i = 0;
					ptr_edb = fi->q.ptr_edb_base - 1;
				}
			} while ((htonl(*ptr_edb) & 0x80000000) != 0x80000000);
		}
		else
			T_MSG("EDB not found while freeing in invalidate_SEST_entry()");
		
		/* Search for its other header structure and destroy it! 
		 */
		if ((ptr_tach_header + 16) < (fi->q.ptr_tachyon_header_base + (MY_PAGE_SIZE/4)))
			ptr_tach_header += 16;
		else
			ptr_tach_header = fi->q.ptr_tachyon_header_base;
		while (temp_ox_id != x_id) {
			temp_ox_id = ntohl(*(ptr_tach_header + 6)) >> 16;
			if (temp_ox_id == x_id) {
				/* Paranoid checking...
				 */
				for (i = 0; i < NO_OF_TACH_HEADERS; i++) 
					if(fi->q.ptr_tachyon_header[i] == ptr_tach_header)
						break;
				if (i < NO_OF_TACH_HEADERS)
					memset(ptr_tach_header, 0xFF, 32);
				else
					T_MSG("Tachyon Header not found while freeing in invalidate_SEST_entry()");
				break;
			}
			else {
				if ((ptr_tach_header + 16) < (fi->q.ptr_tachyon_header_base + (MY_PAGE_SIZE/4)))
					ptr_tach_header += 16;
				else
					ptr_tach_header = fi->q.ptr_tachyon_header_base;
			}
		}
	}
	else {
	u_short sdb_table_indx;
		/* An Inbound Command has completed or needs to be Aborted. 
	 	 * Clear up the SDB buffers.
		 */
		sdb_table_indx = *(fi->q.ptr_sest[x_id] + 5);
		fi->q.sdb_slot_status[sdb_table_indx] = SDB_FREE;
	}
}

static void handle_Inbound_SCSI_Command_interrupt(struct fc_info *fi)
{
u_int *ptr_imq_entry;
int queue_indx, offset;
	ENTER("handle_Inbound_SCSI_Command_interrupt");
	ptr_imq_entry = fi->q.ptr_imqe[fi->q.imq_cons_indx];
	offset = ntohl(*(ptr_imq_entry + 1)) & 0x00000007;
	queue_indx = ntohl(*(ptr_imq_entry + 1)) & 0xFFFF0000;
	queue_indx = queue_indx >> 16;
	/* We discard the SCSI frame as we shouldn't be receiving
	 * a SCSI Command in the first place 
	 */
	/* provide Tachyon will another set of buffers */
	if (offset == (NO_OF_ENTRIES - 1))
		update_SFSBQ_indx(fi);
	LEAVE("handle_Inbound_SCSI_Command_interrupt");
}

static void handle_SFS_interrupt(struct fc_info *fi)
{
u_int *ptr_imq_entry, *buff_addr;
u_int class_of_frame, type_of_frame, s_id, els_type = 0, rctl;
int queue_indx, offset, payload_size, login_state;
u_short received_ox_id, fs_cmnd_code;
	ENTER("handle_SFS_interrupt");
	ptr_imq_entry = fi->q.ptr_imqe[fi->q.imq_cons_indx];
	offset = ntohl(*(ptr_imq_entry + 1)) & 0x00000007;
	queue_indx = ntohl(*(ptr_imq_entry + 1)) & 0xFFFF0000;
	queue_indx = queue_indx >> 16;
	DPRINTK("queue_indx = %d, offset  = %d\n", queue_indx, offset);
	payload_size = ntohl(*(ptr_imq_entry + 2));
	DPRINTK("payload_size = %d", payload_size);

	buff_addr = bus_to_virt(ntohl(*(fi->q.ptr_sfsbq_base + queue_indx*NO_OF_ENTRIES + offset)));

	/* extract Type of Frame */
	type_of_frame = ntohl(*(buff_addr + 4)) & 0xFF000000;
	s_id = ntohl(*(buff_addr + 3)) & 0x00FFFFFF;
	received_ox_id = ntohl(*(buff_addr + 6)) >> 16;
	switch(type_of_frame) {
		case TYPE_BLS:
			rctl = ntohl(*(buff_addr + 2)) & 0xFF000000;
			switch(rctl) {
				case RCTL_BASIC_ABTS:
					/* As an Initiator, we should never be receiving 
					 * this.
		 			 */
					DPRINTK1("ABTS received from S_ID 0x%x with OX_ID = %x", s_id, received_ox_id);
					break;
			}
			break;
		case TYPE_ELS:
			class_of_frame = ntohl(*(buff_addr + 8));
			login_state = sid_logged_in(fi, s_id);
			switch(class_of_frame & 0xFF000000) {
				case ELS_PLOGI:
					if (s_id != fi->g.my_id) {
						u_int ret_code;
						DPRINTK1("PLOGI received from D_ID 0x%x with 0X_ID = %x", s_id, received_ox_id);
						if ((ret_code = plogi_ok(fi, buff_addr, payload_size)) == 0){
							tx_logi_acc(fi, ELS_ACC, s_id, received_ox_id);
							add_to_address_cache(fi, buff_addr);
						}
						else {
							u_short cmnd_code = ret_code >> 16;
							u_short expln_code =  ret_code;
							tx_ls_rjt(fi, s_id, received_ox_id, cmnd_code, expln_code);
						}
					}
					break;
				case ELS_ACC:
					els_type = remove_from_ox_id_list(fi, received_ox_id);
					DPRINTK1("ELS_ACC received from D_ID 0x%x in response to ELS %x", s_id, els_type);
					switch(els_type) {
						case ELS_PLOGI:
							add_to_address_cache(fi, buff_addr);
							tx_prli(fi, ELS_PRLI, s_id, OX_ID_FIRST_SEQUENCE);
							break;
						case ELS_FLOGI:
							add_to_address_cache(fi, buff_addr);
							fi->g.my_id = ntohl(*(buff_addr + 2)) & 0x00FFFFFF;
							fi->g.fabric_present = TRUE;
							fi->g.my_ddaa = fi->g.my_id & 0xFFFF00;
							/* Login to the Name Server 
							 */
							tx_logi(fi, ELS_PLOGI, DIRECTORY_SERVER); 
							break;
						case ELS_NS_PLOGI:
							fi->g.name_server = TRUE;
							add_to_address_cache(fi, buff_addr);
							tx_name_server_req(fi, FCS_RFC_4);
							tx_scr(fi);
							/* Some devices have a delay before 
							 * registering with the Name Server 
							 */
							udelay(500); 
							tx_name_server_req(fi, FCS_GP_ID4);
							break;
						case ELS_PRLI:
							mark_scsi_sid(fi, buff_addr, ADD_ENTRY);
							break;
						case ELS_ADISC:
							if (!(validate_login(fi, buff_addr)))
								tx_logo(fi, s_id, OX_ID_FIRST_SEQUENCE);
							break;
					}
					break;
				case ELS_PDISC:
					DPRINTK1("ELS_PDISC received from D_ID 0x%x", s_id);
					tx_logo(fi, s_id, received_ox_id);
					break;
				case ELS_ADISC:
					DPRINTK1("ELS_ADISC received from D_ID 0x%x", s_id);
					if (node_logged_in_prev(fi, buff_addr))
						tx_adisc(fi, ELS_ACC, s_id, received_ox_id);
					else
						tx_logo(fi, s_id, received_ox_id);
					break;
				case ELS_PRLI:
					DPRINTK1("ELS_PRLI received from D_ID 0x%x", s_id);
					if ((login_state == NODE_LOGGED_IN) || (login_state == NODE_PROCESS_LOGGED_IN)) {
						tx_prli(fi, ELS_ACC, s_id, received_ox_id);
						mark_scsi_sid(fi, buff_addr, ADD_ENTRY);
					}
					else
						tx_logo(fi, s_id, received_ox_id);
					break;
				case ELS_PRLO:
					DPRINTK1("ELS_PRLO received from D_ID 0x%x", s_id);
					if ((login_state == NODE_LOGGED_OUT) || (login_state == NODE_NOT_PRESENT))
						tx_logo(fi, s_id, received_ox_id);
					else
					if (login_state == NODE_LOGGED_IN)

						tx_ls_rjt(fi, s_id, received_ox_id, CMND_NOT_SUPP, NO_EXPLN);
					else
					if (login_state == NODE_PROCESS_LOGGED_IN) {
						tx_prli(fi, ELS_ACC, s_id, received_ox_id);
						mark_scsi_sid(fi, buff_addr, DELETE_ENTRY);
					}
					break;
				case ELS_LS_RJT:
					els_type = remove_from_ox_id_list(fi, received_ox_id);
					DPRINTK1("ELS_LS_RJT received from D_ID 0x%x in response to %x", s_id, els_type);
					/* We should be chking the reason code.
					 */
					switch (els_type) {
						case ELS_ADISC:
							tx_logi(fi, ELS_PLOGI, s_id);
							break;
					}		
					break;
				case ELS_LOGO:
					els_type = remove_from_ox_id_list(fi, received_ox_id);
					DPRINTK1("ELS_LOGO received from D_ID 0x%x in response to %x", s_id, els_type);
					remove_from_address_cache(fi, buff_addr, ELS_LOGO);
					tx_acc(fi, s_id, received_ox_id);
					if (els_type == ELS_ADISC)
						tx_logi(fi, ELS_PLOGI, s_id);
					break;
				case ELS_RSCN:
					DPRINTK1("ELS_RSCN received from D_ID 0x%x", s_id);
					tx_acc(fi, s_id, received_ox_id);
					remove_from_address_cache(fi, buff_addr, ELS_RSCN);
					break;
				case ELS_FARP_REQ:
					/* We do not support FARP.
					   So, silently discard it */
					DPRINTK1("ELS_FARP_REQ received from D_ID 0x%x", s_id);
					break;
				case ELS_ABTX:
					DPRINTK1("ELS_ABTX received from D_ID 0x%x", s_id);
					if ((login_state == NODE_LOGGED_IN) || (login_state == NODE_PROCESS_LOGGED_IN))
						tx_ls_rjt(fi, s_id, received_ox_id, CMND_NOT_SUPP, NO_EXPLN);
					else
						tx_logo(fi, s_id, received_ox_id);
					break;
				case ELS_FLOGI:
					DPRINTK1("ELS_FLOGI received from D_ID 0x%x", s_id);
					if (fi->g.ptp_up == TRUE) {
						/* The node could have come up as an N_Port
						 * in a Loop! So,try initializing as an NL_port
						 */
						take_tachyon_offline(fi);
						/* write AL_TIME & E_D_TOV into the registers */
						writel(TOV_VALUES, fi->t_r.ptr_fm_tov_reg);
						writel(LOOP_INIT_SOFT_ADDRESS, fi->t_r.ptr_fm_config_reg);
						DPRINTK1("FLOGI received, TACHYON initializing as L_Port...\n");
						writel(INITIALIZE, fi->t_r.ptr_fm_control_reg);
					}
					else {
						if ((login_state == NODE_LOGGED_IN) || (login_state == NODE_PROCESS_LOGGED_IN))
							tx_ls_rjt(fi, s_id, received_ox_id, CMND_NOT_SUPP, NO_EXPLN);
						else
							tx_logo(fi, s_id, received_ox_id);
					}
					break;
				case ELS_ADVC:
					DPRINTK1("ELS_ADVC received from D_ID 0x%x", s_id);
					if ((login_state == NODE_LOGGED_IN) || (login_state == NODE_PROCESS_LOGGED_IN))
						tx_ls_rjt(fi, s_id, received_ox_id, CMND_NOT_SUPP, NO_EXPLN);
					else
						tx_logo(fi, s_id, received_ox_id);
					break;
				case ELS_ECHO:
					DPRINTK1("ELS_ECHO received from D_ID 0x%x", s_id);
					if ((login_state == NODE_LOGGED_IN) || (login_state == NODE_PROCESS_LOGGED_IN))
						tx_ls_rjt(fi, s_id, received_ox_id, CMND_NOT_SUPP, NO_EXPLN);
					else
						tx_logo(fi, s_id, received_ox_id);
					break;
				case ELS_ESTC:
					DPRINTK1("ELS_ESTC received from D_ID 0x%x", s_id);
					if ((login_state == NODE_LOGGED_IN) || (login_state == NODE_PROCESS_LOGGED_IN))
						tx_ls_rjt(fi, s_id, received_ox_id, CMND_NOT_SUPP, NO_EXPLN);
					else
						tx_logo(fi, s_id, received_ox_id);
					break;
				case ELS_ESTS:
					DPRINTK1("ELS_ESTS received from D_ID 0x%x", s_id);
					if ((login_state == NODE_LOGGED_IN) || (login_state == NODE_PROCESS_LOGGED_IN))
						tx_ls_rjt(fi, s_id, received_ox_id, CMND_NOT_SUPP, NO_EXPLN);
					else
						tx_logo(fi, s_id, received_ox_id);
					break;
				case ELS_RCS:
					DPRINTK1("ELS_RCS received from D_ID 0x%x", s_id);
					if ((login_state == NODE_LOGGED_IN) || (login_state == NODE_PROCESS_LOGGED_IN))
						tx_ls_rjt(fi, s_id, received_ox_id, CMND_NOT_SUPP, NO_EXPLN);
					else
						tx_logo(fi, s_id, received_ox_id);
					break;
				case ELS_RES:
					DPRINTK1("ELS_RES received from D_ID 0x%x", s_id);
					if ((login_state == NODE_LOGGED_IN) || (login_state == NODE_PROCESS_LOGGED_IN))
						tx_ls_rjt(fi, s_id, received_ox_id, CMND_NOT_SUPP, NO_EXPLN);
					else
						tx_logo(fi, s_id, received_ox_id);
					break;
				case ELS_RLS:
					DPRINTK1("ELS_RLS received from D_ID 0x%x", s_id);
					if ((login_state == NODE_LOGGED_IN) || (login_state == NODE_PROCESS_LOGGED_IN))
						tx_ls_rjt(fi, s_id, received_ox_id, CMND_NOT_SUPP, NO_EXPLN);
					else
						tx_logo(fi, s_id, received_ox_id);
					break;
				case ELS_RRQ:
					DPRINTK1("ELS_RRQ received from D_ID 0x%x", s_id);
					if ((login_state == NODE_LOGGED_IN) || (login_state == NODE_PROCESS_LOGGED_IN))
						tx_ls_rjt(fi, s_id, received_ox_id, CMND_NOT_SUPP, NO_EXPLN);
					else
						tx_logo(fi, s_id, received_ox_id);
					break;
				case ELS_RSS:
					DPRINTK1("ELS_RSS received from D_ID 0x%x", s_id);
					if ((login_state == NODE_LOGGED_IN) || (login_state == NODE_PROCESS_LOGGED_IN))
						tx_ls_rjt(fi, s_id, received_ox_id, CMND_NOT_SUPP, NO_EXPLN);
					else
						tx_logo(fi, s_id, received_ox_id);
					break;
				case ELS_RTV:
					DPRINTK1("ELS_RTV received from D_ID 0x%x", s_id);
					if ((login_state == NODE_LOGGED_IN) || (login_state == NODE_PROCESS_LOGGED_IN))
						tx_ls_rjt(fi, s_id, received_ox_id, CMND_NOT_SUPP, NO_EXPLN);
					else
						tx_logo(fi, s_id, received_ox_id);
					break;
				case ELS_RSI:
					DPRINTK1("ELS_RSI received from D_ID 0x%x", s_id);
					if ((login_state == NODE_LOGGED_IN) || (login_state == NODE_PROCESS_LOGGED_IN))
						tx_ls_rjt(fi, s_id, received_ox_id, CMND_NOT_SUPP, NO_EXPLN);
					else
						tx_logo(fi, s_id, received_ox_id);
					break;
				case ELS_TEST:
					/* No reply sequence */
					DPRINTK1("ELS_TEST received from D_ID 0x%x", s_id);
					break;
				case ELS_RNC:
					DPRINTK1("ELS_RNC received from D_ID 0x%x", s_id);
					if ((login_state == NODE_LOGGED_IN) || (login_state == NODE_PROCESS_LOGGED_IN))
						tx_ls_rjt(fi, s_id, received_ox_id, CMND_NOT_SUPP, NO_EXPLN);
					else
						tx_logo(fi, s_id, received_ox_id);
					break;
				case ELS_RVCS:
					DPRINTK1("ELS_RVCS received from D_ID 0x%x", s_id);
					if ((login_state == NODE_LOGGED_IN) || (login_state == NODE_PROCESS_LOGGED_IN))
						tx_ls_rjt(fi, s_id, received_ox_id, CMND_NOT_SUPP, NO_EXPLN);
					else
						tx_logo(fi, s_id, received_ox_id);
					break;
				case ELS_TPLS:
					DPRINTK1("ELS_TPLS received from D_ID 0x%x", s_id);
					if ((login_state == NODE_LOGGED_IN) || (login_state == NODE_PROCESS_LOGGED_IN))
						tx_ls_rjt(fi, s_id, received_ox_id, CMND_NOT_SUPP, NO_EXPLN);
					else
						tx_logo(fi, s_id, received_ox_id);
					break;
				case ELS_GAID:
					DPRINTK1("ELS_GAID received from D_ID 0x%x", s_id);
					if ((login_state == NODE_LOGGED_IN) || (login_state == NODE_PROCESS_LOGGED_IN))
						tx_ls_rjt(fi, s_id, received_ox_id, CMND_NOT_SUPP, NO_EXPLN);
					else
						tx_logo(fi, s_id, received_ox_id);
					break;
				case ELS_FACT:
					DPRINTK1("ELS_FACT received from D_ID 0x%x", s_id);
					if ((login_state == NODE_LOGGED_IN) || (login_state == NODE_PROCESS_LOGGED_IN))
						tx_ls_rjt(fi, s_id, received_ox_id, CMND_NOT_SUPP, NO_EXPLN);
					else
						tx_logo(fi, s_id, received_ox_id);
					break;
				case ELS_FAN:
					/* Hmmm... You don't support FAN ??? */
					DPRINTK1("ELS_FAN received from D_ID 0x%x", s_id);
					tx_ls_rjt(fi, s_id, received_ox_id, CMND_NOT_SUPP, NO_EXPLN);
					break;
				case ELS_FDACT:
					DPRINTK1("ELS_FDACT received from D_ID 0x%x", s_id);
					if ((login_state == NODE_LOGGED_IN) || (login_state == NODE_PROCESS_LOGGED_IN))
						tx_ls_rjt(fi, s_id, received_ox_id, CMND_NOT_SUPP, NO_EXPLN);
					else
						tx_logo(fi, s_id, received_ox_id);
					break;
				case ELS_NACT:
					DPRINTK1("ELS_NACT received from D_ID 0x%x", s_id);
					if ((login_state == NODE_LOGGED_IN) || (login_state == NODE_PROCESS_LOGGED_IN))
						tx_ls_rjt(fi, s_id, received_ox_id, CMND_NOT_SUPP, NO_EXPLN);
					else
						tx_logo(fi, s_id, received_ox_id);
					break;
				case ELS_NDACT:
					DPRINTK1("ELS_NDACT received from D_ID 0x%x", s_id);
					if ((login_state == NODE_LOGGED_IN) || (login_state == NODE_PROCESS_LOGGED_IN))
						tx_ls_rjt(fi, s_id, received_ox_id, CMND_NOT_SUPP, NO_EXPLN);
					else
						tx_logo(fi, s_id, received_ox_id);
					break;
				case ELS_QoSR:
					DPRINTK1("ELS_QoSR received from D_ID 0x%x", s_id);
					if ((login_state == NODE_LOGGED_IN) || (login_state == NODE_PROCESS_LOGGED_IN))
						tx_ls_rjt(fi, s_id, received_ox_id, CMND_NOT_SUPP, NO_EXPLN);
					else
						tx_logo(fi, s_id, received_ox_id);
					break;
				case ELS_FDISC:
					DPRINTK1("ELS_FDISC received from D_ID 0x%x", s_id);
					if ((login_state == NODE_LOGGED_IN) || (login_state == NODE_PROCESS_LOGGED_IN))
						tx_ls_rjt(fi, s_id, received_ox_id, CMND_NOT_SUPP, NO_EXPLN);
					else
						tx_logo(fi, s_id, received_ox_id);
					break;
				default:
					DPRINTK1("ELS Frame %x received from D_ID 0x%x", class_of_frame, s_id);
					if ((login_state == NODE_LOGGED_IN) || (login_state == NODE_PROCESS_LOGGED_IN))
						tx_ls_rjt(fi, s_id, received_ox_id, CMND_NOT_SUPP, NO_EXPLN);
					else
						tx_logo(fi, s_id, received_ox_id);
					break;
			}
			break;
		case TYPE_FC_SERVICES:
			fs_cmnd_code = (ntohl(*(buff_addr + 10)) & 0xFFFF0000) >>16;
			switch(fs_cmnd_code) {
				case FCS_ACC:
					els_type = remove_from_ox_id_list(fi, received_ox_id);
					DPRINTK1("FCS_ACC received from D_ID 0x%x in response to %x", s_id, els_type);
					if (els_type == FCS_GP_ID4) 
						explore_fabric(fi, buff_addr);
					break;
				case FCS_REJECT:
					DPRINTK1("FCS_REJECT received from D_ID 0x%x in response to %x", s_id, els_type);
					break;
			}
			break;
		case TYPE_LLC_SNAP:
			rx_net_packet(fi, (u_char *)buff_addr, payload_size);
			break;
		default:
			T_MSG("Frame Type %x received from %x", type_of_frame, s_id);
	}

	/* provide Tachyon will another set of buffers */
	if (offset == (NO_OF_ENTRIES - 1))
		update_SFSBQ_indx(fi);
	LEAVE("handle_SFS_interrupt");
}

static void handle_FM_interrupt(struct fc_info *fi)
{
u_int fm_status;
u_int tachyon_status;

	ENTER("handle_FM_interrupt");
	fm_status = readl(fi->t_r.ptr_fm_status_reg);
	tachyon_status = readl(fi->t_r.ptr_tach_status_reg);
	DPRINTK("FM_status = %x, Tachyon_status = %x", fm_status, tachyon_status);
	if (fm_status & LINK_DOWN) {
		T_MSG("Fibre Channel Link DOWN");
		fm_status = readl(fi->t_r.ptr_fm_status_reg);
		
		del_timer(&fi->explore_timer);
		del_timer(&fi->nport_timer);
		del_timer(&fi->lport_timer);
		del_timer(&fi->display_cache_timer);
		fi->g.link_up = FALSE;
		if (fi->g.ptp_up == TRUE)
			fi->g.n_port_try = FALSE;
		fi->g.ptp_up = FALSE;
		fi->g.port_discovery = FALSE;
		fi->g.explore_fabric = FALSE;
		fi->g.perform_adisc = FALSE;

		/* Logout will all nodes */
		if (fi->node_info_list) {
			struct fc_node_info *temp_list = fi->node_info_list;
				while(temp_list) {
					temp_list->login = LOGIN_ATTEMPTED;
					temp_list = temp_list->next;
				}
				fi->num_nodes = 0;
		}

		if ((fi->g.n_port_try == FALSE) && (fi->g.dont_init == FALSE)){
			take_tachyon_offline(fi);
			/* write AL_TIME & E_D_TOV into the registers */
			writel(TOV_VALUES, fi->t_r.ptr_fm_tov_reg);
			
			if ((fi->g.fabric_present == TRUE) && (fi->g.loop_up == TRUE)) {
			u_int al_pa = fi->g.my_id & 0xFF;
				writel((al_pa << 24) | LOOP_INIT_FABRIC_ADDRESS | LOOP_INIT_PREVIOUS_ADDRESS, fi->t_r.ptr_fm_config_reg);
			}
			else 
			if (fi->g.loop_up == TRUE) {
			u_int al_pa = fi->g.my_id & 0xFF;
				writel((al_pa << 24) | LOOP_INIT_PREVIOUS_ADDRESS, fi->t_r.ptr_fm_config_reg);
			}
			else 
				writel(LOOP_INIT_SOFT_ADDRESS, fi->t_r.ptr_fm_config_reg);
			fi->g.loop_up = FALSE;
			DPRINTK1("In LDWN TACHYON initializing as L_Port...\n");
			writel(INITIALIZE, fi->t_r.ptr_fm_control_reg);
		}
	}

    if (fm_status & NON_PARTICIPATING) {
	  	T_MSG("Did not acquire an AL_PA. I am not participating");
    }
	else
	if ((fm_status & LINK_UP) && ((fm_status & LINK_DOWN) == 0)) {
	  T_MSG("Fibre Channel Link UP");
	  if ((fm_status & NON_PARTICIPATING) != TRUE) {
		fi->g.link_up = TRUE;
		if (tachyon_status & OSM_FROZEN) {
			reset_tachyon(fi, ERROR_RELEASE);
			reset_tachyon(fi, OCQ_RESET);
		}
		init_timer(&fi->explore_timer);
		init_timer(&fi->nport_timer);
		init_timer(&fi->lport_timer);
		init_timer(&fi->display_cache_timer);
		if ((fm_status & OLD_PORT) == 0) {
			fi->g.loop_up = TRUE;
			fi->g.ptp_up = FALSE;
			fi->g.my_id = readl(fi->t_r.ptr_fm_config_reg) >> 24;
			DPRINTK1("My AL_PA = %x", fi->g.my_id);
			fi->g.port_discovery = TRUE;
			fi->g.explore_fabric = FALSE;
		}
		else
		if (((fm_status & 0xF0) == OLD_PORT) && ((fm_status & 0x0F) == PORT_STATE_ACTIVE)) {
			fi->g.loop_up = FALSE;
			fi->g.my_id = 0x0;
			/* In a point-to-point configuration, we expect to be
			 * connected to an F_Port. This driver does not yet support
			 * a configuration where it is connected to another N_Port
			 * directly.
			 */
			fi->g.explore_fabric = TRUE;
			fi->g.port_discovery = FALSE;
			if (fi->g.n_port_try == FALSE) {
				take_tachyon_offline(fi);
				/* write R_T_TOV & E_D_TOV into the registers */
				writel(PTP_TOV_VALUES, fi->t_r.ptr_fm_tov_reg);
				writel(BB_CREDIT | NPORT, fi->t_r.ptr_fm_config_reg);
				fi->g.n_port_try = TRUE;
				DPRINTK1("In LUP TACHYON initializing as N_Port...\n");
				writel(INITIALIZE, fi->t_r.ptr_fm_control_reg);
			}
			else {
				fi->g.ptp_up = TRUE;
				tx_logi(fi, ELS_FLOGI, F_PORT); 
			}
		}
		fi->g.my_ddaa = 0x0;
		fi->g.fabric_present = FALSE; 
		/* We havn't sent out any Name Server Reqs */
		fi->g.name_server = FALSE;
		fi->g.alpa_list_index = 0;
		fi->g.ox_id = NOT_SCSI_XID;
		fi->g.my_mtu = FRAME_SIZE;
		
		/* Implicitly LOGO with all logged-in nodes. 
		 */
		if (fi->node_info_list) {
		struct fc_node_info *temp_list = fi->node_info_list;
			while(temp_list) {
				temp_list->login = LOGIN_ATTEMPTED;
				temp_list = temp_list->next;
			}
			fi->num_nodes = 0;
			fi->g.perform_adisc = TRUE;
			//fi->g.perform_adisc = FALSE;
			fi->g.port_discovery = FALSE;
			tx_logi(fi, ELS_FLOGI, F_PORT); 
		}
		else { 
			/* If Link coming up for the _first_ time or no nodes
			 * were logged in before...
			 */
			fi->g.scsi_oxid = 0;
			fi->g.seq_id = 0x00;
			fi->g.perform_adisc = FALSE;
		}

		/* reset OX_ID table */
		while (fi->ox_id_list) {
		struct ox_id_els_map *temp = fi->ox_id_list;
			fi->ox_id_list = fi->ox_id_list->next;
			kfree(temp);
		}
		fi->ox_id_list = NULL;
	  } /* End of if partipating */
	}

	if (fm_status & ELASTIC_STORE_ERROR) {
		/* Too much junk on the Link 
		 */
		/* Trying to clear it up by Txing PLOGI to urself */
		if (fi->g.link_up == TRUE)
			tx_logi(fi, ELS_PLOGI, fi->g.my_id); 
	}

	if (fm_status & LOOP_UP) {
		if (tachyon_status & OSM_FROZEN) {
			reset_tachyon(fi, ERROR_RELEASE);
			reset_tachyon(fi, OCQ_RESET);
		}
	}
	
	if (fm_status & NOS_OLS_RECEIVED){
		if (fi->g.nport_timer_set == FALSE) {
			DPRINTK("NOS/OLS Received");
			DPRINTK("FM_status = %x", fm_status);
			fi->nport_timer.function = nos_ols_timer;
			fi->nport_timer.data = (unsigned long)fi;
			fi->nport_timer.expires = RUN_AT((3*HZ)/100); /* 30 msec */
			init_timer(&fi->nport_timer);
			add_timer(&fi->nport_timer);
			fi->g.nport_timer_set = TRUE;
		}
	}

	if (((fm_status & 0xF0) == OLD_PORT) && (((fm_status & 0x0F) == PORT_STATE_LF1) || ((fm_status & 0x0F) == PORT_STATE_LF2))) {
		DPRINTK1("Link Fail-I in OLD-PORT.");
		take_tachyon_offline(fi);
		reset_tachyon(fi, SOFTWARE_RESET);
	}

	if (fm_status & LOOP_STATE_TIMEOUT){
		if ((fm_status & 0xF0) == ARBITRATING) 
			DPRINTK1("ED_TOV timesout.In ARBITRATING state...");
		if ((fm_status & 0xF0) == ARB_WON)
			DPRINTK1("ED_TOV timesout.In ARBITRATION WON state...");
		if ((fm_status & 0xF0) == OPEN)
			DPRINTK1("ED_TOV timesout.In OPEN state...");
		if ((fm_status & 0xF0) == OPENED)
			DPRINTK1("ED_TOV timesout.In OPENED state...");
		if ((fm_status & 0xF0) == TX_CLS)
			DPRINTK1("ED_TOV timesout.In XMITTED CLOSE state...");
		if ((fm_status & 0xF0) == RX_CLS)
			DPRINTK1("ED_TOV timesout.In RECEIVED CLOSE state...");
		if ((fm_status & 0xF0) == INITIALIZING)
			DPRINTK1("ED_TOV timesout.In INITIALIZING state...");
		DPRINTK1("Initializing Loop...");
		writel(INITIALIZE, fi->t_r.ptr_fm_control_reg);
	}
	
	if ((fm_status & BAD_ALPA) && (fi->g.loop_up == TRUE)) {
	u_char bad_alpa = (readl(fi->t_r.ptr_fm_rx_al_pa_reg) & 0xFF00) >> 8;
		if (tachyon_status & OSM_FROZEN) {
			reset_tachyon(fi, ERROR_RELEASE);
			reset_tachyon(fi, OCQ_RESET);
		}
		/* Fix for B34 */
		tx_logi(fi, ELS_PLOGI, fi->g.my_id); 

		if (!fi->g.port_discovery && !fi->g.perform_adisc) {
			if (bad_alpa != 0xFE)
				DPRINTK("Bad AL_PA = %x", bad_alpa);
		}
		else {
			if ((fi->g.perform_adisc == TRUE) && (bad_alpa == 0x00)) {
				DPRINTK1("Performing ADISC...");
				fi->g.fabric_present = FALSE;
				perform_adisc(fi);
			}
		}
	}
	
	if (fm_status & LIPF_RECEIVED){
		DPRINTK("LIP(F8) Received");
	}

	if (fm_status & LINK_FAILURE) {
		if (fm_status & LOSS_OF_SIGNAL)
			DPRINTK1("Detected Loss of Signal.");
		if (fm_status & OUT_OF_SYNC)
			DPRINTK1("Detected Loss of Synchronization.");
	}

	if (fm_status & TRANSMIT_PARITY_ERROR) {
		/* Bad! Should not happen. Solution-> Hard Reset.
		 */
		T_MSG("Parity Error. Perform Hard Reset!");
	}

	if (fi->g.alpa_list_index >= MAX_NODES){
		if (fi->g.port_discovery == TRUE) {
			fi->g.port_discovery = FALSE;
			add_display_cache_timer(fi);
		}
		fi->g.alpa_list_index = MAX_NODES;
	}

	if (fi->g.port_discovery == TRUE) 
		local_port_discovery(fi);

	LEAVE("handle_FM_interrupt");
	return;
}

static void local_port_discovery(struct fc_info *fi)
{
	if (fi->g.loop_up == TRUE) {
		/* If this is not here, some of the Bad AL_PAs are missed. 
		 */
		udelay(20); 
		if ((fi->g.alpa_list_index == 0) && (fi->g.fabric_present == FALSE)){
			tx_logi(fi, ELS_FLOGI, F_PORT); 
		}
		else {
		int login_state = sid_logged_in(fi, fi->g.my_ddaa | alpa_list[fi->g.alpa_list_index]);
			while ((fi->g.alpa_list_index == 0) || ((fi->g.alpa_list_index < MAX_NODES) && ((login_state == NODE_LOGGED_IN) || (login_state == NODE_PROCESS_LOGGED_IN) || (alpa_list[fi->g.alpa_list_index] == (fi->g.my_id & 0xFF)))))
				fi->g.alpa_list_index++;
			if (fi->g.alpa_list_index < MAX_NODES)
				tx_logi(fi, ELS_PLOGI, alpa_list[fi->g.alpa_list_index]); 
		}
		fi->g.alpa_list_index++;
		if (fi->g.alpa_list_index >= MAX_NODES){
			if (fi->g.port_discovery == TRUE) {
				fi->g.port_discovery = FALSE;
				add_display_cache_timer(fi);
			}
			fi->g.alpa_list_index = MAX_NODES;
		}
	}
}

static void nos_ols_timer(unsigned long data)
{
struct fc_info *fi = (struct fc_info*)data;
u_int fm_status;
	fm_status = readl(fi->t_r.ptr_fm_status_reg);
	DPRINTK1("FM_status in timer= %x", fm_status);
	fi->g.nport_timer_set = FALSE;
	del_timer(&fi->nport_timer);
	if ((fi->g.ptp_up == TRUE) || (fi->g.loop_up == TRUE))
		return;
	if (((fm_status & 0xF0) == OLD_PORT) && (((fm_status & 0x0F) == PORT_STATE_ACTIVE) || ((fm_status & 0x0F) == PORT_STATE_OFFLINE))) {
		DPRINTK1("In OLD-PORT after E_D_TOV.");
		take_tachyon_offline(fi);
		/* write R_T_TOV & E_D_TOV into the registers */
		writel(PTP_TOV_VALUES, fi->t_r.ptr_fm_tov_reg);
		writel(BB_CREDIT | NPORT, fi->t_r.ptr_fm_config_reg);
		fi->g.n_port_try = TRUE;
		DPRINTK1("In timer, TACHYON initializing as N_Port...\n");
		writel(INITIALIZE, fi->t_r.ptr_fm_control_reg);
	}
	else
	if ((fi->g.lport_timer_set == FALSE) && ((fm_status & 0xF0) == LOOP_FAIL)) {
		DPRINTK1("Loop Fail after E_D_TOV.");
		fi->lport_timer.function = loop_timer;
		fi->lport_timer.data = (unsigned long)fi;
		fi->lport_timer.expires = RUN_AT((8*HZ)/100); 
		init_timer(&fi->lport_timer);
		add_timer(&fi->lport_timer);
		fi->g.lport_timer_set = TRUE;
		take_tachyon_offline(fi);
		reset_tachyon(fi, SOFTWARE_RESET);
	}
	else
	if (((fm_status & 0xF0) == OLD_PORT) && (((fm_status & 0x0F) == PORT_STATE_LF1) || ((fm_status & 0x0F) == PORT_STATE_LF2))) {
		DPRINTK1("Link Fail-II in OLD-PORT.");
		take_tachyon_offline(fi);
		reset_tachyon(fi, SOFTWARE_RESET);
	}
}

static void loop_timer(unsigned long data)
{
struct fc_info *fi = (struct fc_info*)data;
	fi->g.lport_timer_set = FALSE;
	del_timer(&fi->lport_timer);
	if ((fi->g.ptp_up == TRUE) || (fi->g.loop_up == TRUE))
		return;
}

static void add_display_cache_timer(struct fc_info *fi)
{
	fi->display_cache_timer.function = display_cache_timer;
	fi->display_cache_timer.data = (unsigned long)fi;
	fi->display_cache_timer.expires = RUN_AT(fi->num_nodes * HZ); 
	init_timer(&fi->display_cache_timer);
	add_timer(&fi->display_cache_timer);
}

static void display_cache_timer(unsigned long data)
{
struct fc_info *fi = (struct fc_info*)data;
	del_timer(&fi->display_cache_timer);
	display_cache(fi);
	return;
}

static void reset_tachyon(struct fc_info *fi, u_int value)
{
u_int tachyon_status, reset_done = OCQ_RESET_STATUS | SCSI_FREEZE_STATUS;
int not_done = 1, i = 0;
	writel(value, fi->t_r.ptr_tach_control_reg);
	if (value == OCQ_RESET) 
		fi->q.ocq_prod_indx = 0;
	tachyon_status = readl(fi->t_r.ptr_tach_status_reg);

	/* Software resets are immediately done, whereas other aren't. It 
	about 30 clocks to do the reset */
	if (value != SOFTWARE_RESET) {
		while(not_done) {
			if (i++ > 100000) {
				T_MSG("Reset was unsuccessful! Tachyon Status = %x", tachyon_status);
				break;
			}
			tachyon_status = readl(fi->t_r.ptr_tach_status_reg);
			if ((tachyon_status & reset_done) == 0)
				not_done = 0;
		}
	}
	else {
		write_to_tachyon_registers(fi);
	}
}

static void take_tachyon_offline(struct fc_info *fi)
{
u_int fm_status = readl(fi->t_r.ptr_fm_status_reg);

	/* The first two conditions will never be true. The Manual and
	 * the errata say this. But the current implementation is
	 * decently stable.
	 */	 
	//if ((fm_status & 0xF0) == LOOP_FAIL) {
	if (fm_status == LOOP_FAIL) {
		// workaround as in P. 89 
		writel(HOST_CONTROL, fi->t_r.ptr_fm_control_reg);
		if (fi->g.loop_up == TRUE)
			writel(SOFTWARE_RESET, fi->t_r.ptr_tach_control_reg);
		else {
			writel(OFFLINE, fi->t_r.ptr_fm_control_reg);
			writel(EXIT_HOST_CONTROL, fi->t_r.ptr_fm_control_reg);
		}
	}
	else
	//if ((fm_status & LOOP_UP) == LOOP_UP) {
	if (fm_status == LOOP_UP) {
		writel(SOFTWARE_RESET, fi->t_r.ptr_tach_control_reg);
	}
	else
		writel(OFFLINE, fi->t_r.ptr_fm_control_reg);
}


static void read_novram(struct fc_info *fi)
{
int off = 0;
	fi->n_r.ptr_novram_hw_control_reg = fi->i_r.ptr_ichip_hw_control_reg; 
	fi->n_r.ptr_novram_hw_status_reg = fi->i_r.ptr_ichip_hw_status_reg; 
	iph5526_nr_do_init(fi);
	if (fi->clone_id == PCI_VENDOR_ID_INTERPHASE)
		off = 32;
	
	fi->g.my_node_name_high = (fi->n_r.data[off] << 16) | fi->n_r.data[off+1];
	fi->g.my_node_name_low = (fi->n_r.data[off+2] << 16) | fi->n_r.data[off+3];
	fi->g.my_port_name_high = (fi->n_r.data[off+4] << 16) | fi->n_r.data[off+5];
	fi->g.my_port_name_low = (fi->n_r.data[off+6] << 16) | fi->n_r.data[off+7];
	DPRINTK("node_name = %x %x", fi->g.my_node_name_high, fi->g.my_node_name_low);
	DPRINTK("port_name = %x %x", fi->g.my_port_name_high, fi->g.my_port_name_low);
}

static void reset_ichip(struct fc_info *fi)
{
	/* (i)chip reset */
	writel(ICHIP_HCR_RESET, fi->i_r.ptr_ichip_hw_control_reg);
	/*wait for chip to get reset */
	udelay(10000);
	/*de-assert reset */
	writel(ICHIP_HCR_DERESET, fi->i_r.ptr_ichip_hw_control_reg);
	
	/* enable INT lines on the (i)chip */
	writel(ICHIP_HCR_ENABLE_INTA , fi->i_r.ptr_ichip_hw_control_reg);
	/* enable byte swap */
	writel(ICHIP_HAMR_BYTE_SWAP_ADDR_TR, fi->i_r.ptr_ichip_hw_addr_mask_reg);
}

static void tx_logi(struct fc_info *fi, u_int logi, u_int d_id)
{
int int_required = 1;
u_short ox_id = OX_ID_FIRST_SEQUENCE;
u_int r_ctl = RCTL_ELS_UCTL;
u_int type  = TYPE_ELS | SEQUENCE_INITIATIVE | FIRST_SEQUENCE;
u_int my_mtu = fi->g.my_mtu;
	ENTER("tx_logi");
	/* We dont want interrupted for our own logi. 
	 * It screws up the port discovery process. 
	 */
	if (d_id == fi->g.my_id)
		int_required = 0;
	fill_login_frame(fi, logi);	
	fi->g.type_of_frame = FC_ELS;
	memcpy(fi->g.els_buffer[fi->g.e_i], &fi->g.login, sizeof(LOGIN));
	tx_exchange(fi, (char *)(fi->g.els_buffer[fi->g.e_i]),sizeof(LOGIN), r_ctl, type, d_id, my_mtu, int_required, ox_id, logi);
	fi->g.e_i++;
	if (fi->g.e_i == MAX_PENDING_FRAMES)
		fi->g.e_i = 0;
	LEAVE("tx_logi");
	return;
}

static void tx_logi_acc(struct fc_info *fi, u_int logi, u_int d_id, u_short received_ox_id)
{
int int_required = 0;
u_int r_ctl = RCTL_ELS_SCTL;
u_int type  = TYPE_ELS | EXCHANGE_RESPONDER | LAST_SEQUENCE;
u_int my_mtu = fi->g.my_mtu;
	ENTER("tx_logi_acc");
	fill_login_frame(fi, logi);	
	fi->g.type_of_frame = FC_ELS;
	memcpy(fi->g.els_buffer[fi->g.e_i], &fi->g.login, sizeof(LOGIN));
	tx_exchange(fi, (char *)(fi->g.els_buffer[fi->g.e_i]),sizeof(LOGIN), r_ctl, type, d_id, my_mtu, int_required, received_ox_id, logi);
	fi->g.e_i++;
	if (fi->g.e_i == MAX_PENDING_FRAMES)
		fi->g.e_i = 0;
	LEAVE("tx_logi_acc");
	return;
}

static void tx_prli(struct fc_info *fi, u_int command_code, u_int d_id, u_short received_ox_id)
{
int int_required = 1;
u_int r_ctl = RCTL_ELS_UCTL;
u_int type  = TYPE_ELS | SEQUENCE_INITIATIVE | FIRST_SEQUENCE;
u_int my_mtu = fi->g.my_mtu;
	ENTER("tx_prli");
	if (command_code == ELS_PRLI)
		fi->g.prli.cmnd_code = htons((ELS_PRLI | PAGE_LEN) >> 16);
	else {
		fi->g.prli.cmnd_code = htons((ELS_ACC | PAGE_LEN) >> 16);
		int_required = 0;
		type  = TYPE_ELS | EXCHANGE_RESPONDER | LAST_SEQUENCE;
		r_ctl = RCTL_ELS_SCTL;
	}
	fi->g.prli.payload_length = htons(PRLI_LEN);
	fi->g.prli.type_code = htons(FCP_TYPE_CODE);
	fi->g.prli.est_image_pair = htons(IMAGE_PAIR);
	fi->g.prli.responder_pa = 0;
	fi->g.prli.originator_pa = 0;
	fi->g.prli.service_params = htonl(INITIATOR_FUNC | READ_XFER_RDY_DISABLED);
	fi->g.type_of_frame = FC_ELS;
	memcpy(fi->g.els_buffer[fi->g.e_i], &fi->g.prli, sizeof(PRLI));
	tx_exchange(fi, (char *)(fi->g.els_buffer[fi->g.e_i]), sizeof(PRLI), r_ctl, type, d_id, my_mtu, int_required, received_ox_id, command_code);
	fi->g.e_i++;
	if (fi->g.e_i == MAX_PENDING_FRAMES)
		fi->g.e_i = 0;
	LEAVE("tx_prli");
	return;
}

static void tx_logo(struct fc_info *fi, u_int d_id, u_short received_ox_id)
{
int int_required = 1;
u_int r_ctl = RCTL_ELS_UCTL;
u_int type  = TYPE_ELS | EXCHANGE_RESPONDER | SEQUENCE_RESPONDER | FIRST_SEQUENCE | END_SEQUENCE | SEQUENCE_INITIATIVE;
int size = sizeof(LOGO);
char fc_id[3];
u_int my_mtu = fi->g.my_mtu;
	ENTER("tx_logo");
	fi->g.logo.logo_cmnd = htonl(ELS_LOGO);
	fi->g.logo.reserved = 0;
	memcpy(fc_id, &(fi->g.my_id), 3);
	fi->g.logo.n_port_id_0 = fc_id[0];
	fi->g.logo.n_port_id_1 = fc_id[1];
	fi->g.logo.n_port_id_2 = fc_id[2];
	fi->g.logo.port_name_up = htonl(N_PORT_NAME_HIGH);
	fi->g.logo.port_name_low = htonl(N_PORT_NAME_LOW);
	fi->g.type_of_frame = FC_ELS;
	memcpy(fi->g.els_buffer[fi->g.e_i], &fi->g.logo, sizeof(LOGO));
	tx_exchange(fi, (char *)(fi->g.els_buffer[fi->g.e_i]),size, r_ctl, type, d_id, my_mtu, int_required, received_ox_id, ELS_LOGO);
	fi->g.e_i++;
	if (fi->g.e_i == MAX_PENDING_FRAMES)
		fi->g.e_i = 0;
	LEAVE("tx_logo");
}

static void tx_adisc(struct fc_info *fi, u_int cmnd_code, u_int d_id, u_short received_ox_id)
{
int int_required = 0;
u_int r_ctl = RCTL_ELS_SCTL;
u_int type  = TYPE_ELS | EXCHANGE_RESPONDER | SEQUENCE_RESPONDER | FIRST_SEQUENCE | END_SEQUENCE;
int size = sizeof(ADISC);
u_int my_mtu = fi->g.my_mtu;
	fi->g.adisc.ls_cmnd_code = htonl(cmnd_code);
	fi->g.adisc.hard_address = htonl(0);
	fi->g.adisc.port_name_high = htonl(N_PORT_NAME_HIGH);	
	fi->g.adisc.port_name_low = htonl(N_PORT_NAME_LOW);	
	fi->g.adisc.node_name_high = htonl(NODE_NAME_HIGH);	
	fi->g.adisc.node_name_low = htonl(NODE_NAME_LOW);	
	fi->g.adisc.n_port_id = htonl(fi->g.my_id);
	if (cmnd_code == ELS_ADISC) {
		int_required = 1;
		r_ctl = RCTL_ELS_UCTL;
		type  = TYPE_ELS | SEQUENCE_INITIATIVE | FIRST_SEQUENCE;
	}
	fi->g.type_of_frame = FC_ELS;
	memcpy(fi->g.els_buffer[fi->g.e_i], &fi->g.adisc, size);
	tx_exchange(fi, (char *)(fi->g.els_buffer[fi->g.e_i]),size, r_ctl, type, d_id, my_mtu, int_required, received_ox_id, cmnd_code);
	fi->g.e_i++;
	if (fi->g.e_i == MAX_PENDING_FRAMES)
		fi->g.e_i = 0;
}

static void tx_ls_rjt(struct fc_info *fi, u_int d_id, u_short received_ox_id, u_short reason_code, u_short expln_code)
{
int int_required = 0;
u_int r_ctl = RCTL_ELS_SCTL;
u_int type  = TYPE_ELS | EXCHANGE_RESPONDER | LAST_SEQUENCE;
int size = sizeof(LS_RJT);
u_int my_mtu = fi->g.my_mtu;
	ENTER("tx_ls_rjt");
	fi->g.ls_rjt.cmnd_code = htonl(ELS_LS_RJT);
	fi->g.ls_rjt.reason_code = htonl((reason_code << 16) | expln_code); 
	fi->g.type_of_frame = FC_ELS;
	memcpy(fi->g.els_buffer[fi->g.e_i], &fi->g.ls_rjt, size);
	tx_exchange(fi, (char *)(fi->g.els_buffer[fi->g.e_i]),size, r_ctl, type, d_id, my_mtu, int_required, received_ox_id, ELS_LS_RJT);
	fi->g.e_i++;
	if (fi->g.e_i == MAX_PENDING_FRAMES)
		fi->g.e_i = 0;
	LEAVE("tx_ls_rjt");
}

static void tx_abts(struct fc_info *fi, u_int d_id, u_short ox_id)
{
int int_required = 1;
u_int r_ctl = RCTL_BASIC_ABTS;
u_int type  = TYPE_BLS | SEQUENCE_INITIATIVE | FIRST_SEQUENCE;
int size = 0;
u_int my_mtu = fi->g.my_mtu;
	ENTER("tx_abts");
	fi->g.type_of_frame = FC_BLS;
	tx_exchange(fi, NULL, size, r_ctl, type, d_id, my_mtu, int_required, ox_id, RCTL_BASIC_ABTS);
	LEAVE("tx_abts");
}

static u_int plogi_ok(struct fc_info *fi, u_int *buff_addr, int size)
{
int ret_code = 0;
u_short mtu = ntohl(*(buff_addr + 10)) & 0x00000FFF;
u_short class3 = ntohl(*(buff_addr + 25)) >> 16;
u_short class3_conc_seq = ntohl(*(buff_addr + 27)) >> 16;
u_short open_seq = ntohl(*(buff_addr + 28)) >> 16;
	DPRINTK1("mtu = %x class3 = %x conc_seq = %x open_seq = %x", mtu, class3, class3_conc_seq, open_seq);	
	size -= TACHYON_HEADER_LEN;
	if (!(class3 & 0x8000)) {
		DPRINTK1("Received PLOGI with class3 = %x", class3);
		ret_code = (LOGICAL_ERR << 16) | NO_EXPLN;
		return ret_code;
	}
	if (mtu < 256) {
		DPRINTK1("Received PLOGI with MTU set to %x", mtu);
		ret_code = (LOGICAL_ERR << 16) | RECV_FIELD_SIZE;
		return ret_code;
	}
	if (size != PLOGI_LEN) {	
		DPRINTK1("Received PLOGI of size %x", size);
		ret_code = (LOGICAL_ERR << 16) | INV_PAYLOAD_LEN;
		return ret_code;
	}
	if (class3_conc_seq == 0) {	
		DPRINTK1("Received PLOGI with conc_seq == 0");
		ret_code = (LOGICAL_ERR << 16) | CONC_SEQ;
		return ret_code;
	}
	if (open_seq == 0) {	
		DPRINTK1("Received PLOGI with open_seq == 0");
		ret_code = (LOGICAL_ERR << 16) | NO_EXPLN;
		return ret_code;
	}

	/* Could potentially check for more fields, but might end up
	   not talking to most of the devices. ;-) */
	/* Things that could get checked are:
	   common_features = 0x8800
	   total_concurrent_seq = at least 1
	*/
	return ret_code;
}

static void tx_acc(struct fc_info *fi, u_int d_id, u_short received_ox_id)
{
int int_required = 0;
u_int r_ctl = RCTL_ELS_SCTL;
u_int type  = TYPE_ELS | EXCHANGE_RESPONDER | LAST_SEQUENCE;
int size = sizeof(ACC);
u_int my_mtu = fi->g.my_mtu;
	ENTER("tx_acc");
	fi->g.acc.cmnd_code = htonl(ELS_ACC);
	fi->g.type_of_frame = FC_ELS;
	memcpy(fi->g.els_buffer[fi->g.e_i], &fi->g.acc, size);
	tx_exchange(fi, (char *)(fi->g.els_buffer[fi->g.e_i]),size, r_ctl, type, d_id, my_mtu, int_required, received_ox_id, ELS_ACC);
	fi->g.e_i++;
	if (fi->g.e_i == MAX_PENDING_FRAMES)
		fi->g.e_i = 0;
	LEAVE("tx_acc");
}


static void tx_name_server_req(struct fc_info *fi, u_int req)
{
int int_required = 1, i, size = 0;
u_short ox_id = OX_ID_FIRST_SEQUENCE;
u_int type  = TYPE_FC_SERVICES | SEQUENCE_INITIATIVE | FIRST_SEQUENCE;
u_int r_ctl = FC4_DEVICE_DATA | UNSOLICITED_CONTROL;
u_int my_mtu = fi->g.my_mtu, d_id = DIRECTORY_SERVER;
CT_HDR ct_hdr;
	ENTER("tx_name_server_req");
	/* Fill up CT_Header */
	ct_hdr.rev_in_id = htonl(FC_CT_REV);
	ct_hdr.fs_type = DIRECTORY_SERVER_APP;
	ct_hdr.fs_subtype = NAME_SERVICE;
	ct_hdr.options = 0;
	ct_hdr.resv1 = 0;
	ct_hdr.cmnd_resp_code = htons(req >> 16);
	ct_hdr.max_res_size = 0;
	ct_hdr.resv2 = 0;
	ct_hdr.reason_code = 0;
	ct_hdr.expln_code = 0;
	ct_hdr.vendor_unique = 0;
	
	fi->g.type_of_frame = FC_ELS;
	switch(req) {
		case FCS_RFC_4:
			memcpy(&(fi->g.rfc_4.ct_hdr), &ct_hdr, sizeof(CT_HDR));
			fi->g.rfc_4.s_id = htonl(fi->g.my_id);
			for (i = 0; i < 32; i++)
				fi->g.rfc_4.bit_map[i] = 0;
			/* We support IP & SCSI */
			fi->g.rfc_4.bit_map[2] = 0x01;
			fi->g.rfc_4.bit_map[3] = 0x20;
			size = sizeof(RFC_4);
			memcpy(fi->g.els_buffer[fi->g.e_i], &fi->g.rfc_4, size);
			tx_exchange(fi, (char *)(fi->g.els_buffer[fi->g.e_i]),size, r_ctl, type, d_id, my_mtu, int_required, ox_id, req);
			break;
		case FCS_GP_ID4:
			memcpy(&(fi->g.gp_id4.ct_hdr), &ct_hdr, sizeof(CT_HDR));
			fi->g.gp_id4.port_type = htonl(PORT_TYPE_NX_PORTS);
			size = sizeof(GP_ID4);
			memcpy(fi->g.els_buffer[fi->g.e_i], &fi->g.gp_id4, size);
			tx_exchange(fi, (char *)(fi->g.els_buffer[fi->g.e_i]),size, r_ctl, type, d_id, my_mtu, int_required, ox_id, req);
			break;
	}
	fi->g.e_i++;
	if (fi->g.e_i == MAX_PENDING_FRAMES)
		fi->g.e_i = 0;
	LEAVE("tx_name_server_req");
}

static void tx_scr(struct fc_info *fi)
{
int int_required = 1, size = sizeof(SCR);
u_short ox_id = OX_ID_FIRST_SEQUENCE;
u_int type  = TYPE_ELS | SEQUENCE_INITIATIVE | FIRST_SEQUENCE;
u_int r_ctl = RCTL_ELS_UCTL;
u_int my_mtu = fi->g.my_mtu, d_id = FABRIC_CONTROLLER;
	ENTER("tx_scr");
	fi->g.scr.cmnd_code = htonl(ELS_SCR);
	fi->g.scr.reg_function = htonl(FULL_REGISTRATION);
	fi->g.type_of_frame = FC_ELS;
	memcpy(fi->g.els_buffer[fi->g.e_i], &fi->g.scr, size);
	tx_exchange(fi, (char *)(fi->g.els_buffer[fi->g.e_i]),size, r_ctl, type, d_id, my_mtu, int_required, ox_id, ELS_SCR);
	fi->g.e_i++;
	if (fi->g.e_i == MAX_PENDING_FRAMES)
		fi->g.e_i = 0;
	LEAVE("tx_scr");
}

static void perform_adisc(struct fc_info *fi)
{
int count = 0;
	/* Will be set to TRUE when timer expires in a PLDA environment. 
	 */
	fi->g.port_discovery = FALSE;

	if (fi->node_info_list) {
		struct fc_node_info *temp_list = fi->node_info_list;
		while(temp_list) {
			/* Tx ADISC to all non-fabric based 
	 	 	 * entities.
	 	 	 */
			if ((temp_list->d_id & 0xFF0000) != 0xFF0000)
				tx_adisc(fi, ELS_ADISC, temp_list->d_id, OX_ID_FIRST_SEQUENCE);
			temp_list = temp_list->next;
			udelay(20);
			count++;
		}
	}
	/* Perform Port Discovery after timer expires.
	 * We are giving time for the ADISCed nodes to respond
	 * so that we dont have to perform PLOGI to those whose
	 * login are _still_ valid.
	 */
	fi->explore_timer.function = port_discovery_timer;
	fi->explore_timer.data = (unsigned long)fi;
	fi->explore_timer.expires = RUN_AT((count*3*HZ)/100); 
	init_timer(&fi->explore_timer);
	add_timer(&fi->explore_timer);
}

static void explore_fabric(struct fc_info *fi, u_int *buff_addr)
{
u_int *addr = buff_addr + 12; /* index into payload */
u_char control_code;
u_int d_id;
int count = 0;
	ENTER("explore_fabric");
	DPRINTK1("entering explore_fabric");

	/*fi->g.perform_adisc = TRUE;
	fi->g.explore_fabric = TRUE;
	perform_adisc(fi);*/
	
	do {
		d_id = ntohl(*addr) & 0x00FFFFFF;
		if (d_id != fi->g.my_id) {
			if (sid_logged_in(fi, d_id) == NODE_NOT_PRESENT)
				tx_logi(fi, ELS_PLOGI, d_id); 
			else
			if (sid_logged_in(fi, d_id) == NODE_LOGGED_OUT)
				tx_adisc(fi, ELS_ADISC, d_id, OX_ID_FIRST_SEQUENCE); 
			count++;
		}
		control_code = (ntohl(*addr) & 0xFF000000) >> 24;
		addr++;
		DPRINTK1("cc = %x, d_id = %x", control_code, d_id);
	} while (control_code != 0x80);
	
	fi->explore_timer.function = fabric_explore_timer;
	fi->explore_timer.data = (unsigned long)fi;
	/* We give 30 msec for each device to respond and then send out
	 * our SCSI enquiries. 
	 */
	fi->explore_timer.expires = RUN_AT((count*3*HZ)/100); 
	init_timer(&fi->explore_timer);
	add_timer(&fi->explore_timer);

	DPRINTK1("leaving explore_fabric");
	LEAVE("explore_fabric");
}

static void fabric_explore_timer(unsigned long data)
{
struct fc_info *fi = (struct fc_info*)data;
	del_timer(&fi->explore_timer);

	if ((fi->g.loop_up == TRUE) && (fi->g.ptp_up == FALSE)) {
		/* Initiate Local Port Discovery on the Local Loop.
		 */
		fi->g.port_discovery = TRUE;
		fi->g.alpa_list_index = 1;
		local_port_discovery(fi);
	}
	fi->g.explore_fabric = FALSE;
	return;
}

static void port_discovery_timer(unsigned long data)
{
struct fc_info *fi = (struct fc_info*)data;
	del_timer(&fi->explore_timer);
	
	if ((fi->g.loop_up == TRUE) && (fi->g.explore_fabric != TRUE)) {
		fi->g.port_discovery = TRUE;
		fi->g.alpa_list_index = 1;
		local_port_discovery(fi);
	}
	fi->g.perform_adisc = FALSE;
	return;
}

static void add_to_ox_id_list(struct fc_info *fi, u_int transaction_id, u_int cmnd_code)
{
struct ox_id_els_map *p, *q = fi->ox_id_list, *r = NULL;
int size = sizeof(struct ox_id_els_map);
	while (q != NULL) {
		r = q;
		q = q->next;
	}
	p = (struct ox_id_els_map *)kmalloc(size, GFP_ATOMIC);
	if (p == NULL) {
		T_MSG("kmalloc failed in add_to_ox_id_list()");
		return;
	}
	p->ox_id = transaction_id;
	p->els = cmnd_code;
	p->next = NULL;
	if (fi->ox_id_list == NULL)
		fi->ox_id_list = p;
	else
		r->next = p;
	return;
}

static u_int remove_from_ox_id_list(struct fc_info *fi, u_short received_ox_id)
{
struct ox_id_els_map *p = fi->ox_id_list, *q = fi->ox_id_list;
u_int els_type;
	while (q != NULL) {
		if (q->ox_id == received_ox_id) {

			if (q == fi->ox_id_list) 
				fi->ox_id_list = fi->ox_id_list->next;
			else
				if (q->next == NULL) 
					p->next = NULL;
			else 
					p->next = q->next;

			els_type = q->els;
			kfree(q);
			return els_type;
		}
		p = q;
		q = q->next;
	}
	if (q == NULL)
		DPRINTK2("Could not find ox_id %x in ox_id_els_map", received_ox_id);
	return 0;
}

static void build_tachyon_header(struct fc_info *fi, u_int my_id, u_int r_ctl, u_int d_id, u_int type, u_char seq_id, u_char df_ctl, u_short ox_id, u_short rx_id, char *data)
{
u_char alpa = d_id & 0x0000FF;
u_int dest_ddaa = d_id &0xFFFF00;

	ENTER("build_tachyon_header");
	DPRINTK("d_id = %x, my_ddaa = %x", d_id, fi->g.my_ddaa);
	/* Does it have to go to/thru a Fabric? */
	if ((dest_ddaa != 0) && ((d_id == F_PORT) || (fi->g.fabric_present && (dest_ddaa != fi->g.my_ddaa))))
		alpa = 0x00;
	fi->g.tach_header.resv = 0x00000000;
	fi->g.tach_header.sof_and_eof = SOFI3 | EOFN;
	fi->g.tach_header.dest_alpa = alpa;
	/* Set LCr properly to have enuff credit */
	if (alpa == REPLICATE)
		fi->g.tach_header.lcr_and_time_stamp = htons(0xC00);/* LCr=3 */
	else
		fi->g.tach_header.lcr_and_time_stamp = 0;
	fi->g.tach_header.r_ctl_and_d_id = htonl(r_ctl | d_id);
	fi->g.tach_header.vc_id_and_s_id = htonl(my_id);
	fi->g.tach_header.type_and_f_cntl = htonl(type);
	fi->g.tach_header.seq_id = seq_id;
	fi->g.tach_header.df_cntl = df_ctl;
	fi->g.tach_header.seq_cnt = 0;
	fi->g.tach_header.ox_id = htons(ox_id);
	fi->g.tach_header.rx_id = htons(rx_id);
	fi->g.tach_header.ro = 0;
	if (data) {
		/* We use the Seq_Count to keep track of IP frames in the
		 * OCI_interrupt handler. Initial Seq_Count of IP frames is 1.
		 */
		if (fi->g.type_of_frame == FC_BROADCAST)
			fi->g.tach_header.seq_cnt = htons(0x1);
		else
			fi->g.tach_header.seq_cnt = htons(0x2);
		fi->g.tach_header.nw_header.d_naa = htons(0x1000);
		fi->g.tach_header.nw_header.s_naa = htons(0x1000);
		memcpy(&(fi->g.tach_header.nw_header.dest_high), data, 2);
		memcpy(&(fi->g.tach_header.nw_header.dest_low), data + 2, 4);
		memcpy(&(fi->g.tach_header.nw_header.source_high), data + 6, 2);
		memcpy(&(fi->g.tach_header.nw_header.source_low), data + 8, 4);
	}
	LEAVE("build_tachyon_header");
}

static void build_EDB(struct fc_info *fi, char *data, u_short flags, u_short len)
{
	fi->g.edb.buf_addr = ntohl((u_int)virt_to_bus(data));
	fi->g.edb.ehf = ntohs(flags);
	if (len % 4)
		len += (4 - (len % 4));
	fi->g.edb.buf_len = ntohs(len);
}

static void build_ODB(struct fc_info *fi, u_char seq_id, u_int d_id, u_int len, u_int cntl, u_short mtu, u_short ox_id, u_short rx_id, int NW_header, int int_required, u_int frame_class)
{
	fi->g.odb.seq_d_id = htonl(seq_id << 24 | d_id);
	fi->g.odb.tot_len = len;
	if (NW_header)
		fi->g.odb.tot_len += NW_HEADER_LEN;
	if (fi->g.odb.tot_len % 4)
		fi->g.odb.tot_len += (4 - (fi->g.odb.tot_len % 4));
	fi->g.odb.tot_len = htonl(fi->g.odb.tot_len);
	switch(int_required) {
		case NO_COMP_AND_INT:
			fi->g.odb.cntl = htons(ODB_CLASS_3 | ODB_EE_CREDIT | ODB_NO_INT | ODB_NO_COMP | cntl);
			break;
		case INT_AND_COMP_REQ:
			fi->g.odb.cntl = htons(ODB_CLASS_3 | ODB_EE_CREDIT | cntl);
			break;
		case NO_INT_COMP_REQ:
			fi->g.odb.cntl = htons(ODB_CLASS_3 | ODB_EE_CREDIT | ODB_NO_INT | cntl);
			break;
	}
	fi->g.odb.rx_id = htons(rx_id);
	fi->g.odb.cs_enable = 0;
	fi->g.odb.cs_seed = htons(1);

	fi->g.odb.hdr_addr = htonl(virt_to_bus(fi->q.ptr_tachyon_header[fi->q.tachyon_header_indx]));
	fi->g.odb.frame_len = htons(mtu);

	if (NW_header) {
		/* The pointer to the sk_buff is in here. Freed up when the
		 * OCI_interrupt is received.
		 */
		fi->g.odb.trans_id = htonl(frame_class);
		fi->g.odb.hdr_len = TACHYON_HEADER_LEN + NW_HEADER_LEN;
	}
	else {
		/* helps in tracking transmitted OX_IDs */
		fi->g.odb.trans_id = htonl((frame_class & 0xFFFF0000) | ox_id);
		fi->g.odb.hdr_len = TACHYON_HEADER_LEN;
	}
	fi->g.odb.hdr_len = htons(fi->g.odb.hdr_len);
		
	fi->g.odb.edb_addr = htonl(virt_to_bus(fi->q.ptr_edb[fi->q.edb_buffer_indx]));
}

static void fill_login_frame(struct fc_info *fi, u_int logi)
{
int i;
	fi->g.login.ls_cmnd_code= htonl(logi);
	fi->g.login.fc_ph_version = htons(PH_VERSION);
	if (fi->g.loop_up)
		fi->g.login.buff_to_buff_credit = htons(LOOP_BB_CREDIT);
	else
	if (fi->g.ptp_up)
		fi->g.login.buff_to_buff_credit = htons(PT2PT_BB_CREDIT);
	if ((logi != ELS_FLOGI) || (logi == ELS_ACC))
		fi->g.login.common_features = htons(PLOGI_C_F);
	else
	if (logi == ELS_FLOGI)
		fi->g.login.common_features = htons(FLOGI_C_F);
	fi->g.login.recv_data_field_size = htons(FRAME_SIZE);
	fi->g.login.n_port_total_conc_seq = htons(CONCURRENT_SEQUENCES);
	fi->g.login.rel_off_by_info_cat = htons(RO_INFO_CATEGORY);
	fi->g.login.ED_TOV = htonl(E_D_TOV);
	fi->g.login.n_port_name_high = htonl(N_PORT_NAME_HIGH);
	fi->g.login.n_port_name_low = htonl(N_PORT_NAME_LOW);
	fi->g.login.node_name_high = htonl(NODE_NAME_HIGH);
	fi->g.login.node_name_low = htonl(NODE_NAME_LOW);
	
	/* Fill Class 1 parameters */
	fi->g.login.c_of_s[0].service_options = htons(0);
	fi->g.login.c_of_s[0].initiator_ctl = htons(0);
	fi->g.login.c_of_s[0].recipient_ctl = htons(0);
	fi->g.login.c_of_s[0].recv_data_field_size = htons(0);
	fi->g.login.c_of_s[0].concurrent_sequences = htons(0);
	fi->g.login.c_of_s[0].n_port_end_to_end_credit = htons(0);
	fi->g.login.c_of_s[0].open_seq_per_exchange = htons(0);
	fi->g.login.c_of_s[0].resv = htons(0);

	/* Fill Class 2 parameters */
	fi->g.login.c_of_s[1].service_options = htons(0);
	fi->g.login.c_of_s[1].initiator_ctl = htons(0);
	fi->g.login.c_of_s[1].recipient_ctl = htons(0);
	fi->g.login.c_of_s[1].recv_data_field_size = htons(0);
	fi->g.login.c_of_s[1].concurrent_sequences = htons(0);
	fi->g.login.c_of_s[1].n_port_end_to_end_credit = htons(0);
	fi->g.login.c_of_s[1].open_seq_per_exchange = htons(0);
	fi->g.login.c_of_s[1].resv = htons(0);

	/* Fill Class 3 parameters */
	if (logi == ELS_FLOGI)
		fi->g.login.c_of_s[2].service_options  = htons(SERVICE_VALID | SEQUENCE_DELIVERY);
	else
		fi->g.login.c_of_s[2].service_options  = htons(SERVICE_VALID);
	fi->g.login.c_of_s[2].initiator_ctl = htons(0);
	fi->g.login.c_of_s[2].recipient_ctl = htons(0);
	fi->g.login.c_of_s[2].recv_data_field_size = htons(FRAME_SIZE);
	fi->g.login.c_of_s[2].concurrent_sequences = htons(CLASS3_CONCURRENT_SEQUENCE);
	fi->g.login.c_of_s[2].n_port_end_to_end_credit = htons(0);
	fi->g.login.c_of_s[2].open_seq_per_exchange = htons(CLASS3_OPEN_SEQUENCE);
	fi->g.login.c_of_s[2].resv = htons(0);
	
	for(i = 0; i < 4; i++) {
		fi->g.login.resv[i] = 0;
		fi->g.login.vendor_version_level[i] = 0;
	}
}


/* clear the Interrupt Latch on the (i)chip, so that you can receive 
 * Interrupts from Tachyon in future 
 */
static void reset_latch(struct fc_info *fi)
{
	writel(readl(fi->i_r.ptr_ichip_hw_status_reg) | ICHIP_HSR_INT_LATCH, fi->i_r.ptr_ichip_hw_status_reg);
}

static void update_OCQ_indx(struct fc_info *fi)
{
	fi->q.ocq_prod_indx++;
	if (fi->q.ocq_prod_indx == OCQ_LENGTH)
		fi->q.ocq_prod_indx = 0;
	writel(fi->q.ocq_prod_indx, fi->t_r.ptr_ocq_prod_indx_reg);
}

static void update_IMQ_indx(struct fc_info *fi, int count)
{
	fi->q.imq_cons_indx += count;
	if (fi->q.imq_cons_indx >= IMQ_LENGTH)
		fi->q.imq_cons_indx -= IMQ_LENGTH;
	writel(fi->q.imq_cons_indx, fi->t_r.ptr_imq_cons_indx_reg);
}

static void update_SFSBQ_indx(struct fc_info *fi)
{
	fi->q.sfsbq_prod_indx++;
	if (fi->q.sfsbq_prod_indx == SFSBQ_LENGTH)
		fi->q.sfsbq_prod_indx = 0;
	writel(fi->q.sfsbq_prod_indx, fi->t_r.ptr_sfsbq_prod_reg);
}

static void update_MFSBQ_indx(struct fc_info *fi, int count)
{
	fi->q.mfsbq_prod_indx += count;
	if (fi->q.mfsbq_prod_indx >= MFSBQ_LENGTH)
		fi->q.mfsbq_prod_indx -= MFSBQ_LENGTH;
	writel(fi->q.mfsbq_prod_indx, fi->t_r.ptr_mfsbq_prod_reg);
}


static void update_tachyon_header_indx(struct fc_info *fi)
{
	fi->q.tachyon_header_indx++;
	if (fi->q.tachyon_header_indx == NO_OF_TACH_HEADERS)
		fi->q.tachyon_header_indx = 0;
}

static void update_EDB_indx(struct fc_info *fi)
{
	fi->q.edb_buffer_indx++;
	if (fi->q.edb_buffer_indx == EDB_LEN)
		fi->q.edb_buffer_indx = 0;
}

static int iph5526_open(struct device *dev)
{
	dev->tbusy = 0;
	dev->interrupt = 0;
	dev->start = 1;
	MOD_INC_USE_COUNT;
	return 0;
}

static int iph5526_close(struct device *dev)
{
	dev->tbusy = 1;
	dev->start = 0;
	MOD_DEC_USE_COUNT;
	return 0;
}

static int iph5526_send_packet(struct sk_buff *skb, struct device *dev)
{
struct fc_info *fi = (struct fc_info*)dev->priv;
int status = 0;
short type = 0;
u_long flags;
	ENTER("iph5526_send_packet");
	if (dev->tbusy) {
		printk(KERN_WARNING "%s: DEVICE BUSY\n", dev->name);
		dev->tbusy = 0;
		fi->fc_stats.rx_dropped++;
		dev->trans_start = jiffies;
		return 0;
	}
	if (test_and_set_bit(0, (void*)&dev->tbusy) != 0) {
		printk(KERN_WARNING "%s: Transmitter access conflict.\n", 
dev->name);
		fi->fc_stats.rx_dropped++;
		return 1;
	}
	else {
		struct fcllc *fcllc;
		/* Strip off the pseudo header.
		 */
		skb->data = skb->data + 2*FC_ALEN; 
		skb->len = skb->len - 2*FC_ALEN;
		fcllc = (struct fcllc *)skb->data;
		type = ntohs(fcllc->ethertype);

		spin_lock_irqsave(&fi->fc_lock, flags);
		switch(type) {
			case ETH_P_IP:
				status = tx_ip_packet(skb, skb->len, fi);
				break;
			case ETH_P_ARP:
				status = tx_arp_packet(skb->data, skb->len, fi);
				break;
			default:
				T_MSG("WARNING!!! Received Unknown Packet Type... Discarding...");
				fi->fc_stats.rx_dropped++;
				break;
		}
		spin_unlock_irqrestore(&fi->fc_lock, flags);
	}

	if (status) {
		fi->fc_stats.tx_bytes += skb->len;
		fi->fc_stats.tx_packets++;
	}
	else
		fi->fc_stats.rx_dropped++;
	dev->trans_start = jiffies;
	dev->tbusy = 0;
	/* We free up the IP buffers in the OCI_interrupt handler.
	 * status == 0 implies that the frame was not transmitted. So the
	 * skb is freed here.
	 */
	if ((type == ETH_P_ARP) || (status == 0))
		dev_kfree_skb(skb);
	mark_bh(NET_BH);
	LEAVE("iph5526_send_packet");
	return 0;
}

static int iph5526_change_mtu(struct device *dev, int mtu)
{
	return 0;
}

static int tx_ip_packet(struct sk_buff *skb, unsigned long len, struct fc_info *fi)
{
u_int d_id;
int int_required = 1;
u_int r_ctl = FC4_DEVICE_DATA | UNSOLICITED_DATA;
u_int type = TYPE_LLC_SNAP;
u_short ox_id = OX_ID_FIRST_SEQUENCE;
u_int mtu;
struct fc_node_info *q;

	ENTER("tx_ip_packet");
	q = look_up_cache(fi, skb->data - 2*FC_ALEN);
	if (q != NULL) {
		d_id = q->d_id;
		DPRINTK("Look-Up Cache Succeeded for d_id = %x", d_id);
		mtu = q->mtu;
		if (q->login == LOGIN_COMPLETED){
			fi->g.type_of_frame = FC_IP;
			return tx_exchange(fi, skb->data, len, r_ctl, type, d_id, mtu, int_required, ox_id, virt_to_bus(skb));
		}
		
		if (q->d_id == BROADCAST) {
		struct fc_node_info *p = fi->node_info_list;
		int return_value = FALSE;
			fi->g.type_of_frame = FC_BROADCAST;
			/* Do unicast to local nodes.
			 */
			int_required = 0;
			while(p != NULL) {
				d_id = p->d_id;
				if ((d_id & 0xFFFF00) == fi->g.my_ddaa)
					return_value |= tx_exchange(fi, skb->data, len, r_ctl, type, d_id, fi->g.my_mtu, int_required, ox_id, TYPE_LLC_SNAP);
				p = p->next;
			}
			kfree(q);
			return return_value;
		}
		
		if (q->login != LOGIN_COMPLETED) {	
			DPRINTK1("Node not logged in... Txing PLOGI to %x", d_id);
			/* FIXME: we are dumping the frame here */
			tx_logi(fi, ELS_PLOGI, d_id); 
		}
	}
	DPRINTK2("Look-Up Cache Failed");
	LEAVE("tx_ip_packet");
	return 0;
}

static int tx_arp_packet(char *data, unsigned long len, struct fc_info *fi)
{
u_int opcode = data[ARP_OPCODE_0]; 
u_int d_id;
int int_required = 0, return_value = FALSE;
u_int r_ctl = FC4_DEVICE_DATA | UNSOLICITED_DATA;
u_int type = TYPE_LLC_SNAP;
u_short ox_id = OX_ID_FIRST_SEQUENCE;
u_int my_mtu = fi->g.my_mtu;
	ENTER("tx_arp_packet");

	opcode = opcode << 8 | data[ARP_OPCODE_1];
	fi->g.type_of_frame = FC_IP;

	if (opcode == ARPOP_REQUEST) {
	struct fc_node_info *q = fi->node_info_list;
		d_id = BROADCAST;
		return_value |= tx_exchange(fi, data, len, r_ctl, type, d_id, my_mtu, int_required, ox_id, TYPE_LLC_SNAP);
		/* Some devices support HW_TYPE 0x01 */
		memcpy(fi->g.arp_buffer, data - 2*FC_ALEN, len + 2*FC_ALEN);
		fi->g.arp_buffer[9 + 2*FC_ALEN] = 0x01;
		return_value |= tx_exchange(fi, (char *)(fi->g.arp_buffer + 2*FC_ALEN), len, r_ctl, type, d_id, my_mtu, int_required, ox_id, TYPE_LLC_SNAP);

		/* Do unicast to local nodes.
		 */
		while(q != NULL) {
			fi->g.type_of_frame = FC_BROADCAST;
			d_id = q->d_id;
			if ((d_id & 0xFFFF00) == fi->g.my_ddaa) {
				return_value |= tx_exchange(fi, data, len, r_ctl, type, d_id, my_mtu, int_required, ox_id, TYPE_LLC_SNAP);
				// Some devices support HW_TYPE 0x01
				memcpy(fi->g.arp_buffer, data - 2*FC_ALEN, len + 2*FC_ALEN);
				fi->g.arp_buffer[9 + 2*FC_ALEN] = 0x01;
				return_value |= tx_exchange(fi, (char *)(fi->g.arp_buffer + 2*FC_ALEN), len, r_ctl, type, d_id, my_mtu, int_required, ox_id, TYPE_LLC_SNAP);
			}
			q = q->next;
		}
		return return_value;
	}
	else
	if (opcode == ARPOP_REPLY) {
	struct fc_node_info *q; u_int mtu;
		DPRINTK("We are sending out an ARP reply");
		q = look_up_cache(fi, data - 2*FC_ALEN);
		if (q != NULL) {
			d_id = q->d_id;
			DPRINTK("Look-Up Cache Succeeded for d_id = %x", d_id);
			mtu = q->mtu;
			if (q->login == LOGIN_COMPLETED){
				tx_exchange(fi, data, len, r_ctl, type, d_id, mtu, int_required, ox_id, TYPE_LLC_SNAP);
				/* Some devices support HW_TYPE 0x01 */
				memcpy(fi->g.arp_buffer, data - 2*FC_ALEN, len + 2*FC_ALEN);
				fi->g.arp_buffer[9 + 2*FC_ALEN] = 0x01;
				return tx_exchange(fi, (char *)(fi->g.arp_buffer + 2*FC_ALEN), len, r_ctl, type, d_id, my_mtu, int_required, ox_id, TYPE_LLC_SNAP);
			}
			else {
				DPRINTK1("Node not logged in... Txing PLOGI to %x", d_id);
				tx_logi(fi, ELS_PLOGI, d_id); /* FIXME: we are dumping the frame here */
			}
		}
		DPRINTK2("Look-Up Cache Failed");
	}
	else {
		T_MSG("Warning!!! Invalid Opcode in ARP Packet!");
	}
	LEAVE("tx_arp_packet");
	return 0;
}


static void rx_net_packet(struct fc_info *fi, u_char *buff_addr, int payload_size)
{
struct device *dev = fi->dev;
struct sk_buff *skb;
u_int skb_size = 0;
struct fch_hdr fch;
	ENTER("rx_net_packet");
	skb_size = payload_size - TACHYON_HEADER_LEN;
	DPRINTK("skb_size = %d", skb_size);
	fi->fc_stats.rx_bytes += skb_size - 2;
	skb = dev_alloc_skb(skb_size);
	if (skb == NULL) {
		printk(KERN_NOTICE "%s: In rx_net_packet() Memory squeeze, dropping packet.\n", dev->name);
		fi->fc_stats.rx_dropped++;
		return;
	}
	/* Skip over the Tachyon Frame Header.
	 */
	buff_addr += TACHYON_HEADER_LEN; 

	memcpy(fch.daddr, buff_addr + 2, FC_ALEN);
	memcpy(fch.saddr, buff_addr + 10, FC_ALEN);
	buff_addr += 2;
	memcpy(buff_addr, fch.daddr, FC_ALEN);
	memcpy(buff_addr + 6, fch.saddr, FC_ALEN);
	skb_reserve(skb, 2);
	memcpy(skb_put(skb, skb_size - 2), buff_addr, skb_size - 2);
	skb->dev = dev;
	skb->protocol = fc_type_trans(skb, dev);
	DPRINTK("protocol = %x", skb->protocol);
	
	/* Hmmm... to accept HW Type 0x01 as well... 
	 */
	if (skb->protocol == ntohs(ETH_P_ARP))
		skb->data[1] = 0x06;
	netif_rx(skb);
	fi->fc_stats.rx_packets++;
	LEAVE("rx_net_packet");
}


static void rx_net_mfs_packet(struct fc_info *fi, struct sk_buff *skb)
{
struct device *dev = fi->dev;
struct fch_hdr fch;
	ENTER("rx_net_mfs_packet");
	/* Construct your Hard Header */
	memcpy(fch.daddr, skb->data + 2, FC_ALEN);
	memcpy(fch.saddr, skb->data + 10, FC_ALEN);
	skb_pull(skb, 2);
	memcpy(skb->data, fch.daddr, FC_ALEN);
	memcpy(skb->data + 6, fch.saddr, FC_ALEN);
	skb->dev = dev;
	skb->protocol = fc_type_trans(skb, dev);
	DPRINTK("protocol = %x", skb->protocol);
	netif_rx(skb);
	LEAVE("rx_net_mfs_packet");
}

unsigned short fc_type_trans(struct sk_buff *skb, struct device *dev) 
{
struct fch_hdr *fch=(struct fch_hdr *)skb->data;
struct fcllc *fcllc;
	skb->mac.raw = skb->data;
	fcllc = (struct fcllc *)(skb->data + sizeof(struct fch_hdr) + 2);
	skb_pull(skb,sizeof(struct fch_hdr) + 2);

	if(*fch->daddr & 1) {
		if(!memcmp(fch->daddr,dev->broadcast,FC_ALEN)) 	
			skb->pkt_type = PACKET_BROADCAST;
		else
			skb->pkt_type = PACKET_MULTICAST;
	}
	else if(dev->flags & IFF_PROMISC) {
		if(memcmp(fch->daddr, dev->dev_addr, FC_ALEN))
			skb->pkt_type=PACKET_OTHERHOST;
	}
	
	/* Strip the SNAP header from ARP packets since we don't 
	 * pass them through to the 802.2/SNAP layers.
	 */

	if (fcllc->dsap == EXTENDED_SAP &&
		(fcllc->ethertype == ntohs(ETH_P_IP) ||
		 fcllc->ethertype == ntohs(ETH_P_ARP))) {
		skb_pull(skb, sizeof(struct fcllc));
		return fcllc->ethertype;
	}
	return ntohs(ETH_P_802_2);
}

static int tx_exchange(struct fc_info *fi, char *data, u_int len, u_int r_ctl, u_int type, u_int d_id, u_int mtu, int int_required, u_short tx_ox_id, u_int frame_class)
{
u_char df_ctl; 
int NW_flag = 0, h_size, return_value;
u_short rx_id = RX_ID_FIRST_SEQUENCE;
u_int tachyon_status;
u_int my_id = fi->g.my_id;
	ENTER("tx_exchange");

	tachyon_status = readl(fi->t_r.ptr_tach_status_reg);
	DPRINTK("Tachyon Status = %x len = %d MTU = %d", tachyon_status, len, mtu);
	if (tachyon_status & OSM_FROZEN) {
		reset_tachyon(fi, ERROR_RELEASE);
		reset_tachyon(fi, OCQ_RESET);
		DPRINTK("Tachyon Status = %x len = %d MTU = %d", tachyon_status, len, mtu);
	}
	if (tx_ox_id == OX_ID_FIRST_SEQUENCE) {
		switch(fi->g.type_of_frame) {
			case FC_SCSI_READ:
				tx_ox_id = fi->g.scsi_oxid | SCSI_READ_BIT;
				break;
			case FC_SCSI_WRITE:
				tx_ox_id = fi->g.scsi_oxid;
				break;
			default:
				tx_ox_id = fi->g.ox_id;
				break;
		}
	}
	else {
		switch(fi->g.type_of_frame) {
			case FC_SCSI_READ:
				rx_id = fi->g.scsi_oxid | SCSI_READ_BIT;
				break;
			case FC_SCSI_WRITE:
				rx_id = fi->g.scsi_oxid;
				break;
			case FC_BLS:
				rx_id = RX_ID_FIRST_SEQUENCE;
				break;
			default:
				rx_id = fi->g.ox_id;
				break;
		}
	}

	if (type == TYPE_LLC_SNAP) {
		df_ctl = 0x20;
		NW_flag = 1;
		/* Multi Frame Sequence ? If yes, set RO bit */
		if (len > mtu)
			type |= RELATIVE_OFF_PRESENT;
		build_tachyon_header(fi, my_id, r_ctl, d_id, type, fi->g.seq_id, df_ctl, tx_ox_id, rx_id, data - 2*FC_ALEN);
	}
	else {
		df_ctl = 0;
		/* Multi Frame Sequence ? If yes, set RO bit */
		if (len > mtu)
			type |= RELATIVE_OFF_PRESENT;
		build_tachyon_header(fi, my_id, r_ctl, d_id, type, fi->g.seq_id, df_ctl, tx_ox_id, rx_id, NULL);
	}

	/* Get free Tachyon Headers and EDBs */
	if (get_free_header(fi) || get_free_EDB(fi))
		return 0;

	if ((type & 0xFF000000) == TYPE_LLC_SNAP) {
		h_size =  TACHYON_HEADER_LEN + NW_HEADER_LEN;
		memcpy(fi->q.ptr_tachyon_header[fi->q.tachyon_header_indx], &(fi->g.tach_header), h_size);
	}
	else 
		memcpy(fi->q.ptr_tachyon_header[fi->q.tachyon_header_indx], &(fi->g.tach_header), TACHYON_HEADER_LEN);

	return_value = tx_sequence(fi, data, len, mtu, d_id, tx_ox_id, rx_id, fi->g.seq_id, NW_flag, int_required, frame_class);
	
	switch(fi->g.type_of_frame) {
		case FC_SCSI_READ:
		case FC_SCSI_WRITE:
			update_scsi_oxid(fi);
			break;
		case FC_BLS:
			break;
		default:
			fi->g.ox_id++;
			if (fi->g.ox_id == 0xFFFF)
				fi->g.ox_id = NOT_SCSI_XID;
			break;
	}

	if (fi->g.seq_id == MAX_SEQ_ID)
		fi->g.seq_id = 0;
	else
		fi->g.seq_id++;
	LEAVE("tx_exchange");
	return return_value;
}

static int tx_sequence(struct fc_info *fi, char *data, u_int len, u_int mtu, u_int d_id, u_short ox_id, u_short rx_id, u_char seq_id, int NW_flag, int int_required, u_int frame_class)
{
u_int cntl = 0;
int return_value;
	ENTER("tx_sequence");
	build_EDB(fi, data, EDB_END, len);
	memcpy(fi->q.ptr_edb[fi->q.edb_buffer_indx], &(fi->g.edb), sizeof(EDB));
	build_ODB(fi, seq_id, d_id, len, cntl, mtu, ox_id, rx_id, NW_flag, int_required, frame_class);
	memcpy(fi->q.ptr_odb[fi->q.ocq_prod_indx], &(fi->g.odb), sizeof(ODB));
	if (fi->g.link_up != TRUE) {
		DPRINTK2("Fibre Channel Link not up. Dropping Exchange!");
		return_value = FALSE;
	}
	else {
		/* To be on the safe side, a check should be included
		 * at this point to check if we are overrunning 
		 * Tachyon.
		 */
		update_OCQ_indx(fi);
		return_value = TRUE;
	}
	update_EDB_indx(fi);
	update_tachyon_header_indx(fi);
	LEAVE("tx_sequence");
	return return_value;
}

static int get_free_header(struct fc_info *fi)
{
u_short temp_ox_id;
u_int *tach_header, initial_indx = fi->q.tachyon_header_indx;
	/* Check if the header is in use.
	 * We could have an outstanding command.
	 * We should find a free slot as we can queue a
	 * maximum of 32 SCSI commands only. 
	 */
	tach_header = fi->q.ptr_tachyon_header[fi->q.tachyon_header_indx];
	temp_ox_id = ntohl(*(tach_header + 6)) >> 16;
	/* We care about the SCSI writes only. Those are the wicked ones
	 * that need an additional set of buffers.
	 */
	while(temp_ox_id <= MAX_SCSI_XID) {
		update_tachyon_header_indx(fi);
		if (fi->q.tachyon_header_indx == initial_indx) {
			/* Should never happen.
			 */
			T_MSG("No free Tachyon headers available");
			reset_tachyon(fi, SOFTWARE_RESET);
			return 1;
		}
		tach_header = fi->q.ptr_tachyon_header[fi->q.tachyon_header_indx];
		temp_ox_id = ntohl(*(tach_header + 6)) >> 16;
	}
	return 0;
}

static int get_free_EDB(struct fc_info *fi)
{
unsigned int initial_indx = fi->q.edb_buffer_indx;
	/* Check if the EDB is in use.
	 * We could have an outstanding SCSI Write command.
	 * We should find a free slot as we can queue a
	 * maximum of 32 SCSI commands only. 
	 */
	while (fi->q.free_edb_list[fi->q.edb_buffer_indx] != EDB_FREE) {
		update_EDB_indx(fi);
		if (fi->q.edb_buffer_indx == initial_indx) {
			T_MSG("No free EDB buffers avaliable")
			reset_tachyon(fi, SOFTWARE_RESET);
			return 1;
		}
	}
	return 0;
}		

static int validate_login(struct fc_info *fi, u_int *base_ptr)
{
struct fc_node_info *q = fi->node_info_list;
char n_port_name[PORT_NAME_LEN];
char node_name[NODE_NAME_LEN];
u_int s_id;
	ENTER("validate_login");
	/*index to Port Name in the payload. We need the 8 byte Port Name */
	memcpy(n_port_name, base_ptr + 10, PORT_NAME_LEN);
	memcpy(node_name, base_ptr + 12, NODE_NAME_LEN);
	s_id = ntohl(*(base_ptr + 3)) & 0x00FFFFFF;
	
	/* check if Fibre Channel IDs have changed */
	while(q != NULL) {	
		if (memcmp(n_port_name, q->hw_addr, PORT_NAME_LEN) == 0) {
			if ((s_id != q->d_id) || (memcmp(node_name, q->node_name, NODE_NAME_LEN) != 0)) {
				DPRINTK1("Fibre Channel ID of Node has changed. Txing LOGO.");
				return 0;
			}
			q->login = LOGIN_COMPLETED;
#if DEBUG_5526_2
			display_cache(fi);
#endif
			return 1;
		}
		q = q->next;
	}
	DPRINTK1("Port Name does not match. Txing LOGO.");
	return 0;
	LEAVE("validate_login");
}

static void add_to_address_cache(struct fc_info *fi, u_int *base_ptr)
{
int size = sizeof(struct fc_node_info);
struct fc_node_info *p, *q = fi->node_info_list, *r = NULL;
char n_port_name[PORT_NAME_LEN];
u_int s_id;
	ENTER("add_to_address_cache");
	/*index to Port Name in the payload. We need the 8 byte Port Name */
	memcpy(n_port_name, base_ptr + 13, PORT_NAME_LEN);
	s_id = ntohl(*(base_ptr + 3)) & 0x00FFFFFF;
	
	/* check if info already exists */
	while(q != NULL) {	
		if (memcmp(n_port_name, q->hw_addr, PORT_NAME_LEN) == 0) {
			if (s_id != q->d_id) {
				memcpy(&(q->c_of_s[0]), base_ptr + 17, 3 * sizeof(CLASS_OF_SERVICE));
				q->mtu = ntohl(*(base_ptr + 10)) & 0x00000FFF;
				q->d_id = s_id;
				memcpy(q->node_name, base_ptr + 15, NODE_NAME_LEN);
			}
			q->login = LOGIN_COMPLETED;
			q->scsi = FALSE;
			fi->num_nodes++;
#if DEBUG_5526_2
			display_cache(fi);
#endif
			return;
		}
		r = q;
		q = q->next;
	}
	p = (struct fc_node_info *)kmalloc(size, GFP_ATOMIC);
	if (p == NULL) {
		T_MSG("kmalloc failed in add_to_address_cache()");
		return;
	}
	memcpy(&(p->c_of_s[0]), base_ptr + 17, 3 * sizeof(CLASS_OF_SERVICE));
	p->mtu = ntohl(*(base_ptr + 10)) & 0x00000FFF;
	p->d_id = s_id;
	memcpy(p->hw_addr, base_ptr + 13, PORT_NAME_LEN);
	memcpy(p->node_name, base_ptr + 15, NODE_NAME_LEN);
	p->login = LOGIN_COMPLETED;
	p->scsi = FALSE;
	p->target_id = 0xFF;
	p->next = NULL;
	if (fi->node_info_list == NULL)
		fi->node_info_list = p;
	else
		r->next = p;
	fi->num_nodes++;
#if DEBUG_5526_2
	display_cache(fi);
#endif
	LEAVE("add_to_address_cache");
	return;
}

static void remove_from_address_cache(struct fc_info *fi, u_int *base_ptr, u_int cmnd_code)
{
struct fc_node_info *q = fi->node_info_list;
u_int s_id;
	ENTER("remove_from_address_cache");
	s_id = ntohl(*(base_ptr + 3)) & 0x00FFFFFF;
	switch(cmnd_code) {
		case ELS_LOGO:
			/* check if info exists */
			while (q != NULL) {
				if (s_id == q->d_id) {
					if (q->login == LOGIN_COMPLETED)
						q->login = LOGIN_ATTEMPTED;
					if (fi->num_nodes > 0)
						fi->num_nodes--;
#if DEBUG_5526_2
					display_cache(fi);
#endif
					return;
				}
				q = q->next;
			}
			DPRINTK1("ELS_LOGO received from node 0x%x which is not logged-in", s_id);
			break;
		case ELS_RSCN:
		{
		int payload_len = ntohl(*(base_ptr + 8)) & 0xFF;
		int no_of_pages, i;
		u_char address_format;
		u_short received_ox_id = ntohl(*(base_ptr + 6)) >> 16;
		u_int node_id, mask, *page_ptr = base_ptr + 9;
			if ((payload_len < 4) || (payload_len > 256)) {
				DPRINTK1("RSCN with invalid payload length received");
				tx_ls_rjt(fi, s_id, received_ox_id, LOGICAL_ERR, RECV_FIELD_SIZE);
				return;
			}
			/* Page_size includes the Command Code */
			no_of_pages = (payload_len / 4) - 1;
			for (i = 0; i < no_of_pages; i++) {
				address_format = ntohl(*page_ptr) >> 24; 
				node_id = ntohl(*page_ptr) & 0x00FFFFFF;
				switch(address_format) {
					case PORT_ADDRESS_FORMAT:
						rscn_handler(fi, node_id);
						break;
					case AREA_ADDRESS_FORMAT:
					case DOMAIN_ADDRESS_FORMAT:
						if (address_format == AREA_ADDRESS_FORMAT)
							mask = 0xFFFF00;
						else
							mask = 0xFF0000;
						while(q != NULL) {
							if ((q->d_id & mask) == (node_id & mask)) 
								rscn_handler(fi, q->d_id);
							q = q->next;
						}
						/* There might be some new nodes to be 
						 * discovered. But, some of the earlier 
						 * requests as a result of the RSCN might be 
						 * in progress. We dont want to duplicate that 
						 * effort. So letz call SCR after a lag.
						 */
						fi->explore_timer.function = scr_timer;
						fi->explore_timer.data = (unsigned long)fi;
						fi->explore_timer.expires = RUN_AT((no_of_pages*3*HZ)/100); 
						init_timer(&fi->explore_timer);
						add_timer(&fi->explore_timer);
						break;
					default:
						T_MSG("RSCN with invalid address format received");
						tx_ls_rjt(fi, s_id, received_ox_id, LOGICAL_ERR, NO_EXPLN);
				}
				page_ptr += 1;
			} /* end of for loop */
		} /* end of case RSCN: */	
		break;
	}
#if DEBUG_5526_2
	display_cache(fi);
#endif
	LEAVE("remove_from_address_cache");
}

static void rscn_handler(struct fc_info *fi, u_int node_id)
{
struct fc_node_info *q = fi->node_info_list;
int login_state = sid_logged_in(fi, node_id);
	if ((login_state == NODE_LOGGED_IN) || (login_state == NODE_PROCESS_LOGGED_IN)) {
		while(q != NULL) {
			if (q->d_id == node_id) {
				q->login = LOGIN_ATTEMPTED;
				if (fi->num_nodes > 0)
					fi->num_nodes--;
				break;
			}
			else
				q = q->next;
		}
	}
	else
	if (login_state == NODE_LOGGED_OUT)
		tx_adisc(fi, ELS_ADISC, node_id, OX_ID_FIRST_SEQUENCE); 
	else
	if (login_state == NODE_LOGGED_OUT)
		tx_logi(fi, ELS_PLOGI, node_id);
}

static void scr_timer(unsigned long data)
{
struct fc_info *fi = (struct fc_info *)data;
	del_timer(&fi->explore_timer);
	tx_name_server_req(fi, FCS_GP_ID4);
}

static int sid_logged_in(struct fc_info *fi, u_int s_id)
{
struct fc_node_info *temp = fi->node_info_list;
	while(temp != NULL)
		if ((temp->d_id == s_id) && (temp->login == LOGIN_COMPLETED)) {
			if (temp->scsi != FALSE)
				return NODE_PROCESS_LOGGED_IN;
			else
				return NODE_LOGGED_IN;
		}
		else
		if ((temp->d_id == s_id) && (temp->login != LOGIN_COMPLETED))
			return NODE_LOGGED_OUT;
		else
			temp = temp->next;
	return NODE_NOT_PRESENT;
}

static void mark_scsi_sid(struct fc_info *fi, u_int *buff_addr, u_char action)
{
struct fc_node_info *temp = fi->node_info_list;
u_int s_id;
u_int service_params;
	s_id = ntohl(*(buff_addr + 3)) & 0x00FFFFFF;
	service_params = ntohl(*(buff_addr + 12)) & 0x000000F0;
	while(temp != NULL)
		if ((temp->d_id == s_id) && (temp->login == LOGIN_COMPLETED)) {
			if (action == DELETE_ENTRY) {
				temp->scsi = FALSE;
#if DEBUG_5526_2
				display_cache(fi);
#endif
				return;
			}
			/* Check if it is a SCSI Target */
			if (!(service_params & TARGET_FUNC)) {
				temp->scsi = INITIATOR;	
#if DEBUG_5526_2
				display_cache(fi);
#endif
				return;
			}
			temp->scsi = TARGET;
			/* This helps to maintain the target_id no matter what your
			 *  Fibre Channel ID is.
			 */
			if (temp->target_id == 0xFF) {
				if (fi->g.no_of_targets <= MAX_SCSI_TARGETS)
					temp->target_id = fi->g.no_of_targets++;
				else
					T_MSG("MAX TARGETS reached!");
			}
			else
				DPRINTK1("Target_id %d already present", temp->target_id);
#if DEBUG_5526_2
			display_cache(fi);
#endif
			return;
		}
		else
			temp = temp->next;
	return;
}

static int node_logged_in_prev(struct fc_info *fi, u_int *buff_addr)
{
struct fc_node_info *temp;
u_char *data = (u_char *)buff_addr;
u_int s_id;
char node_name[NODE_NAME_LEN];
	s_id = ntohl(*(buff_addr + 3)) & 0x00FFFFFF;
	memcpy(node_name, buff_addr + 12, NODE_NAME_LEN);
	/* point to port_name in the ADISC payload */
	data += 10 * 4;
	/* point to last 6 bytes of port_name */
	data += 2;
	temp = look_up_cache(fi, data);
	if (temp != NULL) {
		if ((temp->d_id == s_id) && (memcmp(node_name, temp->node_name, NODE_NAME_LEN) == 0)) {
			temp->login = LOGIN_COMPLETED;
#if DEBUG_5526_2
			display_cache(fi);
#endif
			return TRUE;
		}
	}
	return FALSE;
}

static struct fc_node_info *look_up_cache(struct fc_info *fi, char *data)
{
struct fc_node_info *temp_list = fi->node_info_list, *q;
u_char n_port_name[FC_ALEN], temp_addr[FC_ALEN];
	ENTER("look_up_cache");
	memcpy(n_port_name, data, FC_ALEN);
	while(temp_list) {
		if (memcmp(n_port_name, &(temp_list->hw_addr[2]), FC_ALEN) == 0)
			return temp_list;
		else
			temp_list = temp_list->next;
	}
	
	/* Broadcast IP ?
	 */
	temp_addr[0] = temp_addr[1] = temp_addr[2] = 0xFF;
	temp_addr[3] = temp_addr[4] = temp_addr[5] = 0xFF;
	if (memcmp(n_port_name, temp_addr, FC_ALEN) == 0) {
		q = (struct fc_node_info *)kmalloc(sizeof(struct fc_node_info), GFP_ATOMIC);
		if (q == NULL) {
			T_MSG("kmalloc failed in look_up_cache()");
			return NULL;
		}
		q->d_id = BROADCAST;
		return q;
	}
	LEAVE("look_up_cache");
	return NULL;
}

static int display_cache(struct fc_info *fi)
{
struct fc_node_info *q = fi->node_info_list;
#if DEBUG_5526_2
struct ox_id_els_map *temp_ox_id_list = fi->ox_id_list;
#endif
int count = 0, j;
	printk("\nFibre Channel Node Information for %s\n", fi->name);
	printk("My FC_ID = %x, My WWN = %x %x, ", fi->g.my_id, fi->g.my_node_name_high, fi->g.my_node_name_low);
	if (fi->g.ptp_up == TRUE)
		printk("Port_Type = N_Port\n");
	if (fi->g.loop_up == TRUE)
		printk("Port_Type = L_Port\n");
	while(q != NULL) {
		printk("WWN = ");
		for (j = 0; j < PORT_NAME_LEN; j++)
			printk("%x ", q->hw_addr[j]); 
		printk("FC_ID = %x, ", q->d_id);
		printk("Login = ");
		if (q->login == LOGIN_COMPLETED)
			printk("ON ");
		else
			printk("OFF ");
		if (q->scsi == TARGET)
			printk("Target_ID = %d ", q->target_id);
		printk("\n");
		q = q->next;
		count++;
	}

#if DEBUG_5526_2
	printk("OX_ID -> ELS Map\n");
	while(temp_ox_id_list) {
			printk("ox_id = %x, ELS = %x\n", temp_ox_id_list->ox_id, temp_ox_id_list->els);
			temp_ox_id_list = temp_ox_id_list->next;
	}
#endif

	return 0;
}

static struct net_device_stats * iph5526_get_stats(struct device *dev)
{	
struct fc_info *fi = (struct fc_info*)dev->priv; 
	return (struct net_device_stats *) &fi->fc_stats;
}


/* SCSI stuff starts here */

static struct proc_dir_entry proc_scsi_iph5526 =  {
	PROC_SCSI_IPH5526_FC, 7, "iph5526", S_IFDIR, S_IRUGO | S_IXUGO, 2
};


int iph5526_detect(Scsi_Host_Template *tmpt)
{
struct Scsi_Host *host = NULL;
struct iph5526_hostdata *hostdata;
struct fc_info *fi = NULL;
int no_of_hosts = 0, timeout, i, j, count = 0;
u_int pci_maddr = 0;
struct pci_dev *pdev = NULL;

	tmpt->proc_dir = &proc_scsi_iph5526;
	if (pci_present() == 0) {
		printk("iph5526: PCI not present\n");
		return 0;
	}

	for (i = 0; i <= MAX_FC_CARDS; i++) 
		fc[i] = NULL;

	for (i = 0; i < clone_list[i].vendor_id != 0; i++)
	while ((pdev = pci_find_device(clone_list[i].vendor_id, clone_list[i].device_id, pdev))) {
	unsigned short pci_command;
		if (count < MAX_FC_CARDS) {
			fc[count] = kmalloc(sizeof(struct fc_info), GFP_ATOMIC);
			if (fc[count] == NULL) {
				printk("iph5526.c: Unable to register card # %d\n", count + 1);
				return no_of_hosts;
			}
			memset(fc[count], 0, sizeof(struct fc_info));
		}
		else {
			printk("iph5526.c: Maximum Number of cards reached.\n");
			return no_of_hosts;
		}
			
		fi = fc[count];
		sprintf(fi->name, "fc%d", count);

		host = scsi_register(tmpt, sizeof(struct iph5526_hostdata));
		hostdata = (struct iph5526_hostdata *)host->hostdata;
		memset(hostdata, 0 , sizeof(struct iph5526_hostdata));
		for (j = 0; j < MAX_SCSI_TARGETS; j++)
			hostdata->tag_ages[j] = jiffies;
		hostdata->fi = fi;
		fi->host = host;
		//host->max_id = MAX_SCSI_TARGETS;
		host->max_id = 5;
		host->hostt->use_new_eh_code = 1;
		host->this_id = tmpt->this_id;

		pci_maddr = pdev->base_address[0];
		if ( (pci_maddr & PCI_BASE_ADDRESS_SPACE) != PCI_BASE_ADDRESS_SPACE_MEMORY) {
			printk("iph5526.c : Cannot find proper PCI device base address.\n");
			scsi_unregister(host);
			kfree(fc[count]);
			fc[count] = NULL;
			continue;
		}
		
		pci_maddr &= PCI_BASE_ADDRESS_MEM_MASK;
		DPRINTK("pci_maddr = %x", pci_maddr);
		pci_read_config_word(pdev, PCI_COMMAND, &pci_command);
			
		pci_irq_line = pdev->irq;
		printk("iph5526.c: PCI BIOS reports %s at i/o %#x, irq %d.\n", clone_list[i].name, pci_maddr, pci_irq_line);
		fi->g.mem_base = ioremap(pci_maddr & PAGE_MASK, 1024);
		
		/* We use Memory Mapped IO. The initial space contains the
		 * PCI Configuration registers followed by the (i) chip
		 * registers followed by the Tachyon registers.
		 */
		/* Thatz where (i)chip maps Tachyon Address Space.
		 */
		fi->g.tachyon_base = (u_long)fi->g.mem_base + TACHYON_OFFSET + ( pci_maddr & ~PAGE_MASK );
		DPRINTK("fi->g.tachyon_base = %x", (u_int)fi->g.tachyon_base);
		if (fi->g.mem_base == NULL) {
			printk("iph5526.c : ioremap failed!!!\n");
			scsi_unregister(host);
			kfree(fc[count]);
			fc[count] = NULL;
			continue;
		}	
		DPRINTK("IRQ1 = %d\n", pci_irq_line);
		printk(version);
		fi->base_addr = (long) pdev;

		if (pci_irq_line) {
		int irqval = 0;
			/* Found it, get IRQ.
			 */
			irqval = request_irq(pci_irq_line, &tachyon_interrupt, pci_irq_line ? SA_SHIRQ : 0, fi->name, host);
			if (irqval) {
				printk("iph5526.c : Unable to get IRQ %d (irqval = %d).\n", pci_irq_line, irqval);
				scsi_unregister(host);
				kfree(fc[count]);
				fc[count] = NULL;
				continue;
			}
			host->irq = fi->irq = pci_irq_line;
			pci_irq_line = 0;
			fi->clone_id = clone_list[i].vendor_id;
		}

		if (!initialize_register_pointers(fi) || !tachyon_init(fi)) {
			printk("iph5526.c: TACHYON initialization failed for card # %d!!!\n", count + 1);
			free_irq(host->irq, host);
			scsi_unregister(host);
			if (fi) 
				clean_up_memory(fi);
			kfree(fc[count]);
			fc[count] = NULL;
			break;
		}
		DPRINTK1("Fibre Channel card initialized");
		/* Wait for the Link to come up and the login process 
		 * to complete. 
		 */
		for(timeout = jiffies + 10*HZ; (timeout > jiffies) && ((fi->g.link_up == FALSE) || (fi->g.port_discovery == TRUE) || (fi->g.explore_fabric == TRUE) || (fi->g.perform_adisc == TRUE));)
			barrier();
		
		count++;
		no_of_hosts++;
	}
	DPRINTK1("no_of_hosts = %d",no_of_hosts);
	
	/* This is to make sure that the ACC to the PRLI comes in 
	 * for the last ALPA. 
	 */
	udelay(1000000); /* Ugly! Let the Gods forgive me */

	DPRINTK1("leaving iph5526_detect\n");
	return no_of_hosts;
}


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

int iph5526_queuecommand(Scsi_Cmnd *Cmnd, void (*done) (Scsi_Cmnd *))
{
int int_required = 0;
u_int r_ctl = FC4_DEVICE_DATA | UNSOLICITED_COMMAND;
u_int type = TYPE_FCP | SEQUENCE_INITIATIVE;
u_int frame_class = Cmnd->target;
u_short ox_id = OX_ID_FIRST_SEQUENCE;
struct Scsi_Host *host = Cmnd->host;
struct iph5526_hostdata *hostdata = (struct iph5526_hostdata*)host->hostdata;
struct fc_info *fi = hostdata->fi;
struct fc_node_info *q;
u_long flags;
	ENTER("iph5526_queuecommand");

	spin_lock_irqsave(&fi->fc_lock, flags);
	Cmnd->scsi_done = done;

	if (Cmnd->device->tagged_supported) {
		switch(Cmnd->tag) {
			case SIMPLE_QUEUE_TAG:
				hostdata->cmnd.fcp_cntl = FCP_CNTL_QTYPE_SIMPLE;
				break;
			case HEAD_OF_QUEUE_TAG:
				hostdata->cmnd.fcp_cntl = FCP_CNTL_QTYPE_HEAD_OF_Q;
				break;
			case  ORDERED_QUEUE_TAG:
				hostdata->cmnd.fcp_cntl = FCP_CNTL_QTYPE_ORDERED;
				break;
			default:
				if ((jiffies - hostdata->tag_ages[Cmnd->target]) > (5 * HZ)) {
					hostdata->cmnd.fcp_cntl = FCP_CNTL_QTYPE_ORDERED;
					hostdata->tag_ages[Cmnd->target] = jiffies;
				}
				else
					hostdata->cmnd.fcp_cntl = FCP_CNTL_QTYPE_SIMPLE;
				break;
		}
	}
	/*else
		hostdata->cmnd.fcp_cntl = FCP_CNTL_QTYPE_UNTAGGED;
	*/

	hostdata->cmnd.fcp_addr[3] = 0;
	hostdata->cmnd.fcp_addr[2] = 0;
	hostdata->cmnd.fcp_addr[1] = 0;
	hostdata->cmnd.fcp_addr[0] = htons(Cmnd->lun);

	memcpy(&hostdata->cmnd.fcp_cdb, Cmnd->cmnd, Cmnd->cmd_len);
	hostdata->cmnd.fcp_data_len = htonl(Cmnd->request_bufflen);

	/* Get an used OX_ID. We could have pending commands.
	 */
	if (get_scsi_oxid(fi))
		return 1;
	fi->q.free_scsi_oxid[fi->g.scsi_oxid] = OXID_INUSE;	

	/* Maintain a handler so that we can associate the done() function
	 * on completion of the SCSI command. 
	 */
	hostdata->cmnd_handler[fi->g.scsi_oxid] = Cmnd;

	switch(Cmnd->cmnd[0]) {
		case WRITE_6:
		case WRITE_10:
		case WRITE_12:
			fi->g.type_of_frame = FC_SCSI_WRITE;
			hostdata->cmnd.fcp_cntl = htonl(FCP_CNTL_WRITE | hostdata->cmnd.fcp_cntl);
			break;
		default:
			fi->g.type_of_frame = FC_SCSI_READ;
			hostdata->cmnd.fcp_cntl = htonl(FCP_CNTL_READ | hostdata->cmnd.fcp_cntl);
	}
	
	memcpy(fi->q.ptr_fcp_cmnd[fi->q.fcp_cmnd_indx], &(hostdata->cmnd), sizeof(fcp_cmd));	
	
	q = resolve_target(fi, Cmnd->target);

	if (q == NULL) {
	u_int bad_id = fi->g.my_ddaa | 0xFE;
		/* We transmit to an non-existant AL_PA so that the "done" 
		 * function can be called while receiving the interrupt 
		 * due to a Timeout for a bad AL_PA. In a PTP configuration,
		 * the int_required field is set, since there is no notion
		 * of AL_PAs. This approach sucks, but works alright!
		 */
		if (fi->g.ptp_up == TRUE)
			int_required = 1;
		tx_exchange(fi, (char *)(&(hostdata->cmnd)), sizeof(fcp_cmd), r_ctl, type, bad_id, fi->g.my_mtu, int_required, ox_id, FC_SCSI_BAD_TARGET);
		spin_unlock_irqrestore(&fi->fc_lock, flags);
		DPRINTK1("Target ID %x not present", Cmnd->target);
		return 0;
	}
	if (q->login == LOGIN_COMPLETED) {
		if (add_to_sest(fi, Cmnd, q)) {
			DPRINTK1("add_to_sest() failed.");
			spin_unlock_irqrestore(&fi->fc_lock, flags);
			return 0;
		}
		tx_exchange(fi, (char *)(fi->q.ptr_fcp_cmnd[fi->q.fcp_cmnd_indx]), sizeof(fcp_cmd), r_ctl, type, q->d_id, q->mtu, int_required, ox_id, frame_class << 16);
		update_FCP_CMND_indx(fi);
	}
	spin_unlock_irqrestore(&fi->fc_lock, flags);
	/* If q != NULL, then we have a SCSI Target. 
	 * If q->login != LOGIN_COMPLETED, then that device could be 
	 * offline temporarily. So we let the command to time-out. 
	 */
	LEAVE("iph5526_queuecommand");
	return 0;
}

int iph5526_abort(Scsi_Cmnd *Cmnd)
{
struct Scsi_Host *host = Cmnd->host;
struct iph5526_hostdata *hostdata = (struct iph5526_hostdata *)host->hostdata;
struct fc_info *fi = hostdata->fi;
struct fc_node_info *q;
u_int r_ctl = FC4_DEVICE_DATA | UNSOLICITED_COMMAND;
u_int type = TYPE_FCP | SEQUENCE_INITIATIVE;
u_short ox_id = OX_ID_FIRST_SEQUENCE;
int int_required = 1, i, abort_status = FALSE;
u_long flags;
	
	ENTER("iph5526_abort");
	
	spin_lock_irqsave(&fi->fc_lock, flags);
	
	q = resolve_target(fi, Cmnd->target);
	if (q == NULL) {
	u_int bad_id = fi->g.my_ddaa | 0xFE;
		/* This should not happen as we should always be able to
		 * resolve a target id. But, jus in case...
		 * We transmit to an non-existant AL_PA so that the done 
		 * function can be called while receiving the interrupt 
		 * for a bad AL_PA. 
		 */
		DPRINTK1("Unresolved Target ID!");
		tx_exchange(fi, (char *)(&(hostdata->cmnd)), sizeof(fcp_cmd), r_ctl, type, bad_id, fi->g.my_mtu, int_required, ox_id, FC_SCSI_BAD_TARGET);
		DPRINTK1("Target ID %x not present", Cmnd->target);
		spin_unlock_irqrestore(&fi->fc_lock, flags);
		return FAILED;
	}

	/* If q != NULL, then we have a SCSI Target. If 
	 * q->login != LOGIN_COMPLETED, then that device could 
	 * be offline temporarily. So we let the command to time-out. 
	 */

	/* Get the OX_ID for the Command to be aborted.
	 */
	for (i = 0; i <= MAX_SCSI_XID; i++) {
		if (hostdata->cmnd_handler[i] == Cmnd) {
			hostdata->cmnd_handler[i] = NULL;
			ox_id = i;
			break;
		}
	}
	if (i > MAX_SCSI_XID) {
		T_MSG("Command could not be resolved to OX_ID");
		spin_unlock_irqrestore(&fi->fc_lock, flags);
		return FAILED;
	}

	switch(Cmnd->cmnd[0]) {
		case WRITE_6:
		case WRITE_10:
		case WRITE_12:
			break;
		default:
			ox_id |= SCSI_READ_BIT;
	}
	abort_status = abort_exchange(fi, ox_id);
	
	if ((q->login == LOGIN_COMPLETED) && (abort_status == TRUE)) {
		/* Then, transmit an ABTS to the target. The rest 
		 * is done when the BA_ACC is received for the ABTS.
 	 	 */
		tx_abts(fi, q->d_id, ox_id);
	}
	else {
	u_int STE_bit;
	u_short x_id;
		/* Invalidate resources for that Exchange.
		 */
		x_id = ox_id & MAX_SCSI_XID;
		STE_bit = ntohl(*fi->q.ptr_sest[x_id]);
		if (STE_bit & SEST_V) {
			*(fi->q.ptr_sest[x_id]) &= htonl(SEST_INV);
			invalidate_SEST_entry(fi, ox_id);
		}
	}

	LEAVE("iph5526_abort");
	spin_unlock_irqrestore(&fi->fc_lock, flags);
	return SUCCESS;
}

static int abort_exchange(struct fc_info *fi, u_short ox_id)
{
u_short x_id;
volatile u_int flush_SEST, STE_bit;
	x_id = ox_id & MAX_SCSI_XID;
	DPRINTK1("Aborting Exchange %x", ox_id);

	STE_bit = ntohl(*fi->q.ptr_sest[x_id]);
	/* Is the Exchange still active?.
	 */
	if (STE_bit & SEST_V) {
		if (ox_id & SCSI_READ_BIT) {
			/* If the Exchange to be aborted is Inbound, 
			 * Flush the SEST Entry from Tachyon's Cache.
			 */
			*(fi->q.ptr_sest[x_id]) &= htonl(SEST_INV);
			flush_tachyon_cache(fi, ox_id);
			flush_SEST = readl(fi->t_r.ptr_tach_flush_oxid_reg);
			while ((flush_SEST & 0x80000000) != 0) 
				flush_SEST = readl(fi->t_r.ptr_tach_flush_oxid_reg);
			STE_bit = ntohl(*fi->q.ptr_sest[x_id]);
			while ((STE_bit & 0x80000000) != 0)
				STE_bit = ntohl(*fi->q.ptr_sest[x_id]);
			flush_SEST = readl(fi->t_r.ptr_tach_flush_oxid_reg);
			invalidate_SEST_entry(fi, ox_id);
		}
		else {
		int i;
		u_int *ptr_edb;
			/* For In-Order Reassembly, the following is done:
			 * First, write zero as the buffer length in the EDB. 
		 	 */
			ptr_edb = bus_to_virt(ntohl(*(fi->q.ptr_sest[x_id] + 7)));
			for (i = 0; i < EDB_LEN; i++)
				if (fi->q.ptr_edb[i] == ptr_edb)
					break;
			if (i < EDB_LEN) 
				*ptr_edb = *ptr_edb & 0x0000FFFF;
			else
				T_MSG("EDB not found while clearing in abort_exchange()");
		}
		DPRINTK1("Exchange %x invalidated", ox_id);
		return TRUE;
	}
	else {
		DPRINTK1("SEST Entry for exchange %x not valid", ox_id);
		return FALSE;
	}	
}

static void flush_tachyon_cache(struct fc_info *fi, u_short ox_id)
{
volatile u_int tachyon_status;
	if (fi->g.loop_up == TRUE) {
		writel(HOST_CONTROL, fi->t_r.ptr_fm_control_reg);
		/* Make sure that the Inbound FIFO is empty.
		 */
		do {
			tachyon_status = readl(fi->t_r.ptr_tach_status_reg);
			udelay(200);
		}while ((tachyon_status & RECEIVE_FIFO_EMPTY) == 0);
		/* Ok. Go ahead and flushhhhhhhhh!
		 */
		writel(0x80000000 | ox_id, fi->t_r.ptr_tach_flush_oxid_reg);
		writel(EXIT_HOST_CONTROL, fi->t_r.ptr_fm_control_reg);
		return;
	}
	if (fi->g.ptp_up == TRUE) {
		take_tachyon_offline(fi);
		/* Make sure that the Inbound FIFO is empty.
		 */
		do {
			tachyon_status = readl(fi->t_r.ptr_tach_status_reg);
			udelay(200);
		}while ((tachyon_status & RECEIVE_FIFO_EMPTY) == 0);
		writel(0x80000000 | ox_id, fi->t_r.ptr_tach_flush_oxid_reg);
		/* Write the Initialize command to the FM Control reg.
		 */
		fi->g.n_port_try = TRUE;
		DPRINTK1("In abort_exchange, TACHYON initializing as N_Port...\n");
		writel(INITIALIZE, fi->t_r.ptr_fm_control_reg);
	}
}

static struct fc_node_info *resolve_target(struct fc_info *fi, u_char target)
{
struct fc_node_info *temp = fi->node_info_list;
	while(temp != NULL)
		if (temp->target_id == target) {
			if ((temp->scsi == TARGET) && (temp->login == LOGIN_COMPLETED))
				return temp;
			else {
				if (temp->login != LOGIN_COMPLETED) {
					/* The Target is not currently logged in.
					 * It could be a Target on the Local Loop or
					 * on a Remote Loop connected through a switch.
					 * In either case, we will know whenever the Target
					 * comes On-Line again. We let the command to 
					 * time-out so that it gets retried.
					 */
					T_MSG("Target %d not logged in.", temp->target_id);
					tx_logi(fi, ELS_PLOGI, temp->d_id);
					return temp;
				}
				else {
					if (temp->scsi != TARGET) {
						/* For some reason, we did not get a response to
						 * PRLI. Letz try it again...
						 */
						DPRINTK1("Node not PRLIied. Txing PRLI...");
						tx_prli(fi, ELS_PRLI, temp->d_id, OX_ID_FIRST_SEQUENCE);
					}
				}
				return temp;
			}
		}
		else
			temp = temp->next;
	return NULL;
}

static int add_to_sest(struct fc_info *fi, Scsi_Cmnd *Cmnd, struct fc_node_info *ni)
{
/* we have at least 1 buffer, the terminator */
int no_of_sdb_buffers = 1, i; 
int no_of_edb_buffers = 0; 
u_int *req_buffer = (u_int *)Cmnd->request_buffer;
u_int *ptr_sdb = NULL;
struct scatterlist *sl1, *sl2 = NULL;
int no_of_sg = 0;

	switch(fi->g.type_of_frame) {
		case FC_SCSI_READ:
			fi->g.inb_sest_entry.flags_and_byte_offset = htonl(INB_SEST_VED);
			fi->g.inb_sest_entry.byte_count = 0;
			fi->g.inb_sest_entry.no_of_recvd_frames = 0;
			fi->g.inb_sest_entry.no_of_expected_frames = 0;
			fi->g.inb_sest_entry.last_fctl = 0;

			if (Cmnd->use_sg) {
				no_of_sg = Cmnd->use_sg;
				sl1 = sl2 = (struct scatterlist *)Cmnd->request_buffer;
				for (i = 0; i < no_of_sg; i++) {
					no_of_sdb_buffers += sl1->length / SEST_BUFFER_SIZE;
					if (sl1->length % SEST_BUFFER_SIZE)
						no_of_sdb_buffers++;
					sl1++;
				}
			}
			else {
				no_of_sdb_buffers += Cmnd->request_bufflen / SEST_BUFFER_SIZE;
				if (Cmnd->request_bufflen % SEST_BUFFER_SIZE)
					no_of_sdb_buffers++;
			} /* if !use_sg */

			/* We are working with the premise that at the max we would
			 * get a scatter-gather buffer containing 63 buffers
			 * of size 1024 bytes each. Is it a _bad_ assumption?
			 */
			if (no_of_sdb_buffers > 512) {
				T_MSG("Number of SDB buffers needed = %d", no_of_sdb_buffers);
				T_MSG("Disable Scatter-Gather!!!");
				return 1;
			}
				

			/* Store it in the sdb_table so that we can retrieve that
			 * free up the memory when the Read Command completes.
			 */
			if (get_free_SDB(fi))
				return 1;
			ptr_sdb = fi->q.ptr_sdb_slot[fi->q.sdb_indx];
			fi->q.sdb_slot_status[fi->q.sdb_indx] = SDB_BUSY;
			fi->g.inb_sest_entry.sdb_address = htonl(virt_to_bus(ptr_sdb));

			if (Cmnd->use_sg) {
			int count = 0, j;
				for(i = 0; i < no_of_sg; i++) {
				char *addr_ptr = sl2->address;
					count = sl2->length / SEST_BUFFER_SIZE;
					if (sl2->length % SEST_BUFFER_SIZE)
						count++;
					for (j = 0; j < count; j++) {
						*(ptr_sdb) = htonl(virt_to_bus(addr_ptr));
						addr_ptr += SEST_BUFFER_SIZE;
						ptr_sdb++;
					}
					count = 0;
					sl2++;
				}
			}
			else {
				for (i = 0; i < no_of_sdb_buffers - 1; i++) {
					*(ptr_sdb) = htonl(virt_to_bus(req_buffer));
					req_buffer += SEST_BUFFER_SIZE/4;
					ptr_sdb++;
				}
			}
			*(ptr_sdb) = htonl(0x1); /* Terminator */
			
			/* The scratch pad is used to hold the index into the SDB.
			 */
			fi->g.inb_sest_entry.scratch_pad = fi->q.sdb_indx;
			fi->g.inb_sest_entry.expected_ro = 0;
			fi->g.inb_sest_entry.buffer_index = 0;
			fi->g.inb_sest_entry.buffer_offset = 0;
			memcpy(fi->q.ptr_sest[fi->g.scsi_oxid], &fi->g.inb_sest_entry, sizeof(INB_SEST_ENTRY));
			break;
		case FC_SCSI_WRITE:
			fi->g.outb_sest_entry.flags_and_did = htonl(OUTB_SEST_VED | ni->d_id);
			fi->g.outb_sest_entry.max_frame_len = htons(ni->mtu << 4);
			fi->g.outb_sest_entry.cntl = htons(ODB_CLASS_3 | ODB_EE_CREDIT | ODB_NO_INT | ODB_NO_COMP);
			fi->g.outb_sest_entry.total_seq_length = INV_SEQ_LEN;
			fi->g.outb_sest_entry.link = htons(OUTB_SEST_LINK);
			fi->g.outb_sest_entry.transaction_id = htonl(fi->g.scsi_oxid);
			fi->g.outb_sest_entry.seq_id = fi->g.seq_id;
			fi->g.outb_sest_entry.reserved = 0x0;
			fi->g.outb_sest_entry.header_length = htons(TACHYON_HEADER_LEN);
		
			{
			u_char df_ctl = 0;
			u_short rx_id = RX_ID_FIRST_SEQUENCE;
			u_int r_ctl = FC4_DEVICE_DATA | SOLICITED_DATA;
			u_int type = TYPE_FCP | SEQUENCE_INITIATIVE;
				/* Multi Frame Sequence ? If yes, set RO bit. 
				 */
				if (Cmnd->request_bufflen > ni->mtu)
					type |= RELATIVE_OFF_PRESENT;
				build_tachyon_header(fi, fi->g.my_id, r_ctl, ni->d_id, type, fi->g.seq_id, df_ctl, fi->g.scsi_oxid, rx_id, NULL);
				if (get_free_header(fi) || get_free_EDB(fi))
					return 1;
				memcpy(fi->q.ptr_tachyon_header[fi->q.tachyon_header_indx], &(fi->g.tach_header), TACHYON_HEADER_LEN);
				fi->g.outb_sest_entry.header_address = htonl(virt_to_bus(fi->q.ptr_tachyon_header[fi->q.tachyon_header_indx]));
				update_tachyon_header_indx(fi);
			}

			if (Cmnd->use_sg) {
				no_of_sg = Cmnd->use_sg;
				sl1 = sl2 = (struct scatterlist *)Cmnd->request_buffer;
				for (i = 0; i < no_of_sg; i++) {
					no_of_edb_buffers += sl1->length / SEST_BUFFER_SIZE;
					if (sl1->length % SEST_BUFFER_SIZE)
						no_of_edb_buffers++;
					sl1++;
				}
			}
			else {
				no_of_edb_buffers += Cmnd->request_bufflen / SEST_BUFFER_SIZE;
				if (Cmnd->request_bufflen % SEST_BUFFER_SIZE)
					no_of_edb_buffers++;
			} /* if !use_sg */


			/* We need "no_of_edb_buffers" _contiguous_ EDBs 
			 * that are FREE. Check for that first.
			 */
			for (i = 0; i < no_of_edb_buffers; i++) {
			int j;
				if ((fi->q.edb_buffer_indx + no_of_edb_buffers) >= EDB_LEN)
					fi->q.edb_buffer_indx = 0;
				if (fi->q.free_edb_list[fi->q.edb_buffer_indx + i] != EDB_FREE) {
					for (j = 0; j < i; j++)
						update_EDB_indx(fi);
					if (get_free_EDB(fi))
						return 1;
					i = 0;
				}
			}

			/* We got enuff FREE EDBs.
			 */
			if (Cmnd->use_sg) {
				fi->g.outb_sest_entry.edb_address = htonl(virt_to_bus(fi->q.ptr_edb[fi->q.edb_buffer_indx]));
				sl1 = (struct scatterlist *)Cmnd->request_buffer;
				for(i = 0; i < no_of_sg; i++) {
				int count = 0, j;
					count = sl1->length / SEST_BUFFER_SIZE;
					for (j = 0; j < count; j++) {
						build_EDB(fi, (char *)sl1->address, 0, SEST_BUFFER_SIZE);
						memcpy(fi->q.ptr_edb[fi->q.edb_buffer_indx], &(fi->g.edb), sizeof(EDB));
						/* Mark this EDB as being in use */
						fi->q.free_edb_list[fi->q.edb_buffer_indx] = EDB_BUSY;
						/* We have already made sure that we have enuff
				 	 	 * free EDBs that are contiguous. So this is 
						 * safe.
				 	 	 */
						update_EDB_indx(fi);
						sl1->address += SEST_BUFFER_SIZE;
					}
					/* Just in case itz not a multiple of 
					 * SEST_BUFFER_SIZE bytes.
					 */
					if (sl1->length % SEST_BUFFER_SIZE) {
						build_EDB(fi, (char *)sl1->address, 0, sl1->length % SEST_BUFFER_SIZE);
						memcpy(fi->q.ptr_edb[fi->q.edb_buffer_indx], &(fi->g.edb), sizeof(EDB));
						fi->q.free_edb_list[fi->q.edb_buffer_indx] = EDB_BUSY;
						update_EDB_indx(fi);
					}
					sl1++;
				}
				/* The last EDB is special. It needs the "end bit" to
				 * be set.
				 */
				*(fi->q.ptr_edb[fi->q.edb_buffer_indx - 1] + 1) = *(fi->q.ptr_edb[fi->q.edb_buffer_indx - 1] + 1) | ntohs(EDB_END);
			}
			else {
			int count = 0, j;
				fi->g.outb_sest_entry.edb_address = htonl(virt_to_bus(fi->q.ptr_edb[fi->q.edb_buffer_indx]));
				count = Cmnd->request_bufflen / SEST_BUFFER_SIZE;
				for (j = 0; j < count; j++) {
					build_EDB(fi, (char *)req_buffer, 0, SEST_BUFFER_SIZE);
					memcpy(fi->q.ptr_edb[fi->q.edb_buffer_indx], &(fi->g.edb), sizeof(EDB));
					/* Mark this EDB as being in use */
					fi->q.free_edb_list[fi->q.edb_buffer_indx] = EDB_BUSY;
					/* We have already made sure that we have enuff
			 	 	 * free EDBs that are contiguous. So this is 
					 * safe.
			 	 	 */
					update_EDB_indx(fi);
					req_buffer += SEST_BUFFER_SIZE;
				}
				/* Just in case itz not a multiple of 
				 * SEST_BUFFER_SIZE bytes.
				 */
				if (Cmnd->request_bufflen % SEST_BUFFER_SIZE) {
					build_EDB(fi, (char *)req_buffer, EDB_END, Cmnd->request_bufflen % SEST_BUFFER_SIZE);
					memcpy(fi->q.ptr_edb[fi->q.edb_buffer_indx], &(fi->g.edb), sizeof(EDB));
					fi->q.free_edb_list[fi->q.edb_buffer_indx] = EDB_BUSY;
					update_EDB_indx(fi);
				}
				else {
					/* Mark the last EDB as the "end edb".
					 */
					*(fi->q.ptr_edb[fi->q.edb_buffer_indx - 1] + 1) = *(fi->q.ptr_edb[fi->q.edb_buffer_indx - 1] + 1) | htons(EDB_END);
				}
			}

			/* Finally we have something to send!.
			 */
			memcpy(fi->q.ptr_sest[fi->g.scsi_oxid], &fi->g.outb_sest_entry, sizeof(OUTB_SEST_ENTRY));
			break;
		}		
	return 0;
}

static void update_FCP_CMND_indx(struct fc_info *fi)
{
	fi->q.fcp_cmnd_indx++;
	if (fi->q.fcp_cmnd_indx == NO_OF_FCP_CMNDS)
		fi->q.fcp_cmnd_indx = 0;
}

static int get_scsi_oxid(struct fc_info *fi)
{
u_short initial_oxid = fi->g.scsi_oxid;
	/* Check if the OX_ID is in use.
	 * We could have an outstanding SCSI command.
	 */
	while (fi->q.free_scsi_oxid[fi->g.scsi_oxid] != OXID_AVAILABLE) {
		update_scsi_oxid(fi);
		if (fi->g.scsi_oxid == initial_oxid) {
			T_MSG("No free OX_IDs avaliable")
			reset_tachyon(fi, SOFTWARE_RESET);
			return 1;
		}
	}
	return 0;
}		

static void update_scsi_oxid(struct fc_info *fi)
{
	fi->g.scsi_oxid++;
	if (fi->g.scsi_oxid == (MAX_SCSI_XID + 1))
		fi->g.scsi_oxid = 0;
}

static int get_free_SDB(struct fc_info *fi)
{
unsigned int initial_indx = fi->q.sdb_indx;
	/* Check if the SDB is in use.
	 * We could have an outstanding SCSI Read command.
	 * We should find a free slot as we can queue a
	 * maximum of 32 SCSI commands only. 
	 */
	while (fi->q.sdb_slot_status[fi->q.sdb_indx] != SDB_FREE) {
		update_SDB_indx(fi);
		if (fi->q.sdb_indx == initial_indx) {
			T_MSG("No free SDB buffers avaliable")
			reset_tachyon(fi, SOFTWARE_RESET);
			return 1;
		}
	}
	return 0;
}		

static void update_SDB_indx(struct fc_info *fi)
{
	fi->q.sdb_indx++;
	if (fi->q.sdb_indx == NO_OF_SDB_ENTRIES)
		fi->q.sdb_indx = 0;
}

int iph5526_release(struct Scsi_Host *host)
{
struct iph5526_hostdata *hostdata = (struct iph5526_hostdata*)host->hostdata;
struct fc_info *fi = hostdata->fi;
	free_irq(host->irq, host);
	iounmap(fi->g.mem_base);
	return 0;
}

const char *iph5526_info(struct Scsi_Host *host)
{
static char buf[80];
	sprintf(buf, "Interphase 5526 Fibre Channel PCI SCSI Adapter using IRQ %d\n", host->irq);
	return buf;
}

#ifdef MODULE

#define NAMELEN		8	/* # of chars for storing dev->name */

static struct device *dev_fc[MAX_FC_CARDS];

static int io =  0;
static int irq  = 0;
static int bad  = 0;	/* 0xbad = bad sig or no reset ack */
static int scsi_registered;


int init_module(void)
{
int i = 0;

	driver_template.module = &__this_module;
	scsi_register_module(MODULE_SCSI_HA, &driver_template);
	if (driver_template.present)
		scsi_registered = TRUE; 
	else {
		printk("iph5526: SCSI registeration failed!!!\n");
		scsi_registered = FALSE;
		scsi_unregister_module(MODULE_SCSI_HA, &driver_template);
	}

	while(fc[i] != NULL) {
		dev_fc[i] = NULL;
		dev_fc[i] = init_fcdev(dev_fc[i], 0);	
		if (dev_fc[i] == NULL) {
			printk("iph5526.c: init_fcdev failed for card #%d\n", i+1);
			break;
		}
		dev_fc[i]->irq = irq;
		dev_fc[i]->mem_end = bad;
		dev_fc[i]->base_addr = io;
		dev_fc[i]->init = iph5526_probe;
		dev_fc[i]->priv = fc[i];
		fc[i]->dev = dev_fc[i];
		if (register_fcdev(dev_fc[i]) != 0) {
			kfree_s(dev_fc[i], sizeof(struct device));
			dev_fc[i] = NULL;
			if (i == 0) {
				printk("iph5526.c: IP registeration failed!!!\n");
				return -ENODEV;
			}
		}
		i++;
	}
	if (i == 0)
		return -ENODEV;
	
	return 0;
}

void cleanup_module(void)
{
int i = 0;
	while(fc[i] != NULL) {
	struct device *dev = fc[i]->dev;
	void *priv = dev->priv;
		fc[i]->g.dont_init = TRUE;
		take_tachyon_offline(fc[i]);
		unregister_fcdev(dev);
		clean_up_memory(fc[i]);
		if (dev->priv)
			kfree(priv);
		kfree(dev);
		dev = NULL;
		i++;
	}
	if (scsi_registered == TRUE)
		scsi_unregister_module(MODULE_SCSI_HA, &driver_template); 
}
#endif /* MODULE */

void clean_up_memory(struct fc_info *fi)
{
int i,j;
	ENTER("clean_up_memory");
	if (fi->q.ptr_mfsbq_base)
		free_pages((u_long)bus_to_virt(ntohl(*(fi->q.ptr_mfsbq_base))), 5);
	DPRINTK("after kfree2");
	for (i = 0; i < SFSBQ_LENGTH; i++)
		for (j = 0; j < NO_OF_ENTRIES; j++)
			if (fi->q.ptr_sfs_buffers[i*NO_OF_ENTRIES + j])
				kfree(fi->q.ptr_sfs_buffers[i*NO_OF_ENTRIES + j]);
	DPRINTK("after kfree1");
	if (fi->q.ptr_ocq_base)
		free_page((u_long)fi->q.ptr_ocq_base);
	if (fi->q.ptr_imq_base)
		free_page((u_long)fi->q.ptr_imq_base);
	if (fi->q.ptr_mfsbq_base)
		free_page((u_long)fi->q.ptr_mfsbq_base);
	if (fi->q.ptr_sfsbq_base)
		free_page((u_long)fi->q.ptr_sfsbq_base);
	if (fi->q.ptr_edb_base)
		free_pages((u_long)fi->q.ptr_edb_base, 5);
	if (fi->q.ptr_sest_base)
		free_pages((u_long)fi->q.ptr_sest_base, 5);
	if (fi->q.ptr_tachyon_header_base)
		free_page((u_long)fi->q.ptr_tachyon_header_base);
	if (fi->q.ptr_sdb_base)
		free_pages((u_long)fi->q.ptr_sdb_base, 5);
	if (fi->q.ptr_fcp_cmnd_base)
		free_page((u_long)fi->q.ptr_fcp_cmnd_base);
	DPRINTK("after free_pages"); 
	if (fi->q.ptr_host_ocq_cons_indx)
		kfree(fi->q.ptr_host_ocq_cons_indx);
	if (fi->q.ptr_host_hpcq_cons_indx)
		kfree(fi->q.ptr_host_hpcq_cons_indx);
	if (fi->q.ptr_host_imq_prod_indx)
		kfree(fi->q.ptr_host_imq_prod_indx);
	DPRINTK("after kfree3");
	while (fi->node_info_list) {
	struct fc_node_info *temp_list = fi->node_info_list;
		fi->node_info_list = fi->node_info_list->next;
		kfree(temp_list);
	}
	while (fi->ox_id_list) {
	struct ox_id_els_map *temp = fi->ox_id_list;
		fi->ox_id_list = fi->ox_id_list->next;
		kfree(temp);
	}
	LEAVE("clean_up_memory");
}

static int initialize_register_pointers(struct fc_info *fi)
{
ENTER("initialize_register_pointers");
if(fi->g.tachyon_base == 0)
	return -ENOMEM; 
	
fi->i_r.ptr_ichip_hw_control_reg	= ICHIP_HW_CONTROL_REG_OFF + fi->g.tachyon_base;
fi->i_r.ptr_ichip_hw_status_reg = ICHIP_HW_STATUS_REG_OFF + fi->g.tachyon_base;
fi->i_r.ptr_ichip_hw_addr_mask_reg = ICHIP_HW_ADDR_MASK_REG_OFF + fi->g.tachyon_base;
fi->t_r.ptr_ocq_base_reg = OCQ_BASE_REGISTER_OFFSET + fi->g.tachyon_base;
fi->t_r.ptr_ocq_len_reg = OCQ_LENGTH_REGISTER_OFFSET + fi->g.tachyon_base;
fi->t_r.ptr_ocq_prod_indx_reg = OCQ_PRODUCER_REGISTER_OFFSET + fi->g.tachyon_base;
fi->t_r.ptr_ocq_cons_indx_reg = OCQ_CONSUMER_REGISTER_OFFSET + fi->g.tachyon_base;
fi->t_r.ptr_imq_base_reg = IMQ_BASE_REGISTER_OFFSET + fi->g.tachyon_base;
fi->t_r.ptr_imq_len_reg = IMQ_LENGTH_REGISTER_OFFSET + fi->g.tachyon_base;
fi->t_r.ptr_imq_cons_indx_reg = IMQ_CONSUMER_REGISTER_OFFSET + fi->g.tachyon_base;
fi->t_r.ptr_imq_prod_indx_reg = IMQ_PRODUCER_REGISTER_OFFSET + fi->g.tachyon_base;
fi->t_r.ptr_mfsbq_base_reg = MFSBQ_BASE_REGISTER_OFFSET + fi->g.tachyon_base;
fi->t_r.ptr_mfsbq_len_reg = MFSBQ_LENGTH_REGISTER_OFFSET + fi->g.tachyon_base;
fi->t_r.ptr_mfsbq_prod_reg = MFSBQ_PRODUCER_REGISTER_OFFSET + fi->g.tachyon_base;
fi->t_r.ptr_mfsbq_cons_reg = MFSBQ_CONSUMER_REGISTER_OFFSET + fi->g.tachyon_base;
fi->t_r.ptr_mfsbuff_len_reg = MFS_LENGTH_REGISTER_OFFSET + fi->g.tachyon_base;
fi->t_r.ptr_sfsbq_base_reg = SFSBQ_BASE_REGISTER_OFFSET + fi->g.tachyon_base;
fi->t_r.ptr_sfsbq_len_reg = SFSBQ_LENGTH_REGISTER_OFFSET + fi->g.tachyon_base;
fi->t_r.ptr_sfsbq_prod_reg = SFSBQ_PRODUCER_REGISTER_OFFSET + fi->g.tachyon_base;
fi->t_r.ptr_sfsbq_cons_reg = SFSBQ_CONSUMER_REGISTER_OFFSET + fi->g.tachyon_base;
fi->t_r.ptr_sfsbuff_len_reg = SFS_LENGTH_REGISTER_OFFSET + fi->g.tachyon_base;
fi->t_r.ptr_sest_base_reg = SEST_BASE_REGISTER_OFFSET + fi->g.tachyon_base;
fi->t_r.ptr_sest_len_reg = SEST_LENGTH_REGISTER_OFFSET + fi->g.tachyon_base;
fi->t_r.ptr_scsibuff_len_reg = SCSI_LENGTH_REGISTER_OFFSET + fi->g.tachyon_base;
fi->t_r.ptr_tach_config_reg = TACHYON_CONFIG_REGISTER_OFFSET + fi->g.tachyon_base;
fi->t_r.ptr_tach_control_reg = TACHYON_CONTROL_REGISTER_OFFSET + fi->g.tachyon_base;
fi->t_r.ptr_tach_status_reg = TACHYON_STATUS_REGISTER_OFFSET + fi->g.tachyon_base;
fi->t_r.ptr_tach_flush_oxid_reg = TACHYON_FLUSH_SEST_REGISTER_OFFSET + fi->g.tachyon_base;
fi->t_r.ptr_fm_config_reg = FMGR_CONFIG_REGISTER_OFFSET + fi->g.tachyon_base;
fi->t_r.ptr_fm_control_reg = FMGR_CONTROL_REGISTER_OFFSET + fi->g.tachyon_base;
fi->t_r.ptr_fm_status_reg = FMGR_STATUS_REGISTER_OFFSET + fi->g.tachyon_base;
fi->t_r.ptr_fm_tov_reg = FMGR_TIMER_REGISTER_OFFSET + fi->g.tachyon_base;
fi->t_r.ptr_fm_wwn_hi_reg = FMGR_WWN_HI_REGISTER_OFFSET + fi->g.tachyon_base;
fi->t_r.ptr_fm_wwn_low_reg = FMGR_WWN_LO_REGISTER_OFFSET + fi->g.tachyon_base;
fi->t_r.ptr_fm_rx_al_pa_reg = FMGR_RCVD_ALPA_REGISTER_OFFSET + fi->g.tachyon_base;

LEAVE("initialize_register_pointers");
return 1;
}



/*
 * Local variables:
 *  compile-command: "gcc -DKERNEL -Wall -O6 -fomit-frame-pointer -I/usr/src/linux/net/tcp -c iph5526.c"
 *  version-control: t
 *  kept-new-versions: 5
 * End:
 */
